Skip to content

fix: P0 auth and tenant-scoping security fixes (GH-036, GH-042, GH-046, GH-049, GH-052, GH-075, GH-102, GH-272) - #3573

Merged
tofikwest merged 6 commits into
mainfrom
fix/GH-036-p0-auth-fixes
Sep 23, 2026
Merged

tofikwest merged 6 commits into
mainfrom
fix/GH-036-p0-auth-fixes

Conversation

@dennisofficial

@dennisofficial dennisofficial commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Draft PR fixing eight P0 security findings tracked in Glass House. All changes are minimal, in-place fixes — no refactoring.

  • Notifications / Novu #36 — SoA endpoints trusted organizationId from the request body. Every SOAController handler now overwrites dto.organizationId with the session-derived @OrganizationId() value before calling the service (previously only get-setup and export did). Closes cross-tenant read/tamper/destroy of any org's ISO 27001 SoA, including the auto-fill SSE exfil path.
  • Feature Request: Trust Portal #42 — POST /v1/trust-access/:org/reclaim returned the live access link/token in the HTTP response and 404'd when no grant existed (email-enumeration oracle). It now returns an identical generic message either way and only emails the link to the requester.
  • use bun instead of npm #46 — task-automation IDOR: AutomationsService keyed lookups on automationId alone. A shared verifyAutomationAccess helper now confirms the automation belongs to the task in the URL and the caller's organization before any read/update/delete/run/version operation.
  • run seed on merge to upsert #49 / chore: Update branding assets and dependencies #75 (same defect, filed twice) — device-agent-release.yml fired on push to any branch and ran branch-controlled build scripts (bun install postinstall, package:mac|win, electron-builder.config.js) with Apple + SSL.com code-signing secrets in scope. Push trigger is now limited to main/release (manual staging builds remain via workflow_dispatch), and the secret-bearing jobs (build-macos, build-windows, upload-s3) are gated behind the staging/production GitHub environments.
  • feat: editor switch to liveblocks #52 — the 12 task-automation 'use server' actions did no authn/authz and accepted orgId from the caller. All now resolve the session and fail closed when unauthenticated; caller-supplied orgId must equal the session's active org; S3 keys must be prefixed with the active org; automationId-only actions verify ownership via the automation's task before proxying to the enterprise API.
  • Add more apis #102 — healAndSetAccessToken / createAccessToken minted a Trigger.dev run-read token for any run id with no auth. Both now require a session with an active organization and only mint when the run id is recorded against that org (onboarding job, knowledge base document, or remediation batch).
  • no toast or invite to portal #272 — findPublishedTrustByRouteId auto-created a Trust row with status: 'published' (or flipped a draft to published) when called from unauthenticated endpoints. It now only resolves already-published rows and 404s otherwise; publishing remains exclusively behind the authenticated, permission-gated settings controller.

Verification

  • apps/api: NODE_ENV=test bunx jest src/soa src/tasks/automations src/trust-portal — 228 tests pass, including new specs asserting the org-override per SoA handler, cross-tenant 404s for automations, the generic reclaim response, and no-create/no-publish behavior for unpublished trust rows. Two controller suites fail at module load with a pre-existing DATABASE_URL/TLS guard error — reproduced identically at origin/main, unrelated to this diff.
  • apps/app: bunx vitest run src/actions/trigger/heal-access-token.test.ts — 10/10 pass (new spec: no session, no active org, unowned run id, per-model ownership, cross-org rejection).
  • Typecheck: tsc --noEmit for apps/api and apps/app shows zero errors in any touched file; remaining errors are pre-existing in unrelated spec files.
  • Lint: changed app files are eslint-clean; new API spec code follows the existing spec style (baseline already carries the same unbound-method error class on these files; lint is not CI-gated).
  • device-agent-release.yml validated as parseable YAML.

