Skip to content

fix(cloud): RBAC matrix, sensitive-fill hygiene, audit verify cap - #425

Merged
mohabbis merged 2 commits into
masterfrom
cursor/audit-rbac-secrets-bddc
Aug 10, 2026
Merged

fix(cloud): RBAC matrix, sensitive-fill hygiene, audit verify cap#425
mohabbis merged 2 commits into
masterfrom
cursor/audit-rbac-secrets-bddc

Conversation

@mohabbis

@mohabbis mohabbis commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Summary

Follow-up to #424. Closes the remaining safe P1 leftovers from cloud/docs/ARCHITECTURE_DECISIONS.md without waiting on connectors (P1-3) or VIEWER/APPROVER schema work.

Trust & safety

  • Touches files / filesystem mutation: none

  • Touches OS input: none

  • Touches screenshots / screen contents: worker skips screenshots for sensitive fills (fill.sensitive or classifyStep gate); editor masks sensitive fill values with type="password"

  • Touches network: none new

  • Touches authentication / secrets: role gates on publish/create/approve/start; run timeline no longer ships extract cleartext (output: null)

  • Touches app / window state: none

  • Risky actions remain deny-by-default (approve/publish admin-only; reject stays open)

  • No silent delete or silent overwrite

  • New Tauri commands have a module and a risk class — N/A (cloud only)

  • Experimental features stay gated — N/A

Changes

  • P1-5: canPublishWorkflow / canApproveRun → OWNER/ADMIN; canStartRun → any member; wired into create/publish/demo/settings PATCH, approve route, POST /api/runs, and buildRunView.canApprove
  • P1-2: password input for sensitive fills; shouldCaptureScreenshot also consults classifyStep; unit tests added
  • P1-4 mitigation: run UI always returns output: null (journal still holds values for {{ }} templating)
  • P1-1: /api/audit/verify defaults to mode=head; mode=full capped by GHOST_AUDIT_VERIFY_MAX_EVENTS (default 50k → 413); audit page uses expected-head check
  • Docs / .env.example / turbo.json updated

Validation

Tip SHA: 5ed77f1

  • pnpm typecheck (cloud) — pass
  • pnpm test (cloud) — pass (worker 124; full suite green under Postgres/Redis)
  • pnpm build (cloud) — pass

Legacy desktop cargo/make checks intentionally not run (cloud-only change).

Risks / follow-up

Still open (need design / larger PR):

  • P1-3 secret references (blocked on connectors)
  • Real P1-4 journal payload allow-list / erasable side store
  • VIEWER / APPROVER roles
  • P1-9 per-org fairness
  • P1-6 DNS-rebinding + env-gated --no-sandbox
  • Durable checkpointed full audit verify
Open in Web Open in Cursor 

cursoragent and others added 2 commits August 10, 2026 17:39
Close remaining safe P1 leftovers after #424:
- OWNER/ADMIN-only publish/create/approve; MEMBER may start and reject
- Password inputs + classifyStep-aligned screenshot skip for sensitive fills
- Run timeline never ships extract cleartext to the browser
- Audit verify defaults to head mode with a hard full-chain cap

Co-authored-by: Muhammad Rafiq <mohabbis@users.noreply.github.com>
Wire the role helper into POST /api/runs so future VIEWER restrictions
do not need a second pass over the start path.

Co-authored-by: Muhammad Rafiq <mohabbis@users.noreply.github.com>
@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ghost Ready Ready Preview Aug 10, 2026 5:40pm

@mohabbis
mohabbis marked this pull request as ready for review August 10, 2026 17:47
@mohabbis
mohabbis merged commit 61cecb3 into master Aug 10, 2026
18 checks passed

@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: 5ed77f13f1

ℹ️ 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 +53 to +54
if (approve) {
const actor = await loadActor(orgId, userId);

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 current membership before allowing rejection

When an administrator removes a user from the organization, that user's JWT can remain valid for up to 12 hours, but loadActor runs only for approve. The removed user can therefore still submit decision: "reject" for a known pending gate, marking a forward run FAILED or rejecting compensation even though they are no longer the “member” this policy intends to authorize. Load and require a current actor for both decisions, then apply the OWNER/ADMIN capability check only to approval.

AGENTS.md reference: AGENTS.md:L101-L112

Useful? React with 👍 / 👎.

Comment on lines 125 to 127
where: { runId },
orderBy: { seq: "asc" },
});

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 Enforce the run-journal cap before loading events

When runId refers to a journal larger than GHOST_AUDIT_VERIFY_MAX_EVENTS, this query materializes the entire journal before checking runEvents.length and returning 413. A run can have far more journal events than the organization audit-event count used by the earlier guard, so an authenticated full-verify request can still consume the web tier's memory and CPU—the failure this cap is meant to prevent. Count first or query at most cap + 1 rows before attempting verification.

Useful? React with 👍 / 👎.

Comment on lines +47 to +50
const orgRow = await prisma.organization.findUniqueOrThrow({
where: { id: orgId },
select: { auditChainHead: true },
});

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 Read the expected and actual audit heads atomically

