Name the Project on approval cards and refuse another Organization's approval#2194

merged1
main
agent-sandbox/fu4-approval-project
opened26d agobyNNick
View on GitHubGitHub import enabledPR last imported 21d ago

Two problems with approvals.

Cards did not name the Project. Two pending Deploys to production in different Projects both read "Deploy production". A Store write's card now reads Deploy shop/production. Operations keep their own name, such as "Drain fra-1". ployz org approval ls output is unchanged.

Another Organization's approval id saved unasked. The config handler checked x-ployz-approval only when the Organization had an approver. In an anonymous Organization, an id from another Organization was ignored and the write saved. The handler now checks the id on every write that asks for approval, before hasApprover. An id this Organization cannot see is refused with not_found "No such approval.", the shape gateOperation uses, where before it was invalid_argument. Writes that never ask still ignore the header.

An approval from the same Organization but another Project or command still cannot pass, because its approved digest has to match the plan.

Bets

  • Agent sidebar: the Approval card. Its title now names the Project. Approving a write in one Organization never counts in another. The bet is unchanged.

Tests

  • approvals.postgres.test.ts, "an approval from another Organization is no approval here, whether or not this one asks, and nothing saves": a foreign id gets 404 not_found in both a claimed and an anonymous Organization, and nothing is written. The Organization's own approved id then publishes.
  • approval-card.test.tsx, "names the Project, so two Deploys to production read apart".

Both fail on main and pass here. The anonymous case was the silent save.

Checks

1 pending Β· 10 successful Β· 6 skipped
Checks in progress

In progress1

ab5f383
Synced from GitHub

Codex Review Summary

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

ReviewStatusCommitReview trigger
πŸ“ Code Review⚠️ Failed 2026-10-17T13:52:24.234856Z1b85264PR opened
ℹ️ 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.

Synced from GitHub

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Synced from GitHub

Review in Change Stack β†’

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

βš™οΈ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 317d004a-1852-4063-873e-642284a13ddc
πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between f145fac4b26bafa0d171f283f09aa3f9689fcdb5 and ab5f383be907ab5fa2f8f44da8fd2537ae3a4800.

πŸ“’ Files selected for processing: 1
  • dashboard/src/routes/api/config/-config.handler.ts
ascii
1+---------------------------------------------------------------------------+
2| Edge cases called - they'd like to be included in your 'done' definition. |
3+---------------------------------------------------------------------------+
4 \
5 \ (\__/)
6 (β€’γ……β€’)
7 / γ€€ γ₯

No actionable comments were generated in the recent review. πŸŽ‰

ℹ️ Recent review info
βš™οΈ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: f2d37434-31b4-4105-85bf-e6e1b1401dea
πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between f145fac4b26bafa0d171f283f09aa3f9689fcdb5 and f145fac4b26bafa0d171f283f09aa3f9689fcdb5.

πŸ“’ Files selected for processing: 10
  • dashboard/src/modules/identity/claim.server.postgres.test.ts
  • dashboard/src/modules/identity/claim.server.ts
  • dashboard/src/modules/identity/verification-sweep.inngest.ts
  • dashboard/src/modules/identity/verification-sweep.server.postgres.test.ts
  • dashboard/src/modules/identity/verification-sweep.server.ts
  • dashboard/src/modules/inngest/index.ts
  • dashboard/src/modules/inngest/policies.test.ts
  • dashboard/src/routes/claim/$secret.tsx
  • dashboard/src/routes/claim/-components/use-restored.test.tsx
  • dashboard/src/routes/claim/-components/use-restored.ts
πŸ“ Summary

Summary by CodeRabbit

  • New Features
    • Approval cards include the project in the displayed resource location, helping distinguish similarly named resources.
    • Expired sign-in verification records are now cleaned up automatically.
  • Bug Fixes
    • Unknown approval IDs, including IDs from another organization, now return a not-found response.
    • Approval IDs are validated even when no approver is configured, preventing invalid approvals from being accepted.
    • Invalid claim links now show a not-found page, and restored claim pages reload their status.

Approval display and validation now use project identity and return not_found for missing or foreign IDs. Claim handling distinguishes unknown secrets from expired claims, and claim pages handle not-found states and persisted page restoration. An hourly Inngest function deletes expired verification rows.

Changes: Approval handling

Layer / File(s)Summary
Approval subject display
dashboard/src/modules/approvals/approvals.ts, dashboard/src/modules/approvals/approvals.commands.server.ts, dashboard/src/components/agent-sidebar/approval-card.tsx, dashboard/src/components/agent-sidebar/approval-card.test.tsx
Diff approval subjects include project identity. Pending cards use project/name when a project exists, and tests check project-qualified Deploy and Save labels.
Organization-scoped approval validation
dashboard/src/modules/approvals/approvals.server.ts, dashboard/src/routes/api/config/-config.handler.ts, dashboard/src/modules/approvals/approvals.postgres.test.ts
The config handler validates supplied approvals even when the organization has no approver. Missing or foreign approval IDs return not_found; tests verify retry behavior and confirm foreign IDs do not write to the Store.

