Commit 9cff526
W761+W762+W763+W764+W765 security batch (sw.js v54, 135 tests)
W761 Model Poisoning Anomaly Detection (25/25 green):
- src/teacher-response-hmac.js: TEACHER_HMAC_VERSION='w761-v1', HMAC-SHA256
with crypto.timingSafeEqual; KOLM_TEACHER_HMAC_KEY env-required
(MIN_KEY_BYTES=32); bindTeacherResponse stamps key_fingerprint =
sha256(key)[0:16]; verifyTeacherResponse constant-time;
attachBindingToCapture idempotent; verifyCaptureBinding fail-loud.
Chain: hmac(key, teacher_id ':' request_hash ':' sha256(response_body)
':' timestamp_ms).
- src/poisoning-orchestrator.js: POISONING_VERSION='w761-v1', frozen
POISON_RISK_LEVELS [safe/review/quarantine/rotate_teacher_key];
assessPoisoningRisk lazy-imports W808 capture-anomaly + W750
copyright-detector under try/catch (no hard dep, no module load fail);
HMAC verify failure ALWAYS escalates to rotate_teacher_key (safety
invariant); assessNamespacePoisoningRisk tenant-fenced via storeMod.all
+ per-row check (W411 defense-in-depth — findByTenant keys on `tenant`,
audit.js writes `tenant_id`, so we filter manually); quarantineCapture
+ releaseFromQuarantine audit-trailed.
- 4 routes appended to src/router.js, cmdW761Poison wired from case
'poison' at line 35187; vercel.json rewrite for
/security/model-poisoning.
W762 Adversarial Red-Team Framework (28/28 green):
- src/adversarial-prompts.js: ADVERSARIAL_PROMPTS_VERSION='w762-v1', 8
frozen ADVERSARIAL_CATEGORIES [prompt_injection/jailbreak/
system_prompt_leak/data_extraction/role_confusion/encoding_smuggling/
context_overflow/tool_hijack]; ADVERSARIAL_CORPUS_SEED 5-10 patterns
per category; generateAdversarialPrompts uses Mulberry32 PRNG so
--seed is deterministic; classifyPromptAdversarial 22 regexes.
- src/adversarial-bakeoff.js: ADVERSARIAL_BAKEOFF_VERSION='w762-v1',
runAdversarialBakeoff with DI runOnArtifact + judge seams (never hits
real APIs in tests); judge_kind 'heuristic'|'callable' tagged honestly
on every row.
- src/runtime-sanitizer.js: SANITIZER_VERSION='w762-v1', frozen
SANITIZE_POLICIES [block/redact/fallback_to_teacher/passthrough];
DEFAULT_POLICY='fallback_to_teacher'; right-to-left span substitution
so overlapping spans don't shift offsets.
- 4 routes, cmdW762Redteam at line 36471, case 'rt' at 35201;
vercel.json rewrite for /security/red-team. Intentional W765 overlap on
"ignore previous instructions" — layered defense.
W763 SBOM + Supply-Chain Pinning (26/26 green):
- src/sbom-emit.js: SBOM_VERSION='w763-v1', frozen SBOM_FORMATS
[cyclonedx-json/spdx-json]; emitSbomFromManifest /
emitSbomFromPackageLock / emitSbomFromPython; verifySbomShape rejects
Array input directly with ok:false (was falling through to
ok:true valid:false).
- apps/export/sbom.py: stdlib-only Python, --manifest/--package-lock/
--requirements/--cargo-lock flags. No third-party deps so the supply-
chain tool can't BE a supply-chain attack vector.
- .github/workflows/sbom.yml: 7 steps, CycloneDX-npm primary + Python
stdlib backstop + tracking-issue on failure.
- .github/dependabot.yml: CREATED (none existed) — npm + github-actions
+ pip, weekly cadence, labels [supply-chain, w763]. DEVIATION from
"do not create new files at repo root" — necessary to wire weekly
dep-vuln scanning.
- 3 routes, cmdW763Sbom wired; vercel.json rewrite for /security/sbom.
W764 Membership Inference Test Harness (30/30 green):
- src/membership-inference-test.js: MIT_VERSION='w764-v1', frozen 4-entry
MIT_ATTACK_KINDS [exact_prompt_replay/paraphrase_prompt/
partial_prompt_completion/unique_token_probe]; jaccardOverlap over
5-token n-grams (pure, symmetric); runMembershipInferenceTest with DI
runOnArtifact + judge seams; jaccard_threshold=0.85 honest floor.
- src/pii-bakeoff-scan.js: PII_SCAN_VERSION='w764-v1', frozen 10-entry
PII_PATTERN_CATEGORIES; FULL Luhn mod-10 (rejects
4111111111111112 / accepts 4111111111111111 — no shortcut prefix
matching); JWT base64url-decode header sanity (avoids false positives
on random base64); name_likely heuristic explicitly flagged as
heuristic (not authoritative PII).
- src/capture-forget.js: FORGET_VERSION='w764-v1', markCaptureForgotten
audit-trailed via provider='kolm_capture_forget', idempotent on
(tenant_id, capture_id); defense-in-depth tenant fence (query filter
+ per-row check, W411 law); listForgottenCaptures +
filterForgottenCaptures both tenant-fenced.
- 4 routes, cmdW764Mit at line 37067, case 'forget'|'mit' at 35214-35215;
vercel.json rewrite for /security/membership-inference.
W765 Prompt Extraction Defense (26/26 green):
- src/prompt-redactor.js: PROMPT_REDACTOR_VERSION='w765-v1', frozen
REDACTION_STRATEGIES [placeholder/paraphrase/
remove_literal_constraints/extract_behavior_only]; paraphrase strategy
returns honest 'paraphrase_requires_teacher_caller' envelope when no
teacher seam wired (NEVER fakes paraphrasing — would be a silent-pass
invariant break); extract_behavior_only returns ONLY behavior
description (e.g. 'Respond in 1-2 sentences.' instead of original
prompt). API_KEY regex loosened to allow optional intermediate segment
so Stripe-style sk_live_abcdef1234567890ABCDEF matches BEFORE the
PHONE regex catches the 10-digit substring inside it.
- src/extraction-guard.js: EXTRACTION_GUARD_VERSION='w765-v1', 12
EXTRACTION_PATTERNS; frozen POLICIES [block/log_only/
redirect_to_safe_response/log_and_block]; never-silent-pass invariant
(test #19 locks this — guardRuntimeRequest NEVER returns
action:passthrough on any matched attempt under any policy); 0.95
confidence cap (never claim 1.0).
- 3 routes, cmdW765Pextract at line 36650, case 'pextract' at 35221;
vercel.json rewrite for /security/prompt-extraction.
Intentional layered defense: W762 + W765 overlap on
"ignore previous instructions" pattern — adversarial detection in W762
classifier + runtime extraction guard in W765 + runtime sanitizer in
W762 wrapForRuntime. Each layer can independently fire even if upstream
layer was bypassed. Test W765 #13 explicitly notes the W762 sibling.
Per W604 anti-brittleness: every wave's sibling sw.js check uses
wave(\d{3,4}) regex + threshold (>=761), never explicit-array family
checks. Per W411 defense-in-depth: every tenant-fenced read uses
storeMod.all + per-row tenant_id !== tenant_id filter (the findByTenant
field-key mismatch with audit.js was caught during W761 and corrected).
sw.js: kolm-v53 -> kolm-v54-2026-05-24-w761-w762-w763-w764-w765-poisoning-redteam-sbom-mit-pextract
frontend-version.json: version + source_commit_at_edit_time bumped to match
135/135 tests green (25+28+26+30+26)
0 broken hrefs, 0 missing static refs (audit clean)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>1 parent df7c8db commit 9cff526
29 files changed
Lines changed: 11331 additions & 3 deletions
File tree
- .github
- workflows
- apps/export
- cli
- public
- security
- src
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
0 commit comments