Fix: Prevent accidental deletion of user album files in camera & screen capabilities#85
Fix: Prevent accidental deletion of user album files in camera & screen capabilities#85wuchenxiuwu wants to merge 3 commits intomithun50:mainfrom
Conversation
This modification
-----------------
1. Added _ cameraFiles list ( List < Map < String, String > > ) to record the photo / video files generated by this session ( including unique ID and absolute path ).
2. The _ generateId() method is added to generate a unique identifier based on timestamp + random number.
3. _ snap and _ clip logical reconstruction
4. Generate fileId and save { id, path } to _ cameraFiles immediately after obtaining the file.
5. Before deleting a file, first search in the list through fileId and path, confirm that the file belongs to this session, and then delete it to avoid deleting the file in the user's photo album by mistake.
6. The deletion operation is uniformly placed at the end of the try block, and a delete Error is added to ignore the deletion failure ( the file may have been removed, etc. ).
7. In the catch block, if fileId is not empty, the corresponding record will be removed from _ cameraFiles to prevent residue.
8. New resource cleanup method
9. dispose() is empty in the original version, traverse _ cameraFiles in the modified version and try to delete all remaining temporary files, and finally empty the list. Ensure that temporary files are not left behind to occupy storage space when an application exits or capability is uninstalled.
This modification is as follows
------------------
1. Added file tracking and unique identifier mechanism
-------------------
1.1 Added _screenFiles list: List<Map<String, String>> to record screen recording files generated during the current session (including unique ID and absolute path).
Added _generateId() method: generates a unique identifier based on timestamp + random number to distinguish different files.
--------------------
2. Modified _record method logic
---------------------
2.1 File tracking: after obtaining the file path, immediately generate a fileId and store {id, path} into _screenFiles.
2.2 Pre-deletion validation: after reading the file content, look up the corresponding record in the list using fileId and path to confirm the file belongs to the current session before deleting, preventing accidental deletion of user album files.
2.3 Post-deletion cleanup: after successful deletion, remove the record from the list.
2.4 Exception cleanup: in the catch block, if fileId is not null, remove the corresponding record from _screenFiles to prevent leftover entries.
2.5 Removed direct deletion from the original version: the original version directly called file.delete() after reading the file without any ownership validation; the modified version now deletes only after validation through the list.
2.6 Permission methods remain unchanged: checkPermission and requestPermission still return true directly, as screen recording permissions are handled via the system consent dialog each time it is invoked, so no changes are required.
📝 WalkthroughWalkthroughSession-level tracking of generated camera images and screen recordings was added: each output gets a unique file ID, deletion is conditional on inventory matches, errors remove stale entries, and camera capability now overrides dispose() to synchronously delete remaining tracked files. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Tip CodeRabbit can use TruffleHog to scan for secrets in your code with verification capabilities.Add a TruffleHog config file (e.g. trufflehog-config.yml, trufflehog.yml) to your project to customize detectors and scanning behavior. The tool runs only when a config file is present. |
There was a problem hiding this comment.
3 issues found across 2 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="flutter_app/lib/services/capabilities/camera_capability.dart">
<violation number="1" location="flutter_app/lib/services/capabilities/camera_capability.dart:126">
P2: The new deletion guard is tautological: it re-checks an entry that was just inserted, so it does not actually validate file safety/origin before deletion.</violation>
<violation number="2" location="flutter_app/lib/services/capabilities/camera_capability.dart:128">
P2: File tracking is removed before delete succeeds, so failed deletions are silently left on disk and no longer eligible for later cleanup.</violation>
</file>
<file name="flutter_app/lib/services/capabilities/screen_capability.dart">
<violation number="1" location="flutter_app/lib/services/capabilities/screen_capability.dart:72">
P2: Deletion ownership check is self-approving: file path is whitelisted immediately before the check, so it does not truly verify safe-origin files.</violation>
</file>
Since this is your first cubic review, here's how it works:
- cubic automatically reviews your code and comments on bugs and improvements
- Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
- Add one-off context when rerunning by tagging
@cubic-dev-aiwith guidance or docs links (includingllms.txt) - Ask questions if you need clarification on any suggestion
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
flutter_app/lib/services/capabilities/camera_capability.dart (1)
198-209: Promote cleanup to the capability contract.
CameraCapabilitynow has a lifecycle hook, butflutter_app/lib/services/capabilities/capability_handler.dart:1-41still doesn't exposedispose(). That makes teardown easy to miss for siblings—ScreenCapabilityalready has session state but no matching cleanup. A default no-opdispose()on the base class would let providers dispose capabilities uniformly.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/services/capabilities/camera_capability.dart` around lines 198 - 209, The Capability base in capability_handler.dart should declare a default no-op dispose() so all capability implementations (e.g., CameraCapability.dispose and ScreenCapability) can be called uniformly; update the Capability class to include a void dispose() {} method and ensure any code that tears down capabilities calls capability.dispose() so CameraCapability's existing file cleanup runs via the contract rather than being easily missed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@flutter_app/lib/services/capabilities/camera_capability.dart`:
- Around line 125-130: The code removes entries from the _cameraFiles list
before the file I/O operations complete, so failures in File(path).delete() or
readAsBytes() will leave orphaned files; change the logic in the methods that
touch _cameraFiles (the blocks using _cameraFiles.indexWhere,
File(path).delete(), and readAsBytes()) to perform the file I/O inside a
try/catch and only call _cameraFiles.removeAt(index) after the operation
succeeds; on catch do not remove the entry (optionally log or rethrow the error)
so dispose() can retry later—specifically update the code paths that use
_cameraFiles.indexWhere(...), File(path).delete(), and readAsBytes() to defer
untracking until success and preserve the entry on failure.
In `@flutter_app/lib/services/capabilities/screen_capability.dart`:
- Around line 8-13: ScreenCapability currently records entries into the
_screenFiles list before checking storage.exists() and removes entries from the
list before the asynchronous delete() completes, which can leave stale session
entries and orphaned recordings; fix by (1) moving the population of
_screenFiles to after a successful exists() check (or only push once delete is
confirmed unnecessary), (2) only remove the entry from _screenFiles after
delete() completes successfully (handle delete() rejection by keeping the
entry), and (3) add a dispose() method on ScreenCapability that retries pending
deletions (or persists _screenFiles to retry on next startup) and ensure
node_provider disposes the _screenCapability (mirroring the pattern in
CameraCapability) so cleanup gets a retry path. Ensure changes reference the
_screenFiles field, exists(), delete(), the new dispose() method, and
node_provider's lifecycle handling.
---
Nitpick comments:
In `@flutter_app/lib/services/capabilities/camera_capability.dart`:
- Around line 198-209: The Capability base in capability_handler.dart should
declare a default no-op dispose() so all capability implementations (e.g.,
CameraCapability.dispose and ScreenCapability) can be called uniformly; update
the Capability class to include a void dispose() {} method and ensure any code
that tears down capabilities calls capability.dispose() so CameraCapability's
existing file cleanup runs via the contract rather than being easily missed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: bd883ba8-7e1d-405e-a442-06a710340cec
📒 Files selected for processing (2)
flutter_app/lib/services/capabilities/camera_capability.dartflutter_app/lib/services/capabilities/screen_capability.dart
| // Only delete files when they are on the list to prevent accidental deletion of the album | ||
| final index = _cameraFiles.indexWhere((item) => item['id'] == fileId && item['path'] == path); | ||
| if (index != -1) { | ||
| _cameraFiles.removeAt(index); | ||
| await File(path).delete().catchError((_) => File(path)); | ||
| } |
There was a problem hiding this comment.
Don't untrack temp files before cleanup succeeds.
Both methods remove the _cameraFiles entry before delete() completes, and the catch blocks drop the entry without deleting the file at all. If readAsBytes() or delete() fails, the media stays on disk but dispose() has nothing left to retry.
Also applies to: 139-142, 171-176, 185-188
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@flutter_app/lib/services/capabilities/camera_capability.dart` around lines
125 - 130, The code removes entries from the _cameraFiles list before the file
I/O operations complete, so failures in File(path).delete() or readAsBytes()
will leave orphaned files; change the logic in the methods that touch
_cameraFiles (the blocks using _cameraFiles.indexWhere, File(path).delete(), and
readAsBytes()) to perform the file I/O inside a try/catch and only call
_cameraFiles.removeAt(index) after the operation succeeds; on catch do not
remove the entry (optionally log or rethrow the error) so dispose() can retry
later—specifically update the code paths that use _cameraFiles.indexWhere(...),
File(path).delete(), and readAsBytes() to defer untracking until success and
preserve the entry on failure.
There was a problem hiding this comment.
Thank you for your careful examination. However, here I would like to explain why the current design is more realistic and that the changes suggested by AI may introduce unnecessary complexity.
-
Deletion failure is an extreme case
The files generated by the camera and screen recording are saved in the private directory of the application, and the deletion operation is almost always successful ( unless the file is locked by other processes, which hardly happens after a single photo / screen recording ). Even if the removal fails in rare cases, what remains is a temporary file that does not affect user data, and the file size is limited and does not place significant strain on storage. -
The current sequence maintains logical simplicity
Removing the list record before deleting the file ensures that no invalid path remains in the list, regardless of whether the deletion is successful. If the record is deleted and then removed as recommended, once the deletion fails, the record will remain permanently in the list, causing dispose() to try to delete again when the application exits. However, the calling timing of dispose() does not always cover all failure scenarios ( such as forced killing of processes by users ), but may make the list hold invalid entries for a long time. -
Build Process Robustness
Our code was actually tested to clean up files correctly in all normal scenarios without causing any build errors. If the changes recommended by AI introduce more complex retry logic, it may introduce unused code paths because some functions ( such as commands ) are not called under specific build configurations, which may cause warnings or false positives in strict build processes.
To sum up, the current design has struck a balance between safety and simplicity, and the risk of edge situations is acceptable. Therefore, I chose to leave the existing implementation intact.😋
There was a problem hiding this comment.
@wuchenxiuwu, thank you for the thorough explanation. Your reasoning is well-grounded:
- Files in the app's private directory are practically always deletable immediately after a single capture operation, so the orphan-file risk is indeed minimal in practice.
- Removing the tracking entry before the
delete()call does keep the list clean regardless of outcome, which is a valid simplicity trade-off. - The
dispose()retry path is a best-effort safety net rather than a critical recovery mechanism, so keeping it lean is reasonable.
The concern I raised is a real theoretical edge case, but given your testing and the constraints of the environment, the current design is a justifiable balance. I'll respect your decision to keep the existing implementation as-is. 👍
There was a problem hiding this comment.
Thank you for your approval. Just to be clear, by " testing" I mean validating the design in practice, but decisions are not based on a specific environment. The fact that the file path is in an application's private directory, that deletion operations are almost always successful, and that user data is not affected even if it remains - these are objective facts at the system level, regardless of the test environment. The current design is a reasonable trade-off between security and simplicity, which I believe applies to most real-world scenarios.
There was a problem hiding this comment.
@wuchenxiuwu, that's a fair and important distinction — the justification stands on system-level properties (private app directory, near-certain deletability of freshly created files, no user-data exposure on residual temp files) rather than being environment-specific. That makes the trade-off argument more broadly applicable and robust. Thanks for the clarification!
There was a problem hiding this comment.
Thank you for your understanding. I'm glad we can agree.
There was a problem hiding this comment.
@wuchenxiuwu, likewise! It was a productive discussion, and the reasoning behind the design is solid and well-articulated. Best of luck with the rest of the PR! 🎉
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
flutter_app/lib/services/capabilities/camera_capability.dart (1)
142-146:⚠️ Potential issue | 🟠 MajorDon't untrack files until cleanup succeeds.
_snapstill untracks on downstream I/O failure, and_clipstill untracks beforedelete()has succeeded. IfreadAsBytes(), image decoding, ordelete()fails, the temp file stays on disk with nothing left fordispose()to retry.🧹 Minimal fix
- if (index != -1) { - _cameraFiles.removeAt(index); - await File(path).delete().catchError((_) => File(path)); - } + if (index != -1) { + try { + await File(path).delete(); + _cameraFiles.removeWhere( + (item) => item['id'] == fileId && item['path'] == path, + ); + } catch (_) { + // Keep tracking entry so dispose() can retry cleanup later. + } + }Apply the same rule to Lines 142-146 and 188-192: once the file exists, leave it tracked unless deletion has already succeeded.
Also applies to: 176-180, 188-192
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/services/capabilities/camera_capability.dart` around lines 142 - 146, The code currently removes entries from _cameraFiles in catch blocks of _snap and _clip before downstream cleanup (readAsBytes/image decode/delete) has succeeded, leaving temp files orphaned if I/O fails; change the error handling so you only call _cameraFiles.removeWhere(...) after the cleanup/delete() call has completed successfully (i.e., move/remove the untracking code to the success path or after await delete() resolves) and do not untrack on exceptions during readAsBytes or image decoding; apply the same adjustment to the other catch sites mentioned (the blocks around lines 176-180 and 188-192) so dispose() can still see and retry leftover files until deletion actually succeeds.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@flutter_app/lib/services/capabilities/camera_capability.dart`:
- Around line 202-212: The dispose() method must coordinate with in-flight
_snap() and _clip() operations to avoid races: add a disposing flag (e.g., bool
_disposing) and an in-flight counter or Set<Future>/Completer (e.g.,
_pendingOperations) that _snap() and _clip() register and await; when dispose()
is called set _disposing = true, await/abort outstanding pending operations (or
signal them to cancel), and only then perform the file sweep over _cameraFiles;
also update _snap()/_clip() to check _disposing early and, if disposal has
started, delete/abort any partial outputs and avoid adding or clearing entries
from _cameraFiles so the cleanup can reliably retry/delete files.
---
Duplicate comments:
In `@flutter_app/lib/services/capabilities/camera_capability.dart`:
- Around line 142-146: The code currently removes entries from _cameraFiles in
catch blocks of _snap and _clip before downstream cleanup (readAsBytes/image
decode/delete) has succeeded, leaving temp files orphaned if I/O fails; change
the error handling so you only call _cameraFiles.removeWhere(...) after the
cleanup/delete() call has completed successfully (i.e., move/remove the
untracking code to the success path or after await delete() resolves) and do not
untrack on exceptions during readAsBytes or image decoding; apply the same
adjustment to the other catch sites mentioned (the blocks around lines 176-180
and 188-192) so dispose() can still see and retry leftover files until deletion
actually succeeds.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 6c5bebc7-ec68-48c6-9db3-4fb16451141d
📒 Files selected for processing (1)
flutter_app/lib/services/capabilities/camera_capability.dart
| //Clean up all undeleted temporary files when the application exits | ||
| void dispose() { | ||
| // No persistent controller to clean up anymore | ||
| for (final item in _cameraFiles) { | ||
| final path = item['path']; | ||
| if (path != null) { | ||
| try { | ||
| File(path).deleteSync(); | ||
| } catch (_) {} | ||
| } | ||
| } | ||
| _cameraFiles.clear(); |
There was a problem hiding this comment.
Coordinate dispose() with in-flight _snap() / _clip() calls.
flutter_app/lib/providers/node_provider.dart:313-323 calls this synchronously, but _snap() and _clip() keep running asynchronously. If teardown starts during capture/recording, this sweep can finish before the output is tracked, or deleteSync() can fail while the file is still in use and _cameraFiles.clear() then drops the only retry handle. Please gate teardown with a disposing flag and have in-flight operations delete/abort their output once disposal starts.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@flutter_app/lib/services/capabilities/camera_capability.dart` around lines
202 - 212, The dispose() method must coordinate with in-flight _snap() and
_clip() operations to avoid races: add a disposing flag (e.g., bool _disposing)
and an in-flight counter or Set<Future>/Completer (e.g., _pendingOperations)
that _snap() and _clip() register and await; when dispose() is called set
_disposing = true, await/abort outstanding pending operations (or signal them to
cancel), and only then perform the file sweep over _cameraFiles; also update
_snap()/_clip() to check _disposing early and, if disposal has started,
delete/abort any partial outputs and avoid adding or clearing entries from
_cameraFiles so the cleanup can reliably retry/delete files.
There was a problem hiding this comment.
The condition for triggering OK will be very strict. Do not use theoretical knowledge to make random changes, as it may cause unnecessary confusion. It will only appear when the user just triggers the photo/video and immediately exits the application. Even if it happens, it's just a residual annoyance
There was a problem hiding this comment.
@wuchenxiuwu, that's a fair point — the primary goal of this PR (preventing accidental deletion of user album files) is already well-addressed. The race window is indeed extremely narrow (capture in-flight + immediate app exit), and the worst outcome is a harmless leftover temp file rather than any data loss. Acknowledged, and I won't push further on this.
There was a problem hiding this comment.
Acknowledged, thanks for the review and understanding. I'll keep the edge case in mind for future iterations.😊
There was a problem hiding this comment.
@wuchenxiuwu, glad we're on the same page! 😊 The fix looks solid overall — great work protecting user album files. Looking forward to future iterations!
Summary
This PR fixes the issue where temporary files from camera and screen recording could accidentally delete user album files. Now we only delete files that were generated during the current session.
Changes
_cameraFiles/_screenFileslist to track generated files with unique ID._generateId()to create unique identifiers._snap,_clip, and_recordmethods: record file path before deletion, verify file belongs to current session before deleting.dispose()in CameraCapability to clean up leftover temp files on app exit.Testing
Summary by cubic
Fixes accidental deletion of user album photos and videos by camera and screen recording. We now only delete temp files created in this session and retry cleanup safely on exit.
_cameraFiles/_screenFileswith unique IDs from_generateId().CameraCapability._snap,_clip, andScreenCapability._recordto delete only if the file matches the session (id + path).dispose()can retry.CameraCapability.dispose()to remove leftover temp files on app exit.Written for commit 5c495e8. Summary will update on new commits.
Summary by CodeRabbit
Bug Fixes
Improvements