Claim secret handling

Layer / File(s)Summary
Claim secret states
dashboard/src/modules/identity/claim.server.ts, dashboard/src/modules/identity/claim.server.postgres.test.ts
Claim reads distinguish unknown secrets (not_found) from retired secrets (expired). Tests check invalid and modified secrets.
Claim page states and restoration
dashboard/src/routes/claim/$secret.tsx, dashboard/src/routes/claim/-components/use-restored.ts, dashboard/src/routes/claim/-components/use-restored.test.tsx
The claim page renders a not-found view with links based on sign-in state and reloads after persisted page restoration. The hook listens for persisted pageshow events and removes its listener on cleanup.

Expired verification sweep

Layer / File(s)Summary
Verification row sweep
dashboard/src/modules/identity/verification-sweep.server.ts, dashboard/src/modules/identity/verification-sweep.server.postgres.test.ts
The server-only sweep deletes verification rows that expired before the database’s current time. A database-backed test checks that unexpired rows remain.
Sweep scheduling and registration
dashboard/src/modules/identity/verification-sweep.inngest.ts, dashboard/src/modules/inngest/index.ts, dashboard/src/modules/inngest/policies.test.ts
The hourly sweep function uses three retries and concurrency limited to one. The Inngest registry and policy tests include the function.

Priority: βž– Normal

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

Change: Bug fix

Sequence Diagram(s)

mermaid
1sequenceDiagram
2 participant Inngest
3 participant SweepFunction
4 participant Database
5 Inngest->>SweepFunction: Run hourly sweep step
6 SweepFunction->>Database: Delete rows expired before now
7 Database-->>SweepFunction: Return deletion result
Merge Risk: βšͺ Minimal Β· up to f145f

The approval changes appear mergeable after normal checks. No unresolved issue introduced by this PR was identified.

Security architecture risk: πŸ”΅ Low Β· up to f145f

The approval changes strengthen organization isolation, and claim ownership checks remain intact. Remaining uncertainty concerns automatic cleanup across authentication record types whose expiration and replay-protection rules have not all been verified.

Retained concerns No architecture-level concerns identified.

Security review details

Security Blast Radius

  • observed β€” The cleanup's maximum data scope is expired verification records across all tenants sharing its database: the DELETE has no organization filter. Its predicate and schedule are fixed rather than request-controlled. Configuration writes remain scoped to the resolved caller's organization.

Security Findings and Attack Paths

  • observed β€” An unauthenticated caller can now distinguish an unmatched claim-secret hash from a recognized expired or retired link. This is additional observable state relative to the base, but neither terminal response includes organization details. Recognition requires a matching secret derived with HMAC-SHA256, and retired secrets still cannot claim an organization. The inspected evidence does not establish a material authorization or confidentiality defect from this distinction.

Trust Boundaries and Controls

  • observed β€” Approval lookup binds an attacker-supplied ID to the caller's organization. For approval-eligible writes, foreign IDs now fail before Store execution even in anonymous organizations. Where approval is required, an approved digest must match the Store's computed plan. Anonymous-only organizations deliberately retain not_required behavior, so the header is not an authorization requirement there.
  • observed β€” The existing approval lifecycle retains organization-scoped recording, transactional advisory locking, pending-plan deduplication, agent decision refusal, digest-bound approval decisions, and a pending-status conditional update. The PR changes validation ordering and presentation rather than weakening these decision controls.

Resilience and Maintainability Implications

  • inferred β€” The sweep is retry-compatible because each execution is a single expiration-filtered DELETE; repeated execution does not restore consumed state or extend validity. Failures retain their cause and propagate to the scheduler. Local claim bindings are consumed by delete-and-return and accepted only while unexpired, limiting interference with concurrent cleanup. Recovery after exhausted retries and the external authentication library's complete concurrency and expiry semantics remain unverified.

Hardening Proposals

  • proposed β€” Before relying on shared-table cleanup, validate the actual dependency contracts for OAuth state, authorization codes, and DPoP replay reservations. In particular, establish that reservation expiry covers the full proof-acceptance window and that concurrent cleanup, consumption, and retries cannot revive a credential or replay a proof. The current sweep test uses seeded record shapes rather than those end-to-end security flows.
Pre-merge checks: Passed 4 Β· Failed 1

Failed Failed checks: 1 warning

Check nameStatusExplanationResolution
Docstring CoverageWarningDocstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 9 functions across 17 files.Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check nameStatusExplanation
Title checkPassedThe title clearly summarizes both primary changes: naming Projects on approval cards and rejecting approvals from another Organization.
Description checkPassedThe description is directly related to the changeset and explains the approval-card behavior, cross-Organization approval rejection, and associated tests.
Linked Issues checkPassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes checkPassedCheck skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
πŸ“ Generate docstrings
  • Commit to this branch
  • Create a new PR
πŸ§ͺ Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix Β· Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

Added 1 commit

  • Name the Project on approval cards and refuse another Organization's approvalab5f383