@blamejs/exceptd-skills 0.12.16 → 0.12.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +69 -0
- package/bin/exceptd.js +23 -5
- package/data/_indexes/_meta.json +2 -2
- package/data/playbooks/ai-api.json +24 -4
- package/data/playbooks/containers.json +23 -4
- package/data/playbooks/cred-stores.json +18 -3
- package/data/playbooks/crypto-codebase.json +18 -3
- package/data/playbooks/crypto.json +12 -2
- package/data/playbooks/framework.json +15 -3
- package/data/playbooks/hardening.json +21 -4
- package/data/playbooks/kernel.json +10 -2
- package/data/playbooks/mcp.json +15 -3
- package/data/playbooks/runtime.json +17 -3
- package/data/playbooks/sbom.json +16 -3
- package/data/playbooks/secrets.json +30 -5
- package/lib/auto-discovery.js +34 -1
- package/lib/schemas/playbook.schema.json +7 -1
- package/lib/sign.js +163 -2
- package/lib/verify.js +149 -2
- package/manifest-snapshot.json +1 -1
- package/manifest-snapshot.sha256 +1 -1
- package/manifest.json +45 -40
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,74 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.12.18 — 2026-05-14
|
|
4
|
+
|
|
5
|
+
**Patch: e2e FP-check attestations for v0.12.17 indicator backfill. v0.12.17 publish payload.**
|
|
6
|
+
|
|
7
|
+
The v0.12.17 tag exists on git but never reached npm — release.yml's validate gate's `npm run test:e2e` failed 4/20 scenarios because v0.12.17's indicator FP-check backfill (audit K) caused the runner to downgrade hits to inconclusive without operator attestation, dropping RWEP scores below the e2e expect thresholds.
|
|
8
|
+
|
|
9
|
+
v0.12.18 ships the v0.12.17 fix payload plus FP-check attestations on the affected e2e scenarios:
|
|
10
|
+
|
|
11
|
+
- `12-crypto-codebase-md5-eol`: attest FP checks for `weak-hash-import` + `no-ml-kem-implementation`
|
|
12
|
+
- `15-cred-stores-aws-static`: attest FP checks for `aws-static-key-present`
|
|
13
|
+
- `16-containers-root-user`: attest FP checks for `dockerfile-runs-as-root`; lower `adjusted` threshold from 15 → 10 (only `dockerfile-from-latest` carries an `rwep_inputs` entry on the containers playbook; the FP-attested `dockerfile-runs-as-root` fires but doesn't drive RWEP)
|
|
14
|
+
- `20-ai-api-openai-dotfile`: attest FP checks for `cleartext-api-key-in-dotfile` + `long-lived-aws-keys`
|
|
15
|
+
|
|
16
|
+
Attestation shape per the E1 contract (v0.12.12): `signal_overrides: { '<indicator>__fp_checks': { '0': true, '1': true, ... } }` — each entry means "I've verified that this FP scenario does NOT apply; this is a real hit."
|
|
17
|
+
|
|
18
|
+
## 0.12.17 — 2026-05-14
|
|
19
|
+
|
|
20
|
+
**Patch: remaining deferred P1/P2 items from the v0.12.15 audit pile — manifest signing, Windows ACL, indicator FP backfill, schema promotion.**
|
|
21
|
+
|
|
22
|
+
### Manifest signing (audit I P1-4)
|
|
23
|
+
|
|
24
|
+
The previous trust chain signed each skill body individually but the manifest itself was just an unsigned index. A coordinated attacker who could rewrite `manifest.json` + `manifest-snapshot.json` + `manifest-snapshot.sha256` passed every gate (snapshot is checked locally, the sha256 also computed locally).
|
|
25
|
+
|
|
26
|
+
Now: `manifest.json` carries a top-level `manifest_signature` field (Ed25519 over canonical sort-keys representation with the signature field excluded and `normalize()`-applied bytes). `lib/sign.js sign-all` and `lib/sign.js sign-skill` both re-sign the manifest after per-skill work; `lib/verify.js loadManifestValidated()` verifies the manifest signature before iterating skills. Tampered manifest entries (path swap, signature substitution) now fail the manifest-level check. Missing `manifest_signature` field emits a warning but doesn't block (backward-compat for legacy tarballs in the wild).
|
|
27
|
+
|
|
28
|
+
Canonical-form contract documented in both `lib/sign.js` and `lib/verify.js` headers — future shape changes to manifest.json must respect the invariants (sort top-level keys, exclude `manifest_signature`, normalize line endings).
|
|
29
|
+
|
|
30
|
+
### Windows ACL on `.keys/private.pem` (audit I P1-3)
|
|
31
|
+
|
|
32
|
+
`lib/sign.js` previously wrote the private key with `{ mode: 0o600 }`. On POSIX this restricts read access to the owner. On Windows the `mode` argument maps to read/write attributes only, not POSIX permissions; ACLs inherited from the parent directory. A multi-user maintainer workstation or shared CI runner therefore allowed any process under the same desktop user to read the key. Now: on `win32`, `lib/sign.js` calls `icacls /inheritance:r /grant:r ${USERNAME}:F` after writing the private key, narrowing the ACL to the current user. The same restriction is applied via `restrictWindowsAcl(targetPath)` from `scripts/bootstrap.js` when bootstrap creates the keypair. Falls back to a stderr warning if `icacls` is unavailable; doesn't fail key generation.
|
|
33
|
+
|
|
34
|
+
### Indicator FP-check backfill (audit K)
|
|
35
|
+
|
|
36
|
+
36 deterministic indicators across 11 playbooks now carry `false_positive_checks_required[]` entries (the gold-standard pattern from `library-author.gha-workflow-script-injection-sink` in v0.12.13). Per-playbook coverage:
|
|
37
|
+
|
|
38
|
+
- `ai-api` — 4 indicators (cleartext-api-key-in-dotfile, long-lived-aws-keys, gcp-service-account-json, kubeconfig-with-static-token)
|
|
39
|
+
- `containers` — 4 (dockerfile-runs-as-root, dockerfile-curl-pipe-bash, compose-cap-add-sys-admin, compose-host-network)
|
|
40
|
+
- `cred-stores` — 3 (aws-static-key-present, docker-cleartext-auth, credentials-file-bad-perms)
|
|
41
|
+
- `crypto-codebase` — 3 (weak-hash-import, weak-cipher-mode, tls-old-protocol)
|
|
42
|
+
- `crypto` — 2 (ml-dsa-slh-dsa-absent, openssl-pre-3-5)
|
|
43
|
+
- `framework` — 3 (exception-missing-expiry-or-owner, jurisdiction-without-framework, compound-theater)
|
|
44
|
+
- `hardening` — 4 (kptr-restrict-disabled, yama-ptrace-permissive, kaslr-disabled-at-boot, mitigations-off)
|
|
45
|
+
- `kernel` — 2 (unpriv-userns-enabled, unpriv-bpf-allowed)
|
|
46
|
+
- `mcp` — 3 (mcp-response-ansi-escape, mcp-response-unicode-tag-smuggling, mcp-server-running-as-root)
|
|
47
|
+
- `runtime` — 3 (duplicate-uid-zero, world-writable-in-trusted-path, orphan-privileged-process)
|
|
48
|
+
- `sbom` — 3 (lockfile-no-integrity, kev-listed-match, windsurf-vulnerable-version)
|
|
49
|
+
- `secrets` — 5 (aws-secret-access-key, slack-bot-or-user-token, stripe-secret-key, openai-api-key, anthropic-api-key)
|
|
50
|
+
|
|
51
|
+
Each entry is a 1-line check an AI assistant or operator must satisfy before the indicator's `hit` verdict can drive `classification: detected`. The runner downgrades a hit with unsatisfied FP checks to `inconclusive` (v0.12.12 E1 contract). Backfill complements the playbook-level `false_positive_profile[]` documentation by binding FP checks per-indicator at the schema layer.
|
|
52
|
+
|
|
53
|
+
### Schema promotion (audit K + audit M)
|
|
54
|
+
|
|
55
|
+
`lib/schemas/playbook.schema.json` indicator object now formally declares `false_positive_checks_required[]` and `cve_ref` as optional fields (was unschema'd; produced WARN noise on every validate run). The `cve_ref` field has been load-bearing since v0.12.14 F3 (drives `analyze.matched_cves[]` correlation); the schema declaration just catches up. After the schema promotion + the v0.12.16 enum-drift normalisation, `validate-playbooks` runs 13/13 PASS with zero warnings (was 12/13 PASS + 1 WARN in v0.12.16, 8/13 PASS + 5 WARN in v0.12.15).
|
|
56
|
+
|
|
57
|
+
### Operator-facing surfaces
|
|
58
|
+
|
|
59
|
+
- **`--diff-from-latest` result surfaced in `run` human renderer (audit L F11)**. Operators running with `--diff-from-latest` and no `--json` previously got no visibility on drift; now: `> drift vs prior: unchanged (same evidence_hash as session <prior_id>)` or `> drift vs prior: DRIFTED — evidence_hash differs from session <prior_id>` is added near the classification line. No line when there's no prior attestation for the playbook (don't clutter).
|
|
60
|
+
- **`ai-run` stdin acceptance contract documented in `--help`**. The streaming + no-stream paths both consume "first parseable evidence event wins on stdin; subsequent evidence events ignored; non-evidence chatter silently ignored; invalid JSON exits 1." Was implicit behavior; now explicit so AI agents calling `exceptd ai-run` know the contract.
|
|
61
|
+
|
|
62
|
+
### Auto-discovery hygiene (audit M P3-O)
|
|
63
|
+
|
|
64
|
+
`lib/auto-discovery.js discoverNewKev` previously hardcoded `severity: 'high'` on every KEV-discovered diff. Now uses `deriveKevSeverity(kevEntry)` — returns `'critical'` when `knownRansomwareCampaignUse === 'Known'` OR `dueDate` is within 7 days; otherwise `'high'`. Downstream PR-body categorization can now route ransomware-use + imminent-due-date KEVs differently.
|
|
65
|
+
|
|
66
|
+
### Tests
|
|
67
|
+
|
|
68
|
+
- 20 new regression tests in `tests/audit-i-l-m-fixes.test.js` covering: Windows ACL helper export, manifest canonical-bytes determinism (stale signature + key-order invariants), sign + verify round trip, live manifest carries valid signature, tampered signature/skill detection, backward-compat `missing` status, `--diff-from-latest` renderer string/branch assertions, ai-run help-text content, `deriveKevSeverity` matrix, `discoverNewKev` propagation against synthetic KEV feed.
|
|
69
|
+
|
|
70
|
+
Test count: 740 → 760 (+20 + 1 reworked). Predeploy gates: 14/14. Skills: 38/38 signed; manifest itself signed.
|
|
71
|
+
|
|
3
72
|
## 0.12.16 — 2026-05-14
|
|
4
73
|
|
|
5
74
|
**Patch: highest-impact P1 security findings from the v0.12.15 audit pile.**
|
package/bin/exceptd.js
CHANGED
|
@@ -1124,6 +1124,20 @@ Flags:
|
|
|
1124
1124
|
Stdin event grammar (one JSON object per line):
|
|
1125
1125
|
{"event":"evidence","payload":{"observations":{},"verdict":{}}}
|
|
1126
1126
|
|
|
1127
|
+
Stdin acceptance contract (Audit L F22):
|
|
1128
|
+
In streaming mode, ai-run reads JSON-Lines from stdin until the FIRST
|
|
1129
|
+
parseable {"event":"evidence","payload":{...}} line. That line wins:
|
|
1130
|
+
subsequent evidence events on the same run are ignored (the handler
|
|
1131
|
+
marks itself \`handled\` and refuses re-entry). Non-evidence chatter
|
|
1132
|
+
(status updates, the host AI's own progress events) is silently
|
|
1133
|
+
ignored — the host can interleave its own JSON events without
|
|
1134
|
+
triggering a phase transition. Invalid JSON on any line exits 1 with
|
|
1135
|
+
an {"event":"error","reason":"invalid JSON on stdin: ..."} frame.
|
|
1136
|
+
|
|
1137
|
+
If the host needs to send multiple evidence batches, spawn a separate
|
|
1138
|
+
ai-run per batch (each produces an independent session_id). Use
|
|
1139
|
+
--no-stream + --evidence <file> for single-shot single-batch runs.
|
|
1140
|
+
|
|
1127
1141
|
Emits phases: govern → direct → look → await_evidence → detect → analyze
|
|
1128
1142
|
→ validate → close, then {"event":"done","ok":true,"session_id":"..."}.
|
|
1129
1143
|
Errors emit {"event":"error","reason":"..."} and exit non-zero.`,
|
|
@@ -2072,14 +2086,18 @@ function cmdRun(runner, args, runOpts, pretty) {
|
|
|
2072
2086
|
// F11: surface --diff-from-latest verdict in the human renderer. Pre-fix
|
|
2073
2087
|
// operators had to add --json to see whether the run drifted from the
|
|
2074
2088
|
// previous attestation. Now one summary line follows the classification.
|
|
2089
|
+
// - unchanged: same evidence_hash as prior → reassuring single line.
|
|
2090
|
+
// - drifted: evidence differs → loud DRIFTED marker.
|
|
2091
|
+
// - no_prior_attestation_for_playbook: no line — don't clutter the
|
|
2092
|
+
// output when there is nothing to compare against.
|
|
2075
2093
|
if (obj.diff_from_latest) {
|
|
2076
2094
|
const dfl = obj.diff_from_latest;
|
|
2077
|
-
if (dfl.status === "
|
|
2078
|
-
lines.push(`> drift vs prior: (
|
|
2079
|
-
} else {
|
|
2080
|
-
|
|
2081
|
-
lines.push(`> drift vs prior: ${dfl.status}${priorTag}`);
|
|
2095
|
+
if (dfl.status === "unchanged") {
|
|
2096
|
+
lines.push(`> drift vs prior: unchanged (same evidence_hash as session ${dfl.prior_session_id})`);
|
|
2097
|
+
} else if (dfl.status === "drifted") {
|
|
2098
|
+
lines.push(`> drift vs prior: DRIFTED — evidence_hash differs from session ${dfl.prior_session_id}`);
|
|
2082
2099
|
}
|
|
2100
|
+
// no_prior_attestation_for_playbook intentionally produces no line.
|
|
2083
2101
|
}
|
|
2084
2102
|
const cves = obj.phases?.analyze?.matched_cves || [];
|
|
2085
2103
|
const baseline = obj.phases?.analyze?.catalog_baseline_cves || [];
|
package/data/_indexes/_meta.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": "1.1.0",
|
|
3
|
-
"generated_at": "2026-05-
|
|
3
|
+
"generated_at": "2026-05-14T19:30:40.635Z",
|
|
4
4
|
"generator": "scripts/build-indexes.js",
|
|
5
5
|
"source_count": 50,
|
|
6
6
|
"source_hashes": {
|
|
7
|
-
"manifest.json": "
|
|
7
|
+
"manifest.json": "c0edebf10be0a638970d4e9e4c95459815f0226fc69276d1c965a71fa39b324f",
|
|
8
8
|
"data/atlas-ttps.json": "20339e0ae3cd89c06f1385be31c50f408f827edc2e8ab8aef026ade3bcf0a917",
|
|
9
9
|
"data/attack-techniques.json": "6db08a8e8a4d03d9309b1d185112de7f3c9595d2cd3d24566b7ce0b3b8aa5d1a",
|
|
10
10
|
"data/cve-catalog.json": "6e198d414a3a86dcae93ef36a2b1978734d0b1224fa66ba5184819ea0e3fb49f",
|
|
@@ -474,7 +474,12 @@
|
|
|
474
474
|
"description": "Primary credential-exposure indicator. Userland code execution → key extraction in milliseconds.",
|
|
475
475
|
"confidence": "deterministic",
|
|
476
476
|
"deterministic": true,
|
|
477
|
-
"attack_ref": "T1552.001"
|
|
477
|
+
"attack_ref": "T1552.001",
|
|
478
|
+
"false_positive_checks_required": [
|
|
479
|
+
"Decode the key prefix — sk-test-*, sk_test_*, anthropic-test-*, hf_dummy_* and similar documented placeholder formats are SDK quickstart fixtures; demote to miss.",
|
|
480
|
+
"If the dotfile is under examples/, tests/, fixtures/, docs/, sdk-quickstart/ or a vendor's reference-repo path, treat as documentation; demote to miss unless the key validates against the vendor's live API.",
|
|
481
|
+
"Verify the key length meets the format's minimum entropy floor (real OpenAI sk-* >=48 chars after prefix; Anthropic sk-ant-* >=40 chars; Google AIza* >=39 chars). Below the floor is a placeholder; demote."
|
|
482
|
+
]
|
|
478
483
|
},
|
|
479
484
|
{
|
|
480
485
|
"id": "long-lived-aws-keys",
|
|
@@ -483,7 +488,12 @@
|
|
|
483
488
|
"description": "Long-lived AWS keys in dotfile — high-value target.",
|
|
484
489
|
"confidence": "deterministic",
|
|
485
490
|
"deterministic": true,
|
|
486
|
-
"attack_ref": "T1552.001"
|
|
491
|
+
"attack_ref": "T1552.001",
|
|
492
|
+
"false_positive_checks_required": [
|
|
493
|
+
"If the access-key prefix is AKIAIOSFODNN7EXAMPLE / wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY or any other documented AWS sample credential pair, demote to miss — these are AWS-published doc fixtures.",
|
|
494
|
+
"Verify the credentials file resides under examples/, tests/, fixtures/, or a documented vendor-reference path before flagging; demote when so.",
|
|
495
|
+
"Cross-check via aws sts get-caller-identity (or equivalent dry-run) — if the key is already deactivated/deleted at the vendor side it is a stale revoked credential, demote to lower confidence."
|
|
496
|
+
]
|
|
487
497
|
},
|
|
488
498
|
{
|
|
489
499
|
"id": "gcp-service-account-json",
|
|
@@ -492,7 +502,12 @@
|
|
|
492
502
|
"description": "Service-account JSON key (long-lived) in user dotfile — known anti-pattern.",
|
|
493
503
|
"confidence": "deterministic",
|
|
494
504
|
"deterministic": true,
|
|
495
|
-
"attack_ref": "T1552.001"
|
|
505
|
+
"attack_ref": "T1552.001",
|
|
506
|
+
"false_positive_checks_required": [
|
|
507
|
+
"If the private_key field is the literal value 'PLACEHOLDER' / 'REDACTED' / a PEM body of fewer than 1000 chars, the file is a stub; demote to miss.",
|
|
508
|
+
"Verify the client_email domain — *@gserviceaccount.com is real; *@example.com, *@test.iam.gserviceaccount.com or fixture-shaped emails are doc samples, demote.",
|
|
509
|
+
"If the file lives under examples/, tests/, fixtures/, terraform-module-examples/, or a documented quickstart path AND no GOOGLE_APPLICATION_CREDENTIALS env points at it, demote."
|
|
510
|
+
]
|
|
496
511
|
},
|
|
497
512
|
{
|
|
498
513
|
"id": "kubeconfig-with-static-token",
|
|
@@ -501,7 +516,12 @@
|
|
|
501
516
|
"description": "Static kube token persists in dotfile.",
|
|
502
517
|
"confidence": "deterministic",
|
|
503
518
|
"deterministic": true,
|
|
504
|
-
"attack_ref": "T1552.001"
|
|
519
|
+
"attack_ref": "T1552.001",
|
|
520
|
+
"false_positive_checks_required": [
|
|
521
|
+
"If the cluster server URL is https://127.0.0.1:* / https://localhost:* / *.kind / minikube / k3d / docker-for-desktop — this is a local-only kubeconfig with no external blast radius; demote to lower confidence.",
|
|
522
|
+
"Verify the token field is not the documented kind/minikube/k3d default-bootstrap-token shape (length and prefix match the local-dev distro). If so, demote.",
|
|
523
|
+
"If the kubeconfig sits inside a CI runner workspace (path matches /home/runner/, /github/workspace/, /builds/, /workspace/) and the token is OIDC-exchanged at job start, treat as ephemeral; demote."
|
|
524
|
+
]
|
|
505
525
|
},
|
|
506
526
|
{
|
|
507
527
|
"id": "ai-api-egress-from-unexpected-process",
|
|
@@ -425,7 +425,12 @@
|
|
|
425
425
|
"description": "Container will run as root inside the namespace. Combined with any namespace escape, root-on-host.",
|
|
426
426
|
"confidence": "deterministic",
|
|
427
427
|
"deterministic": true,
|
|
428
|
-
"attack_ref": "T1611"
|
|
428
|
+
"attack_ref": "T1611",
|
|
429
|
+
"false_positive_checks_required": [
|
|
430
|
+
"If the FROM base is a distroless / chainguard / nonroot variant (gcr.io/distroless/*:nonroot, cgr.dev/chainguard/*:latest, *:*-nonroot, scratch with a static binary) AND no later RUN/USER escalates to root, the runtime UID is nonroot by base; demote to miss.",
|
|
431
|
+
"If the Kubernetes pod spec consuming this image sets runAsNonRoot: true + a non-zero runAsUser, kubelet refuses to start the container as root; demote to lower confidence.",
|
|
432
|
+
"If the Dockerfile is under a multi-stage build and only the final stage's USER is missing while an intermediate builder stage runs as root (acceptable for builder stages), check the final stage only."
|
|
433
|
+
]
|
|
429
434
|
},
|
|
430
435
|
{
|
|
431
436
|
"id": "dockerfile-curl-pipe-bash",
|
|
@@ -434,7 +439,12 @@
|
|
|
434
439
|
"description": "Build-time supply-chain compromise primitive. Compromised CDN or DNS rebind = arbitrary code in image.",
|
|
435
440
|
"confidence": "deterministic",
|
|
436
441
|
"deterministic": true,
|
|
437
|
-
"attack_ref": "T1195.002"
|
|
442
|
+
"attack_ref": "T1195.002",
|
|
443
|
+
"false_positive_checks_required": [
|
|
444
|
+
"If the curl target is a major-vendor official install path (sh.rustup.rs, get.docker.com, get.pnpm.io, raw.githubusercontent.com/nvm-sh/nvm, sigstore/cosign release URL) AND the pipe is preceded by a documented sha256/gpg verify step in the same RUN, the chain is integrity-checked; demote to lower confidence.",
|
|
445
|
+
"If the URL is an internal-mirror with --tlsv1.3 + --proto =https + a pinned --cacert and the org's release-engineering runbook documents this mirror, treat as in-policy; demote.",
|
|
446
|
+
"Confirm the RUN does not chain to an `sudo` / privilege-elevation primitive — pipe-to-shell as an unprivileged builder USER materially reduces blast radius; report at high but not deterministic."
|
|
447
|
+
]
|
|
438
448
|
},
|
|
439
449
|
{
|
|
440
450
|
"id": "compose-privileged",
|
|
@@ -452,7 +462,12 @@
|
|
|
452
462
|
"description": "Dangerous capability granted. SYS_ADMIN especially is functionally equivalent to root-on-host.",
|
|
453
463
|
"confidence": "deterministic",
|
|
454
464
|
"deterministic": true,
|
|
455
|
-
"attack_ref": "T1611"
|
|
465
|
+
"attack_ref": "T1611",
|
|
466
|
+
"false_positive_checks_required": [
|
|
467
|
+
"If the container is a node-exporter / cadvisor / falco / sysdig / metrics-collector sidecar AND its image and pod identity match a documented host-introspection allowlist, the capability is intentional; demote to lower confidence (still report; revisit boundary).",
|
|
468
|
+
"If cap_drop: [ALL] precedes cap_add AND the user namespace is remapped (userns-remap configured at the daemon level), the effective root inside the container is unprivileged on the host; report at high but not deterministic.",
|
|
469
|
+
"If the compose file is under tests/, integration/, e2e/ or a development-only directory AND is excluded from production deploy pipelines (check CI for compose-file references), treat as non-shipped configuration."
|
|
470
|
+
]
|
|
456
471
|
},
|
|
457
472
|
{
|
|
458
473
|
"id": "compose-host-network",
|
|
@@ -461,7 +476,11 @@
|
|
|
461
476
|
"description": "Host namespace sharing. Cross-process visibility + tampering primitive.",
|
|
462
477
|
"confidence": "deterministic",
|
|
463
478
|
"deterministic": true,
|
|
464
|
-
"attack_ref": "T1610"
|
|
479
|
+
"attack_ref": "T1610",
|
|
480
|
+
"false_positive_checks_required": [
|
|
481
|
+
"If the service is a known service-mesh data-plane (envoy / linkerd-proxy / istio-proxy / cilium-agent) or a host-metrics sidecar (node-exporter / cadvisor) AND the deployment is documented on an operator-maintained host-network allowlist, demote.",
|
|
482
|
+
"Verify the compose file is not a dev-only docker-compose.override.yml that won't ship — diff against the production compose; if host-network is dev-only, mark in-scope-for-dev-only."
|
|
483
|
+
]
|
|
465
484
|
},
|
|
466
485
|
{
|
|
467
486
|
"id": "compose-docker-sock-mount",
|
|
@@ -420,7 +420,12 @@
|
|
|
420
420
|
"description": "Long-lived AWS IAM user key. AAL1-equivalent. Static credential.",
|
|
421
421
|
"confidence": "deterministic",
|
|
422
422
|
"deterministic": true,
|
|
423
|
-
"attack_ref": "T1552.001"
|
|
423
|
+
"attack_ref": "T1552.001",
|
|
424
|
+
"false_positive_checks_required": [
|
|
425
|
+
"If the AKIA* value is AKIAIOSFODNN7EXAMPLE / a documented AWS-sample credential pair, demote — these are AWS-published doc fixtures.",
|
|
426
|
+
"Verify the key is live via `aws sts get-caller-identity --profile <name>` (or dry-run equivalent). If the key is already deactivated / deleted upstream, demote to lower confidence (still flag for cleanup).",
|
|
427
|
+
"If the profile name pattern matches an org-documented break-glass profile (e.g. profile = 'breakglass-*' with a documented quarterly-rotation procedure tied to it), accept the exception with a TTL test."
|
|
428
|
+
]
|
|
424
429
|
},
|
|
425
430
|
{
|
|
426
431
|
"id": "kube-static-token",
|
|
@@ -447,7 +452,12 @@
|
|
|
447
452
|
"description": "Docker registry credentials in cleartext (base64 is not encryption). credHelpers/credsStore would route to OS keychain or cloud-IAM-federated path.",
|
|
448
453
|
"confidence": "deterministic",
|
|
449
454
|
"deterministic": true,
|
|
450
|
-
"attack_ref": "T1552.001"
|
|
455
|
+
"attack_ref": "T1552.001",
|
|
456
|
+
"false_positive_checks_required": [
|
|
457
|
+
"Decode the auth value — if the decoded user is '<token>' / 'AWS' / 'oauth2accesstoken' / '00000000-0000-0000-0000-000000000000' (vendor-documented pseudo-users for short-lived token auth) AND the docker login was scripted with an exchange step, the credential is ephemeral; demote.",
|
|
458
|
+
"If the registry is a local-only registry (127.0.0.1:*, registry.local, kind.local, *.svc.cluster.local) on a dev workstation, blast radius is local; report at high but not deterministic.",
|
|
459
|
+
"Verify the credsStore field is not set globally (top-level credsStore overrides per-registry omission); if globally configured, demote."
|
|
460
|
+
]
|
|
451
461
|
},
|
|
452
462
|
{
|
|
453
463
|
"id": "npm-pat-present",
|
|
@@ -498,7 +508,12 @@
|
|
|
498
508
|
"description": "Permissive permissions on a credential file. Local-user theft primitive.",
|
|
499
509
|
"confidence": "deterministic",
|
|
500
510
|
"deterministic": true,
|
|
501
|
-
"attack_ref": "T1552.004"
|
|
511
|
+
"attack_ref": "T1552.004",
|
|
512
|
+
"false_positive_checks_required": [
|
|
513
|
+
"If the host filesystem is on Windows / WSL with no POSIX mode enforcement OR is a network share where mode bits are emulated, posix-mode is meaningless — verify the share is ACL-protected (e.g. ntfs ACL grants only the user); demote when ACL-tight.",
|
|
514
|
+
"If the file is a 0-byte placeholder / symlink to a credential broker socket or to a tmpfs path that's wiped on logout, the readable bits do not yield a usable credential; demote.",
|
|
515
|
+
"On a shared multi-user host the operator may have an explicit ACL-by-design model (e.g. 0640 with a group ACL granting the deploy-group read). If the runbook documents the intent AND `getfacl` confirms the named-group restriction, accept the exception."
|
|
516
|
+
]
|
|
502
517
|
},
|
|
503
518
|
{
|
|
504
519
|
"id": "all-stores-empty-or-federated",
|
|
@@ -559,7 +559,12 @@
|
|
|
559
559
|
"value": "Any file imports or constructs an MD5 / SHA1 hash and the output flows into a security context. Patterns: `crypto\\.createHash\\(['\"](md5|sha1|sha-1)['\"]\\)`, `hashlib\\.(md5|sha1)\\(`, `MessageDigest\\.getInstance\\(['\"](MD5|SHA-1)['\"]\\)`, `crypto/md5`, `crypto/sha1`, `Digest::(MD5|SHA1)\\.`",
|
|
560
560
|
"description": "Weak hash primitive shipped in library source. Hard fail unless inline comment justifies non-security usage AND no flow into auth/integrity code path.",
|
|
561
561
|
"confidence": "deterministic",
|
|
562
|
-
"deterministic": true
|
|
562
|
+
"deterministic": true,
|
|
563
|
+
"false_positive_checks_required": [
|
|
564
|
+
"If the call site is under tests/, spec/, fixtures/, examples/ or carries an inline `// non-security: <reason>` / `# non-cryptographic: <reason>` comment AND the hash output flows into a checksum-only path (ETag, content-addressed cache key, dedupe), demote to miss.",
|
|
565
|
+
"If the file implements a documented legacy-protocol shim (TLS interop, ntlm, sha1-rsa cert parsing for compat) and the hash is consumed by a verify-only path against externally-fixed inputs, accept with an expiry test (deprecation date).",
|
|
566
|
+
"Confirm the hash output reaches an authn/integrity sink via simple grep on the assigned variable name reaching crypto.verify / hmac / signature.update / token comparison. If no such flow exists in this file, demote."
|
|
567
|
+
]
|
|
563
568
|
},
|
|
564
569
|
{
|
|
565
570
|
"id": "weak-cipher-mode",
|
|
@@ -567,7 +572,12 @@
|
|
|
567
572
|
"value": "AES with ECB mode anywhere: `aes-\\d+-ecb`, `AES/ECB/`, `Cipher\\.getInstance\\(['\"]AES['\"]\\)` (default mode ECB). Or DES/3DES anywhere: `des-cbc`, `des-ede3`, `\\\"des\\\"`, `\\\"3des\\\"`, `DES/`, `DESede/`. Or RC4: `\\\"rc4\\\"`, `arc4`, `ARCFOUR`.",
|
|
568
573
|
"description": "Catastrophic cipher choice. ECB leaks plaintext patterns; DES/3DES is below current security margins; RC4 is broken.",
|
|
569
574
|
"confidence": "deterministic",
|
|
570
|
-
"deterministic": true
|
|
575
|
+
"deterministic": true,
|
|
576
|
+
"false_positive_checks_required": [
|
|
577
|
+
"If the path is under tests/ / fixtures/ / known-answer-tests/ and carries a `// test-only` or `# KAT vector` marker, this is an interop / known-answer test against a published vector; demote to miss.",
|
|
578
|
+
"If the library is explicitly a legacy-protocol parser (e.g. an NTLM / Kerberos-RC4 / PKCS#12-3DES compatibility shim) AND its SECURITY.md declares the legacy scope, accept with a deprecation-deadline test rather than hard fail.",
|
|
579
|
+
"Confirm the construction is actually instantiated in a production code path — `grep -r '<cipher-var>' src/ --include='*.{js,ts,py,java,go}'`. If only referenced in `if (legacy) {}` dead-branch code, demote."
|
|
580
|
+
]
|
|
571
581
|
},
|
|
572
582
|
{
|
|
573
583
|
"id": "rsa-1024-anywhere",
|
|
@@ -649,7 +659,12 @@
|
|
|
649
659
|
"value": "`secureProtocol:\\s*['\"](TLSv1_method|TLSv1_1_method|SSLv23_method|SSLv3_method)['\"]`, `minVersion:\\s*['\"](TLSv1(\\.0|\\.1)?)['\"]`, `ssl_version=ssl\\.PROTOCOL_TLSv1(_1)?`, `MinTlsVersion::TLSv1`.",
|
|
650
660
|
"description": "TLS < 1.2 minimum in shipped TLS-context construction.",
|
|
651
661
|
"confidence": "deterministic",
|
|
652
|
-
"deterministic": true
|
|
662
|
+
"deterministic": true,
|
|
663
|
+
"false_positive_checks_required": [
|
|
664
|
+
"If the path is under tests/ / fixtures/ AND the test purpose is asserting that the library REJECTS the legacy protocol (negative test), demote.",
|
|
665
|
+
"If the construction is gated behind a feature flag whose default value is OFF AND the README documents the flag as an opt-in for legacy compatibility only, treat as a deprecated-but-supported path rather than a default-insecure ship.",
|
|
666
|
+
"Confirm there is no override-construction later (e.g. a subsequent `setMinProtoVersion(TLS_1_3)` on the same context) — partial defaults are common; the effective minimum is the highest set."
|
|
667
|
+
]
|
|
653
668
|
},
|
|
654
669
|
{
|
|
655
670
|
"id": "no-crypto-agility-abstraction",
|
|
@@ -453,7 +453,12 @@
|
|
|
453
453
|
"value": "Within the openssl-kem-algorithms artifact (its `openssl list -signature-algorithms` half): output does NOT contain ML-DSA-44/65/87 AND does NOT contain SLH-DSA-*",
|
|
454
454
|
"description": "TLS library cannot use PQC signatures. Certificate-chain PQC migration is blocked.",
|
|
455
455
|
"confidence": "deterministic",
|
|
456
|
-
"deterministic": true
|
|
456
|
+
"deterministic": true,
|
|
457
|
+
"false_positive_checks_required": [
|
|
458
|
+
"Confirm whether oqsprovider / aws-lc / wolfssl is loaded alongside OpenSSL — `openssl list -providers` may show a PQC-providing module that supplies ML-DSA via a non-default path. If so, demote.",
|
|
459
|
+
"Verify the binary is statically linked or chroot-jailed against a different libcrypto than the one being interrogated; rerun the list command using the actual library the service has loaded (lsof / /proc/<pid>/maps).",
|
|
460
|
+
"If the host's role is documented as classical-only (e.g. an air-gapped HSM-fronted signer with offline key custody) AND a separate PQC-signing tier exists upstream, accept with an explicit roadmap milestone."
|
|
461
|
+
]
|
|
457
462
|
},
|
|
458
463
|
{
|
|
459
464
|
"id": "openssl-pre-3-5",
|
|
@@ -461,7 +466,12 @@
|
|
|
461
466
|
"value": "Within the openssl-version artifact: output starts with 'OpenSSL 1.', 'OpenSSL 2.', 'OpenSSL 3.0', 'OpenSSL 3.1', 'OpenSSL 3.2', 'OpenSSL 3.3', or 'OpenSSL 3.4'; cross-check libssl-libraries artifact to detect coexisting older libcrypto.so loaded via LD_LIBRARY_PATH or vendored builds (LibreSSL, BoringSSL, vendored OpenSSL under /usr/local/lib)",
|
|
462
467
|
"description": "Pre-3.5 OpenSSL lacks native ML-KEM. Requires oqsprovider or upgrade.",
|
|
463
468
|
"confidence": "deterministic",
|
|
464
|
-
"deterministic": true
|
|
469
|
+
"deterministic": true,
|
|
470
|
+
"false_positive_checks_required": [
|
|
471
|
+
"If the host is on a vendor-supported LTS (RHEL 8/9, Ubuntu 20.04/22.04, SLES 15, Amazon Linux 2/2023), verify the distro's documented PQC backport status via `rpm -q --changelog openssl` / `dpkg -s openssl` — RHEL 9.5+, Ubuntu 24.04 ship oqsprovider as a package; mark backport state explicitly.",
|
|
472
|
+
"If openssl-providers also lists an active oqsprovider AND oqsprovider's ML-KEM appears in the kem-algorithms list, the pre-3.5 base is provider-augmented; demote to lower confidence (still flag the upgrade path).",
|
|
473
|
+
"Confirm the queried `openssl` binary is the one services link against (lsof on representative daemons + /proc/<pid>/maps grep for libcrypto). If services link against a newer vendored 3.5+ libcrypto, the system openssl version is misleading."
|
|
474
|
+
]
|
|
465
475
|
},
|
|
466
476
|
{
|
|
467
477
|
"id": "sshd-no-pqc-kex",
|
|
@@ -477,7 +477,11 @@
|
|
|
477
477
|
"value": "Any exception register entry with rwep_at_acceptance >= 70 lacking (a) explicit calendar expiry, (b) named risk-acceptance owner at correct authority level, (c) tested compensating controls",
|
|
478
478
|
"description": "Theater fingerprint #3 detection.",
|
|
479
479
|
"confidence": "deterministic",
|
|
480
|
-
"deterministic": true
|
|
480
|
+
"deterministic": true,
|
|
481
|
+
"false_positive_checks_required": [
|
|
482
|
+
"Confirm the artifact actually represents the live exception register. If the input is a draft / historical / archived register snapshot (filename includes -draft / -archive / -YYYY-Q< number >), demote.",
|
|
483
|
+
"If expiry/owner/controls are stored in a sidecar system (ServiceNow, Jira, IRM tool) linked by ID, the indicator may falsely fire on a register that intentionally normalises to ID-references. Verify the linked record's fields before flagging."
|
|
484
|
+
]
|
|
481
485
|
},
|
|
482
486
|
{
|
|
483
487
|
"id": "jurisdiction-without-framework",
|
|
@@ -485,7 +489,11 @@
|
|
|
485
489
|
"value": "jurisdictional_footprint contains EU / UK / AU / SG / JP / IN / CA / HK / TW / IL / CH / ID / VN AND framework_mapping_matrix does NOT contain the corresponding binding framework (NIS2 / DORA / EU AI Act / CAF / Essential 8 / APRA / MAS TRM / NISC / CERT-In / OSFI B-10)",
|
|
486
490
|
"description": "Theater fingerprint #4 detection — operating in a regulated jurisdiction without framework mapping.",
|
|
487
491
|
"confidence": "deterministic",
|
|
488
|
-
"deterministic": true
|
|
492
|
+
"deterministic": true,
|
|
493
|
+
"false_positive_checks_required": [
|
|
494
|
+
"If the organisation has filed a documented `out-of-scope` declaration with the relevant regulator (e.g. NIS2 size-threshold exclusion <50 employees + <EUR 10M turnover; DORA scoping out as non-financial entity; EU AI Act non-AI-deployer attestation) AND the declaration is current within the regulator's revalidation window, demote.",
|
|
495
|
+
"If the jurisdictional footprint is data-only (e.g. an EU IP block accessing a CDN, no establishment / no targeted offering) AND no EU data subjects' personal data is processed, the binding framework may not attach; confirm against the regulator's territoriality test."
|
|
496
|
+
]
|
|
489
497
|
},
|
|
490
498
|
{
|
|
491
499
|
"id": "mapping-without-tempo",
|
|
@@ -509,7 +517,11 @@
|
|
|
509
517
|
"value": "Three or more theater fingerprints fire on the same framework control (e.g. ISO 27001:2022 A.8.30 fires patterns #1 + #2 + #6 simultaneously)",
|
|
510
518
|
"description": "Compound theater — single control structurally insufficient across multiple threat classes.",
|
|
511
519
|
"confidence": "deterministic",
|
|
512
|
-
"deterministic": true
|
|
520
|
+
"deterministic": true,
|
|
521
|
+
"false_positive_checks_required": [
|
|
522
|
+
"Confirm the multiple fingerprints fire on the SAME control (not different controls aggregated against the same audit scope). Cross-fingerprint stacks against different controls are not compound theater; they are inventory of distinct gaps.",
|
|
523
|
+
"Verify each constituent fingerprint individually passed its own FP checks. If any of the three is itself a coincidence-hit (e.g. exception-missing-expiry on a draft register), recompute the count without it."
|
|
524
|
+
]
|
|
513
525
|
}
|
|
514
526
|
],
|
|
515
527
|
"false_positive_profile": [
|
|
@@ -403,7 +403,11 @@
|
|
|
403
403
|
"description": "/proc/kallsyms is world-readable; KASLR is effectively defeated for any local user. Decisive for kernel LPE exploitability.",
|
|
404
404
|
"confidence": "deterministic",
|
|
405
405
|
"deterministic": true,
|
|
406
|
-
"attack_ref": "T1068"
|
|
406
|
+
"attack_ref": "T1068",
|
|
407
|
+
"false_positive_checks_required": [
|
|
408
|
+
"If the host is in an active kdump / perf / kernel-debugging session AND the rationale is captured in the operator runbook with a documented restoration deadline, accept with a TTL test (must flip to >=1 within N days).",
|
|
409
|
+
"Confirm /proc/kallsyms actually leaks pointers — `head -1 /proc/kallsyms` as an unprivileged user. If addresses are zeroed despite kptr_restrict=0 (e.g. CAP_SYSLOG-only path active via systemd), demote to lower confidence."
|
|
410
|
+
]
|
|
407
411
|
},
|
|
408
412
|
{
|
|
409
413
|
"id": "unprivileged-userns-enabled",
|
|
@@ -430,7 +434,12 @@
|
|
|
430
434
|
"description": "Yama ptrace_scope=0 allows any process to ptrace any same-UID process. Credential-theft + privilege-pivot primitive.",
|
|
431
435
|
"confidence": "deterministic",
|
|
432
436
|
"deterministic": true,
|
|
433
|
-
"attack_ref": "T1212"
|
|
437
|
+
"attack_ref": "T1212",
|
|
438
|
+
"false_positive_checks_required": [
|
|
439
|
+
"If the host is a developer workstation in a documented debug-allowlist AND an enforcing MAC profile (AppArmor `aa-status` enforced / SELinux `getenforce` == Enforcing) restricts cross-UID ptrace, the kernel-level open is constrained at a higher layer; demote to lower confidence.",
|
|
440
|
+
"If a containerised workload depends on ptrace for documented observability tooling (strace-based tracer, gdb-based debugger run-as-CAP_SYS_PTRACE), confirm the broader scope is justified by that workload and is not host-wide.",
|
|
441
|
+
"Confirm the system isn't a single-user dev VM with no network exposure — ptrace_scope=0 on an isolated VM has materially lower blast radius than on a multi-tenant host."
|
|
442
|
+
]
|
|
434
443
|
},
|
|
435
444
|
{
|
|
436
445
|
"id": "kaslr-disabled-at-boot",
|
|
@@ -439,7 +448,11 @@
|
|
|
439
448
|
"description": "Boot-time KASLR disabled. Makes catalogued kernel LPEs deterministic.",
|
|
440
449
|
"confidence": "deterministic",
|
|
441
450
|
"deterministic": true,
|
|
442
|
-
"attack_ref": "T1068"
|
|
451
|
+
"attack_ref": "T1068",
|
|
452
|
+
"false_positive_checks_required": [
|
|
453
|
+
"If the host is in an active kdump / kgdb / kernel-debugging session AND the rationale is documented in the operator runbook with a documented restoration deadline, accept with TTL.",
|
|
454
|
+
"Confirm the cmdline option actually took effect — `dmesg | grep -i kaslr` should show KASLR offsets when active. An nokaslr token in cmdline but a KASLR-active kernel (some distros override) means the boot parameter was ignored; demote."
|
|
455
|
+
]
|
|
443
456
|
},
|
|
444
457
|
{
|
|
445
458
|
"id": "mitigations-off",
|
|
@@ -448,7 +461,11 @@
|
|
|
448
461
|
"description": "Boot-time CPU mitigations disabled. Spectre/Meltdown/MDS/Retbleed/Downfall class becomes exploitable.",
|
|
449
462
|
"confidence": "deterministic",
|
|
450
463
|
"deterministic": true,
|
|
451
|
-
"attack_ref": "T1068"
|
|
464
|
+
"attack_ref": "T1068",
|
|
465
|
+
"false_positive_checks_required": [
|
|
466
|
+
"If the host is HPC/benchmark-tagged (slurm node, MPI scratch node, vendor-perf-test rig) AND not network-exposed AND mitigations=off is documented in the operator runbook with a network-isolation control, accept the exception with a quarterly revalidation test.",
|
|
467
|
+
"Confirm the host is single-tenant (no multi-user, no untrusted workloads). Mitigations are speculative-execution defences against same-host attackers; on a single-tenant control-plane the threat may not apply."
|
|
468
|
+
]
|
|
452
469
|
},
|
|
453
470
|
{
|
|
454
471
|
"id": "selinux-not-enforcing",
|
|
@@ -384,7 +384,11 @@
|
|
|
384
384
|
"description": "Unprivileged user namespaces enabled — required for several catalogued LPE classes.",
|
|
385
385
|
"confidence": "deterministic",
|
|
386
386
|
"deterministic": true,
|
|
387
|
-
"attack_ref": "T1611"
|
|
387
|
+
"attack_ref": "T1611",
|
|
388
|
+
"false_positive_checks_required": [
|
|
389
|
+
"If a documented rootless-container runtime (podman/rootless, buildah, lima, colima, GitHub Actions runners) is the workload AND unprivileged userns is a documented requirement, accept the exception — but pair with an LSM (SELinux/AppArmor) enforcing profile that constrains exec/mount inside the userns.",
|
|
390
|
+
"Verify CONFIG_USER_NS is built into the kernel — distros that ship with userns disabled at config-time make the sysctl moot regardless of value. `grep CONFIG_USER_NS /boot/config-$(uname -r)` should show =y for the indicator to be live."
|
|
391
|
+
]
|
|
388
392
|
},
|
|
389
393
|
{
|
|
390
394
|
"id": "unpriv-bpf-allowed",
|
|
@@ -393,7 +397,11 @@
|
|
|
393
397
|
"description": "Unprivileged BPF allowed — primitive for several kernel LPE classes.",
|
|
394
398
|
"confidence": "deterministic",
|
|
395
399
|
"deterministic": true,
|
|
396
|
-
"attack_ref": "T1068"
|
|
400
|
+
"attack_ref": "T1068",
|
|
401
|
+
"false_positive_checks_required": [
|
|
402
|
+
"Verify CONFIG_BPF_SYSCALL=y AND CONFIG_BPF_JIT=y in the running kernel config (`grep CONFIG_BPF /boot/config-$(uname -r)`). If BPF is compiled out, the sysctl is moot; demote.",
|
|
403
|
+
"If an enforcing LSM profile (SELinux / AppArmor) restricts bpf() syscall for unprivileged users via a tested policy, the kernel-level open is constrained at a higher layer; demote to lower confidence."
|
|
404
|
+
]
|
|
397
405
|
}
|
|
398
406
|
],
|
|
399
407
|
"false_positive_profile": [
|
package/data/playbooks/mcp.json
CHANGED
|
@@ -550,7 +550,11 @@
|
|
|
550
550
|
"value": "ps shows an MCP server process running with effective UID 0 or with elevated capabilities (CAP_SYS_ADMIN, CAP_NET_ADMIN)",
|
|
551
551
|
"description": "MCP server with elevated privileges turns a tool-response RCE into immediate root.",
|
|
552
552
|
"confidence": "deterministic",
|
|
553
|
-
"deterministic": true
|
|
553
|
+
"deterministic": true,
|
|
554
|
+
"false_positive_checks_required": [
|
|
555
|
+
"If the MCP server's documented purpose requires elevated capability (kernel-introspection / host-firewall / docker-control MCP) AND it is on a vendor-published privileged-allowlist with a signed manifest, accept with explicit scope.",
|
|
556
|
+
"Verify root-on-host vs root-in-user-namespace — inspect /proc/<pid>/status Cap* fields and userns mapping. A rootless-container MCP running as in-namespace UID 0 has materially lower blast radius; demote to lower confidence."
|
|
557
|
+
]
|
|
554
558
|
},
|
|
555
559
|
{
|
|
556
560
|
"id": "copilot-yolo-mode-flag",
|
|
@@ -579,7 +583,11 @@
|
|
|
579
583
|
"confidence": "deterministic",
|
|
580
584
|
"deterministic": true,
|
|
581
585
|
"atlas_ref": "AML.T0051",
|
|
582
|
-
"attack_ref": "T1059"
|
|
586
|
+
"attack_ref": "T1059",
|
|
587
|
+
"false_positive_checks_required": [
|
|
588
|
+
"If the MCP tool is a documented terminal-emulation / shell-output renderer (the tool's purpose is to relay terminal output and the host AI assistant renders it inline) AND the escape bytes are SGR-only with no cursor-movement / screen-clear / OSC-8 codes, the escape carries no instruction-coercion payload; demote.",
|
|
589
|
+
"Confirm the escape appears in a tool-output payload AND not in a tools/list metadata field — metadata is the high-leverage smuggling site; output may be expected. If only in output for a documented terminal tool, demote."
|
|
590
|
+
]
|
|
583
591
|
},
|
|
584
592
|
{
|
|
585
593
|
"id": "mcp-response-unicode-tag-smuggling",
|
|
@@ -588,7 +596,11 @@
|
|
|
588
596
|
"description": "Unicode Tag-range smuggling — zero-width to humans, tokenized by the model. Source: Embrace the Red (Rehberger, 2025).",
|
|
589
597
|
"confidence": "deterministic",
|
|
590
598
|
"deterministic": true,
|
|
591
|
-
"atlas_ref": "AML.T0051"
|
|
599
|
+
"atlas_ref": "AML.T0051",
|
|
600
|
+
"false_positive_checks_required": [
|
|
601
|
+
"Tag-range codepoints have no documented legitimate use in MCP transport. Confirm the file content is actually a JSON payload (not, e.g., a captured raw email body that contains a U+E0000-tagged language hint per RFC 4646 / RFC 5646 BCP-47 legacy). For genuine MCP payloads, presence is unambiguous; demote only if the payload is from a non-MCP capture mislabeled in the artifact.",
|
|
602
|
+
"Cross-check against the MCP server's package signature / publisher field — if the server is signed by a first-party allowlisted publisher AND the codepoint appears only in a documented `language-tag` schema field, treat as legacy-language-tag (RFC 6082 marked Tag deprecated 2011); still flag for review."
|
|
603
|
+
]
|
|
592
604
|
},
|
|
593
605
|
{
|
|
594
606
|
"id": "mcp-response-instruction-coercion",
|
|
@@ -430,7 +430,12 @@
|
|
|
430
430
|
"description": "Multiple UID=0 accounts. T1136.001 persistence pattern. Outside legitimate installs.",
|
|
431
431
|
"confidence": "deterministic",
|
|
432
432
|
"deterministic": true,
|
|
433
|
-
"attack_ref": "T1136.001"
|
|
433
|
+
"attack_ref": "T1136.001",
|
|
434
|
+
"false_positive_checks_required": [
|
|
435
|
+
"If the second UID-0 account is `toor` on FreeBSD / `tor` on certain BSDs (documented alternate-shell root) or is a vendor-documented sudo-replacement account (e.g. AlmaLinux's `almauser`, Amazon Linux's `ec2-user` aliased), check the OS-vendor documentation; demote if intentional.",
|
|
436
|
+
"Cross-check `/etc/shadow` for the second account — if the password field is `!` / `*` (locked) AND no SSH authorized_keys / kerberos principal exists, the account is non-authentication-bearing; report at high but not deterministic.",
|
|
437
|
+
"If the host is a documented break-glass jumpbox with a renamed-root account replacing `root` (and `root` itself is removed), this is intentional; accept with the runbook reference."
|
|
438
|
+
]
|
|
434
439
|
},
|
|
435
440
|
{
|
|
436
441
|
"id": "listening-socket-unknown-bind",
|
|
@@ -457,7 +462,11 @@
|
|
|
457
462
|
"description": "Hijack-execution-flow primitive. Any non-root process can replace the file.",
|
|
458
463
|
"confidence": "deterministic",
|
|
459
464
|
"deterministic": true,
|
|
460
|
-
"attack_ref": "T1574.005"
|
|
465
|
+
"attack_ref": "T1574.005",
|
|
466
|
+
"false_positive_checks_required": [
|
|
467
|
+
"Verify the file has the sticky bit set (1777-style mode on /opt subdirs that intentionally permit per-user write). Sticky-bit mode 1777 + ownership root:root limits cross-user replacement and is documented for some package-managers; demote to lower confidence.",
|
|
468
|
+
"If the file is a 0-byte stamp / unix-socket / FIFO under /opt that's documented for the application (e.g. mode 0666 socket for /opt/vendor/sock), the indicator is misclassified by the gatherer; demote."
|
|
469
|
+
]
|
|
461
470
|
},
|
|
462
471
|
{
|
|
463
472
|
"id": "orphan-privileged-process",
|
|
@@ -466,7 +475,12 @@
|
|
|
466
475
|
"description": "Privileged orphan in a writable temp path. Common implant shape.",
|
|
467
476
|
"confidence": "deterministic",
|
|
468
477
|
"deterministic": true,
|
|
469
|
-
"attack_ref": "T1055"
|
|
478
|
+
"attack_ref": "T1055",
|
|
479
|
+
"false_positive_checks_required": [
|
|
480
|
+
"If the process executable matches a known anti-malware / VPN-client / EDR-agent that documentedly drops a binary under /var/tmp for sandbox-extraction reasons (e.g. CrowdStrike Falcon, SentinelOne, MS Defender Linux), check the org's endpoint-agent allowlist; demote if matched.",
|
|
481
|
+
"Verify the binary checksum (sha256) against the org's allowlisted-deploys inventory. A known-hash match means the binary is approved; demote. A mismatch means the path is unsanctioned and the indicator stands.",
|
|
482
|
+
"If the process was launched by `nohup` / `setsid` / `at` from an interactive session and is documented in the system's `at` queue or operator scratch list, accept as ephemeral with a TTL."
|
|
483
|
+
]
|
|
470
484
|
}
|
|
471
485
|
],
|
|
472
486
|
"false_positive_profile": [
|
package/data/playbooks/sbom.json
CHANGED
|
@@ -639,7 +639,11 @@
|
|
|
639
639
|
"value": "Within the repo-lockfiles artifact: any pinned dependency lacks an integrity field (sha512/sha384/sha256, sri-integrity, go.sum-style hash)",
|
|
640
640
|
"description": "Theater fingerprint #2 detection — name-pinned without integrity.",
|
|
641
641
|
"confidence": "deterministic",
|
|
642
|
-
"deterministic": true
|
|
642
|
+
"deterministic": true,
|
|
643
|
+
"false_positive_checks_required": [
|
|
644
|
+
"If the missing-integrity entries are local-path / workspace / git+ssh refs (`file:`, `link:`, `workspace:`, `git+ssh:`) that the package manager intentionally omits integrity for, demote — workspace refs are integrity-checked at the monorepo root.",
|
|
645
|
+
"Confirm the lockfile is the one the build actually consumes — repos can have stale lockfiles in `archive/` or `pre-migration/` subdirs. Check the build script's referenced lockfile path."
|
|
646
|
+
]
|
|
643
647
|
},
|
|
644
648
|
{
|
|
645
649
|
"id": "transitive-deps-incomplete-sbom",
|
|
@@ -681,7 +685,11 @@
|
|
|
681
685
|
"description": "Direct match for CVE-2026-30615.",
|
|
682
686
|
"confidence": "deterministic",
|
|
683
687
|
"deterministic": true,
|
|
684
|
-
"attack_ref": "T1190"
|
|
688
|
+
"attack_ref": "T1190",
|
|
689
|
+
"false_positive_checks_required": [
|
|
690
|
+
"If the Windsurf binary is the vendor-published patched build identified by sha256 hash (cross-reference Codeium's signed release manifest), the recorded version string may not advance until the next major release; demote when the binary hash matches the patched-build allowlist.",
|
|
691
|
+
"Confirm the installation is on a network-isolated jumpbox / air-gapped dev workstation with documented offline procedure — exploit primitive requires network egress to attacker C2. Demote when the host is provably air-gapped."
|
|
692
|
+
]
|
|
685
693
|
},
|
|
686
694
|
{
|
|
687
695
|
"id": "kev-listed-match",
|
|
@@ -689,7 +697,12 @@
|
|
|
689
697
|
"value": "Any package-matches-catalogued-cve match resolves to a CVE with cisa_kev=true in the catalog",
|
|
690
698
|
"description": "KEV-listed match — fast-path escalation required.",
|
|
691
699
|
"confidence": "deterministic",
|
|
692
|
-
"deterministic": true
|
|
700
|
+
"deterministic": true,
|
|
701
|
+
"false_positive_checks_required": [
|
|
702
|
+
"Pull the vex-statements artifact for the matched CVE. If a VEX entry exists with status `not_affected` (justification: `component_not_present` / `vulnerable_code_not_in_execute_path` / `vulnerable_code_cannot_be_controlled_by_adversary`) AND `status_notes` references a maintainer review, demote — but keep on the regression schedule.",
|
|
703
|
+
"Verify the matched package version is actually present in the live deploy (not just in a build-time/dev-time dependency) — runtime presence elevates; build-time-only presence demotes to high.",
|
|
704
|
+
"If the matched VEX entry has `status: fixed` AND the lockfile resolution is newer-than-fix-version, the match is stale and should be demoted; re-run the dependency walker to refresh."
|
|
705
|
+
]
|
|
693
706
|
},
|
|
694
707
|
{
|
|
695
708
|
"id": "tanstack-worm-payload-files",
|