Skip to content

fix(security): replace attachment-view URL JWTs with one-time grants - #512

Draft
seonghobae wants to merge 10 commits into
feat/attachment-view-access-grant-413from
fix/attachment-view-grant-runtime-413
Draft

fix(security): replace attachment-view URL JWTs with one-time grants#512
seonghobae wants to merge 10 commits into
feat/attachment-view-access-grant-413from
fix/attachment-view-grant-runtime-413

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Buyer and security impact

This bounded stacked #413 slice migrates only the attachment-view browser flow from a general session JWT in the URL to a short-lived, one-time, purpose- and attachment-bound opaque access grant. It preserves authenticated JSON/PAT behavior and does not yet migrate SSE or calendar transports, so it does not close #413.

Exact current stack and scope

Current parent: feat/attachment-view-access-grant-413@d4c7f1163149f46fc4aab73a18c3f9228c3c9752 (#510).
Current contributor head: f2b08715663946405719f742f2f5597a4f45ee4a.

Fresh parent→child comparison is ahead-only, zero-behind, and limited to the ten attachment-view runtime/client/evidence paths:

  • cloud-sync-core.js and the security-envelope cloud-sync.js;
  • server/app_core.mjs and the security-envelope server/app.mjs;
  • tests/api/attachment-view-access-grant.test.mjs;
  • tests/api/session-revocation.test.mjs;
  • tests/api/smoke.mjs;
  • tests/api/smoke-orchestrator-provider.mjs;
  • tests/unit/attachment-view-client.test.mjs;
  • package.json.

The current parent’s access-grant domain, SQLite persistence/audit outbox, protected contextual-orchestrator behavior, Microsoft Project XML delimiter hardening, security documentation, and canonical parser regressions are preserved. In particular, the child core now tracks the current parent’s hardened cloud-sync.js implementation while the small cloud-sync.js envelope adds only the attachment-view grant navigation boundary.

Runtime contract

  • authenticated POST /api/projects/:project_id/attachments/:attachment_id/view-grant mints an attachment-view grant only after tenant/project/attachment authorization;
  • the response is bounded, no-store, and returns the one-time opaque secret only at mint time;
  • the browser requests a fresh grant immediately before opening a document and never reuses the general session JWT as query authority;
  • the view route redeems only an exact attachment-view purpose/audience/project/attachment binding;
  • one-time consumption remains atomic in the SQLite adapter, so replay fails;
  • logout-all, password/token-version change, membership removal/re-add, project deletion, and attachment deletion invalidate outstanding grants through the parent lifecycle contract;
  • legacy general session JWT query transport is rejected for the migrated attachment-view path while normal bearer/PAT API access remains unchanged;
  • client and API regressions prove cross-project, cross-attachment, cross-tenant, expired/replayed, revoked, and malformed grants cannot access an artifact;
  • secret values do not enter audit metadata or downstream Clearfolio URLs.

TDD, reconciliation, and causal CI repair

The focused attachment-view route and client contracts preceded the runtime implementation. The branch was reconciled again after #510 moved so it could not drop hash-only storage, membership-version consumption, transactional audit evidence, or later protected-base XML-import hardening.

The reconciliation is semantic rather than tree-blind: the attachment runtime intentionally split the monolithic browser module into a current-parent core plus a small security envelope. The current merge therefore refreshes cloud-sync-core.js from the exact current parent’s cloud-sync.js, preserves the child envelope, and carries the parent documentation/parser tests unchanged. This avoids reintroducing the stale pre-hardening parser while retaining the one-time-grant navigation behavior.

An earlier exact-head Server Tests failure also identified a stale smoke assumption about the predecessor mock-orchestrator string. The branch added tests/api/smoke-orchestrator-provider.mjs, a deterministic authenticated loopback provider harness that delegates unrelated network calls to Node’s real transport rather than weakening production orchestrator semantics or skipping the broad smoke suite.

Current exact-head evidence

All predecessor-head workflow and review evidence is historical. Fresh exact-current-head workflows for f2b08715663946405719f742f2f5597a4f45ee4a are authoritative and non-passing until terminal. This PR remains Draft until every applicable repository and organization check is terminal-success, exact owned-production coverage is established, valid findings are zero, and qualifying independent current-head approval exists under live policy.

Standards, rollback, and remaining scope

The parent doctoring records RFC 6750, RFC 9700/BCP 240, RFC 8725, hash-only persistence, version-bound redemption, and audit durability. This runtime slice narrows only attachment-view URL authority. Rollback restores the prior client/route behavior together with focused regressions; it must not delete persisted grant/audit data without a separately reviewed migration decision.

This PR does not close #413. SSE bootstrap, calendar subscription rotation/revocation, PostgreSQL adapter parity, management UI/Figma flows, observability/quotas, and final rejection of all general session JWT query transports remain separate bounded work.

Merge gate

Do not integrate independently of #510 and #506. After the prerequisite stack reaches protected develop, reconcile this exact bounded semantic diff onto the resulting live head and rerun every then-applicable CI, browser E2E, coverage/docstring, SAST, security, dependency, supply-chain, package/provenance, migration/recovery, resolved-thread, and qualifying independent-approval gate. No predecessor-head, author-only, skipped, neutral, status-only, synthetic, or model-only evidence transfers.

Refs #413

@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c18dec0f-9748-41de-9a38-910e24783851

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@seonghobae
seonghobae marked this pull request as ready for review August 15, 2026 20:21

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@seonghobae
seonghobae marked this pull request as draft August 16, 2026 07:59
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