Skip to content

chore(deps): bump svgo from 3.3.3 to 3.3.4 in /docs-site in the docs-site-npm-security group across 1 directory - #512

Open
dependabot[bot] wants to merge 179 commits into
mainfrom
dependabot/npm_and_yarn/docs-site/docs-site-npm-security-fce16190a4
Open

chore(deps): bump svgo from 3.3.3 to 3.3.4 in /docs-site in the docs-site-npm-security group across 1 directory#512
dependabot[bot] wants to merge 179 commits into
mainfrom
dependabot/npm_and_yarn/docs-site/docs-site-npm-security-fce16190a4

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Jul 24, 2026

Copy link
Copy Markdown
Contributor

Bumps the docs-site-npm-security group with 1 update in the /docs-site directory: svgo.

Updates svgo from 3.3.3 to 3.3.4

Release notes

Sourced from svgo's releases.

v3.3.4

What's Changed

Security

Support

SVGO v3 is not officially supported, please consider upgrading to SVGO v4 instead. We've backported this fix as there are security implications, but there is no commitment to do this for more complex changes in future.

Consider reading our Migration Guide from v3 to v4 which should ease the process.

Commits

Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore <dependency name> major version will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself)
  • @dependabot ignore <dependency name> minor version will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself)
  • @dependabot ignore <dependency name> will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself)
  • @dependabot unignore <dependency name> will remove all of the ignore conditions of the specified dependency
  • @dependabot unignore <dependency name> <ignore condition> will remove the ignore condition of the specified dependency and ignore conditions
    You can disable automated security fix PRs for this repo from the Security Alerts page.

haksungjang and others added 30 commits May 31, 2026 22:12
Resolves GHSA-3g43-6gmg-66jw, GHSA-35jp-ww65-95wh, GHSA-pjwm-pj3p-43mv,
GHSA-898c-q2cr-xwhg (3 high + 1 medium, all axios — the only open
Dependabot alerts; the "49" push warning was stale post-force-push).

axios is a direct dep in apps/frontend; the lock changes only axios and
its transitive proxy-from-env (1.1.0 → 2.1.0). All requests go through a
single axios.create({baseURL}) with relative paths (src/lib/api.ts), so
the 1.8 allowAbsoluteUrls change is moot. typecheck / lint / unit (1121
passed) / build all green.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
* feat(docs): ko-style 한국어 번역투 린터 + 기존 문서 정비

im-not-ai 개념(10대분류 × 심각도)을 결정적 정규식 린터로 구현해
한국어 문서(docs-site/i18n/ko)의 번역투를 점검한다. LLM·외부 의존성 없음.

도구 (tools/ko-style/):
- patterns.json  번역투 카탈로그 16룰 (S1/S2/S3)
- lint.mjs       무의존 Node ESM 엔진 (코드펜스/URL/주석/front-matter 마스킹)
- hook.mjs       PostToolUse 훅 — KO .md 편집 시 S1/S2 즉시 피드백
- selftest.mjs   example_bad/ok + 마스킹 가드 회귀 검증
- README.md      카탈로그 문법·심각도·사용법

강제 수단 (CI 게이트 외 3갈래):
- .claude/settings.json   PostToolUse(Edit|Write|MultiEdit) 훅 배선
- .claude/commands/ko-style.md  /ko-style 온디맨드 점검 커맨드
- doc-writer.md·i18n-specialist.md·CLAUDE.md DoD  작성 단계 규칙

정비: KO 문서 7개 11건(S2 9·S3 2) 번역투 능동태/자연어로 교정 →
전체 48파일 S3까지 0건. docs-uat parity 불변(주석 미수정).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* feat(ko-style): AI 글투 예방 규칙(이모지·단계 라벨) + 방법론 문서화

im-not-ai 방법론을 반영해 정규식으로 정밀하게 잡히는 AI 글투만 규칙화:
- decorative-emoji (S2): 장식 이모지 차단 (→ 화살표는 제외)
- mechanical-step-label (S3): '1단계:'/'Step 1:' 라벨 차단

가운뎃점(·)·화살표(→)·과한 볼드는 이 레포의 디자인 시스템·도메인 표기와
충돌(KO 문서 실측 ·391·→219·볼드68줄, 상당수 정당)하므로 자동 차단하지 않고
README 가이드로 남김. 18룰 selftest PASS, 48파일 lint CLEAN.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* fix(docs): scans.md 잔여 번역투 정비 ('위치합니다'·'가지고 있습니다')

ko-style 음절-융합 패턴 수정으로 새로 탐지된 잔여 2건을 능동/자연어로 교정.
전체 48파일 S3까지 0건, selftest 18룰 PASS, docs-uat parity 불변.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* fix(ko-style): HTML 주석 마스킹을 정규식→문자열 스캔 (CodeQL js/bad-tag-filter)

CodeQL 이 lint.mjs 의 '<!--.*?-->' (L70) 와 '<!--/-->' 테스트(L112)를
high(js/bad-tag-filter)로 지적 — --!> 종료·newline 포함 주석을 놓침.
실제로는 보안 sanitizer 가 아니라 산문 마스킹용이지만, indexOf 기반
maskDelimited() 선형 스캔으로 교체해 휴리스틱을 원천 제거. 동작 동일
(selftest 18룰+5가드 PASS, 48파일 lint CLEAN).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Adds .github/dependabot.yml so security vulnerabilities raise automatic
fix PRs — automating the manual axios bump done in #328.

