Skip to content

fix(android): reject self-provider picker roots - #446

Open
veryCrunchy wants to merge 19 commits into
fix/durable-upload-scheduling-recovery-stackfrom
fix/picker-provider-feedback
Open

fix(android): reject self-provider picker roots#446
veryCrunchy wants to merge 19 commits into
fix/durable-upload-scheduling-recovery-stackfrom
fix/picker-provider-feedback

Conversation

@veryCrunchy

@veryCrunchy veryCrunchy commented Sep 5, 2026

Copy link
Copy Markdown
Member

Summary

  • Reject the app’s own DocumentsProvider authority from upload and folder-sync pickers.
  • Handle document, tree, percent-encoded, mixed-case, and Android user-prefixed authority forms.
  • Reject restored self-provider roots before any WebDAV cleanup, construction, or remote scan.
  • Reconcile legacy owned downloads through a journal-scoped cleanup path so pair and account removal can still complete.

Validation

  • Full repository checks passed.
  • Build host: eight focused Android picker, provider, capability, file-sync, and recovery test classes passed.
  • Build host: :androidApp:assembleDebug passed.
  • Exact validated code tree: 96c4e76e3efafc7198ed2b2948411ce1c08dfda5.

@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #446 / NC Native September 5, 2026 15:22 Destroyed
@obiente-cloud

obiente-cloud Bot commented Sep 5, 2026

Copy link
Copy Markdown

Obiente preview

NC Native · 606098fa0c74 · Ready

Open preview

View in Obiente

Obiente updates this comment as the preview changes.

@veryCrunchy
veryCrunchy marked this pull request as ready for review September 5, 2026 15:23
@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #446 / NC Native September 5, 2026 15:23 Destroyed
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 5, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-09T07:20:32.510425Z 606098f New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 538e12112f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #446 / NC Native September 5, 2026 16:01 Destroyed

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 72c0b6edf5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@veryCrunchy
veryCrunchy force-pushed the fix/durable-upload-scheduling-recovery-stack branch from 7dfaeb8 to d963ee4 Compare September 5, 2026 22:42
@veryCrunchy
veryCrunchy force-pushed the fix/picker-provider-feedback branch from 72c0b6e to 548a50d Compare September 5, 2026 22:44
@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #446 / NC Native September 5, 2026 22:44 Destroyed
@veryCrunchy
veryCrunchy force-pushed the fix/durable-upload-scheduling-recovery-stack branch from d963ee4 to b2c2188 Compare September 5, 2026 23:01
@veryCrunchy
veryCrunchy force-pushed the fix/picker-provider-feedback branch from 548a50d to 8bacb4d Compare September 5, 2026 23:02
@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #446 / NC Native September 5, 2026 23:02 Destroyed
@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #446 / NC Native September 5, 2026 23:22 Destroyed

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5e91eb6921

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #446 / NC Native September 5, 2026 23:56 Destroyed

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5bcc25f9d0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@veryCrunchy
veryCrunchy force-pushed the fix/durable-upload-scheduling-recovery-stack branch 3 times, most recently from 6057f39 to a712498 Compare September 6, 2026 04:47
@veryCrunchy
veryCrunchy force-pushed the fix/picker-provider-feedback branch from 5bcc25f to d50a4e9 Compare September 6, 2026 04:48
@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #446 / NC Native September 6, 2026 04:48 Destroyed

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d50a4e9048

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread androidApp/src/main/kotlin/dev/obiente/nextcloudnative/AndroidFileSyncEngine.kt Outdated
Comment thread androidApp/src/main/kotlin/dev/obiente/nextcloudnative/AndroidAccountRemoval.kt Outdated
@veryCrunchy
veryCrunchy force-pushed the fix/durable-upload-scheduling-recovery-stack branch 3 times, most recently from cc5f869 to 337b65f Compare September 6, 2026 05:49
@veryCrunchy
veryCrunchy force-pushed the fix/picker-provider-feedback branch from d50a4e9 to 156ee6f Compare September 6, 2026 05:50
@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #446 / NC Native September 6, 2026 05:50 Destroyed

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 156ee6febf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@veryCrunchy
veryCrunchy force-pushed the fix/durable-upload-scheduling-recovery-stack branch 2 times, most recently from 1caca52 to 6c1d647 Compare September 9, 2026 05:04
@veryCrunchy
veryCrunchy force-pushed the fix/picker-provider-feedback branch from 62a3f5b to 4dee821 Compare September 9, 2026 05:08
@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #446 / NC Native September 9, 2026 05:08 Destroyed

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

return cursor
}
val reference = requireReference(documentId, session)

P1 Badge Require authoritative listings during recovery

When a recovery-authorized query encounters an offline or 5xx response but an older directory cache exists, this fallback returns the cached children as if they were current. AndroidSafDownloadPublisher can consequently interpret a stage or backup omitted from that stale listing as physically absent, remove its ownership row, and let pair/account removal succeed while the recovery file remains on the server; this loses the durable recovery path and can expose a partial stage later. Require an authoritative network listing for recovery, or retain the transaction when only cached state is available.

AGENTS.md reference: AGENTS.md:L399-L403

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +119 to +120
// Missing credentials cannot safely repair legacy self-provider downloads before removal.
// Commit first; durable owned-state cleanup remains fail-closed and can resume after re-add.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Bind deferred recovery to the re-added session

When an unavailable account is removed with a legacy self-provider download pending, the promised recovery after re-add cannot run: saveSession invokes retryPendingAccountRemovalCleanup before persisting the new session, and that retry reaches retireAndroidFileSyncAccountPairs without a providerRecoverySession; meanwhile the cleanup journal itself prevents loadSession from restoring this account for NextcloudDocumentsProvider. Fresh evidence after the credential-free commit change is that the newly supplied credential is never bound to the deferred provider recovery, so the retry fails and blocks the account from being added again.

