Skip to content

fix(cloud): close P0/P1 audit findings (agent auth, SSRF, reliability) - #424

Merged
mohabbis merged 3 commits into
masterfrom
cursor/audit-security-fixes-bddc
Aug 10, 2026
Merged

fix(cloud): close P0/P1 audit findings (agent auth, SSRF, reliability)#424
mohabbis merged 3 commits into
masterfrom
cursor/audit-security-fixes-bddc

Conversation

@mohabbis

@mohabbis mohabbis commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Summary

Closes the actionable P0/P1 findings from cloud/docs/ARCHITECTURE_DECISIONS.md that do not need a product redesign.

Trust & safety

  • Touches files / filesystem mutation: none

  • Touches OS input: none

  • Touches screenshots / screen contents: yes — skips screenshot capture for fill steps marked sensitive so OTP/card pixels never hit the artifact store

  • Touches network: yesnavigate URLs are checked against a public-URL policy (blocks cloud metadata + RFC1918; loopback allowed for fixtures; other private hosts only if they match APP_URL)

  • Touches authentication / secrets: yes/api/agent/* is bearer-only (session cookies refused); Auth.js routes are rate-limited; minting agent credentials requires OWNER/ADMIN

  • Touches app / window state: none

  • Risky actions remain deny-by-default

  • No silent delete or silent overwrite

  • Experimental features stay gated / out of scope

Changes

  • P0-1: resolveAgentPrincipal refuses session cookies — agents must use a minted API key
  • P0-3: Rate-limit Auth.js GET/POST via the existing Redis limiter (MFA/invite/audit already covered)
  • P1-5 (partial): Minting agent credentials is OWNER/ADMIN only
  • P1-6 (partial): checkPublicHttpUrl + author-time (authoredSteps) and runtime (applyStep) enforcement
  • P1-7: Each BullMQ Worker gets its own Redis connection
  • P1-8: GHOST_RUN_TIMEOUT_MS wall-clock budget (default 30m); heartbeat stops renewing past deadline
  • P1-2 (partial): No screenshots for sensitive fills
  • P1-11 + build race: scripts/require-test-env.mjs gates pnpm test; @ghost/core test depends on build
  • Docs: audit table in ARCHITECTURE_DECISIONS.md updated with status

Still open (need design): P1-1 checkpointed audit verify, P1-3/P1-4 secret refs + journal redaction, full P1-5 VIEWER/APPROVER roles, P1-9 per-org fairness, DNS-rebinding / --no-sandbox.

Validation

  • cd cloud && pnpm typecheck
  • cd cloud && pnpm test — core 241, web 115, worker 120, mcp 1 (all passed; local Postgres+Redis)
  • cd cloud && pnpm build

Risks / follow-up

  • Members can no longer mint agent API keys; Settings UI should surface the 403 clearly (admin-only).
  • Local pnpm test without DB now fails unless GHOST_ALLOW_SKIP_DB_TESTS=1.
  • Loopback navigate remains allowed so fixtures/e2e keep working; RFC1918 still blocked unless it matches APP_URL.
Open in Web Open in Cursor 

cursoragent and others added 2 commits August 10, 2026 06:45
Reject session cookies on /api/agent (bearer only), rate-limit Auth.js,
block navigate SSRF to metadata/RFC1918, give each BullMQ worker its own
Redis connection, add a run wall-clock timeout, skip screenshots on
sensitive fills, require admin to mint agent keys, and fail pnpm test
loudly when DATABASE_URL/REDIS_URL/GHOST_SESSION_KEY are unset.

Co-authored-by: Muhammad Rafiq <mohabbis@users.noreply.github.com>
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 6:49am

Keep navigateStep as a ZodObject so the discriminated union stays sound;
enforce public-URL checks in authoredSteps + applyStep. Fix unsigned IPv4
bitmask compares, serve driver fixtures over loopback HTTP, and type the
Auth.js rate-limit wrapper with NextRequest.

Co-authored-by: Muhammad Rafiq <mohabbis@users.noreply.github.com>
@mohabbis
mohabbis marked this pull request as ready for review August 10, 2026 12:42
@mohabbis
mohabbis merged commit 316614d into master Aug 10, 2026
19 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

assertPublicHttpUrl(step.url);
await page.goto(step.url, { waitUntil: "domcontentloaded", timeout });

P1 Badge Validate every redirect before navigation

When an allowed public URL responds with a redirect to 169.254.169.254 or an RFC1918 host, assertPublicHttpUrl approves only the initial URL and page.goto follows the redirect without another policy check; a subsequent extract step can therefore still read the private response. Enforce the policy on every browser request/redirect, including restore navigation, rather than only before the initial goto.

AGENTS.md reference: AGENTS.md:L203-L206


export function shouldCaptureScreenshot(step: WorkflowStep): boolean {
return !(step.type === "fill" && step.sensitive);

P1 Badge Keep screenshots suppressed after sensitive fills

When a sensitive fill is followed by any ordinary step while the value remains visible, only the fill's screenshot is skipped; the next click, waitFor, or verify calls runStep again and captures the same page, persisting the secret. The updated driver test itself fills a card number and then immediately takes a screenshot during the click, so suppression must remain active until the sensitive field is cleared, obscured, or the page changes.

AGENTS.md reference: AGENTS.md:L201-L201


const heartbeat = setInterval(() => {
if (Date.now() >= runDeadline) return;
void prisma.run

P1 Badge Abort in-flight work when the run deadline expires

When a step is still awaited at the deadline—for example, an unbounded waitFor.ms or a browser action whose timeout extends past the remaining run budget—the heartbeat stops here but the browser operation is not canceled, and the loop cannot raise RUN_TIMEOUT until that operation returns. This still allows a run to wedge the worker indefinitely and eventually lets the expired lease be reclaimed while the original executor remains active; the deadline needs to interrupt and close the in-flight browser work before lease renewal stops.


const required = ["DATABASE_URL", "REDIS_URL", "GHOST_SESSION_KEY"];
const missing = required.filter((k) => !process.env[k]?.trim());

P2 Badge Load cloud/.env before enforcing the test prerequisites

When a developer follows the emitted instruction and copies .env.example to cloud/.env (including via pnpm demo) without exporting those variables into the shell, this guard still reports all three as missing because Node does not load .env automatically and the check runs before Turbo or package-level environment loaders. Consequently the documented local pnpm test path always exits 1; load the cloud env file before checking or invoke the guard with an explicit env-file loader.


const result = await rateLimit(`${key}:${clientKey(req)}`, { limit, windowSeconds });

P1 Badge Derive auth rate-limit keys from a trusted client address

When the supported Node deployment is exposed directly or sits behind a proxy that preserves or appends a caller-supplied X-Forwarded-For, clientKey(req) trusts the first entry, so an attacker can rotate that header on every /api/auth/* POST and receive a fresh 20-attempt bucket each time. This leaves the sign-in brute-force path effectively unbounded outside proxies such as Vercel that overwrite the header; derive the key from trusted platform metadata or normalize forwarding headers at a trusted proxy boundary.

ℹ️ 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".

mohabbis added a commit that referenced this pull request Aug 10, 2026
* fix(cloud): tighten RBAC, redact extract UI, and cap audit verify

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>

* fix(cloud): gate run start through canStartRun

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>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Muhammad Rafiq <mohabbis@users.noreply.github.com>
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