Skip to content

[comp] Production Deploy#3463

Merged
tofikwest merged 10 commits into
releasefrom
main
Jul 22, 2026
Merged

[comp] Production Deploy#3463
tofikwest merged 10 commits into
releasefrom
main

Conversation

@github-actions

@github-actions github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

This is an automated pull request to release the candidate branch into production, which will trigger a deployment.
It was created by the [Production PR] action.


Summary by cubic

Ships Internal Audit for ISMS (ISO 27001 clause 9.2, CS‑724): new document with an editable programme, audits register (IA‑YYYY‑NN), Controls Tested, findings, sign‑off, export, and submit gates; includes a DB migration and UI/API changes. Also deploys security fixes—upgrade adm-zip to ^0.6.0, pin engine.io to ^6.6.7, bump body-parser to 2.3.0, and override tar to ^7.5.19—changes policy regenerate to create a new draft, fixes the create‑policy‑version tool schema by renaming the policies DTO to CreatePolicyVersionDto, cleans up detached policy PDFs on draft regenerate, attributes API‑key/service‑token actions to the real actor with full audit logging, and adds a security review workflow/skill.

  • Bug Fixes
    • Enforce audit schedule order: reject an end date before the start date (client + server under document lock).
    • Ensure findings always have a clause/control label: inherit from the linked control; label before control deletion.
    • Controls Tested UX: optional Notes on add; render control‑reference errors; raise a prefilled finding when marking NC/observation.
    • Preserve programme line breaks; ignore whitespace‑only signatories; keep sign‑off input stable during list refreshes.
    • Seed the programme only when empty; make seed/regenerate paths atomic and consistent.
    • Attribute and audit‑log non‑session actions to the correct user/member: store API key creator, resolve actor in the interceptor and controllers (vendors, cloud‑security scan, ISMS bulk measurements/rows), include “[via API key …]” markers, restrict service‑token x‑user‑id to active members, and fix policy singularization in audit text.

Written for commit 206ed96. Summary will update on new commits.

Review in cubic