AGENTS.md reference: AGENTS.md:L428-L430

Useful? React with 👍 / 👎.

Comment on lines +114 to +116
prepare = { prepareAndroidAccountRemoval(appContext, session) },
revalidate = { preflightAndroidAccountRemoval(appContext, session) },
action = action,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recheck SAF ownership under the removal lease

If a folder-sync run starts after prepare() releases its recovery lease and finishes before removal acquires the next lease, it can fail after persisting a new local SAF download transaction. Fresh evidence after adding in-lease revalidation is that preflightAndroidAccountRemoval checks document writebacks and fileSyncOwnedUploads only, so this new local ownership row is invisible and credentials can still be committed as removed, leaving subsequent self-provider cleanup without a usable session. Reconcile or fail closed on pending SAF ownership again while holding the final removal lease.

AGENTS.md reference: AGENTS.md:L428-L430

Useful? React with 👍 / 👎.

Comment on lines +494 to +496
return providerRecovery.run(
document.uri,
AndroidDocumentsProviderRecoveryOperation.OpenRead,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reuse the held lease for recovery content reads

When account-removal recovery must hash a non-empty stage or backup that is not already in an offline or virtual cache, this recovery OpenRead reaches NextcloudDocumentsProvider.openVirtualFileProxy, which calls openFileRangeSession; openTrackedAndroidFileRangeSession then blocks acquiring the same non-reentrant account guard already held by withAndroidFileSyncAccountRecoveryLease. Fresh evidence after the query/listing lease bypass is that the range-open path still has no held-lease mode, so removal hangs while authenticating typical uncached recovery content. Propagate the held lease through range opening or use a recovery read path that does not reacquire it.

AGENTS.md reference: AGENTS.md:L428-L430

Useful? React with 👍 / 👎.

@veryCrunchy
veryCrunchy force-pushed the fix/durable-upload-scheduling-recovery-stack branch from 6c1d647 to 50db150 Compare September 9, 2026 05:43
@veryCrunchy
veryCrunchy force-pushed the fix/picker-provider-feedback branch from 4dee821 to 70a4c3e Compare September 9, 2026 05:49
@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #446 / NC Native September 9, 2026 05:49 Destroyed

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 70a4c3ecf3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

val services = AndroidNextcloudServices(context.applicationContext)
withAndroidFileSyncAccountRecoveryLease(
expectedSession = providerRecoverySession,
resolveSession = { services.loadSession(providerRecoverySession.accountId) },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Allow unregistered credential slots to reach reset

When clearSession() handles IndependentRecoveryUnavailable (for example, a malformed or missing registry with a recoverable independent credential slot), the new production prepare callback reaches this lookup, but services.loadSession(accountId) can only restore an account present in a valid registry and therefore returns null in precisely this state. withExactAccountSession then throws before deleting the slot, even when the account has no folder-sync pairs, leaving the user unable to complete the explicit credential reset. Use the already recovered slot session for this reset path, or avoid acquiring the sync-recovery lease when there are no applicable pairs.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 606098fa0c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +40 to +41
prepare()
return withAndroidAccountRemovalLease(accountIdentity, guard) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Hold the account lease while quiescing range reads

When ordinary account removal races a newly opened seekable DocumentsProvider file, prepare() now runs before the exclusive removal lease. AndroidFileRangeSessionCoordinator.quiesce() snapshots existing registrations, while openTrackedAndroidFileRangeSession() holds the account guard only until it registers the range session; an open that registers after that snapshot can release the guard before removal calls tryWithAccount. The quiesce pass then misses (and removes the registry entry for) this session, allowing credential deletion to proceed while the descriptor can continue issuing authenticated range requests with the retained app password. Keep range-session admission fenced through quiescing and credential removal, or re-quiesce under the final lease.

Useful? React with 👍 / 👎.

Comment on lines +133 to +134
if (recoveryAuthorized) services.listFilesWhileAccountLeaseHeld(session, account.userId, parent.path)
else services.listFiles(session, account.userId, parent.path)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Require network listings for provider retirement

When legacy self-provider recovery runs while the server is offline or returns a 5xx, this call can silently return a cached listing because listFilesWhileAccountLeaseHeld() discards NextcloudFileListingSource, and the surrounding provider code also falls back to offline directory contents. If that cached snapshot predates an interrupted stage or backup, AndroidSafDownloadPublisher.reconcile() treats the missing recovery documents as authoritative and can delete the durable ownership row; pair or account removal then completes while the stage or protected original remains stranded under a reserved recovery name. Recovery-authorized listings must fail closed unless they came from the network.

AGENTS.md reference: AGENTS.md:L428-L430

Useful? React with 👍 / 👎.

buildDocumentUri = { id -> DocumentsContract.buildDocumentUri(authority, id) },
buildChildDocumentsUri = { id -> DocumentsContract.buildChildDocumentsUri(authority, id) },
)
return withAndroidDocumentsProviderRecoveryPermit(bound, documentId, operation) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Handle cross-user provider roots without thread-local permits

When a legacy self-provider tree URI carries an Android user prefix for another profile, such as content://10@<own-authority>/tree/..., this permit is installed only in the caller profile's process and thread, while the rebuilt URI dispatches to the provider instance in user 10. That provider cannot see the ThreadLocal permit and its Binder caller UID is not the caller process UID, so recovery succeeds only accidentally if the other profile currently has the same account active; otherwise the first query or mutation fails and the pending ownership row permanently blocks pair or account removal. The rejection parser explicitly recognizes user-prefixed own authorities, so legacy retirement also needs a provider-independent cross-user path or an explicit safe retirement policy.

AGENTS.md reference: AGENTS.md:L428-L430

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant