Skip to content

feat: guard ADB SMS deletion with fingerprints - #7

Merged
ygpark80 merged 1 commit into
masterfrom
codex/issue-6-safe-sms-delete
Aug 12, 2026
Merged

feat: guard ADB SMS deletion with fingerprints#7
ygpark80 merged 1 commit into
masterfrom
codex/issue-6-safe-sms-delete

Conversation

@ygpark80

@ygpark80 ygpark80 commented Aug 12, 2026

Copy link
Copy Markdown

User description

Closes #6

Changes

  • restrict ADB deletion to one guarded system SMS URI
  • require a Binder-compatible full SHA-256 fingerprint of the live SMS row
  • require QUIK's SMS role and verify both system and local rows are absent
  • reject legacy unguarded and bulk delete forms

Testing

  • ./gradlew testDebugUnitTest — 4 tests, 0 failures
  • ./gradlew :presentation:assembleRelease
  • installed the signed release APK on the connected Galaxy device
  • verified a nonexistent ID changes nothing
  • verified a live ID with a mismatched fingerprint is rejected and remains present
  • verified the legacy /messages/{id} delete URI is rejected

Notes

  • No existing personal SMS was deleted during verification.

CodeAnt-AI Description

Guard ADB SMS deletion against accidental or unauthorized removal

What Changed

  • SMS deletion now accepts only one system message at a time through the guarded /sms/{id} path.
  • Deletion requires the message’s full SHA-256 fingerprint and is rejected if the message changed after it was read.
  • Deletion is allowed only while QUIK is the default SMS app, and both system and local copies must be removed successfully.
  • Legacy unguarded, bulk, invalid, or mismatched deletion requests are rejected without removing messages.
  • Added coverage for fingerprint generation and strict request validation.

Impact

✅ Fewer accidental SMS deletions
✅ Safer ADB message automation
✅ Clear rejection of stale or unauthorized delete requests

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Summary by CodeRabbit

  • New Features

    • Added a secure SMS deletion endpoint requiring a valid fingerprint and default SMS app status.
    • Deletions now verify the SMS record remains unchanged and confirm removal from both system and local data.
  • Bug Fixes

    • Prevented unauthorized, malformed, or multi-record deletion requests.
  • Tests

    • Added coverage for fingerprint generation, validation, escaping, and invalid deletion selections.

@ygpark80 ygpark80 linked an issue Aug 12, 2026 that may be closed by this pull request
@codeant-ai

codeant-ai Bot commented Aug 12, 2026

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Reviewed your PR b93125a Aug 12, 2026 · 05:46 05:49

@codeant-ai

codeant-ai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The ADB provider now supports guarded deletion of one system SMS. It validates a SHA-256 fingerprint, requires QUIK to be the default SMS app, compares the live system record, deletes matching system and local records, and verifies both are absent.

Changes

Guarded SMS deletion

Layer / File(s) Summary
Fingerprint contract and validation
presentation/src/main/java/com/moez/QKSMS/adb/SmsDeleteGuard.kt, presentation/src/test/java/com/moez/QKSMS/adb/SmsDeleteGuardTest.kt
SmsDeleteGuard validates one full hexadecimal SHA-256 fingerprint and creates canonical SMS fingerprints. Tests cover escaping, deterministic hashing, and invalid selections.
System SMS resolution and app-role checks
presentation/src/main/java/com/moez/QKSMS/adb/AdbMessagesProvider.kt
The provider reads the system SMS record, handles missing or duplicate rows, preserves nullable values, and checks default-SMS-app status across Android versions.
Guarded deletion route and verification
presentation/src/main/java/com/moez/QKSMS/adb/AdbMessagesProvider.kt
The provider adds /sms/{contentId}, rejects unsupported deletion inputs, deletes the matching system and local SMS records, and verifies both records are absent.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • circlesac/quik#1: Introduced the ADB SMS deletion flow that this PR hardens with fingerprint and system-record checks.

Sequence Diagram(s)