github-actions Bot and others added 2 commits July 20, 2026 23:09
…) (#3462)

adm-zip < 0.6.0 allows a crafted ZIP to trigger a 4GB memory allocation
(DoS). This is reachable: apps/api/src/questionnaire/utils/content-extractor.ts
runs `new AdmZip(fileBuffer)` on user-uploaded questionnaire files (xlsx/docx
are ZIP containers).

Bumps the direct dependency to ^0.6.0 (patched) and updates bun.lock. Our usage
(new AdmZip, getEntry, getData, addFile, toBuffer) is unchanged in 0.6.0.
Supersedes Dependabot PR #3451, which bumped package.json but left bun.lock
out of sync.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 21, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
app (staging) Ready Ready Preview, Comment Jul 22, 2026 3:07am
comp-framework-editor (staging) Ready Ready Preview, Comment Jul 22, 2026 3:07am
portal (staging) Ready Ready Preview, Comment Jul 22, 2026 3:07am

Request Review

@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.

No issues found across 2 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Re-trigger cubic

…rts (#3464)

- engine.io: add bun override to ^6.6.7. The vulnerable 6.5.5 (Engine.IO
  polling connection-exhaustion DoS, <6.6.7) was pulled transitively via
  @trigger.dev/core -> socket.io@4.7.4. Override collapses it to 6.6.9 (the
  version top-level socket.io@4.8.3 already uses); bun.lock now has a single
  engine.io@6.6.9. Not a reachable DoS for us (we run no engine.io server;
  trigger.dev uses it client-side) but removes the alert.

- body-parser: bump the transitive dep in apps/mcp-server (runtime, via
  express@5.2.1) 2.2.2 -> 2.3.0 (patched; DoS via invalid limit silently
  disabling size enforcement). Lockfile-only; express's ^2.2.1 range permits
  2.3.0, so it stays transitive and survives SDK regeneration.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
#94-#104) (#3466)

A batch of 11 node-tar advisories (Critical->Moderate: decompression DoS, path
traversal, symlink/hardlink escape, PAX parsing differentials) flagged every
tar copy below 7.5.19. bun.lock had tar@6.2.1 (x5, via build tooling: node-gyp,
cacache, @electron/rebuild, app-builder-lib, giget), tar@7.5.11 (fleetctl), and
tar@7.5.20 (already patched).

node-tar backported no fixes to the 6.x line, so the only remediation is
tar >= 7.5.19. Added a bun override "tar": "^7.5.19" — bun.lock now resolves a
single tar@7.5.20 for all consumers. tar@7 is a dual CJS/ESM package with a
stable extract/create API, so the CJS build tools keep working via require().

Reachability was low pre-fix (all vulnerable copies were build-time tooling or
trusted-source CLI extraction, none extract attacker-controlled archives in the
runtime API), but the override removes the vulnerable versions entirely.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…S-724) (#3468)

* feat(isms): internal audit programme, plan and report — clause 9.2 (CS-724)

Adds the ninth ISMS document type: Compliance > ISMS > Internal Audit.

- Programme paragraph (editable, seeded with a default) rendered verbatim
  into the generated document
- Audit instances with server-generated IA-YYYY-NN references, templated
  scope/criteria, auditor pulled from ISMS > Roles > Internal Auditor,
  planned dates, status, and a conclusion verdict + narrative
- Controls Tested table seeded with the default 15-row sample set
  (management-system clauses + high-impact Annex A controls); result +
  notes recorded per row
- Findings with server-generated F-NN references, type/owner/due
  date/status/closure evidence, optional link back to a control row
- Three sign-off slots per audit, frozen into the published version
  snapshot and rendered as the document's sign-off table
- Submit gate: at least one audit, and a conclusion verdict on every
  completed audit (enforced server-side, mirrored in the client)

* fix(isms): align internal-audit components with design-system prop types

The Vercel next build enforces the design-system unions that the local
tsc pass missed: Stack gap has no '5', TableCell omits className, and
Select.onValueChange passes string | null.

* fix(isms): address internal-audit review findings

- scope audit/control/finding lookups to the internal-audit document type
- seed the programme paragraph only when the draft narrative is empty, so
  a concurrent setup call can never overwrite an edit
- trim required text fields (scope, criteria, control reference) so
  whitespace-only values are rejected
- marking a control row non-conformity/observation now opens the
  findings form pre-filled with the linked row (per the ticket flow),
  and the edit form pre-fills clause text like the add form
- preserve unsaved sign-off input across sibling register refreshes
- gate the control-row Save button on validity
- document the audit register fields in the OpenAPI body schema
- cover the internal-audit derivation path in generate.spec

* fix(isms): make the programme seed atomic and re-key the finding prompt

- seed the programme via a conditional update that matches only a NULL
  draft narrative, removing the read-then-write race entirely (and with
  it the duplicated empty-narrative predicate)
- key the prefilled linked-finding form by row and type, so re-marking
  the same control with a different result re-initializes the form

* fix(isms): treat an empty-object narrative as seedable, matching generate

The programme seed's conditional update now matches NULL or {} — the
same empty definition generateNarrative uses — so the two seed paths
can never drift while still never overwriting a populated draft.

* feat(isms): render closure evidence in the exported findings table

Closed corrective actions now show how they were evidenced in the
generated document — appended to the description cell, keeping the
reference document's seven-column findings table.

---------

Co-authored-by: Tofik Hasanov <annexcies@gmail.com>

@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 60 files (changes from recent commits).

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

Re-trigger cubic

Comment thread apps/app/src/app/(app)/[orgId]/documents/isms/components/audit-schema.ts Outdated
Comment thread apps/api/src/isms/isms-audit-finding.service.ts
Comment thread apps/api/src/isms/isms-audit-control.service.ts
Comment thread apps/api/src/isms/isms.service.ts
Comment thread apps/app/src/app/(app)/[orgId]/documents/isms/components/AuditControlRow.tsx Outdated
Comment thread apps/app/src/app/(app)/[orgId]/documents/isms/components/ProgrammeCard.tsx Outdated
Comment thread apps/app/src/app/(app)/[orgId]/documents/isms/components/AuditSignoffCard.tsx Outdated
…verwriting published (#3471)

* fix(policies): create draft version on policy regenerate instead of overwriting published

## Problem

When regenerating a policy, the published version's content is overwritten immediately. This bypasses the approval workflow that should gate any content changes. Regenerating a published policy currently mutates its live content, signedBy metadata, and currentVersionId directly without creating a draft for review.

## Root cause

updatePolicyInDatabase in update-policy-helpers.ts rewrites policy.content, currentVersionId, and signedBy on the live published policy record without changing status or requiring approval. Every other content-change path (createVersion, submitForApproval, acceptChanges) creates a draft version first and only publishes on explicit approval; regenerate was the sole exception.

## Fix

For published or needs_review policies, regenerate now creates a new draft version (incrementing the version number) with the regenerated content, leaving the published version's content, currentVersionId, signedBy, and status unchanged. This mirrors the existing approval workflow: draft versions are created, then approved, then published with the signing flow triggered for all employees.

## Explicitly NOT touched

- Policies not yet published (status: draft)
- The signing flow (runs only on explicit publish, unchanged)
- Approval/rejection workflows
- Version history or deletion logic

## Verification

Added regression test asserting that regenerating a published policy creates a new draft version without modifying the published content or status. Existing policy regenerate and versioning unit tests pass locally ✅

* fix(bug-todo): address cubic review

Address review findings.

* fix(bug-todo): address cubic review

Address review findings.

@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 3 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread apps/api/src/trigger/policies/update-policy-helpers.ts
…y review (#3473)

* fix(isms): harden internal-audit validation and edge cases from deploy review

- reject a planned end date before the start date, client-side (zod
  cross-field rule with inline error) and server-side against the
  effective merged schedule on update
- linked findings inherit the control row's reference as their clause
  label when none is given, and deleting a control row labels its
  label-less findings first — no finding is ever left unlabelled
- add the optional Notes field to the add-control-row form
- render the control-reference validation error in the row editor
- preserve paragraph breaks when displaying the programme
- don't count a whitespace-only signatory as signed
- correct the F-NN reference doc comment (max+1 over surviving rows)

* fix(isms): validate the audit schedule under the document lock

Re-read the stored dates inside the locked transaction so two
concurrent partial updates (one moving the start, one the end) can
never persist an inverted schedule. Adds coverage for update-time
clause-label inheritance.

---------

Co-authored-by: Tofik Hasanov <annexcies@gmail.com>
…h automations (#3469)

* fix(policies): rename CreateVersionDto to avoid swagger collision with automations

## Problem
The create-policy-version MCP tool is completely broken. Callers either hit a 400 error from the API ("property scriptKey should not exist", "property version should not exist") or are blocked by the MCP client schema validation before the call even goes out. There's no valid path to use this tool.

## Root cause
Two DTO classes share the name CreateVersionDto: one in the policies module (with sourceVersionId and changelog) and one in the automations module (with version and scriptKey). NestJS Swagger keys schema components by class name, so they collide in the OpenAPI spec. The merged component ended up with version+scriptKey marked as required. Speakeasy then generated the MCP tool from that collision, making those fields required. But the live policies endpoint validates inbound DTOs with a whitelist that rejects those two fields, causing the 400.

## Fix
Renamed the policies DTO class from CreateVersionDto to CreatePolicyVersionDto to eliminate the name collision in Swagger. This allows the correct schema (sourceVersionId and changelog) to be generated for the policies endpoint and for MCP tool regeneration.

## Explicitly NOT touched
No changes to endpoint handlers, validation pipe config, auth, RBAC, billing, or automations module. The automations CreateVersionDto remains as-is.

## Verification
✅ Unit tests for policies version creation pass locally with the renamed DTO
✅ Added regression test asserting that the policy version endpoint accepts sourceVersionId and changelog and rejects version/scriptKey in the request body

* chore(api): regenerate openapi.json for CreatePolicyVersionDto

Regenerate the committed OpenAPI spec so the create-policy-version MCP tool
is generated from the corrected schema (CreatePolicyVersionDto with optional
sourceVersionId + changelog) instead of the collided CreateVersionDto. The
Speakeasy MCP server builds from packages/docs/openapi.json, so the DTO fix
only reaches the published tool once this artifact is regenerated.

Also catches the spec up to already-merged endpoints whose PRs did not
regenerate it: the ISMS bulk-measurements endpoint, four trust-portal request
bodies, and extra optional fields on two ISMS register endpoints. All changes
are additive — no existing component shape changed and nothing was removed.
…3474)

* fix(policies): delete detached PDF objects when regenerating a draft

Regenerating an uploaded draft cleared its PDF references but left the
objects in S3, orphaning one per regeneration. The detached keys
(policy + current version, deduplicated) are now removed best-effort
after the database update commits; failures are logged with their keys
instead of failing the regeneration.

* fix(policies): capture detached PDF keys under the row lock and use APP_AWS config

The cleanup client now mirrors the other trigger tasks' APP_AWS_*
configuration (region, credentials, optional endpoint) instead of the
default chain, and the detached keys are captured inside the
transaction after a SELECT ... FOR UPDATE on the policy row — a
concurrently uploaded PDF can no longer slip past the cleanup as an
untracked orphan.

---------

Co-authored-by: Tofik Hasanov <annexcies@gmail.com>
Co-authored-by: Tofik Hasanov <72318342+tofikwest@users.noreply.github.com>
…org owner (#3472)

* fix(auth): attribute API-key mutations to the key's creator, not the org owner

API keys were org-scoped with no recorded creator, so ActingUserResolver
attributed every API-key/MCP mutation to the org's oldest owner. In the audit
trail this made all automation look like the owner performed it, masking the
real actor — a problem for a compliance product.

- Add ApiKey.createdByMemberId (nullable, FK to Member, onDelete: SetNull).
  Populated on creation with the acting member. Nullable so legacy keys and
  keys whose creator was removed fall back cleanly.
- create-api-key now records the creating member; validateApiKey surfaces it.
- HybridAuthGuard puts it on the request; ActingUserResolver attributes the
  mutation to the creator (when still an active member of the org), else falls
  back to the org owner as before.
- Tests for both the creator-attribution path and the deactivated-creator
  fallback.

No backfill: existing keys have no recorded creator and keep falling back to
the org owner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(auth): select createdByMemberId in the legacy API-key lookup path

The legacy-key branch's select was missing createdByMemberId (its deeper
indentation meant the earlier bulk edit didn't cover it), so the return that
references legacyMatch.createdByMemberId failed to typecheck. Add the field to
the legacy select to match the primary path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(audit): resolve acting user for API-key mutations so they're logged

The global AuditLogInterceptor early-returned when request.userId was
absent, so API-key (and MCP) mutations were never audit-logged at all —
the createdByMemberId attribution added earlier had no effect on the
trail. Inject ActingUserResolver and resolve the responsible user (key
creator, else org owner) when there's no session userId; skip logging
only when no user can be attributed (no null-FK rows).

Also fixes a pre-existing bug surfaced once the spec could load: the
control mapping/unmapping descriptions read "policie" because the
resolver naive-stripped the trailing "s" of "policies". Use the known
Prisma model name (policies→policy) with an "ies"→"y" fallback.

The interceptor spec never ran before (it pulled better-auth's ESM
subpaths via permission.guard); mock @trycompai/auth like the other
specs so all 41 tests execute.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(auth): attribute API-key mutations to the acting user across silent sinks

The interceptor fix covered automatic @RequirePermission audit logging. This
covers the remaining sinks where an API-key mutation succeeded but attribution
was silently lost or credited to the org owner instead of the responsible user:

- vendors create + triggerAssessment: the controller now resolves the acting
  user and threads it as createdByUserId, so the auto-generated risk-assessment
  task ("created this task") credits the key creator, not the admin fallback.
- cloud-security scan: attribute scan_completed via ActingUserResolver instead
  of raw req.userId (was skipped entirely for API keys).
- policies publish-all: per-policy audit rows were dropped for API-key auth
  (authContext.userId undefined) — resolve the actor first.
- isms createRow: enteredById (a Member FK) was null for API keys — resolve the
  acting member.

ActingUserResolver now populates memberId on every path (session member, key
creator, or fallback owner's member), so Member-FK sinks like isms enteredById
attribute correctly. Owner lookup selects the member id alongside the user id.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(auth): address cubic review — actor attribution edge cases

- vendors create/triggerAssessment: 400 when no actor resolves (org has no
  owner) instead of creating a vendor whose assessment task has no attributed
  user — matches ActingUserResolver's contract.
- cloud-security scan: attribution is best-effort (try/catch) so a transient
  resolver/audit failure can't fail an already-completed scan and invite a
  re-run.
- isms bulkCreateMeasurements: resolve the acting member (session-first, then
  api-key creator/owner) so bulk saves via API key don't persist null enteredById.
- hybrid-auth guard: service-token x-user-id now sets request.memberId so
  Member-FK sinks can attribute service-token-acting mutations.
- tests: createApiKey creator attribution (session forwards memberId, api-key
  forwards null), vendors 400-on-null, isms bulk api-key attribution.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(auth): restrict service-token x-user-id to active members

Cubic follow-up: the x-user-id member lookup didn't filter deactivated /
inactive memberships, so an offboarded user supplied via x-user-id could
receive new audit / enteredById attribution. Add deactivated:false + isActive:true
to the lookup (matching ActingUserResolver's filters); an inactive member now
resolves to no acting user and falls back to owner resolution downstream.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(auth): cover service-token active-membership restriction

Cubic follow-up: add regression coverage for the x-user-id acting-member
resolution — an active member populates request.userId + memberId (and the
lookup is scoped to deactivated:false/isActive:true), while a deactivated/
inactive member sets neither, so the restriction can't silently regress.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(audit): record caller provenance for non-session attributions

Security review follow-up (non-repudiation): API-key / service-token
mutations that resolve to the org owner (or key creator) were recorded in
the audit trail with no marker, so they read as if the user personally acted
in a session. Carry ActingUserResolver's callerLabel through the interceptor
and cloud-security scan — append `[via API key "..."]` to the description and
store `via` in the audit data JSON. Mirrors the existing exception /
scan-mode endpoints. Session actions are unaffected (no label).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(security): add security-review skill, agent, hook, and pre-push gate

Adds a first-party security-review skill + security-reviewer agent covering the
high-risk vuln classes (broken access control, tenant isolation, injection,
secrets, SSRF, auth/session, unsafe uploads, mass assignment, IDOR). Makes it
run automatically via a behavior-based PostToolUse nudge (API surface, services,
route handlers, trigger jobs, by-id data access, plus content patterns for XSS/
raw SQL/exec), inclusion in production-readiness, and a Husky pre-push hard gate
(bypass after review: SECURITY_REVIEWED=1 git push).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(security): auto-run security review before push (PreToolUse agent hook)

Adds a PreToolUse agent hook on `git push` that automatically runs the
security-reviewer on the outgoing diff (origin/main...HEAD) and blocks the push
if it finds an unaddressed P1/P2 — so the security skill triggers automatically
on push instead of relying on a manual run. Fires for Claude-driven pushes; the
Husky pre-push remains the backstop for terminal/CI pushes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(security): add Claude security-review GitHub Action; drop local push block

Adopts Anthropic's official claude-code-security-review Action as the
authoritative gate — runs on every PR, diff-scoped, posts inline findings —
layered with repo-specific instructions (tenant isolation, authz-vs-attribution,
IDOR). Removes the local push friction that doesn't actually run a review: the
Husky pre-push hard block and the PostToolUse reminder. The security actually
runs in two places now: the PreToolUse agent hook (Claude-driven pushes) and the
PR Action (all PRs). Prereq: add an ANTHROPIC_API_KEY repo secret.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(security): remove misfiring PreToolUse security hook

The agent hook fired on non-git-push commands and blocked them (its
allow/block semantics were inverted and the `if` filter didn't scope). The
PR GitHub Action is the reliable auto-run gate; drop the local hook.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(security): review every commit on a PR, not just the first

Set run-every-commit so the security Action re-reviews the latest diff on each
push to a PR. Without it the action runs once per PR and skips later commits,
leaving code added after the first review unchecked while the required check
stays green. Findings remain advisory PR comments (no merge block on noise).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(api): regenerate openapi.json after merging main

Regenerated from the merged code so the spec carries #3469's
CreatePolicyVersionDto rename plus the current ISMS-audit schema (main's
committed spec had drifted). Written as-generated by the dev boot.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@claudfuen

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.106.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants