Skip to content

Commit 5b6620e

Browse files
authored
fix(security): clear the dependency advisories and gate them in CI (#153)
## Related Issue No issue — this came from the Dependabot alerts on `main`. GitHub currently reports **83 vulnerabilities** there (2 critical, 33 high, 41 moderate, 7 low). ## Problem A full-workspace audit returned 88 advisory records across 78 unique advisories. Several land in code that actually runs: the WebSocket server, the protobuf decoder, the router, and the Markdown/diagram renderers inside the web UI that the CLI ships prebuilt as `dist-web`. An unreachable advisory is still a valid finding — reachability changes urgency and test scope, not whether the dependency is vulnerable. The second problem is that nothing was watching. Fixing 78 advisories once and having them drift back is the default outcome without a gate. ## What changed **Advisory floors.** The lockfile now resolves clean at every severity. | Area | Moves | |---|---| | Runtime | `ws` → 8.21.3, `react-router` → 7.18.2, plus `protobufjs`, `@protobufjs/utf8`, `find-my-way`, `ip-address`, `fast-uri` | | Browser | `mermaid` → 11.17.0, `dompurify` → patched, Monaco via patch (below) | | Tooling | `vite` → 6.4.3, plus `esbuild`, `postcss`, `nanoid`, `brace-expansion`, `js-yaml`, `linkify-it`, `qs`, `body-parser` | `tar` is dropped outright rather than bumped — nothing has imported it since the hosted ripgrep bootstrap was removed. **Monaco has no release carrying the fix**, so `patches/monaco-editor@0.55.1.patch` pins its bundled DOMPurify. A patch that only applies on some build paths is worse than no patch, so `flake.nix` adds `./patches` to the fileset — without it the Nix build would install an unpatched Monaco while every other path got the patched one — and `scripts/security/check-artifacts.mjs` asserts the patch survived install rather than trusting that it did. **`dist-web` is rebuilt** in the same commit. The CLI ships the bundle prebuilt, so patched `mermaid`/`dompurify`/Monaco only reach users once the committed bundle is rebuilt against them. The staleness gate enforces this. **The guards**, so this does not have to be redone: - `dependabot.yml` for the update stream. - `codeql.yml` for source analysis. - `security.yml`, which packs the CLI tarball and the VSIX and audits **what those artifacts actually ship**. A clean lockfile says nothing about what ends up inside a published package — the two can disagree, and the artifact is what users install. `docs/security/dependency-remediation-2026-08.md` records the evidence and the ownership split behind each floor, including the rule that discovery sources can corroborate a version but never establish a safe floor on their own. ## Verification Run locally against this branch: | Gate | Result | |---|---| | `pnpm run typecheck` | pass | | `pnpm run lint` | 0 errors (3224 pre-existing warnings) | | `pnpm run test` | 20318 passed; the one failure was `sessionIndex.test.ts` under concurrent load and passes 39/39 isolated | | `pnpm run check:security-artifacts` | every version floor asserted; Monaco patch confirmed present in the installed tree | | `node scripts/check-nix-workspace.mjs` | 18/18 workspace deps present | | `nix build .#pythinker-code` | builds; refreshed `pnpmDeps` hash verified, no mismatch | | pre-push suite | all checks passed in 296s | `check:security-artifacts` additionally needs `.tmp/security-artifacts/{cli,vsix}` populated, which `security.yml` does by packing first — that part is exercised by CI on this PR, not locally. ## Checklist - [x] I have read the CONTRIBUTING document. - [ ] I have linked a related issue — none; this came from the Dependabot alert list. - [x] I have added tests that prove my feature works — `scripts/security/check-artifacts.mjs` and `check-built-browser.mjs` are the tests: they assert the floors and the Monaco patch against the packed artifacts, so a regression fails CI rather than reappearing as an alert. - [x] Ran `gen-changesets` skill — `patch` on `@pymodel/pythinker-code`: "Update the bundled dependencies to releases that carry the current security fixes." The CI and tooling parts carry no changeset; users cannot perceive them. - [x] Ran `gen-docs` skill, or this PR needs no doc update — no user-facing CLI behavior changed. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Expanded diagram rendering support, including flowcharts, state diagrams, timelines, mind maps, radar charts, treemaps, Sankey charts, Venn diagrams, and more. * Added editor language support and syntax highlighting for Python, JavaScript, TypeScript, HTML, JSON, YAML, XML, MDX, Razor, Liquid, Handlebars, FreeMarker, and CSS. * Added improved code, math, Mermaid, D2, and infographic block rendering. * **Bug Fixes** * Strengthened content sanitization and security validation. * Improved diagram rendering reliability and layout behavior. * Addressed dependency vulnerabilities and removed unused packaged assets. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 35c89a7 commit 5b6620e

246 files changed

Lines changed: 7677 additions & 5013 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pymodel/pythinker-code": patch
3+
---
4+
5+
Update the bundled dependencies to releases that carry the current security fixes.

.github/dependabot.yml

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,11 @@ updates:
44
directory: "/"
55
schedule:
66
interval: "weekly"
7-
# Scheduled version-update PRs are disabled on purpose: this workspace's
8-
# engines.node (>=24.15.0 <25) exceeds the Node.js version Dependabot's own
9-
# updater sandbox ships, so ANY `pnpm install`/update it attempts hard-fails
10-
# with ERR_PNPM_UNSUPPORTED_ENGINE (root .npmrc sets engine-strict=true).
11-
# See dependabot/dependabot-core#7426, #4072, #12976 — there is no
12-
# dependabot.yml-level fix; the real fix is lowering engines.node or
13-
# disabling engine-strict in .npmrc, both out of scope here. This entry
14-
# exists only so the `ignore` rules below apply to security-update PRs,
15-
# which open-pull-requests-limit: 0 does not suppress.
7+
# Scheduled version updates remain disabled because Dependabot's updater
8+
# does not satisfy this workspace's strict Node 24.15 engine requirement.
9+
# Security updates remain enabled and no dependency is ignored.
1610
open-pull-requests-limit: 0
17-
ignore:
18-
# Both advisories are on vite/esbuild resolved transitively through
19-
# docs/package.json's vitepress@1.6.4 (vite@5.4.21 in pnpm-lock.yaml),
20-
# a dev-only docs-site dependency. vitepress@1.6.4 pins `vite: 5.4.21`
21-
# as a hard (non-peer) dependency, so pnpm cannot resolve a fix without
22-
# vitepress 2 (alpha only as of 2026-08) — every attempt reopens the
23-
# same failing PR. Unscoped by version on purpose: Dependabot's actual
24-
# target version for this resolution is unverifiable (no open
25-
# Dependabot PRs exist in this repo to read a target off of, and the
26-
# target is not necessarily the advisory's first-patched version), so a
27-
# version-scoped rule can silently fail to match and let the PR back
28-
# in. Cost: this also suppresses vite/esbuild security-update PRs for
29-
# apps/*, which today sit at patched vite@6.4.3+ directly — revisit if
30-
# that changes. Drop both rules once docs/package.json moves to
31-
# vitepress 2 stable.
32-
- dependency-name: "vite"
33-
- dependency-name: "esbuild"
11+
- package-ecosystem: "github-actions"
12+
directory: "/"
13+
schedule:
14+
interval: "weekly"

.github/workflows/codeql.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: CodeQL
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: [main]
7+
schedule:
8+
- cron: '43 4 * * 1'
9+
workflow_dispatch:
10+
11+
permissions:
12+
contents: read
13+
security-events: write
14+
15+
jobs:
16+
analyze:
17+
name: CodeQL
18+
runs-on: ubuntu-latest
19+
timeout-minutes: 30
20+
steps:
21+
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
22+
with:
23+
persist-credentials: false
24+
- uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4
25+
with:
26+
build-mode: none
27+
languages: javascript-typescript
28+
queries: security-extended
29+
- uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4
30+
with:
31+
category: /language:javascript-typescript

.github/workflows/security.yml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
name: Security
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: [main]
7+
schedule:
8+
- cron: '17 4 * * *'
9+
workflow_dispatch:
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
audit:
16+
name: Security audit
17+
runs-on: ubuntu-latest
18+
timeout-minutes: 10
19+
steps:
20+
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
21+
with:
22+
persist-credentials: false
23+
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
24+
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
25+
with:
26+
node-version-file: .nvmrc
27+
- run: pnpm audit --json
28+
29+
dependency-review:
30+
name: Dependency review
31+
if: github.event_name == 'pull_request'
32+
runs-on: ubuntu-latest
33+
timeout-minutes: 10
34+
steps:
35+
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
36+
with:
37+
persist-credentials: false
38+
- uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
39+
with:
40+
fail-on-scopes: runtime, development, unknown
41+
fail-on-severity: low
42+
vulnerability-check: true
43+
44+
artifact-security:
45+
name: Artifact security
46+
if: github.event_name != 'schedule'
47+
runs-on: ubuntu-latest
48+
timeout-minutes: 45
49+
steps:
50+
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
51+
with:
52+
persist-credentials: false
53+
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
54+
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
55+
with:
56+
node-version-file: .nvmrc
57+
cache: pnpm
58+
- run: pnpm install --frozen-lockfile
59+
- run: node --test scripts/security/*.test.mjs
60+
- run: pnpm -C apps/pythinker-code run prepack
61+
- run: pnpm -C apps/desktop run package
62+
- run: pnpm -C docs run build
63+
- run: mkdir -p .tmp/security-artifacts/cli .tmp/security-artifacts/vsix
64+
- run: pnpm --filter @pymodel/pythinker-code pack --pack-destination "${{ github.workspace }}/.tmp/security-artifacts/cli"
65+
- run: pnpm --filter pythinker run package:platform -- --target linux-x64 --out-dir "${{ github.workspace }}/.tmp/security-artifacts/vsix"
66+
- run: pnpm -C apps/pythinker-code run smoke
67+
- run: git diff --exit-code -- apps/pythinker-code/dist-web
68+
- run: pnpm run check:security-artifacts

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ coverage/
1010
.vitest-results/
1111
.DS_Store
1212
.playwright-mcp/
13+
.lavish/
1314
.claude
1415
.conductor
1516
.pythinker-stash-dir

SECURITY.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,17 @@ Alternative channel:
2626

2727
## Our Response
2828

29-
We will acknowledge your report and provide an initial assessment as soon as we can.
29+
We triage Critical and High reports within 24 hours. We target a fix within 72 hours for
30+
Critical and High issues, 7 days for Medium issues, and 30 days for Low issues.
31+
32+
## Dependency Vulnerability Policy
33+
34+
Pull requests cannot add a vulnerability at any severity. The repository uses a full-workspace
35+
audit because build and package steps can ship workspace code declared as a development
36+
dependency. A production-only audit is informational and is not a security gate.
37+
38+
If a vulnerability is disclosed after clean code reaches `main`, the response times above apply.
39+
Dismissed or auto-dismissed alert state does not make a vulnerable dependency acceptable.
3040

3141
## Public Disclosure
3242

apps/desktop/scripts/stage-runtime.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ async function deploy(target: string): Promise<void> {
112112
await run('pnpm', [
113113
'--config.verify-deps-before-run=false', '--filter', deployPackage, 'deploy', '--legacy', '--prod',
114114
'--config.node-linker=hoisted', '--config.auto-install-peers=false', '--config.link-workspace-packages=true',
115+
'--config.allow-unused-patches=true',
115116
deployTargetArgument(repositoryRoot, target),
116117
])
117118
} finally {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"sourceHash": "5fa1f4113f68cc5a0de3b4985ae517b47ca98f832e89e97cf636845fd3a2cd67",
2+
"sourceHash": "c3f3aa64d301b50a33f02b02d5ba123747d52b048b1454d66fb3dee9abd2c416",
33
"sourceFileCount": 388
44
}

0 commit comments

Comments
 (0)