sequenceDiagram
  participant ADB client
  participant AdbMessagesProvider
  participant SmsDeleteGuard
  participant Android SMS provider
  participant Local SMS repositories

  ADB client->>AdbMessagesProvider: Request /sms/{contentId} with fingerprint
  AdbMessagesProvider->>SmsDeleteGuard: Parse fingerprint
  SmsDeleteGuard-->>AdbMessagesProvider: Validated fingerprint
  AdbMessagesProvider->>Android SMS provider: Read current SMS record
  Android SMS provider-->>AdbMessagesProvider: SMS fields
  AdbMessagesProvider->>SmsDeleteGuard: Generate record fingerprint
  SmsDeleteGuard-->>AdbMessagesProvider: SHA-256 fingerprint
  AdbMessagesProvider->>Android SMS provider: Delete system SMS
  AdbMessagesProvider->>Local SMS repositories: Delete local SMS
  AdbMessagesProvider->>Android SMS provider: Verify system record absence
  AdbMessagesProvider->>Local SMS repositories: Verify local record absence
  AdbMessagesProvider-->>ADB client: Return verified deletion result
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: fingerprint guards for ADB SMS deletion.
Linked Issues check ✅ Passed The changes implement guarded single-message deletion, fingerprint validation, default-app checks, post-delete verification, legacy-form rejection, and unit tests for issue #6.
Out of Scope Changes check ✅ Passed The provider, guard, and tests directly support the linked issue and PR objectives; no unrelated changes are identified.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/issue-6-safe-sms-delete

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Aug 12, 2026
Comment on lines +234 to +240
val live = readSystemSms(contentId) ?: return 0
if (!MessageDigest.isEqual(
SmsDeleteGuard.fingerprint(live).toByteArray(Charsets.US_ASCII),
expectedFingerprint.toByteArray(Charsets.US_ASCII)
)) {
throw SecurityException("SMS changed since it was read")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The fingerprint is checked against a snapshot obtained by readSystemSms, but the subsequent unconditional delete only targets the numeric content ID. If the row is modified or replaced between these operations, the supplied fingerprint can validate one row while this delete removes a different row. Make the deletion conditional on the same fingerprint fields, or otherwise perform validation and deletion atomically. [race condition]

Severity Level: Critical 🚨
- ❌ A concurrent SMS update can bypass the deletion guard.
- ⚠️ A stale fingerprint may delete modified message content.
- ⚠️ Default SMS provider operations create the race window.

Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** presentation/src/main/java/com/moez/QKSMS/adb/AdbMessagesProvider.kt
**Line:** 234:240
**Comment:**
	*Race Condition: The fingerprint is checked against a snapshot obtained by `readSystemSms`, but the subsequent unconditional delete only targets the numeric content ID. If the row is modified or replaced between these operations, the supplied fingerprint can validate one row while this delete removes a different row. Make the deletion conditional on the same fingerprint fields, or otherwise perform validation and deletion atomically.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@presentation/src/main/java/com/moez/QKSMS/adb/AdbMessagesProvider.kt`:
- Around line 247-254: Update the deletion flow around the systemUri delete,
messageRepo.deleteMessages, and subsequent verification to durably reconcile
failures between the system provider and local repository. Ensure a process
death or repository exception after the system delete cannot leave the local row
permanently present, using the existing persistence/recovery mechanisms where
available; retain verification as a final consistency check rather than the sole
recovery mechanism.
- Around line 247-248: Update the deletion flow in AdbMessagesProvider around
the systemUri delete and messageRepo.deleteMessages: delete through
Sms.CONTENT_URI using a parameterized predicate covering every raw value in
SMS_FINGERPRINT_COLUMNS, including _id, with IS NULL for null fingerprints, and
treat any count other than 1 as a mismatch. Add recovery or reconciliation
handling so a committed system deletion followed by a crash or local-delete
failure cannot leave the local row undeleted.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 93d94c58-a4d6-4142-a827-6790ba7318e6

📥 Commits

Reviewing files that changed from the base of the PR and between 6c96a52 and b93125a.

📒 Files selected for processing (3)
  • presentation/src/main/java/com/moez/QKSMS/adb/AdbMessagesProvider.kt
  • presentation/src/main/java/com/moez/QKSMS/adb/SmsDeleteGuard.kt
  • presentation/src/test/java/com/moez/QKSMS/adb/SmsDeleteGuardTest.kt

Comment on lines +247 to +248
val systemUri = ContentUris.withAppendedId(Sms.CONTENT_URI, contentId)
if (appContext.contentResolver.delete(systemUri, null, null) != 1) return 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

For Android API 34, does the Telephony SMS ContentProvider honor ContentResolver.delete selection and selectionArgs when the URI is content://sms/{id}? What is the supported way to atomically delete an SMS only when multiple current column values match?

💡 Result:

For Android API 34, the Telephony SMS ContentProvider does not effectively honor the selection and selectionArgs parameters when the URI is specifically formatted as content://sms/{id} [1][2]. When a URI ending in an ID (e.g., content://sms/5) is provided to the delete() method, the SmsProvider matches it against the SMS_ALL_ID case [2]. In this implementation, the provider parses the ID directly from the URI path and calls an internal method (e.g., deleteOneSms) that targets that specific row by its primary key, effectively ignoring any provided selection or selectionArgs [2]. To atomically delete an SMS only when multiple column values match, you should not use the single-ID URI. Instead, use the general Telephony.Sms.CONTENT_URI (which matches the SMS_ALL case in the provider) along with a combined selection string [2]. The supported way to achieve this is: 1. Construct a selection string that combines your desired column criteria using standard SQL logic (e.g., "address=? AND date=? AND body=?") [3]. 2. Provide the corresponding values in the selectionArgs array [4]. 3. Execute the delete operation using the base URI: contentResolver.delete(Telephony.Sms.CONTENT_URI, selection, selectionArgs) [2][3]. Because the SmsProvider uses db.delete(TABLE_SMS, where, whereArgs) for SMS_ALL queries, the database engine will process the selection criteria atomically within the transaction [1][2]. Note that your application must be the default SMS handler on the device to have write access to the Telephony provider [5].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
file="presentation/src/main/java/com/moez/QKSMS/adb/AdbMessagesProvider.kt"
printf '%s\n' "== target section =="
sed -n '210,265p' "$file"
printf '%s\n' "== fingerprint and delete call sites =="
rg -n -C 4 'fingerprint|contentId|ContentUris\.withAppendedId|contentResolver\.delete|Sms\.CONTENT_URI' "$file"
printf '%s\n' "== related helpers and tests =="
rg -n -C 3 'delete.*sms|sms.*delete|fingerprint|/sms/' presentation/src test .github 2>/dev/null || true

Repository: circlesac/quik

Length of output: 16422


🏁 Script executed:

#!/bin/bash
set -eu
file="presentation/src/main/java/com/moez/QKSMS/adb/AdbMessagesProvider.kt"
sed -n '210,265p' "$file"
rg -n -C 4 'fingerprint|contentId|ContentUris\.withAppendedId|contentResolver\.delete|Sms\.CONTENT_URI' "$file"
rg -n -C 3 'delete.*sms|sms.*delete|fingerprint|/sms/' presentation/src test .github 2>/dev/null || true

Repository: circlesac/quik

Length of output: 16329


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '== fingerprint implementation =='
cat -n presentation/src/main/java/com/moez/QKSMS/adb/SmsDeleteGuard.kt
printf '%s\n' '== SMS record and query projection =='
rg -n -C 6 'data class SmsFingerprintRecord|SMS_FINGERPRINT_COLUMNS|SmsFingerprintRecord\(' presentation/src/main/java/com/moez/QKSMS/adb
printf '%s\n' '== provider source references and repository metadata =='
rg -n 'TelephonyProvider|SmsProvider|compileSdk|minSdk|targetSdk' build.gradle* settings.gradle* gradle.properties presentation 2>/dev/null | head -80

Repository: circlesac/quik

Length of output: 9270


🏁 Script executed:

#!/bin/bash
set -eu
cat -n presentation/src/main/java/com/moez/QKSMS/adb/SmsDeleteGuard.kt
rg -n -C 6 'data class SmsFingerprintRecord|SMS_FINGERPRINT_COLUMNS|SmsFingerprintRecord\(' presentation/src/main/java/com/moez/QKSMS/adb
rg -n 'TelephonyProvider|SmsProvider|compileSdk|minSdk|targetSdk' build.gradle* settings.gradle* gradle.properties presentation 2>/dev/null | head -80

Repository: circlesac/quik

Length of output: 9142


Make the system delete conditional and handle partial commits.

  • SmsProvider ignores selection and selectionArgs for an ID URI. Delete through Sms.CONTENT_URI with a parameterized selection for every raw value in SMS_FINGERPRINT_COLUMNS, including _id, and use IS NULL for null values. Treat a delete count other than 1 as a mismatch.
  • The system delete commits before messageRepo.deleteMessages. A crash or local-delete failure can leave the system SMS deleted while the local row remains. Add recovery or reconciliation handling.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@presentation/src/main/java/com/moez/QKSMS/adb/AdbMessagesProvider.kt` around
lines 247 - 248, Update the deletion flow in AdbMessagesProvider around the
systemUri delete and messageRepo.deleteMessages: delete through Sms.CONTENT_URI
using a parameterized predicate covering every raw value in
SMS_FINGERPRINT_COLUMNS, including _id, with IS NULL for null fingerprints, and
treat any count other than 1 as a mismatch. Add recovery or reconciliation
handling so a committed system deletion followed by a crash or local-delete
failure cannot leave the local row undeleted.

Comment on lines +247 to +254
val systemUri = ContentUris.withAppendedId(Sms.CONTENT_URI, contentId)
if (appContext.contentResolver.delete(systemUri, null, null) != 1) return 0

messageRepo.deleteMessages(listOf(local.id))
conversationRepo.updateConversations(listOf(threadId))

check(readSystemSms(contentId) == null && messageRepo.getMessage(local.id) == null) {
"SMS deletion could not be verified"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Recover from a partial system and local delete.

The system provider delete completes before messageRepo.deleteMessages. A process death, database failure, or repository exception between these calls leaves the local SMS row present after the system SMS row is deleted.

Add durable recovery or reconciliation for this two-store operation. The verification check only detects the partial state after mutation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@presentation/src/main/java/com/moez/QKSMS/adb/AdbMessagesProvider.kt` around
lines 247 - 254, Update the deletion flow around the systemUri delete,
messageRepo.deleteMessages, and subsequent verification to durably reconcile
failures between the system provider and local repository. Ensure a process
death or repository exception after the system delete cannot leave the local row
permanently present, using the existing persistence/recovery mechanisms where
available; retain verification as a final consistency check rather than the sole
recovery mechanism.

@ygpark80
ygpark80 merged commit 6b83d9c into master Aug 12, 2026
1 check passed
@ygpark80
ygpark80 deleted the codex/issue-6-safe-sms-delete branch August 12, 2026 05:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: require fingerprints for ADB SMS deletion

1 participant