Security-updates only: every ecosystem sets open-pull-requests-limit: 0
to suppress routine weekly version bumps while still receiving security
fixes (GitHub-supported), each grouped into a single PR per ecosystem to
keep our heavy CI to one run. Covers npm (apps/frontend, docs-site),
pip (apps/backend), docker (apps/frontend, apps/backend), and
github-actions (workflows + actions/scan). Intentionally-vulnerable test
fixtures under apps/backend/tests/e2e/golden/fixtures/** are excluded.

The actual switch (repo "Dependabot security updates" /
automated-security-fixes) was enabled separately.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
…330)

Bumps [playwright](https://github.com/microsoft/playwright) to 1.60.0 and updates ancestor dependency [@playwright/test](https://github.com/microsoft/playwright). These dependencies need to be updated together.


Updates `playwright` from 1.48.2 to 1.60.0
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](microsoft/playwright@v1.48.2...v1.60.0)

Updates `@playwright/test` from 1.48.2 to 1.60.0
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](microsoft/playwright@v1.48.2...v1.60.0)

---
updated-dependencies:
- dependency-name: playwright
  dependency-version: 1.60.0
  dependency-type: indirect
- dependency-name: "@playwright/test"
  dependency-version: 1.60.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
GitHub's default code-scanning setup did not reliably apply
.github/codeql/codeql-config.yml's paths-ignore, leaving 7 test/tooling
alerts flagged despite the config (Math.random test ids, canned URL
substring checks, local QA/docs scripts).

Switch to an advanced CodeQL workflow (.github/workflows/codeql.yml) that
passes config-file to the init action, so apps/*/tests/**, qa-handover/**,
scripts/scan-bench/**, and tools/** are actually excluded. Adds tools/** to
the ignore list (the docs-uat runner was missing). Languages mirror what the
default setup detected (javascript-typescript, python, actions), build-mode
none.

The default setup was disabled in repo settings; the 7 existing alerts were
dismissed (used-in-tests / won't-fix).

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Add a gitleaks workflow that scans the full git history for committed
credentials on every push and PR (HARD FAIL on any finding), complementing
the existing bandit/semgrep SAST. Unlike ci.yml/sast.yml it has no
paths-ignore: secrets can land in any file type and the scan is cheap.

A one-time full-history scan found no real leaked credentials. The 23
generic-api-key/private-key hits are all provable non-secrets (synthetic
unit-test fixtures, deterministic CI/UAT/dogfood dummy SECRET_KEYs, and a
docs live-demo placeholder); they are allowlisted in .gitleaks.toml by path
and by regex. With the config applied gitleaks reports no leaks.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
The project earned the OpenSSF Best Practices passing badge
(bestpractices.dev project 13060). Add the badge to the README badge row.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
* ci(docs): restore GitHub Pages auto-deploy on main push

* fix(docs): bump Docusaurus to 3.10.1 to fix drifted build (mermaid version mismatch + webpack ProgressPlugin reporter break); build green EN+KO on Node 20
…d binaries (#340)

Trivy's worker image scan trips on CVE-2026-42504 (net/textproto MIME-header
decode DoS) present in the Go stdlib statically linked into the vendored
cdxgen-plugins-bin / ORT / Trivy CLI binaries (Go 1.22.7 / 1.25.9 / 1.25.10).
Same UNREACHED category as the existing Go-stdlib suppressions: our scan
entrypoints never feed those binaries attacker-controlled MIME headers.

Suppress with rationale; revisit once the vendored binaries ship a Go ≥1.25.11
rebuild. Unblocks the required image-scan (worker) check.
…dit (C-2) (#339)

A PAT embedded as https://<token>@host in projects.git_url — the documented
inline-credential mechanism the clone path honours — leaked verbatim through
GET /v1/projects/{id}, the audit diff, and the UI Repository field.

- mask_git_url(): scheme-aware userinfo redaction (http/https only; ssh git@host preserved)
- ProjectPublic.git_url field_serializer masks on the wire, covering pre-existing rows
- audit _URL_REDACT_COLUMNS strips userinfo in the diff (host/path preserved, not full mask)
- update_project drops a re-submitted masked git_url so the real stored credential survives
- guard test: read-API + audit-diff + round-trip assertions with adversarial userinfo

Inline credentials remain functional (DB value unchanged); only outbound surfaces mask.
…on (C-1) (#341)

Reuse detection walked only the parent_jti chain upward, so the live
descendant session — the refresh minted by the legitimate rotation that raced
a replayed token — survived. A stolen old token was rejected on replay, yet
the real session stayed valid (and a victim replaying their own old cookie
left the thief's rotated session alive). RFC 6819 §5.2.2.3 requires the whole
family to die.

Replace the ancestor-only _revoke_chain with _revoke_all_active_for_user: on
reuse detection revoke every still-active refresh for the user (one indexed
UPDATE on (user_id, revoked_at)), forcing re-auth on all devices.

Guard test asserts the current/live refresh is also rejected after a replay.
…s (H-3/M-7/M-36) (#342)

Three audit-completeness fixes in the listener + request middleware:

- H-3: RequestIDMiddleware now writes the resolved request_id (including a
  server-generated one) back into scope["headers"], so AuditContextMiddleware
  reads the same value. Previously a request without an inbound X-Request-ID
  got a fresh uuid that never reached the audit context — every audit row's
  request_id was null and could not be correlated with the response header.
- M-7: status-transition update diffs now carry previous_status / new_status
  (additive; the plain `status` key is preserved) so an audit query can answer
  "moved from/to" without stitching adjacent rows.
- M-36: report_downloads is non-audited — an SBOM/NOTICE export must leave only
  a structlog line, not an audit_logs row.

Verified live: a create with no inbound X-Request-ID now records a matching
request_id on its audit row (0/3 recent rows null). Guards added for M-7/M-36.

Follow-up (PR-3b): M-4 (create target_id), M-5 (soft-delete action), M-6
(finding create audit) need a before/after-flush restructure — split out to
keep this change low-risk on the shared listener.
#343)

The in-app notification catalog (notification_kind enum / Pydantic Literal) and
the external-dispatch catalog (dispatcher.NotificationKind) had drifted to a
single shared kind (scan_completed), and the approval disposition path never
emitted anything — a latent break that would surface the moment a producer was
wired (in-app-only kinds crash dispatch with ValueError; dispatch-only kinds
are rejected by the Postgres enum).

- migration 0030: add approval_state_changed to the notification_kind enum so
  an approval requester can receive an in-app row of that kind.
- in-app catalog: add approval_state_changed to the enum values + Literal.
- transition_approval: on a terminal disposition, enqueue the requester's
  notification (in-app + external channels; channels self-skip when unset).
  Best-effort after commit so a broker hiccup never rolls back the transition.
- dispatcher: an unknown kind now logs + returns a skipped report instead of
  raising — a crash would fail the Celery task and drop the in-app row too.
- guard test: pins both catalogs + the crossover kinds; defensive-skip test.

Verified live: reject disposition creates the requester's approval_state_changed
in-app notification via the worker.
…9/M-23) (#344)

The advertised obligation vocabulary, the catalog emissions, and the demo seed
had drifted three ways: the catalog emitted `patent` (Apache-2.0 / GPL-3.0
grants) which was absent from KNOWN_OBLIGATION_KINDS; `dynamic-linking` was
advertised but never emitted; and seed_demo hand-wrote `source_disclosure`
(underscore) instead of the catalog's `source-disclosure`, splitting one
obligation across two kinds.

- vocab: add `patent` so the emitted kind round-trips as known, not "unknown".
- catalog: add KIND_DYNAMIC_LINKING and emit it from _lgpl (the LGPL relink
  right) so the advertised kind is actually reachable (M-23).
- seed_demo: use the hyphenated `source-disclosure`.
- drift guard test: every catalog-emitted kind must be in the vocabulary, and
  dynamic-linking must be reachable — fails loudly on future drift.

Existing demo rows carrying `source_disclosure` normalize on the next re-seed;
a data-normalize migration can chain once the migration head settles.
trigger_scan checked only team access and the per-team concurrency cap, so an
archived project still accepted scans (the report saw 202) — archiving hid the
project from default lists but never disabled new work, leaking worker capacity
and defeating the documented "archive disables scans" contract.

Add a ScanArchivedConflict (409) raised right after the team-access gate, before
any worker slot is reserved. The router's generic ScanError→Problem mapping
surfaces it as application/problem+json with title "Project Archived".

Verified live: triggering a source scan on an archived project returns 409
(3/3); guard test added.

Note: M-2 (project-scoped API key must match the request project_id) needs the
api-key project scope plumbed onto CurrentUser — split to a focused follow-up.
…8/M-1) (#346)

* fix(auth): blocklist common passwords + require VEX justification (H-8/M-1)

H-8: registration and password-reset enforced only the 8-char floor, so
`password1234` / `Password1234` / `123456789012` sailed through — the other
half of NIST 800-63B §5.1.1.2 (reject commonly-used / predictable values) was
missing. Add core.password_policy.is_weak_password and apply it as a shared
field-validator on RegisterRequest.password and ResetPasswordRequest.
new_password. Length stays 8 (CLAUDE.md §3); the guide's "12" wording is
corrected to 8 separately in the docs pass. The check reduces a candidate to
its alphabetic core before matching a base word, so a strong password that
merely embeds a weak substring (e.g. Xq7!adminTr9$pLm2) is not rejected.

M-1: a transition into a VEX analysis state (exploitable / not_affected /
false_positive) now requires a justification of >=10 chars
(VulnerabilityJustificationRequired, 422) — the API previously accepted an
empty / one-word note ("enforced softly").

Verified live: register `password1234`/`123456789012` -> 422, strong -> 201.
Guards: password-policy unit + schema tests, justification min-length unit test.

* test(vuln): supply M-1 justification in status-history transition test
…cated MIME (H-4/M-24/M-25) (#347)

- CycloneDX JSON/XML SBOMs now carry a top-level vulnerabilities[] array:
  every finding maps its internal status onto analysis.state via the shared
  vex_export.CYCLONEDX_STATE_MAP (single source), free-text notes go to
  analysis.detail (never the closed justification enum), and affects[].ref
  joins to the component bom-ref within the same document.
- specVersion 1.5 -> 1.6 (JSON + XML namespace) and format-specific media
  types (application/vnd.cyclonedx+json/+xml, application/spdx+json,
  text/spdx) per the user guide (M-24).
- Docs: correct the NOTICE description (license-grouped, no per-component
  copyright lines yet) and the VEX mapping table (analysis.detail column),
  EN + KO (M-25 docs-direction).
- Guards: VEX state mapping / detail / bom-ref join / XML shape / SPDX
  exclusion / byte-stable re-export with findings.
…/M-32) (#348)

- dev + prod compose: mount the trivy-cache named volume on the backend
  (read-only) next to the worker (rw), and pin TRIVY_CACHE_DIR=/var/lib/trivy
  in dev — the backend's admin health panel read $HOME/.cache/trivy in its
  own container and reported the DB state as 'unknown' forever while the
  worker matched vulnerabilities fine.
- helm (L-2): dedicated trivy-cache PVC (RWX, 2Gi) mounted rw on the worker
  and ro on the backend; fix the configmap env key TRIVY_DB_CACHE_DIR ->
  TRIVY_CACHE_DIR (the old key was never read, so the k8s worker cached into
  the container layer and re-downloaded on every restart).
- admin/disk (M-32): new trivy_db card probing TRIVY_CACHE_DIR, between
  workspace and postgres; schema Literal + FE type + EN/KO labels.
- docs: env-variables / vulnerability-data EN+KO corrected to the real env
  key and shared-volume layout.
…H-2) (#349)

- toAuthUser mapped role from is_superuser alone, so a team_admin was
  treated as a developer across the app chrome: the approvals drawer never
  showed Start Review / Approve / Reject to screen-only team admins.
- New lib/roles.ts effectiveRole() (single source: superuser wins, else
  highest membership role, least-privilege fallback) + usePermissions()
  hook consolidating the four copy-pasted role gates (ApprovalsDrawer,
  AdminLayout, PoliciesPage, CommandMenu).
- Admin users LIST now carries a membership rollup (role + team_count,
  one aggregate query) so the role column shows team_admins and the team
  count cell is no longer an em-dash; FE renders the payload values.
- Guards: role-promotion matrix + usePermissions matrix (vitest), list
  rollup assertions (pytest).

Project-scoped surfaces keep using the server-computed current_user_role.
…ows (M-4/M-5/M-6) (#350)

- M-4: create audit rows now carry the server-generated PK. The listener
  splits into before_flush (diff capture — history resets with the flush)
  and after_flush (PK backfill via primary_key_from_instance + Core INSERT
  on the session connection, same transaction). update/delete rows keep the
  ORM path so a delete-op row referencing a team deleted in the same flush
  is still inserted before the DELETE and cascade-nulled (PR #48 semantics).
- M-5: an archived_at NULL->ts UPDATE is recorded as action=archive
  (ts->NULL as unarchive) so the guide's 'who deleted project X' query has
  a row to find; admin-guide docs corrected EN+KO.
- M-6: worker sessions run no audit listener (installing it would write
  thousands of artifact rows per scan), so the compliance evidence chain
  ('when was CVE X first detected') gets explicit per-finding create rows:
  new emit_finding_create_audits() wired into both persist paths (source
  persist_trivy_findings, container _persist_trivy_report), team-scoped,
  same transaction as the findings — mirroring the auto-approval precedent.
- Drive-by: sbom export license tests used fixed component names and
  collided with leftovers on a persistent dev DB; unique-suffixed.

Guards: target_id backfill (DB), _soft_delete_action matrix, per-finding
audit emission + unassigned-PK/no-op edges; archive assertions updated.
…M-2) (#351)

A project-scoped API key narrowed the synthesized principal to the project's
owning TEAM only, so a single-project CI key could trigger / poll / read
gate data for every OTHER project of the same team (least-privilege breach,
TC-APIKEY-04-006).

- CurrentUser gains api_key_project_id, set only for project-scoped keys
  (None for JWTs and team/org-scoped keys).
- scan_service.trigger_scan + get_scan reject a mismatch with 403 after the
  team gate.
- policy_gate _load_project_for_gate (shared by gate-result + post-pr-comment)
  applies the same boundary as a 404 existence-hide — security review found
  these dual-auth endpoints leaked a sibling project's SCA report (and, with
  dry_run=false, could exfiltrate it to an arbitrary GitHub PR).
- get_api_key_principal now fails closed on a malformed scoped key (scope set
  but scope-id NULL) instead of falling through to the full org membership.

Guards: unit (trigger/get boundary), integration (full HTTP chain: sibling
trigger 403 / own 202 / sibling poll 403 / own poll 200), policy-gate loader
regression (sibling 404 / own ok / JWT unaffected).
…ue key (M-10/M-9) (#352)

- M-10 (code): archiving was gated at team_admin while creating is a
  developer action, contradicting the guide ('developer and above can
  create and archive'). archive_project now uses _can_access_team (team
  membership) and the DELETE route requires only 'developer'. The
  membership boundary still blocks non-members; team_admin-gated *update*
  (settings/visibility) is unchanged.
- M-9 (docs): the unique key per team is the slug, not the name — two
  projects in a team may share a name. Corrected the field table (added a
  Slug row) and the '409' troubleshooting entry, EN + KO.

Guards: developer-archives-own-team (unit + integration 204), non-member
forbidden, split-membership user archives both teams.
…-30/M-12) (#353)

- M-30: ScanDeleteConflict now carries machine-readable RFC 7807 extensions
  (scan_active / scan_release_protected, exactly one true) so automation no
  longer parses the human detail string to tell the two delete-conflict
  reasons apart. The base ScanError gains an empty extensions default and the
  scan-error converter passes **exc.extensions (no-op for errors without
  any), mirroring the cancel-path converter. scan-retention admin docs now
  note scan_active on the active-scan row too (release_protected was already
  documented).
- M-12 (docs): SBOM/NOTICE endpoints existence-hide non-members as 404 (not
  403 like project-detail) because their bodies expose structural detail —
  documented the rationale in the SBOM troubleshooting section, EN + KO. The
  404 is intentional (sbom.py / obligations.py comments); code unchanged.

Guards: delete-conflict extension assertions for both reasons (unit).
)

Re-requesting a finding's current status was reported as a failure — the
single PATCH returned 422 'already in status X' and the bulk endpoint coded
the row success=false / 422 invalid_transition. A client re-applying a
desired VEX state (CI automation, a re-imported VEX doc) read those no-ops
as errors.

- Single PATCH: a no-op now returns 200 with the unchanged detail payload,
  no mutation, no audit row.
- Bulk: a no-op row is success=true, status_code=200, error=already_at_target
  and counts toward the envelope's succeeded total.

Genuine matrix violations stay 422 (with allowed_to). The status code is
NOT moved to 409: 409 is already the optimistic-concurrency conflict on this
surface (if_match mismatch -> FE 'Reload'); reusing it for invalid
transitions would collide. The guide is corrected to document 422 for the
matrix and the bulk per-row outcome codes (transitioned / already_at_target
/ invalid_transition / forbidden / not_found), EN + KO, plus an
already_at_target i18n label.

Guards: single no-op returns unchanged + no audit row; bulk no-op success
coded already_at_target. Existing 422/bulk assertions updated.
#355)

Fourteen guide-vs-implementation drift items from the validation report,
all doc-direction (code is correct, docs realigned), EN + KO:

- L-1 helm chart version 0.3.0 -> 0.10.0 (+ architecture.md spillover)
- L-3 upgrade IMAGE_TAG default 2.0.0 -> 0.10.0
- L-4 gcp-deploy demo password DemoAdmin2026! -> DemoTest2026!
- L-5 evaluation install: drop the absent eval-up.sh / docker-compose.eval.yml,
  document the real dev-compose flow (+ release-notes spillover)
- L-7 daily demo reset: GCP Cloud Scheduler/Terraform -> Hetzner systemd
  units (deploy/hetzner/trustedoss-demo-reset.{service,timer})
- L-8 compose service names: document env-specific worker/beat (prod) vs
  celery-worker/celery-beat (dev); fix log event scan_retention_sweep ->
  scan_retention_done
- L-9 version command: from main import APP_VERSION -> from main import app;
  print(app.version) (2.2.0-dev)
- L-11 backup diagnostics: /backups -> /opt/trustedoss/backups, host-side
  scripts/backup.sh invocation
- L-13 audit page_size 1000 -> 200 (the cap)
- L-19 key-verify example: GET /v1/projects (JWT-only) -> key-authenticated
  scan trigger + poll
- L-20 webhook success 202 -> 200
- L-21 API key surrounding-whitespace is stripped/tolerated
- L-22 log event secret_mismatch -> api_key.auth_failed
- M-33 disk cleanup workspace path -> WORKSPACE_HOST_PATH, scan-id keyed

ko-style 0 findings; docs-site build green (en + ko).

Follow-up flagged: gcp-deploy.md is substantively stale vs the Hetzner
hosting decision; a full GCP->Hetzner rewrite of that page is a larger
drift outside these 14 items.
…ia /auth/me (M-8/M-11) (#356)

- M-8 (code): deleting a team CASCADE-removes its projects + scans/findings,
  so an accidental delete could silently destroy live data. delete_team now
  refuses with 409 (TeamHasProjects, extensions team_has_projects +
  project_count) while any NON-archived project remains — the operator must
  archive them first, matching the guide. Archived projects do not block
  (already retired; the CASCADE finalises them). Active-scan 422 guard
  unchanged. Admin guide (EN+KO) documents the guarded delete.
- M-11 (docs): the guide said a team_admin reads team_id from
  GET /v1/admin/teams, but that endpoint is super_admin-only (team_admin gets
  404). Corrected to GET /auth/me — any signed-in user's memberships[] carry
  team_id/team_name/role. Fixed the path to /auth/me (the auth router mounts
  at /auth, not /v1 — caught live: /v1/auth/me is 404). KO projects.md was
  additionally out of sync (claimed team_id is auto-derived) — realigned to EN.

Guards: live-projects refused (409 + extensions) + archived-only succeeds,
unit + integration.
…r procps (L-6/M-31/L-10) (#357)

- L-6: the backend has no /metrics route (Prometheus exporter unimplemented),
  but the prod Traefik backend router rule routed PathPrefix(/metrics) → a
  404. Dropped /metrics from the rule and corrected the compose comments +
  EN/KO docs (architecture, docker-compose install) to state /metrics is not
  routed.
- M-31: scripts/backup.sh now records has_workspace (boolean) in manifest.json
  — matching the Celery auto-backup manifest field name/type
  (tasks/backup.py:574) — and emits a WARN to stderr when the workspace mount
  is absent (exit stays 0 since the DB dump succeeded, but the backup is
  flagged DB-only). Previously it printed a plain 'skipping' line and exited
  0 with no machine-readable marker.
- L-10: added procps to the worker image OS deps so ps works for the
  orphan-process diagnostics in oncall-runbook.md.

Verified: prod compose YAML parses (7 services), routing rule carries no
/metrics, manifest.json renders a valid JSON boolean, Celery manifest field
matches, shellcheck no new findings, ko-style 0.
haksungjang and others added 28 commits July 16, 2026 23:16
…nted 5 projects (#483)

The seed-baseline agreement (PR-4, tests/verify-specs/PROVENANCE.md) made
seed_demo create the _seed_verify_baseline fixture projects (fx-appr + three
'Project ...' probe rows) on every run. That pushed the super admin's visible
project list from the documented 5 to 8, and the docs-uat quickstart-gate
(expectVisibleProjectCount(5)) has failed on every nightly main run since
2026-06-10.

Fix: an opt-out --demo-only flag skips the baseline block on both the fresh
and the already-seeded paths, and the quickstart guide (EN/KO) now documents
that flag in its seed command — the gate replays the doc's command, so the
gate seeds a clean 5-project demo again. The DEFAULT stays baseline-inclusive:
the verify-specs nightly and the verification team's Tier-3 runs are
unaffected (PROVENANCE.md ground rule 1 intact).

Tests: argparse contract (unit), and a real-Postgres integration test pinning
demo-only == exactly the 5 documented projects / no baseline names, the
short-circuit path honoring the flag, and the default still topping up.
…out (#484)

No failed write may stay silent: a MutationCache-level onError in
lib/queryClient.ts now toasts the RFC 7807 detail (generic i18n fallback)
for any mutation that does not surface its own error. A full audit of all
39 useMutation call sites found every live mutation already surfaces errors
via catch/inline/per-call handlers, so each declares the new
meta.errorToast=false opt-out (35 sites) and the global toast is the safety
net for future mutations (plus the one currently-silent dead mutation,
useDeleteApproval). Mutations defining options.onError are skipped
automatically; meta.errorToast=true forces the toast past rollback-only
handlers; 422 validation problems stay inline per the design-system
feedback rule.

The toast dispatches through a new lib/toastBus.ts that ToastProvider
registers on mount, so non-React code reuses the single W12-B toast region
(same admin-toast testid contract, key=mutation-error).

ErrorBoundary closeout: the fallback strings move to common:errors.* (EN/KO)
and the fallback container gains role=alert so screen readers announce the
crash. Crash-reporter wiring stays deferred until there are real users
(tracked in docs/completeness-master-plan.md).

Tests: 8-case unit contract for the global handler, a provider-wiring test
that asserts the real toast DOM through AppProviders, and ErrorBoundary
a11y/i18n assertions. Full FE suite green (153 files / 1374 tests), tsc
clean, eslint clean.
…rs (W8-#47) (#487)

The flat 200x per-member compression-ratio ceiling rejected real OSS trees on
first upload: Juice Shop 17.0.0 ships two sparse test PDFs at 918x/940x that
inflate to only ~150-225 KB. A high ratio is only dangerous when the inflated
size is large, so the ceiling now applies only to members declaring more than
SOURCE_ARCHIVE_RATIO_GUARD_MIN_BYTES uncompressed (default 10 MiB).

Safety is unchanged: a lying header cannot exploit the exemption (CPython's
zipfile enforces file_size as a hard read limit — understating truncates and
fails CRC), the streamed total-extracted cap remains the authoritative bomb
guard for every member, the impossible-ratio (0 compressed / >0 uncompressed)
reject stays unconditional, and a 42.zip-class member (GB-scale declared
size) still trips the ceiling instantly.

UX: the rejection message now includes the member's sizes and names the two
env knobs that resolve a false positive.

Tests use the REAL captured Juice Shop sparse PDF as a fixture (hardening
rule 3 — no hand-made minimal JSON), plus an above-floor bomb reject and env
default/override pins. Exit criterion verified end-to-end: the actual
juice-shop v17.0.0 release zip (46 MB, 1,275 members) extracts cleanly with
default settings.
The automatic before_flush audit listener only sees DB rows, so two
user-triggered side effects left no audit trail:

- POST /v1/scans/{id}/post-pr-comment writes to GitHub (external HTTP) —
  now audited as sca_pr_comment.posted / .updated with repo, PR number,
  gate verdict, and comment id/url in the diff. dry_run has no side effect
  and stays unaudited (pinned by a negative test). The GitHub token never
  enters the diff (pinned by test).
- POST /v1/projects/{id}/source-archive saves a .zip to the workspace —
  now audited as source_archive.uploaded with archive_id/filename/bytes.

Both rows carry the full request context (actor, team, request_id, ip,
user_agent) and run their diff through mask_sensitive_columns as
defense-in-depth (explicit rows bypass the listener's masking).

save_uploaded_archive now returns a SavedArchive(archive_id, bytes_written,
team_id) tuple so the router needs no Project re-query and no post-save
stat() (which could race retention cleanup and 500 an upload that already
succeeded). Also backfills ip/user_agent on the pre-existing explicit scan-
delete audit row (same one-line pattern).

Survey result recorded for reviewers: backup_service mutations are already
fully audited at their callers (adding service-level rows would double-
audit backup.deleted/pruned), source-preservation tarballs are derived
scan artifacts (deliberately unaudited, consistent with report_downloads),
and upgrade_recommendation is a pure module — no action there.

Producer-Reviewer: security-reviewer round 1 CHANGES_REQUIRED (2 Low,
2 Info — all applied), round 2 APPROVE. 100 affected tests green, full
mypy green.
…eport claim (#486)

The KO docs tree only carried the v0.10.0 release notes — v0.11.0, v0.11.1,
v0.12.0, v0.13.0, and v0.13.1 now have full KO translations (frontmatter ids
and sidebar positions mirror EN; ko-style lint S1/S2/S3 all zero).

Also fixes a docs-drift bug the KO build surfaced: the SBOM guide's Roadmap
bullet (EN and KO) still claimed the vulnerability Excel report was not
implemented — it shipped in v0.13.0 — and the KO variant linked a stale
anchor (#pdf-보고서-다운로드) into the vulnerabilities guide whose heading
became '보고서 다운로드 (PDF 또는 Excel)'. Both languages now state PDF +
Excel exist, scope the outstanding items to the Components Excel export and
the Compliance PDF, and link the current anchor. Docusaurus build is clean
for both locales with zero broken anchors.
…efresh (#489)

- Release notes (EN/KO) for v0.14.0: runtime-scope filtering headline with
  the count-drop caution + SCAN_SCOPE_FILTER_ENABLED=false rollback, EOL
  flagging, iOS lockfile scanning, error-UX/audit closeout, fixes, security.
- CHANGELOG: stamp [Unreleased] as [0.14.0] - 2026-07-16 and add the
  session's entries (audit trail, global error toast, zip-ratio guard fix,
  seed --demo-only, Go 1.25.12).
- Releasing guide (EN/KO): refresh_eol_snapshot.py is now step 1 of the
  release procedure; snapshot refreshed for this release (2026-07-16).
- IMAGE_TAG 0.12.0 -> 0.14.0 (.env.example; was stale since 0.12.0).
- docs/completeness-master-plan.md: the v0.14~v0.17 completeness master
  plan enters the repo as the roadmap tracker, with v0.14.0 item statuses.

Merge LAST after #483/#484/#486/#487/#488 - then tag v0.14.0 (the tag is
the release gate's first live verification, master plan item 3).
* feat(licenses): Korean license summaries + obligation text (C1a)

Commercial-parity candidate C1a (docs/commercial-parity-candidates.md): the
UI chrome was already bilingual, but license summaries and obligation prose
came back from the API as English pass-through. This localizes that content
for the finite 52-license classification catalog — the differentiator for
the domestic (Korean) market TRUSCA targets.

- New services/license_translations.py: a plain-language EN summary (new —
  the catalog never had one) + KO summary per catalog license, and a KO
  rendering of every obligation paragraph. Obligation translations are keyed
  by the English paragraph so the 52 licenses' 48 shared paragraphs are each
  translated once.
- APIs return EN + advisory KO side by side (licenses list/detail summary +
  summary_ko; obligation list/detail text_ko; compliance obligation
  summary_ko). No DB columns, no migration — the code catalog is the source
  and the service layer attaches KO by spdx_id / English text. NOTICE output
  stays English (legal artifact).
- FE useAdvisoryTranslation() centralizes the rule (show KO when reading
  Korean AND a rendering exists, else English) across the license drawer,
  obligation drawer, and compliance grid, each exposing a 'show the original
  English' disclosure. English stays authoritative.

Drift is closed by contract tests (test_catalog_contracts.py): every catalog
paragraph must have a KO rendering and vice versa, every catalog license must
have both summaries, and KO text must actually contain Hangul (a copy-paste
that leaves English in the ko slot fails). FE mirror test guards the two new
advisory locale keys have EN+KO. User guide (EN/KO) documents that KO is
advisory and English is authoritative.

Note: the KO-render path is unit-covered (ObligationDrawer/LicenseDrawer/
ComplianceTab + contracts) rather than e2e, because the e2e seed
(seed_e2e_user --with-obligations) creates synthetic E2E-* licenses that are
outside the catalog and so have no translation — an e2e there would only
exercise the English-fallback branch.

Backend 4678+ unit / 272 licenses+obligations+compliance tests green, full
mypy + ruff clean; FE 1381 tests green, tsc + eslint clean; docs build clean
(0 broken anchors), ko-style S1/S2/S3 zero.

* test(fe): backfill new C1a fields in license/obligation test mocks

The C1a wire types gained required fields (LicenseListItem/DetailResponse
summary+summary_ko, ObligationListItem text_ko, ComplianceObligation
summary_ko). Four existing mock builders constructed these types inline
without the new fields, failing `tsc -b` in CI (my local `tsc -p` did not
follow the test project references). Backfill the fields (null defaults).

* test(fe): use labelMap helper instead of any-cast in C1a mirror test

eslint's no-explicit-any failed the lint job on the two advisory-key parity
checks I added. Reuse the existing labelMap(ns, ...path) traversal helper
(returns Record<string, string>) instead of casting to Record<string, any>.
Commercial-parity candidate C3 (docs/commercial-parity-candidates.md): an
optional `profile` query param on GET /v1/projects/{id}/sbom applies the
project's effective license policy to the export, reusing the existing gate
engine and 4-format exporter.

- policy-annotated: flags each violating component IN PLACE — CycloneDX
  `properties` (trusca:policy:category / :spdx), SPDX `REVIEW` annotations —
  for forbidden AND conditional licenses, without removing anything.
- policy-filtered: drops forbidden components and the CycloneDX
  vulnerabilities[] entries that referenced them (no dangling affects[].ref),
  recording the excluded count on the document.
- No profile (the default) reproduces the pre-C3 export byte-for-byte; the
  40 existing byte-stability tests are unchanged.

The gate's forbidden-count loop was refactored into a reusable
`compute_component_policy_categories` -> {cv_id: ComponentPolicyVerdict}
(shared CATEGORY_RANK, behavior-preserving — 74 gate tests green). No team
policy configured falls back to the persisted static categories, so a
forbidden GPL is still flagged. Annotation dates use the deterministic
scan timestamp (byte-stable), and _xml_safe strips XML-1.0-illegal control
chars from every scan-derived string reaching XML/SPDX text.

FE SbomTab gains a profile selector (default / annotated / filtered) with a
signing-boundary note: cosign signs only the default export; profile
exports are unsigned (documented in the SBOM user guide, EN/KO). Profiled
downloads are named sbom-<slug>-<profile>.<ext>.

Producer-Reviewer: security-reviewer 2 rounds — CHANGES_REQUIRED (rank
duplication + XML control chars) -> both fixed with contract/guard tests ->
APPROVE. 55 sbom_export + 87 gate/API tests green, full mypy + ruff clean,
FE tsc -b + eslint clean, docs build clean, ko-style zero.
Bumps the docs-site-npm-security group with 1 update in the /docs-site directory: [websocket-driver](https://github.com/faye/websocket-driver-node).


Updates `websocket-driver` from 0.7.4 to 0.7.5
- [Changelog](https://github.com/faye/websocket-driver-node/blob/main/CHANGELOG.md)
- [Commits](faye/websocket-driver-node@0.7.4...0.7.5)

---
updated-dependencies:
- dependency-name: websocket-driver
  dependency-version: 0.7.5
  dependency-type: indirect
  dependency-group: docs-site-npm-security
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps the backend-pip-security group with 1 update in the /apps/backend directory: [aiosmtplib](https://github.com/cole/aiosmtplib).


Updates `aiosmtplib` from 3.0.2 to 5.1.1
- [Release notes](https://github.com/cole/aiosmtplib/releases)
- [Changelog](https://github.com/cole/aiosmtplib/blob/main/CHANGELOG.rst)
- [Commits](cole/aiosmtplib@v3.0.2...v5.1.1)

---
updated-dependencies:
- dependency-name: aiosmtplib
  dependency-version: 5.1.1
  dependency-type: direct:production
  dependency-group: backend-pip-security
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* docs: mark C1a + C3 done in master plan, add v0.15.0 CHANGELOG entries

C1a (#491) and C3 (#492) are merged; record them as done in the completeness
master-plan tracker and add their [Unreleased] CHANGELOG entries (Korean
license content; policy-aware SBOM export profiles).

* fix(scan): air-gap gate the post-cdxgen license fetcher (W8-#48)

The post-cdxgen license enrichment (integrations/license_fetcher/) already
resolves a bare requirements.txt / go.mod component's license from its public
registry (PyPI / Maven / crates / pkg.go.dev) by purl and records a concluded
finding — the golden python-pip baseline proves it pulls the unknown ratio
down. But it egressed UNCONDITIONALLY at scan time, with no off switch, which
violated the established air-gap posture (SCANOSS / EOL live-refresh are
gated) and, on an air-gapped deployment, made every unlicensed component pay a
network timeout and poison the fetch cache with a negative.

Add LICENSE_FETCH_ENABLED (default on — only a package name+version leaves the
network, far lower-sensitivity than SCANOSS fingerprints) and gate the fetcher
call in _persist_component_licenses. Off (false/0/no) skips the fetch cleanly:
the component stays license-unknown, no egress, no cache write.

Tests: config-flag accessor (default-on, falsy tokens, call-time read) and a
parametrized integration test proving the gate controls whether the fetcher
runs (stub call count + concluded-finding / cache-row presence) across all
flag values. Documented in .env.example + env-variables reference (EN/KO) and
the 'why so many unknown' user-guide section (EN/KO).

Also folds the deferred v0.15.0 bookkeeping (C1a/C3 tracker + CHANGELOG) that
a docs-only PR could not land through branch protection.
Ruby (Gemfile) and .NET (.csproj) dependencies came back 100% license-unknown
because the post-cdxgen license fetcher had no adapter for their registries.
Add two, following the established crates.py/pypi.py pattern and registered in
PURL_PREFIX_TO_FETCHER:

- rubygems.py: pkg:gem/<name>@<version> → rubygems.org v2 API, reads the
  version's licenses[] array (already SPDX in the gemspec).
- nuget.py: pkg:nuget/<id>@<version> → api.nuget.org v3 registration API,
  reads catalogEntry.licenseExpression (SPDX). Deliberately uses the JSON
  registration API, NOT the .nuspec XML — the project vendors no hardened XML
  parser and parsing untrusted network XML risks entity-expansion DoS.

Both inherit the fetchers' security posture (follow_redirects=False, host-
pinned base URL, error-swallow-to-None, 24h cache, LICENSE_FETCH_ENABLED gate)
and the NuGet catalog-URL hop is guarded to stay on api.nuget.org.

security-reviewer: APPROVE. Applied its two in-scope hardening items —
quote(name, safe='') so a hostile package name cannot introduce a path
separator (with traversal-containment tests), and a conservative RubyGems
rate interval. The shared body-size cap + quote backfill across the other
four fetchers are tracked as a follow-up (completeness master plan §7).

Tests: per-fetcher unit suites (happy path, first-mappable, absent/unmappable
→ None, 404, URL-shape/lowercase pins, off-host catalog refusal, traversal
containment) + the dispatch contract test now covers all six ecosystems.
Docs (EN/KO env-variables + user guide) and CHANGELOG updated. mypy + ruff
clean (532 files), ko-style zero, docs build clean.
…uter dir (K-f2) (#496)

07-11 follow-up, now confirmed a real bug. With SCAN_EXECUTOR=local_docker, an
Android scan of a git repository routed to the sidecar (correctly — Phase K
fixed language detection to yield the real env on git scans) but then read the
compileSdk and ran the build from the OUTER workspace dir, while detection had
run on the resolved clone root a level below (source/ vs source/repo/).
Detection is non-recursive and the sidecar targets a single directory (unlike
the default in-process cdxgen which recurses with -r), so the sidecar picked
the default SDK image and scanned an empty directory. Before Phase K this was
masked: detection returned the wrong env on git scans, so the sidecar was never
routed.

Fix: SbomGenRequest carries the resolved project_root with an effective_root
property (project_root or source_dir); local_docker uses effective_root for the
android compileSdk read and the sidecar scan-target argv. The in-process
executor is unchanged (it recurses from source_dir), and effective_root falls
back to source_dir for non-git / zip-upload scans.

security-reviewer: APPROVE, no findings — effective_root is a worker-
constructed literal path (source_dir or source_dir/'repo'), passed as a quoted
positional argv token to a no-shell subprocess, contained under the workspace;
all sidecar hardening (cap-drop, no-new-privileges, env allow-list, unpinned-
image refusal, workspace-only mount) untouched.

Tests: a git-scan case proving the sidecar resolves the image and scan target
from the inner root and never references the outer dir, plus effective_root
default coverage. k8s executor is not yet implemented, so it is unaffected.
…ns (K-f1) (#497)

Trivy image scans already report whether the scanned image's base OS release
is past its end-of-service-life via the top-level Metadata.OS.EOSL flag, but
the container pipeline only read Results[].Vulnerabilities and dropped the
Metadata block. An image built on an EOL OS (old Debian/Alpine) will never
receive patches for CVEs disclosed after that OS retired — a scan-level risk
no individual package CVE captures.

Backend: extract_os_metadata parses Metadata.OS (family/name/eosl) from the
report we already load; _persist_os_metadata writes it to scan_metadata JSONB
(no migration). family/name derive from the scanned image's os-release files
and are attacker-influenced, so they are clamped to 64/128 chars — the API's
inbound 16 KiB scan_metadata cap does not cover worker-side writes. The write
runs in its OWN best-effort transaction, after the findings commit, so a
malformed report never rolls back a good scan (mirrors scan_source's
detected_env writes). No new egress: EOSL rides Trivy's bundled DB.

Frontend: OsEolPanel renders an 'OS end-of-life' panel on the scan detail page
for container scans when eosl is true — a colored dot plus a literal label
(color is never the only signal), on the High hue family, with the DB-freshness
caveat noted. Source/SBOM scans never render it.

Tests: pure extract_os_metadata unit tests over the recorded real alpine-3.19
image report (EOSL true) + mock report (EOSL false) + malformed shapes + the
attacker-long-string clamp; an integration test proving the OS block lands in
scan_metadata['os']; an FE unit test for readOsBlock narrowing and the panel's
render-only-when-EOL behaviour. Docs (EN/KO) document the panel and the
Trivy-DB-freshness caveat.

security-reviewer: APPROVE (clamp + best-effort transaction verified, no
stored-XSS — React escaping, no new egress).
…/KO) (#498)

Adds the Wave 7 documentation set, EN + KO for each, all wired into the
sidebar and passing the Docusaurus build, docs-uat lint, and ko-style gate.

- user-guide/triage.md — a consolidation page mapping how one finding flows
  across VEX vulnerability triage, component approval, and the build gate,
  instead of leaving those three scattered. Its load-bearing point, grounded in
  policy_gate.py: a rejected component approval does NOT gate the build (the
  gate reads the forbidden license tier + open Critical CVEs only); to block a
  license, promote its tier. Details defer to vulnerabilities.md / approvals.md.
- reference/analysis-types.md — a matrix of the analysis kinds (source SBOM
  scan, container scan, policy gate, reachability) with input/tool/output/when.
  Reachability is represented honestly as planned/best-effort (UI surfaces a
  signal where source data provides it; no govulncheck pipeline ships), matching
  comparison.md and data-sources.md rather than overstating it.
- best-practices/ — a new category of four decision guides (scan-frequency,
  policy-design, team-structure, upgrade-cadence) that link to the on-call
  runbook for incident recovery rather than duplicating it.
- reference/faq.md — a link-hub FAQ over the above and the existing guides.

Also fixes a pre-existing sidebar drift: the v0.13.1 and v0.14.0 release notes
existed (EN + KO) but were never listed in sidebars.ts.

W7-E (retroactive v2.1/2.2/2.3 release notes) is intentionally skipped: the
project uses the 0.x scheme and v0.10-v0.14 notes all already exist; the 'v2.x'
naming survives only in the older tracker, which the master plan overrides — so
there is nothing to backfill. W7-F (DefectDojo/ThreadFix) stays deferred: no
implementation exists and the tracker itself gates it on clarified intent.

ci.yml paths-ignore covers docs/** and *.md but not docs-site/**, so the code
checks still run (and pass on unchanged code); the docs and docs-uat workflows
validate the new pages.
test_report_xlsx_service::test_pure_function_is_deterministic flaked on CI
whenever its two back-to-back builds straddled a one-second boundary. The
builder is documented as pure, but openpyxl leaked the wall clock in two
places: it stamps every zip member's date_time with the save-time clock, and
at save it overwrites docProps/core.xml's <dcterms:modified> with
datetime.now() — ignoring the wb.properties.modified we already set. So two
builds of identical inputs differed byte-for-byte, and the archive leaked the
build host's clock.

The builder now re-pins both to generated_at (already an input) in a
post-save zip repack: every member gets a fixed date_time and core.xml's
created/modified are normalized to the generated_at ISO stamp. The output is
still a valid workbook (openpyxl re-reads it; sheets and pinned modified date
verified).

The old assertion (_build() == _build() back-to-back) only caught this by
luck. Added test_document_timestamps_are_pinned_to_generated_at_not_wall_clock
which asserts the property directly — zip member dates and core.xml modified
both equal generated_at — so it can never flake and can't silently regress.
Adds a Flat / By upgrade toggle to the project Vulnerabilities tab. The
By-upgrade view replaces the flat finding list with the whole-project set of
remediation clusters — Snyk's pattern — where each cluster is the minimum safe
upgrade for one component: the semver-maximum of that component's open
findings' fix versions, i.e. the lowest version that clears every one of its
open CVEs at once.

Backend: a new GET /v1/projects/{id}/vulnerabilities/upgrade-clusters endpoint
+ upgrade_cluster_service. It reuses the existing upgrade-recommendation engine
(recommend_for_component / priority_rank) verbatim and the build gate's own
open-status set (policy_gate._CLOSED_FINDING_STATUSES) — imported, not copied,
so cluster counts can never drift from the gate. Auth, team-visibility, and the
release-snapshot scan_id anchor mirror the flat list endpoint exactly (404/403
before any state, empty 200 when no succeeded scan). Server-computed so counts
are whole-project-accurate rather than per-page. A component whose open
findings have no published fix is grouped under 'No upgrade available' with
reason=no_fix_version rather than given a misleading partial bump.

Frontend: a hand-rolled segmented control (mirrors ComponentsToolbar; no
ToggleGroup dependency) drives local group-by state; the upgrade query is
enabled only in upgrade mode so exactly one query is in flight. Collapsible
cluster cards render the recommended upgrade + fixes-count and expand to the
findings, which open the same existing drawer by finding_id. EN/KO i18n,
severity never color-only (badge + label), 40px density.

Tests: backend integration covers clustering, semver-max recommended version,
no-fix bucket, disposition exclusion + count parity (total == open findings),
priority sort, and auth/existence/bad-scan. Frontend unit covers the ok vs
no_fix cluster rendering and finding→drawer wiring; a harness verb + e2e
scenario cover the toggle and grouped render. OpenAPI snapshot regenerated.

Also records the C1b drop decision (CVE machine-translation) and marks the
v0.16.0 items merged in the completeness master plan.
…le (#501)

Records the review-track decisions for the two P4 UX items:
- W9-#55 (time-series chart): rejected — the metric to plot (risk_score) is
  never persisted (computed per-request), and retention hard-deletes superseded
  scans with their findings after a 7-day grace, so no history accumulates.
  A chart would need prerequisite infra (a metric-snapshot table or retention
  change) plus a charting library the repo lacks. Revisit if real operational
  history and metric persistence land.
- W9-#56 (aggregate-by-component toggle): rejected — component-level grouping
  already exists twice (the just-shipped W9-#53 group-by-upgrade, and the
  Components tab's per-component severity + CVE-count columns with a deep-link).
  The proposal itself flagged it as partially superseded / low priority.

Also marks W9-#53 merged. v0.17.0 UX-expansion track is now closed; remaining
roadmap is the conditional C4 track and the standing debt in §7.
…re-evaluation (#502)

Fixes an accuracy defect: analysis-types.md (Wave 7), comparison.md, and
data-sources.md all claimed reachability is planned / not shipped / 'the backend
does not run govulncheck'. It ships: tasks/scan_reachability.py runs govulncheck
(worker-image binary) as a best-effort post-source-scan enrichment, default-on
REACHABILITY_ENABLED, stamping Go findings reachable/not-reachable/not-analysed,
surfaced by the badge + ?reachable= filter + sort=reachable, and able to feed the
policy gate (fully tested). Corrected EN+KO to: Go ships; other ecosystems not yet
analysed (the multi-language commercial gap).

Also records the C4 re-evaluation. BomLens (now v1.8.2) classifies reachability as
the portal's domain and won't produce it, so the 'BomLens produces -> TRUSCA
ingests' premise is dead. TRUSCA already ships Go reachability, so C4 is reframed
to TRUSCA-owned multi-language reachability and deferred (no offline OSS call-graph
tooling for Java/JS). Master plan §6 + parity-review updated (baseline -> v1.8.2+25,
8th review). Version currency/staleness adopted as parity #18 (implementation next);
regulatory-crosswalk and copyleft-strength recorded as low-value defers.
…mLens parity #18) (#503)

* feat(components): version currency signal — 'behind latest patch' (BomLens parity #18)

Adds a component-level 'version currency' signal, a sibling of the EOL flag
that answers a different question: not 'is this release line dead?' but 'is
this version behind the newest patch of its (still-supported) release line?'.

Fully OFFLINE — reuses the already-vendored endoflife.date snapshot, whose
every cycle carries its newest patch (latest + latestReleaseDate). The same
purl->product->cycle matcher the EOL evaluator uses now also compares the
installed version to the cycle's latest via the tolerant cross-ecosystem
comparator (upgrade_recommendation.compare_versions): installed < latest =>
outdated, >= => current, no cycle/latest/unparseable => unknown. No new
network at scan time.

Backend: migration 0040 adds component_versions.currency_* catalog columns
(mirrors the 0038 EOL columns + a partial index on 'outdated'); evaluate()
carries currency alongside EOL in one pass; stamp_component_version writes it,
so BOTH the scan-persist hook and the weekly refresh beat stamp it for free.
Exposed on the components list/detail and a project-Overview outdated_count,
with an outdated=true filter mirroring the eol=true one.

Frontend: CurrencyBadge (mirrors EolBadge, one tone below at medium — a
maintenance signal, never Critical/High), a Components column, a drawer row,
an Overview 'N outdated' chip deep-linking to the filter, and the toolbar
toggle. EN/KO, a FE<->BE CURRENCY_STATES parity contract, seed fixture on a
component separate from the EOL one so both assert independently.

The deps.dev 'absolute-newest across the ecosystem / N releases behind'
enrichment is a separate opt-in egress path (air-gap gated), not in this MVP.

Tests: currency evaluation matrix (outdated/current/unknown/no-latest/no-match)
+ stamp idempotency, list/detail exposure + outdated filter + IDOR ordering,
CurrencyBadge render + a11y, Overview chip render + deep-link, mirror contract.
OpenAPI snapshot regenerated. Updates parity #18 (closed, offline MVP).

* fix(api): pass outdated_count explicitly in the overview response (hand-built completeness)

The AST hand-built-response completeness contract requires every
ProjectOverviewResponse field to be passed explicitly at the endpoint
construction site; outdated_count (0040) was in the service payload but not
threaded through, so it would silently default on the wire.
…ot tool gap) (#504)

Records the 2026-07-18 reachability investigation and corrects an earlier
inaccurate rationale.

Findings:
- Black Duck ships reachability for Java ONLY, via a mix of static analysis and
  human curation of call paths — not multi-language, not fully automated.
- Offline OSS call-graph tools DO exist per language (JS/TS: Jelly, actively
  maintained BSD-3; Java: WALA/Soot/Eclipse Steady; Python: PyCG/Jarvis), so
  the previous 'no OSS call-graph tools' rationale was wrong.
- The real blocker is DATA: reachability needs to know WHICH function is
  vulnerable per advisory, and only ~1% of advisories carry function-level
  symbols — Go uniquely ~31% (its DB curates symbols), npm/PyPI/Maven far
  lower. That is why govulncheck works turnkey for Go and why even Black Duck
  curates Java symbols by hand.

Decision: defer multi-language reachability, reframed — the moat is the absence
of an open function-level vulnerable-symbol dataset for non-Go ecosystems, not
a lack of call-graph tools. Keep the shipped Go reachability (an automated,
offline edge over Black Duck's Java-only + manual one). C4-1/C4-2 dropped;
C4-3 kept. Master plan §6 and the parity review updated to reference the ADR.
…den path, screenshots (#505)

Close the doc-code drift and onboarding gaps found by the 2026-07 docs
quality review:

- Document the two shipped-but-undocumented user-facing features, EN+KO,
  with docs-uat assertions and freshly captured screenshots:
  * Vulnerabilities "Group by upgrade" view (W9-#53, #500)
  * Version currency "behind latest patch" signal (#503)
- Onboarding golden path: intro now leads with a recommended single
  route (5-min demo -> first real scan -> production); quickstart gains
  step 5 "Scan your first real project"; the docker-compose evaluation
  section delegates its duplicated boot steps to the quickstart (also
  removing its stale seed command that lacked --demo-only).
- ci-integration/github-actions: add a "Before you begin" block covering
  the three unmet prerequisites a new user hits (network-reachable
  portal vs localhost demo, API key, project id).
- reference/faq: fix the reachability contradiction ("planned" vs
  shipped for Go via govulncheck).
- contributor-guide/releasing: add a pre-tag documentation sweep to the
  release checklist (release notes from CHANGELOG [Unreleased], guide
  coverage for every user-facing entry, screenshot capture) so features
  can no longer ship without guide coverage.
- Screenshots: new capture spec (capture_feature_additions.spec.ts, split
  from the bulk matrix for isolated re-capture) and a deterministic
  fixed_version fixture in seed_e2e_user.py so the By-upgrade view
  renders real upgrade clusters; delete eight orphaned 68-byte
  placeholder PNGs under docs/user-guide/img/.

Verified: Docusaurus EN+KO build green (0 broken links/anchors),
docs-uat extract 201 steps, ko-style S1/S2 0, seed unit tests 10 passed,
both capture specs green against the live dev stack.
…506)

Clears Dependabot alert #69 (CVE-2026-48779, high): memory exhaustion
DoS in ws from tiny fragments and data chunks, vulnerable range
>= 8.0.0 < 8.21.0. ws reaches us only as a dev-time transitive of
jsdom and is pinned via the package.json overrides block (introduced
for the websocket-driver cleanup), so the fix is bumping that override
to the advisory's first patched version.

Supply-chain check: 8.21.0 published 2026-05-22 (~8 weeks old), no
advisories against the version itself (Snyk clean), not involved in
the recent npm worm campaigns.
…M-upload naming (#507)

Three P1 items from the 2026-07 docs benchmark review:

- contributor-guide/getting-started: replace the re-stated dev-stack
  boot steps with a delegation to Quickstart steps 1-3. The local copy
  had already drifted: it skipped the env-file and schema-migration
  steps, so following it verbatim left the health-gated worker blocking
  'up'. Last of the three duplicated boot sequences (the other two were
  converged in #505).
- Terminology: 'policy gate' -> 'build gate' across current docs
  (analysis-types, faq, notifications, github-actions, env-variables
  heading, policy-design, comparison). The glossary alias and the
  projects.md reconciliation sentence stay; release notes stay as
  historical records; code identifiers (policy-gate API path,
  policy_gate_failed enum) are untouched. Renamed headings keep their
  old anchors explicitly so no inbound link breaks.
- Naming: the SBOM-upload feature is now called 'SBOM upload' in section
  titles and link texts ('Received SBOMs' / 'uploaded SBOM' variants
  unified); the scans.md anchor #received-sboms-uploaded is preserved.

EN and KO mirrors updated together. Docusaurus EN+KO build green,
docs-uat extract 201 steps, ko-style S1/S2 0.
…BOM, onboarding polish (#508)

Seven P2 items from the 2026-07 docs benchmark review:

- Split the VEX export/import/UI sections (~230 lines) out of
  user-guide/vulnerabilities.md into a new user-guide/vex.md — the page
  mixed reference, how-to and explanation across 21 H2s; the remaining
  page keeps triage (state machine, EPSS/KEV, upgrade views) and links
  via a short pointer section. docs-uat ids move with their content
  (extract green: 32 enrolled docs, 201 steps).
- Move reference/sbom-signature-verification.md to ci-integration/ —
  the content is a five-step how-to, not a reference; inbound links and
  the moved page's own relative links updated.
- Attach a CycloneDX SBOM of the release's own source tree to every
  GitHub Release (anchore/sbom-action in release.yml's release job,
  idempotent via --clobber) and document it in releasing.md — an SCA
  product ships its own SBOM (Dependency-Track convention).
- Quickstart step 5: note that on a fresh stack the first real scan can
  show zero vulnerabilities until the initial Trivy DB download lands
  (the auto re-match fills findings in; no re-scan).
- docs.yml: fetch-depth 0 so showLastUpdateTime (already configured)
  stamps real per-page dates instead of the deploy date.
- Roadmap hygiene in integrations.md and components-and-licenses.md:
  inline 'on the roadmap' promises become present-tense behaviour plus
  a pointer; every promise now lives once in the page's Roadmap section
  (two missing entries added).
- Trivy-DB-not-arrived troubleshooting: verified already converged
  (vulnerability-data.md canonical, scans.md and docker-compose.md are
  pointers) — no change needed.

EN and KO mirrors updated together. Docusaurus EN+KO build green,
docs-uat extract green, ko-style S1/S2 0.
…demo-hosting page (#509)

Three of the six P3 items from the 2026-07 docs benchmark review; the
other two (mass task-title rename, design-system relocation) were
dropped as churn > value, and the 'Introduced in vX.Y' backfill was
scoped down to a going-forward convention (documented, not backfilled).

- New contributor-guide/documentation.md ('Writing docs'): codifies the
  four Diataxis topic types with a 'pick one per page' rule and title
  conventions, the screenshot-restraint criteria (when a screenshot
  earns its place vs. when a code block is the accurate artifact — the
  rationale for installation/ci-integration/reference shipping no
  screenshots), the lightweight 'Introduced in vX.Y' version-note
  convention (going forward, no backfill), and the EN+KO parity rule.
  Wired into the sidebar after testing-guide. EN + KO.
- reference/data-sources.md: the '## Analysis types' section is actually
  a per-finding data-signals matrix — renamed to 'Per-finding data
  signals' (explicit {#per-finding-signals} anchor) so it no longer
  collides with reference/analysis-types.md's page of the same name; the
  cross-reference that used to apologize for the collision is simplified.
  EN + KO.
- installation/gcp-deploy.md (Demo SaaS hosting): drop the orphan 'a
  runbook is being prepared and will be linked here' promise (the ops
  plumbing lives in a private ops repo, stated plainly); fix the two
  inbound links that mislabeled the page as a 'GCP deploy runbook' — it
  is the Hetzner demo-hosting page. EN + KO.

Docusaurus EN+KO build green, docs-uat extract 201 steps, ko-style 0.
…debar (#510)

Adds the CRA compliance reference page (EN + KO) that sidebars.ts already
references. The sidebar entry landed in #509 without the page (a git add
scoping slip), which broke the docs-deploy build on main; this restores
consistency.

- reference/cra-compliance.md (EN + KO): an honest mapping of EU Cyber
  Resilience Act (Regulation (EU) 2024/2847) vulnerability-handling
  (Annex I Part II) and reporting (Art. 14) obligations to shipped
  TRUSCA capabilities — SBOM/CycloneDX+SPDX, VEX, remediation, KEV/EPSS,
  audit trail — with the limits stated plainly (no time-to-remediate SLA
  tracking yet; TRUSCA does not file reports, publish advisories, or
  distribute updates; secure-by-design is out of scope). Framed as tool
  support, not a compliance certificate; explicitly not legal advice.
- completeness-master-plan.md: record the security-column review (§9)
  that produced this page as candidate X2, alongside X1 (finding-age /
  remediation-SLA tracking — the honest gap this page points at) and X3
  (OSS maintenance-health signals), both backlogged.

Docusaurus EN+KO build green (fixes the main deploy), ko-style S1/S2 0.
Bumps the docs-site-npm-security group with 1 update in the /docs-site directory: [svgo](https://github.com/svg/svgo).


Updates `svgo` from 3.3.3 to 3.3.4
- [Release notes](https://github.com/svg/svgo/releases)
- [Commits](svg/svgo@v3.3.3...v3.3.4)

---
updated-dependencies:
- dependency-name: svgo
  dependency-version: 3.3.4
  dependency-type: indirect
  dependency-group: docs-site-npm-security
...

Signed-off-by: dependabot[bot] <[email protected]>
@dependabot dependabot Bot added the dependencies Pull requests that update a dependency file label Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant