Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
f41f006
chore: merge release v3.101.2 back to main [skip ci]
github-actions[bot] Jul 14, 2026
0f7581b
fix(device-agent): implement the installer-cleanup fix (#3381)
github-actions[bot] Jul 14, 2026
421826b
CS-408 [Bug] - org chart image doesn't update after upload, delete, o…
github-actions[bot] Jul 14, 2026
7577a37
fix(deps): remediate dependency security vulnerabilities (#3403)
Marfuen Jul 14, 2026
d403e9c
fix(deps): remediate dependency security vulnerabilities (165→16, 4 c…
Marfuen Jul 14, 2026
029e899
chore(deps): bump semantic-release to v25 (clears sigstore advisory, …
Marfuen Jul 15, 2026
dcec288
fix(security): prevent SSRF in task-automation enterprise API calls (…
Marfuen Jul 15, 2026
59a6b0f
fix(deps): patch tmp and js-yaml in mcp-server (Dependabot #58/#59/#7…
Marfuen Jul 15, 2026
a25f56e
ci: submit resolved bun tree to GitHub dependency graph (accurate SBO…
Marfuen Jul 15, 2026
e47e6fd
fix(ci): scan bun.lock via file: input so the SBOM isn't empty (#3414)
Marfuen Jul 15, 2026
5f47024
fix(ci): pin Syft 1.46.0 in SBOM action (1.42.3 can't parse bun.lock)…
Marfuen Jul 15, 2026
598ff8c
fix(deps): override uuid to ^11.1.1 (Dependabot #85) (#3418)
Marfuen Jul 15, 2026
32329be
fix(security): resolve open CodeQL alerts (XSS, sanitization, workflo…
Marfuen Jul 15, 2026
67c9f4a
feat(framework-editor): raise requirement description limit to 100,00…
tofikwest Jul 15, 2026
35487c2
Merge pull request #3420 from trycompai/tofik/frame-2-requirement-des…
tofikwest Jul 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/database-migrations-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ on:
branches:
- main
workflow_dispatch: # Allows manual triggering

permissions:
contents: read # only needs to check out the repo to run migrations

jobs:
migrate:
name: Run Database Migrations
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/database-migrations-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ on:
branches:
- release
workflow_dispatch: # Allows manual triggering

permissions:
contents: read # only needs to check out the repo to run migrations

jobs:
migrate:
name: Run Database Migrations
Expand Down
61 changes: 61 additions & 0 deletions .github/workflows/sbom.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: SBOM

# Publishes an accurate SBOM to GitHub's dependency graph.
#
# GitHub does NOT natively parse `bun.lock`, so out of the box its dependency
# graph, exported SBOM, and Dependabot are blind to the bun dependency tree
# (they only see declared package.json ranges + the npm package-lock.json in
# apps/mcp-server). Syft DOES parse bun.lock with fully resolved versions
# (overrides applied), so this workflow scans bun.lock with Syft and submits
# the resolved tree via GitHub's Dependency Submission API. (apps/mcp-server's
# package-lock.json is already covered by GitHub's native npm parsing, so we
# only need to submit the bun tree.) After this runs on `main`, GitHub's own
# "Export SBOM" and Dependabot reflect the real bun tree.
#
# NOTE: we scan bun.lock via the `file:` input, not `path: .`. Syft's default
# DIRECTORY catalogers do not include the bun cataloger, so a directory scan
# silently skips bun.lock (it only picks up package-lock.json + actions). A
# file scan forces the bun cataloger and yields the full resolved tree.

on:
push:
branches: [main]
paths:
- 'bun.lock'
- '.github/workflows/sbom.yml'
schedule:
- cron: '0 6 * * 1' # weekly, Monday 06:00 UTC — refresh against new advisories
workflow_dispatch:

permissions:
contents: write # required by the Dependency Submission API

concurrency:
group: sbom-${{ github.ref }}
cancel-in-progress: true

jobs:
sbom:
name: Generate & submit SBOM
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

# Scans bun.lock directly (see NOTE above). `dependency-snapshot: true`
# submits the resolved tree to the dependency graph; the SBOM is also
# uploaded as a workflow artifact for anyone who wants the file directly.
- name: Generate SBOM & submit to dependency graph
# Pinned to a full commit SHA (not the mutable @v0 tag): this action
# runs with contents:write, so a retag/compromise must not be able to
# swap in altered code. Bump the SHA + comment together to upgrade.
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
file: bun.lock
format: spdx-json
artifact-name: sbom.spdx.json
dependency-snapshot: true
# sbom-action v0.24.0 bundles Syft 1.42.3, which CANNOT parse bun.lock
# (it yields 1 package — an empty snapshot). Pin a Syft that can:
# verified 1.46.0 → 2496 resolved deps. Bump alongside the action SHA.
syft-version: v1.46.0
4 changes: 4 additions & 0 deletions .github/workflows/trigger-tasks-deploy-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ on:
push:
branches:
- main

permissions:
contents: read # only needs to check out the repo to build & deploy

jobs:
deploy:
runs-on: warp-ubuntu-latest-arm64-4x
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/trigger-tasks-deploy-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ on:
branches:
- release

permissions:
contents: read # only needs to check out the repo to build & deploy

jobs:
deploy:
runs-on: warp-ubuntu-latest-arm64-4x
Expand Down
8 changes: 4 additions & 4 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@
"@aws-sdk/lib-storage": "3.1013.0",
"@aws-sdk/s3-request-presigner": "3.1013.0",
"@browserbasehq/sdk": "2.6.0",
"@browserbasehq/stagehand": "^3.2.1",
"@browserbasehq/stagehand": "^3.7.0",
"@inference/tracing": "^0.0.21",
"@maced/api-client": "^0.9.2",
"@mendable/firecrawl-js": "^4.9.3",
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1",
"@nestjs/platform-express": "^11.1.5",
"@nestjs/swagger": "^11.2.0",
"@nestjs/swagger": "^11.4.5",
"@nestjs/throttler": "^6.5.0",
"@prisma/adapter-pg": "7.6.0",
"@prisma/client": "7.6.0",
Expand All @@ -92,8 +92,8 @@
"adm-zip": "^0.5.16",
"ai": "^6.0.175",
"archiver": "^7.0.1",
"axios": "^1.12.2",
"better-auth": "^1.4.22",
"axios": "^1.16.0",
"better-auth": "^1.6.13",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"docx": "^9.7.1",
Expand Down
11 changes: 11 additions & 0 deletions apps/api/src/framework-editor/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Shared framework-editor DTO limits.
*
* Requirement descriptions carry full regulatory control text, which can be very
* long: NIST SP800-53r5 PL-2 exceeds 6,000 chars and many HITRUST CSF
* requirements run past 70,000 (FRAME-2). The DB column is unbounded `text`; the
* only ceiling is API-side `@MaxLength` validation, so this single constant is
* the source of truth across every requirement-description path (create /
* update / batch-update / framework import) to keep them from drifting.
*/
export const REQUIREMENT_DESCRIPTION_MAX_LENGTH = 100_000;
Original file line number Diff line number Diff line change
Expand Up @@ -64,23 +64,25 @@ describe('ImportFrameworkDto', () => {
expect(await validatePayload(basePayload())).toHaveLength(0);
});

// Bug A — the import path must allow the same 10,000-char requirement
// descriptions the standalone requirement editor allows (NIST PL-2 > 6000).
it('accepts a 10,000-char requirement description', async () => {
// Bug A — the import path must allow the same 100,000-char requirement
// descriptions the standalone requirement editor allows (FRAME-2: NIST PL-2
// > 6000, HITRUST CSF requirements exceed 70,000).
it('accepts a 100,000-char requirement description', async () => {
expect(
await validatePayload(basePayload({ requirementDescription: 'x'.repeat(10_000) })),
await validatePayload(basePayload({ requirementDescription: 'x'.repeat(100_000) })),
).toHaveLength(0);
});

it('rejects a requirement description longer than 10,000 chars', async () => {
it('rejects a requirement description longer than 100,000 chars', async () => {
const messages = await validatePayload(
basePayload({ requirementDescription: 'x'.repeat(10_001) }),
basePayload({ requirementDescription: 'x'.repeat(100_001) }),
);
expect(messages.some((m) => m.includes('10000'))).toBe(true);
expect(messages.some((m) => m.includes('100000'))).toBe(true);
});

// Import is the bulk-load path for externally-authored frameworks (NIST), so
// every description field accepts 10,000 — not just requirements.
// FRAME-2 raised requirement descriptions to 100,000, but control / policy /
// task template descriptions keep their existing 10,000 cap — the ticket is
// requirements-only, so this divergence is deliberate.
it('accepts 10,000-char control / policy / task descriptions', async () => {
const long = 'x'.repeat(10_000);
const payload = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from '@db';
import { MaxJsonSize } from '../../validators/max-json-size.validator';
import { IsObjectOrArray } from '../../validators/is-object-or-array.validator';
import { REQUIREMENT_DESCRIPTION_MAX_LENGTH } from '../../constants';

class ImportFrameworkMetaDto {
@ApiProperty()
Expand Down Expand Up @@ -60,12 +61,13 @@ class ImportRequirementDto {
@MaxLength(255)
identifier?: string;

// Matches the standalone requirement DTOs (FRAME-2). NIST SP800-53 control
// text routinely exceeds 5000 chars (e.g. PL-2 > 6000).
// Matches the standalone requirement DTOs (FRAME-2). Regulatory control text
// can be very long: NIST SP800-53r5 PL-2 > 6000 chars, and many HITRUST CSF
// requirements exceed 70,000 — hence the shared 100,000 ceiling.
@ApiProperty()
@IsString()
@IsNotEmpty()
@MaxLength(10000)
@MaxLength(REQUIREMENT_DESCRIPTION_MAX_LENGTH)
description: string;

@ApiPropertyOptional()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ describe('BatchUpdateRequirementsDto', () => {
expect(errors).toHaveLength(0);
});

// ── nested description length (FRAME-2: limit raised 5,000 → 10,000) ─
it('accepts a nested description at the 10,000-char limit', async () => {
// ── nested description length (FRAME-2: limit raised 10,000 → 100,000) ─
it('accepts a nested description at the 100,000-char limit', async () => {
const dto = toDto({
updates: [{ id: 'frq_1', description: 'x'.repeat(10_000) }],
updates: [{ id: 'frq_1', description: 'x'.repeat(100_000) }],
});
const errors = await validate(dto, {
whitelist: true,
Expand All @@ -36,9 +36,9 @@ describe('BatchUpdateRequirementsDto', () => {
expect(errors).toHaveLength(0);
});

it('rejects a nested description longer than 10,000 chars', async () => {
it('rejects a nested description longer than 100,000 chars', async () => {
const dto = toDto({
updates: [{ id: 'frq_1', description: 'x'.repeat(10_001) }],
updates: [{ id: 'frq_1', description: 'x'.repeat(100_001) }],
});
const errors = await validate(dto, {
whitelist: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Min,
ValidateNested,
} from 'class-validator';
import { REQUIREMENT_DESCRIPTION_MAX_LENGTH } from '../../constants';

class BatchUpdateRequirementItem {
@ApiProperty()
Expand All @@ -32,7 +33,7 @@ class BatchUpdateRequirementItem {
@ApiProperty()
@IsString()
@IsOptional()
@MaxLength(10000)
@MaxLength(REQUIREMENT_DESCRIPTION_MAX_LENGTH)
description?: string;

@ApiProperty()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,18 @@ describe('CreateRequirementDto', () => {
expect(errors).toHaveLength(0);
});

// ── description length (FRAME-2: limit raised 5,000 → 10,000) ──────
it('accepts a description at the 10,000-char limit', async () => {
const dto = toDto({ ...VALID_BASE, description: 'x'.repeat(10_000) });
// ── description length (FRAME-2: limit raised 10,000 → 100,000) ────
it('accepts a description at the 100,000-char limit', async () => {
const dto = toDto({ ...VALID_BASE, description: 'x'.repeat(100_000) });
const errors = await validate(dto, {
whitelist: true,
forbidNonWhitelisted: true,
});
expect(errors).toHaveLength(0);
});

it('rejects a description longer than 10,000 chars', async () => {
const dto = toDto({ ...VALID_BASE, description: 'x'.repeat(10_001) });
it('rejects a description longer than 100,000 chars', async () => {
const dto = toDto({ ...VALID_BASE, description: 'x'.repeat(100_001) });
const errors = await validate(dto, {
whitelist: true,
forbidNonWhitelisted: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsInt, IsNotEmpty, IsOptional, IsString, MaxLength, Min } from 'class-validator';
import { REQUIREMENT_DESCRIPTION_MAX_LENGTH } from '../../constants';

export class CreateRequirementDto {
@ApiProperty({ example: 'frk_abc123' })
Expand All @@ -22,7 +23,7 @@ export class CreateRequirementDto {

@ApiProperty({ example: 'Control environment requirements' })
@IsString()
@MaxLength(10000)
@MaxLength(REQUIREMENT_DESCRIPTION_MAX_LENGTH)
description: string;

@ApiPropertyOptional({ example: 'Access Control' })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,18 @@ describe('UpdateRequirementDto', () => {
expect(errors).toHaveLength(0);
});

// ── description length (FRAME-2: limit raised 5,000 → 10,000) ──────
it('accepts a description at the 10,000-char limit', async () => {
const dto = toDto({ description: 'x'.repeat(10_000) });
// ── description length (FRAME-2: limit raised 10,000 → 100,000) ────
it('accepts a description at the 100,000-char limit', async () => {
const dto = toDto({ description: 'x'.repeat(100_000) });
const errors = await validate(dto, {
whitelist: true,
forbidNonWhitelisted: true,
});
expect(errors).toHaveLength(0);
});

it('rejects a description longer than 10,000 chars', async () => {
const dto = toDto({ description: 'x'.repeat(10_001) });
it('rejects a description longer than 100,000 chars', async () => {
const dto = toDto({ description: 'x'.repeat(100_001) });
const errors = await validate(dto, {
whitelist: true,
forbidNonWhitelisted: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ApiPropertyOptional } from '@nestjs/swagger';
import { IsInt, IsOptional, IsString, MaxLength, Min } from 'class-validator';
import { REQUIREMENT_DESCRIPTION_MAX_LENGTH } from '../../constants';

export class UpdateRequirementDto {
@ApiPropertyOptional()
Expand All @@ -17,7 +18,7 @@ export class UpdateRequirementDto {
@ApiPropertyOptional()
@IsString()
@IsOptional()
@MaxLength(10000)
@MaxLength(REQUIREMENT_DESCRIPTION_MAX_LENGTH)
description?: string;

@ApiPropertyOptional()
Expand Down
10 changes: 5 additions & 5 deletions apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"@aws-sdk/s3-request-presigner": "3.1013.0",
"@azure/core-rest-pipeline": "^1.21.0",
"@browserbasehq/sdk": "2.6.0",
"@browserbasehq/stagehand": "^3.0.5",
"@browserbasehq/stagehand": "^3.7.0",
"@date-fns/tz": "^1.2.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0",
Expand Down Expand Up @@ -85,8 +85,8 @@
"@xyflow/react": "^12.10.0",
"ai": "^6.0.175",
"ai-elements": "^1.6.1",
"axios": "^1.9.0",
"better-auth": "^1.4.22",
"axios": "^1.16.0",
"better-auth": "^1.6.13",
"botid": "^1.5.5",
"canvas-confetti": "^1.9.3",
"class-variance-authority": "^0.7.1",
Expand All @@ -102,7 +102,7 @@
"mammoth": "^1.11.0",
"motion": "^12.35.0",
"nanoid": "^5.1.6",
"next": "^16.2.0",
"next": "^16.2.6",
"next-safe-action": "^8.0.3",
"next-themes": "^0.4.4",
"nuqs": "^2.4.3",
Expand Down Expand Up @@ -171,7 +171,7 @@
"trigger.dev": "4.4.3",
"typescript": "^5.8.3",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.4"
"vitest": "^3.2.6"
},
"exports": {
"./src/lib/encryption": "./src/lib/encryption.ts"
Expand Down
Loading
Loading