Product decisions to be aware of

  1. Signing-secret rotation (run seed on merge to upsert #49/chore: Update branding assets and dependencies #75) — the workflow is now gated, but the findings recommend treating the Apple/SSL.com credentials as compromised and rotating them. That's an operational step outside this PR.
  2. Environment protection rules (run seed on merge to upsert #49/chore: Update branding assets and dependencies #75) — the environment: gates only bite if the staging and production environments have protection rules (required reviewers / restricted branches) configured in repo settings. Worth confirming after merge.
  3. Feature-branch staging builds — pushes to feature branches no longer auto-build staging device-agent releases; use workflow_dispatch for those.
  4. getAutomationRunStatus residual gap (feat: editor switch to liveblocks #52) — enterprise/trigger run ids have no org mapping in this app's database, so that action can only require an authenticated session, not verify run ownership. Closing it fully needs the enterprise service to scope runs by org.
  5. Reclaim rate limiting (Feature Request: Trust Portal #42) — the finding also suggests per-IP/org rate limiting on the reclaim endpoint; not implemented here (generic response already removes the enumeration signal).

Summary by cubic

Fixes eight P0 security issues in auth and tenant-scoping across the API and web app.

Security fixes

  • SoA endpoints now overwrite organizationId from the request body with the session-derived org.
  • Task-automation reads, updates, deletes, runs, and versions verify the automation belongs to the URL task and caller's org.
  • Trust-portal reclaim returns a generic message with no access link or token, whether or not a grant exists.
  • Public trust endpoints no longer auto-create or auto-publish trust rows; they only resolve already-published rows.
  • Task-automation server actions now require a session, enforce the active org for orgId and S3 keys, and verify automation ownership.
  • Trigger.dev access-token minting requires a session and verifies the run id is recorded for the active org.

Operational notes

  • Device-agent release workflow is restricted to main/release; manual staging builds are via workflow_dispatch, and secret-bearing jobs are gated behind staging/production environments — confirm those environments have protection rules configured.
  • Rotate the Apple and SSL.com signing secrets.
  • Rate limiting on the reclaim endpoint isn't implemented in this PR.
  • getAutomationRunStatus only enforces an authenticated session since run ids can't be mapped to orgs locally.

Written for commit 849a320. Summary will update on new commits.

Review in cubic

Every SOA handler now overwrites dto.organizationId with the trusted
@organizationId() session value before calling the service, closing the
cross-tenant read/tamper/destroy gap in save-answer, auto-fill,
create-document, ensure-setup, approve, decline, and submit-for-approval.

Refs GH-36
automationId lookups are now verified against the task in the URL and
the caller's organization via a shared verifyAutomationAccess helper, so
an automationId from another org's task 404s on read, update, delete,
runs, versions, and publish instead of leaking or mutating it.

Refs GH-46
reclaimAccess no longer returns the access link/token in the response
body and returns an identical generic message whether or not a grant
exists, removing the unauthenticated token disclosure and the
email-enumeration oracle; the link is only emailed to the requester.

findPublishedTrustByRouteId no longer auto-creates a published Trust row
or flips drafts to published: public endpoints only resolve rows that
are already published and 404 otherwise, so an unauthenticated caller
can no longer force-publish an organization's trust portal.

Refs GH-42, GH-272
Every exported server action now resolves the caller's session and
active organization and fails closed when unauthenticated. Actions that
take orgId must match the session's active org; S3 keys must be prefixed
with the active org; automationId-only actions verify ownership through
the automation's task before proxying to the enterprise API.

Refs GH-52
healAndSetAccessToken and createAccessToken now require a session with
an active organization and only mint a Trigger.dev run-read token when
the run id is recorded against that organization (onboarding job,
knowledge base document, or remediation batch).

Refs GH-102
The device-agent release pipeline runs branch-controlled build scripts
with Apple and SSL.com code-signing secrets in scope, so push triggers
are now limited to main and release (manual staging builds remain via
workflow_dispatch), and the secret-bearing jobs are gated behind the
staging/production GitHub environments.

Refs GH-49, GH-75
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@dennisofficial
dennisofficial marked this pull request as ready for review September 22, 2026 16:48

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 13 files

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread apps/app/src/actions/trigger/heal-access-token.ts
Comment thread apps/api/src/tasks/automations/automations.service.ts
Comment thread .github/workflows/device-agent-release.yml
Comment thread .github/workflows/device-agent-release.yml
Comment thread apps/api/src/soa/soa.controller.ts
Comment thread apps/app/src/actions/trigger/heal-access-token.test.ts
Comment thread apps/api/src/trust-portal/trust-access.service.ts
@tofikwest
tofikwest merged commit 0ccfcc2 into main Sep 23, 2026
1 of 2 checks passed
@tofikwest
tofikwest deleted the fix/GH-036-p0-auth-fixes branch September 23, 2026 14:49
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.

3 participants