When an audit event is appended between this organization-head read and the later tail query, the statements observe different snapshots: orgRow.auditChainHead can be the old head while tail.hash is the newly committed head, causing an intact chain to be returned as intact: false. The audit page has the same race because its head and tail queries run separately. Read both values in a repeatable-read transaction or otherwise from one database snapshot so ordinary concurrent activity cannot produce a false tampering alarm.

AGENTS.md reference: AGENTS.md:L19-L25

Useful? React with 👍 / 👎.

Comment on lines +75 to +78
if (runId) {
const runHead = await verifyRunHead(orgId, runId);
if (!runHead) return NextResponse.json({ error: "not found" }, { status: 404 });
body.run = runHead;

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 Preserve the organization anchor in head-mode run verification

When the terminal run timeline calls /api/audit/verify?runId=... without an explicit mode, this assignment returns verifyRunHead, which omits the previous anchored and anchorMatches checks. The UI therefore labels the journal “intact” solely because its tail matches the mutable Run.journalHead and silently suppresses the anchor status; rewriting both the journal and that column now passes even when the independently recorded organization-ledger anchor disagrees. Retain the anchor comparison in head mode or have this integrity UI request full verification.

AGENTS.md reference: AGENTS.md:L19-L25

Useful? React with 👍 / 👎.

Comment on lines +31 to +33
const actor = await loadActor(orgId, userId);
if (!actor || !canPublishWorkflow(actor.role)) {
return Response.json({ error: "forbidden" }, { status: 403 });

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 Hide admin-only workflow controls from members

When a MEMBER uses the existing workflow UI, the unchanged /workflows, /workflows/new, and workflow-detail pages still expose Create demo, New workflow, Publish, concurrency, and separate-approver controls even though these new checks reject every corresponding request with 403. In particular, CreateDemoButton swallows the failure and appears to do nothing, while the other controls let members edit before failing at save. Read the membership role in these server pages and hide or disable the admin-only controls while leaving Run available.

Useful? React with 👍 / 👎.

Comment on lines +341 to +345
| P1-1 | `/api/audit/verify` loads the entire org chain with no pagination. | The "prove your audit log is intact" feature is the first thing that breaks at scale, and it takes the web tier with it. | **Partial.** Default `mode=head` (expected-tail check); `mode=full` capped by `GHOST_AUDIT_VERIFY_MAX_EVENTS` (413 over cap). Audit page no longer loads the full chain. Durable checkpointed walk still open. |
| P1-2 | Screenshots are captured after every step, including `fill` steps marked `sensitive`, and `step-*.png` is on the servable allow-list. OTP and card fields are `type="text"`, so nothing masks them. | Cardholder data at rest in the blob store. | **Partial.** Worker skips screenshots when `fill.sensitive` **or** `classifyStep` gates the fill; editor uses `type="password"` for sensitive fills. Next-step bleed / secret references still open. |
| P1-3 | Secret values are stored in plaintext in the workflow definition; `sensitive` is a label. There is no secret-reference mechanism. | A database dump is every customer credential. The values also leave via the agent API, which returns `latestVersion.steps` verbatim. | Open — blocked on connector credentials (§5 step 6). |
| P1-4 | `extract` outputs are written into the hash-chained journal in cleartext. | A GDPR erasure request against an intentionally immutable chain is unresolvable. Fix the shape before there is data in it. | Open — needs journal payload allow-list / redaction design. |
| P1-5 | **No RBAC on any business operation.** A `MEMBER` can publish workflows, start runs, approve sensitive steps, and mint agent keys. Separation of duties is enforced (approver ≠ triggerer) but any two colleagues satisfy it. | "Human approval" currently means "anyone with a login." | **Partial.** Minting agent credentials is now OWNER/ADMIN only. Publish / start-run / approve still any member; VIEWER/APPROVER roles not added yet. |
| P1-4 | `extract` outputs are written into the hash-chained journal in cleartext. | A GDPR erasure request against an intentionally immutable chain is unresolvable. Fix the shape before there is data in it. | **Partial.** Org audit never carried extract text; run timeline no longer ships `RunStep.output` to the browser. Journal payload allow-list / erasable side store still open. |
| P1-5 | **No RBAC on any business operation.** A `MEMBER` can publish workflows, start runs, approve sensitive steps, and mint agent keys. Separation of duties is enforced (approver ≠ triggerer) but any two colleagues satisfy it. | "Human approval" currently means "anyone with a login." | **Partial.** Mint / publish / create / approve are OWNER/ADMIN only; start + reject stay open to MEMBER. VIEWER/APPROVER roles not added yet. |

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 Update the canonical trust-pipeline documentation

This change alters cloud API and trust behavior—the publish/approval role matrix, audit verification modes and limits, and sensitive-screenshot handling—but only updates cloud/docs/ARCHITECTURE_DECISIONS.md. The matching canonical docs/trust-pipeline.md still describes approval and verification without these authorization and integrity boundaries, even though repository instructions require cloud API/job/trust behavior changes to update both cloud docs and the trust-pipeline document in the same change.

AGENTS.md reference: AGENTS.md:L223-L228

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.

2 participants