@blamejs/exceptd-skills 0.15.50 → 0.15.52
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 +10 -0
- package/NOTICE +1 -1
- package/bin/exceptd.js +1 -1
- package/data/_indexes/_meta.json +10 -10
- package/data/_indexes/activity-feed.json +2 -2
- package/data/_indexes/catalog-summaries.json +6 -6
- package/data/_indexes/chains.json +775 -0
- package/data/_indexes/section-offsets.json +25 -25
- package/data/_indexes/token-budget.json +9 -9
- package/data/attack-techniques.json +30 -9
- package/data/cve-catalog.json +455 -7
- package/data/cwe-catalog.json +15 -5
- package/data/framework-control-gaps.json +29 -10
- package/data/playbooks/sbom.json +150 -1
- package/data/zeroday-lessons.json +251 -1
- package/lib/prefetch.js +2 -2
- package/manifest.json +45 -45
- package/package.json +1 -1
- package/sbom.cdx.json +59 -44
- package/scripts/check-changelog-extract.js +158 -0
- package/scripts/check-test-coverage.README.md +1 -1
- package/scripts/check-test-coverage.js +3 -3
- package/scripts/check-version-tags.js +31 -2
- package/scripts/predeploy.js +13 -0
- package/scripts/release.js +5 -0
- package/scripts/verify-shipped-tarball.js +1 -1
- package/skills/supply-chain-integrity/skill.md +2 -0
|
@@ -544,7 +544,8 @@
|
|
|
544
544
|
"opened_date": "2026-05-13",
|
|
545
545
|
"evidence_cves": [
|
|
546
546
|
"CVE-2026-45321",
|
|
547
|
-
"MAL-2026-SHAI-HULUD-OSS"
|
|
547
|
+
"MAL-2026-SHAI-HULUD-OSS",
|
|
548
|
+
"MAL-2026-TRAPDOOR-CROSS-ECOSYSTEM"
|
|
548
549
|
],
|
|
549
550
|
"atlas_refs": [
|
|
550
551
|
"AML.T0010",
|
|
@@ -907,7 +908,9 @@
|
|
|
907
908
|
"MAL-2026-3083",
|
|
908
909
|
"MAL-2026-NODE-IPC-STEALER",
|
|
909
910
|
"MAL-2026-SHAI-HULUD-OSS",
|
|
910
|
-
"MAL-2026-TANSTACK-MINI"
|
|
911
|
+
"MAL-2026-TANSTACK-MINI",
|
|
912
|
+
"CVE-2022-23812",
|
|
913
|
+
"MAL-2026-MOIKA-DEPCONFUSION"
|
|
911
914
|
],
|
|
912
915
|
"atlas_refs": [
|
|
913
916
|
"AML.T0010",
|
|
@@ -2057,7 +2060,9 @@
|
|
|
2057
2060
|
"evidence_cves": [
|
|
2058
2061
|
"CVE-2026-45321",
|
|
2059
2062
|
"MAL-2026-3083",
|
|
2060
|
-
"MAL-2026-NODE-IPC-STEALER"
|
|
2063
|
+
"MAL-2026-NODE-IPC-STEALER",
|
|
2064
|
+
"CVE-2022-23812",
|
|
2065
|
+
"MAL-2026-MOIKA-DEPCONFUSION"
|
|
2061
2066
|
],
|
|
2062
2067
|
"atlas_refs": [
|
|
2063
2068
|
"AML.T0010",
|
|
@@ -2098,7 +2103,8 @@
|
|
|
2098
2103
|
"CVE-2025-30154",
|
|
2099
2104
|
"CVE-2026-48027",
|
|
2100
2105
|
"MAL-2026-SHAI-HULUD-OSS",
|
|
2101
|
-
"MAL-2026-TANSTACK-MINI"
|
|
2106
|
+
"MAL-2026-TANSTACK-MINI",
|
|
2107
|
+
"MAL-2026-TRAPDOOR-CROSS-ECOSYSTEM"
|
|
2102
2108
|
],
|
|
2103
2109
|
"atlas_refs": [
|
|
2104
2110
|
"AML.T0010"
|
|
@@ -3668,7 +3674,8 @@
|
|
|
3668
3674
|
"CVE-2026-45321",
|
|
3669
3675
|
"MAL-2026-3083",
|
|
3670
3676
|
"MAL-2026-NODE-IPC-STEALER",
|
|
3671
|
-
"MAL-2026-SHAI-HULUD-OSS"
|
|
3677
|
+
"MAL-2026-SHAI-HULUD-OSS",
|
|
3678
|
+
"MAL-2026-MOIKA-DEPCONFUSION"
|
|
3672
3679
|
],
|
|
3673
3680
|
"atlas_refs": [
|
|
3674
3681
|
"AML.T0010",
|
|
@@ -5900,7 +5907,10 @@
|
|
|
5900
5907
|
"CVE-2026-46333",
|
|
5901
5908
|
"CVE-2026-5760",
|
|
5902
5909
|
"CVE-2026-9082",
|
|
5903
|
-
"MAL-2026-SHAI-HULUD-OSS"
|
|
5910
|
+
"MAL-2026-SHAI-HULUD-OSS",
|
|
5911
|
+
"CVE-2022-23812",
|
|
5912
|
+
"MAL-2026-TRAPDOOR-CROSS-ECOSYSTEM",
|
|
5913
|
+
"MAL-2026-MOIKA-DEPCONFUSION"
|
|
5904
5914
|
],
|
|
5905
5915
|
"atlas_refs": [],
|
|
5906
5916
|
"attack_refs": [
|
|
@@ -6116,7 +6126,10 @@
|
|
|
6116
6126
|
"MAL-2026-NODE-IPC-STEALER",
|
|
6117
6127
|
"MAL-2026-RUBYGEMS-BUFFERZONECORP-SLEEPER",
|
|
6118
6128
|
"MAL-2026-SHAI-HULUD-OSS",
|
|
6119
|
-
"MAL-2026-TANSTACK-MINI"
|
|
6129
|
+
"MAL-2026-TANSTACK-MINI",
|
|
6130
|
+
"CVE-2022-23812",
|
|
6131
|
+
"MAL-2026-TRAPDOOR-CROSS-ECOSYSTEM",
|
|
6132
|
+
"MAL-2026-MOIKA-DEPCONFUSION"
|
|
6120
6133
|
],
|
|
6121
6134
|
"atlas_refs": [
|
|
6122
6135
|
"AML.T0010",
|
|
@@ -6707,7 +6720,9 @@
|
|
|
6707
6720
|
"CVE-2025-68665",
|
|
6708
6721
|
"CVE-2025-6965",
|
|
6709
6722
|
"CVE-2026-22778",
|
|
6710
|
-
"MAL-2025-AI-FOUND-FFMPEG-BIGSLEEP"
|
|
6723
|
+
"MAL-2025-AI-FOUND-FFMPEG-BIGSLEEP",
|
|
6724
|
+
"MAL-2026-TRAPDOOR-CROSS-ECOSYSTEM",
|
|
6725
|
+
"MAL-2026-MOIKA-DEPCONFUSION"
|
|
6711
6726
|
],
|
|
6712
6727
|
"theater_test": {
|
|
6713
6728
|
"claim": "We are compliant with Art-15 (Accuracy, robustness, and cybersecurity of high-risk AI systems) because we follow the documented requirement: Article 15 — high-risk AI systems must be designed and developed so as to achieve an appropriate level of accuracy, robustness, and cybersecurity throughout their lifecycle. Anchored on the assumption",
|
|
@@ -7519,7 +7534,10 @@
|
|
|
7519
7534
|
"opened_at": "2026-05-18",
|
|
7520
7535
|
"evidence_cves": [
|
|
7521
7536
|
"MAL-2025-PYPI-COLORAMA-SOLANA-STEALER",
|
|
7522
|
-
"MAL-2026-RUBYGEMS-BUFFERZONECORP-SLEEPER"
|
|
7537
|
+
"MAL-2026-RUBYGEMS-BUFFERZONECORP-SLEEPER",
|
|
7538
|
+
"MAL-2026-MOIKA-DEPCONFUSION",
|
|
7539
|
+
"CVE-2022-23812",
|
|
7540
|
+
"MAL-2026-TRAPDOOR-CROSS-ECOSYSTEM"
|
|
7523
7541
|
],
|
|
7524
7542
|
"theater_test": {
|
|
7525
7543
|
"claim": "We are compliant with A.5.21 (Managing information security in the ICT supply chain) because we follow the documented requirement: Annex A.5.21 — processes and procedures to manage information security risks associated with the ICT supply chain. Anchored on initial vendor / supplier assessment, contractual security clauses, and o",
|
|
@@ -8052,7 +8070,8 @@
|
|
|
8052
8070
|
"opened_date": "2026-05-28",
|
|
8053
8071
|
"evidence_cves": [
|
|
8054
8072
|
"CVE-2025-30066",
|
|
8055
|
-
"CVE-2026-48027"
|
|
8073
|
+
"CVE-2026-48027",
|
|
8074
|
+
"MAL-2026-MOIKA-DEPCONFUSION"
|
|
8056
8075
|
],
|
|
8057
8076
|
"atlas_refs": [
|
|
8058
8077
|
"AML.T0010"
|
package/data/playbooks/sbom.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_meta": {
|
|
3
3
|
"id": "sbom",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.3.1",
|
|
5
5
|
"last_threat_review": "2026-05-13",
|
|
6
6
|
"threat_currency_score": 96,
|
|
7
7
|
"changelog": [
|
|
@@ -43,6 +43,21 @@
|
|
|
43
43
|
"nis2-art21-2d",
|
|
44
44
|
"dora-art28"
|
|
45
45
|
]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"version": "1.2.1",
|
|
49
|
+
"date": "2026-05-30",
|
|
50
|
+
"summary": "Add capability-creep behavioral detector: flags a dependency that gains network / filesystem-write / shell-spawn / credential-access / install-script capability across a version bump (pre-disclosure supply-chain signal), with five false-positive checks and a matching false-positive profile."
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"version": "1.3.0",
|
|
54
|
+
"date": "2026-05-30",
|
|
55
|
+
"summary": "Add a package-capability taxonomy (network / filesystem / shell / env / eval / install-script / telemetry / native-binary): a package-capability-surface evidence artifact plus an absolute capability-surface screen that flags a no-CVE package whose install-script combines with shell/network/env/eval (the credential-harvesting delivery shape), complementing the across-version-bump capability-creep detector."
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"version": "1.3.1",
|
|
59
|
+
"date": "2026-05-30",
|
|
60
|
+
"summary": "Add three supply-chain detection-depth indicators: typosquat/homoglyph package-name detection (reusing the vendored codepoint-class confusable tables), a static content red-flag screen (obfuscation / high-entropy / trivial-package / eval+dynamic-require, T1027), and a dependency-confusion resolution-source check (internal scope served by the public registry / inflated-version squat, correlating to MAL-2026-MOIKA-DEPCONFUSION) — each with a paired look artifact and false-positive profile."
|
|
46
61
|
}
|
|
47
62
|
],
|
|
48
63
|
"owner": "@blamejs/supply-chain",
|
|
@@ -602,6 +617,34 @@
|
|
|
602
617
|
"source": "Read .github/workflows/*.yml and *.yaml — extract `on:` triggers, `permissions:`, and `uses: actions/cache@*` step references",
|
|
603
618
|
"description": "CVE-2026-45321 architectural pre-condition check — detects pull_request_target + id-token:write + shared actions/cache co-residency in the same repo.",
|
|
604
619
|
"required": false
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
"id": "package-capability-surface",
|
|
623
|
+
"type": "config_file",
|
|
624
|
+
"source": "For each installed/locked npm + PyPI package, read its manifest to enumerate declared/observable capabilities: package.json scripts (preinstall/install/postinstall), bin, gypfile/binding.gyp (native-binary build), and dependencies on known network/child-process modules; PyPI setup.py/pyproject.toml build hooks + .so/.pyd payloads. Classify each package into the capability vocabulary: network, filesystem, shell, env, eval, install-script, telemetry, native-binary.",
|
|
625
|
+
"description": "Package-capability surface per dependency, using a capability taxonomy (network / filesystem / shell / env / eval / install-script / telemetry / native-binary). Complements the CVE-match register: a package with no catalogued CVE can still carry an outsized capability surface (an install-script that spawns a shell and reads env is the Shai-Hulud delivery shape). Capability is a property of the package, independent of any known vulnerability.",
|
|
626
|
+
"required": false
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
"id": "package-name-similarity-surface",
|
|
630
|
+
"type": "config_file",
|
|
631
|
+
"source": "For each direct dependency, capture {package name, resolved registry host, a download/popularity signal where available} and compute (a) the Levenshtein edit-distance to a set of high-download popular package names and (b) a homoglyph-normalized / confusable-folded form of the name (via vendor/blamejs/codepoint-class.js) to surface visually-confusable substitutions. Record the closest popular-name match + distance per dependency.",
|
|
632
|
+
"description": "Package-name similarity surface — the evidence the typosquat/homoglyph detector reads. Captures edit-distance and confusable-normalized name forms so name-impersonation is computable offline without a registry call (the popularity set is a static bundled list; absence of a live download count degrades to edit-distance + homoglyph only).",
|
|
633
|
+
"required": false
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
"id": "package-source-content-surface",
|
|
637
|
+
"type": "config_file",
|
|
638
|
+
"source": "For each installed/locked package, statically read its shipped source and record: readable-source-present vs minified-only, sourcemap presence, high-entropy-string ratio (Shannon entropy over string literals), effective LOC count, and dynamic eval / dynamic require/import usage. No execution — static read of the on-disk package files only.",
|
|
639
|
+
"description": "Package source-content surface — the static evidence the obfuscation screen reads. Distinct from package-capability-surface (which classifies declared/observable capabilities); this captures source SHAPE (minification, entropy, size, dynamic-eval) for the content red-flag screen.",
|
|
640
|
+
"required": false
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
"id": "dep-confusion-resolution-config",
|
|
644
|
+
"type": "config_file",
|
|
645
|
+
"source": "Read scope-to-registry resolution config: .npmrc + $HOME/.npmrc (@scope:registry= lines, registry=, always-auth), .yarnrc.yml (npmScopes), pip.conf / pip.ini (index-url, extra-index-url), and any private-registry manifest listing internal @scopes / package names. Cross with repo-lockfiles resolved URLs to determine which registry actually served each internal-scope name.",
|
|
646
|
+
"description": "Resolution-source configuration — distinguishes an internal-namespace package correctly pinned to a private registry (safe) from one resolvable off the public registry (dependency-confusion exposed). Without it the indicator can only see inflated-version heuristics; with it the resolution-source check becomes high-confidence.",
|
|
647
|
+
"required": false
|
|
605
648
|
}
|
|
606
649
|
],
|
|
607
650
|
"collection_scope": {
|
|
@@ -800,6 +843,87 @@
|
|
|
800
843
|
"description": "Mitigation gap for CVE-2026-45321 and similar fresh-publish worms. Without a registry cooldown, `npm install` will accept a version published seconds ago. Recommended: `before=72h` (npm 11+) or `minimumReleaseAge=4320` minutes. The worm was caught publicly within 20 minutes; 72h is overkill-safe.",
|
|
801
844
|
"confidence": "high",
|
|
802
845
|
"deterministic": false
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
"id": "dependency-capability-creep-across-version-bump",
|
|
849
|
+
"type": "behavioral_signal",
|
|
850
|
+
"value": "For any dependency present in repo-lockfiles, npm-global-packages, or container-image-layers whose resolved version advanced since the last recorded inventory (or across two lockfile revisions in scope), compare the package's capability surface between the two versions. CAPABILITY SURFACE = (a) network egress (new fetch/http/https/net/dgram/ws imports or new outbound hosts), (b) filesystem write beyond the package's own dir, (c) process/shell spawn (child_process/exec/spawn, os/system, subprocess, Runtime.exec), (d) environment / credential access (process.env enumeration, reads of ~/.aws ~/.ssh ~/.npmrc ~/.kube ~/.config, keychain/secrets API), (e) install-lifecycle scripts newly present or newly non-trivial (preinstall/install/postinstall, setup.py/cfg build hooks, gem extensions, Cargo build.rs). FIRES when a version bump GAINS one or more capability categories the prior in-scope version did not exercise — a capability the dependent never opted into when it first selected the package.",
|
|
851
|
+
"description": "Capability-creep — a dependency that suddenly gains network / filesystem-write / shell-spawn / credential-access / install-script capability across a version bump. Models the core behavioral supply-chain signal: most maintainer-account takeovers and protestware injections surface first as a new capability class in an otherwise-unchanged package, before any CVE is catalogued. Pre-disclosure detector — fires on behavior delta, not on a known-CVE match.",
|
|
852
|
+
"confidence": "medium",
|
|
853
|
+
"deterministic": false,
|
|
854
|
+
"attack_ref": "T1195.001",
|
|
855
|
+
"atlas_ref": "AML.T0010",
|
|
856
|
+
"false_positive_checks_required": [
|
|
857
|
+
"Confirm the new capability is not explained by a documented feature in the version's release notes / CHANGELOG that the dependent intentionally upgraded to consume (e.g. a logging library that added an opt-in OTLP-over-HTTP exporter). A capability gain matching a documented, in-changelog feature the operator pulled deliberately is benign — demote to inconclusive and record the changelog citation.",
|
|
858
|
+
"Distinguish a genuine capability gain from a refactor that merely RELOCATED an existing capability (the prior version already spawned a child process via a transitive dep; the new version inlines it). Diff the EFFECTIVE capability of the prior resolved tree (package + its own deps), not the single package file — relocation across the package boundary is not a gain. Demote when the capability was already present transitively.",
|
|
859
|
+
"Exclude build-time / dev-time-only dependencies whose new capability never reaches a runtime-loaded path (e.g. a test runner gaining child_process). Confirm runtime reachability before elevating; build/dev-only capability gain demotes to low.",
|
|
860
|
+
"Rule out the package's own first-party major-version migration where the maintainer key, publish provenance (SLSA / npm --provenance), and signing identity are UNCHANGED across the bump and the version follows the project's normal release cadence (not a sub-72h fresh publish). Unchanged provenance + normal cadence + documented breaking-change major demotes; an unchanged-version-string-but-changed-content republish, a new/rotated maintainer key, or a provenance attestation that regressed from present-to-absent ELEVATES (re-publication-over / account-takeover signature).",
|
|
861
|
+
"For install-lifecycle scripts: confirm the script is newly present OR newly non-trivial versus the prior version — many packages have always shipped a postinstall (e.g. native-addon compile via node-gyp). A long-standing, content-stable lifecycle script is not creep; only a newly-added or newly-mutated one fires."
|
|
862
|
+
]
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
"id": "package-capability-creep",
|
|
866
|
+
"type": "config_value",
|
|
867
|
+
"value": "Within the package-capability-surface artifact: any direct or transitive package combines an install-time hook (install-script: preinstall/install/postinstall, or PyPI build hook) with at least one high-trust capability (shell, network, env, or eval). Capability vocabulary: network / filesystem / shell / env / eval / install-script / telemetry / native-binary. The install-script+shell+network+env quadruple is the credential-harvesting delivery shape (CVE-2026-45321 Mini Shai-Hulud, CVE-2024-3094 XZ class); install-script+native-binary is the compiled-payload shape.",
|
|
868
|
+
"description": "Capability-surface screen — flags packages whose capability surface is disproportionate to their declared function, independent of CVE match. A no-CVE package that runs an install-script which spawns a shell, opens the network, and reads env vars is the supply-chain delivery primitive even when the catalog shows zero matched CVEs. Surfaces alongside, not in place of, package-matches-catalogued-cve; absolute-surface complement to the across-version-bump capability-creep detector.",
|
|
869
|
+
"confidence": "medium",
|
|
870
|
+
"deterministic": false,
|
|
871
|
+
"attack_ref": "T1195.002",
|
|
872
|
+
"false_positive_checks_required": [
|
|
873
|
+
"Build-tooling and native-addon packages legitimately need install-script + native-binary (node-gyp, sass-embedded, esbuild, sharp, bcrypt, prebuild-install, electron, playwright, puppeteer). If the package is on the operator documented build-dependency allowlist AND its publisher identity / provenance attestation matches the expected one, demote — capability presence is expected for this class.",
|
|
874
|
+
"Confirm the install-script actually exercises the high-trust capability at install time, not merely that the package transitively depends on a network/shell module used only at runtime in an unrelated code path. Read the referenced script: a postinstall that only runs node-gyp rebuild is native-binary, not network+shell+env. Demote when the capability is declared-but-not-install-reachable.",
|
|
875
|
+
"Telemetry-only capability (a postinstall that pings an analytics endpoint with no env/credential read) is a noise/consent concern, not a credential-exfil primitive. Demote network+telemetry-without-env to informational unless env or filesystem-credential-path access co-occurs.",
|
|
876
|
+
"If the capability surface was read from a stale or non-build lockfile (archive/ or pre-migration/ subdir), it does not reflect what installs. Confirm against the build-consumed lockfile path before firing (same stale-lockfile check as lockfile-no-integrity)."
|
|
877
|
+
]
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
"id": "dependency-name-typosquat",
|
|
881
|
+
"type": "behavioral_signal",
|
|
882
|
+
"value": "For each DIRECT dependency name in repo-lockfiles / npm-global-packages / pip-packages, flag when the name is within Levenshtein edit-distance 1-2 of a SPECIFIC high-download popular package it is NOT (typosquat — e.g. lodahs vs lodash, crossenv vs cross-env), OR uses a homoglyph / visually-confusable Unicode substitution of a popular name (route the name through the vendored vendor/blamejs/codepoint-class.js confusable/bidi detection — a Latin/Cyrillic/Greek lookalike codepoint inside an otherwise-ASCII clone of a popular name is a near-certain lure), OR is a scope/namespace lookalike of a well-known org scope. FIRES on name-confusability independent of any CVE match — the typosquat lure precedes payload execution.",
|
|
883
|
+
"description": "Typosquat / homoglyph package-name detector. Flags a direct dependency whose NAME impersonates a popular package by edit-distance or visually-confusable codepoint substitution, a pre-disclosure lure signal that no CVE-match or capability screen catches. Reuses the vendored codepoint-class confusable detection (added v0.15.50). Distinct axis from the dependency-confusion resolution check (name-similarity vs resolution-source).",
|
|
884
|
+
"confidence": "medium",
|
|
885
|
+
"deterministic": false,
|
|
886
|
+
"attack_ref": "T1195.002",
|
|
887
|
+
"atlas_ref": "AML.T0010",
|
|
888
|
+
"false_positive_checks_required": [
|
|
889
|
+
"The package IS the popular one: confirm via download-count / publish-history that the flagged name is not itself the canonical high-download package before treating proximity as a squat. Demote when the package is the legitimate target it resembles.",
|
|
890
|
+
"Intentional short-name alias or sanctioned scoped fork the operator deliberately consumes (e.g. an internal mirror named close to upstream). Cross-check the operator documented dependency allowlist; demote when the near-name is a known sanctioned alias.",
|
|
891
|
+
"Common-word collision: short generic names (ms, qs, fs) sit within edit-distance of many strings by chance. Require a SPECIFIC high-download package the flagged dep plausibly impersonates, not any string within distance 2; demote incidental proximity with no plausible impersonation target.",
|
|
892
|
+
"Homoglyph false alarm: confirm the confusable/non-ASCII codepoint actually substitutes for an ASCII letter in a known popular package name (a Cyrillic a inside an ASCII clone), not an incidental legitimately-non-Latin name that has no popular ASCII twin. Demote names with no ASCII-lookalike target."
|
|
893
|
+
]
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
"id": "package-content-obfuscation-screen",
|
|
897
|
+
"type": "config_value",
|
|
898
|
+
"value": "Within package-source-content-surface: flag a package that ships ONLY minified/obfuscated source with no readable source and no sourcemap, OR a high ratio of high-entropy string literals (packed/encrypted payload), OR is a trivial package (under ~10 effective LOC) that NONETHELESS declares an install-script or network capability (a thin shell wrapping a payload), OR combines dynamic eval with dynamic require/import. These are CONTENT red-flags orthogonal to the capability taxonomy — a package can be capability-light yet content-obfuscated.",
|
|
899
|
+
"description": "Static content red-flag screen — obfuscation / high-entropy / minified-only / trivial-package / eval+dynamic-require. Complements (does not duplicate) the capability-surface screens: capability is WHAT the package can do, content is HOW its source is shaped. Maps T1027 (Obfuscated Files or Information).",
|
|
900
|
+
"confidence": "medium",
|
|
901
|
+
"deterministic": false,
|
|
902
|
+
"attack_ref": "T1027",
|
|
903
|
+
"false_positive_checks_required": [
|
|
904
|
+
"Legitimate minified dist bundle: many front-end packages ship a minified dist/ alongside readable src/ or a sourcemap. Confirm there is NO readable source AND no sourcemap anywhere in the package before flagging minified-only; demote when readable source or a .map file exists.",
|
|
905
|
+
"Known-good build-output / WASM / native-addon packages legitimately carry high-entropy blobs (embedded WASM, prebuilt binaries, fixtures). Cross-check the operator build-dependency allowlist + publisher provenance; demote expected high-entropy classes.",
|
|
906
|
+
"Trivial-package false alarm: a genuinely tiny utility (is-number) is trivial WITHOUT an install-script or network capability. Only elevate trivial packages that ALSO declare install-script or network capability (the co-occurrence is the signal); demote trivial-but-inert packages.",
|
|
907
|
+
"eval / dynamic-require in a legitimate plugin loader or config system (some established frameworks use dynamic require by design). Confirm the eval+dynamic-require pair is in a freshly-published or low-trust package, not a long-standing well-known framework; demote established frameworks with documented dynamic loading."
|
|
908
|
+
]
|
|
909
|
+
},
|
|
910
|
+
{
|
|
911
|
+
"id": "dependency-confusion-internal-scope-public-resolution",
|
|
912
|
+
"type": "config_value",
|
|
913
|
+
"value": "Within repo-lockfiles + dep-confusion-resolution-config: a dependency whose scope/name matches an organizational-internal naming pattern (a @scope that ALSO appears in the org private-registry manifests, .npmrc/.yarnrc scope-to-registry maps, or pip internal extra-index-url) resolved from the PUBLIC registry (registry.npmjs.org / pypi.org) rather than the internal one — AND/OR a resolved version is anomalously inflated (e.g. 99.x.x, a major far above the package real release history) such that default resolution prefers the public package. The defining condition is RESOLUTION-SOURCE confusion: an internal-namespace identifier served by the public registry, regardless of payload.",
|
|
914
|
+
"description": "Dependency-confusion / namespace-squat resolution check. Fires when an internal-looking package name is satisfied from the public registry, or an inflated semver indicates a public squat outranking the private package — the resolution-layer signature that precedes any payload. Generalizes MAL-2026-MOIKA-DEPCONFUSION to any internal-scope squat; complements the capability-surface screens (postinstall stager) and the catalog CVE-match (specific known names). Distinct axis from the typosquat name-similarity detector.",
|
|
915
|
+
"confidence": "high",
|
|
916
|
+
"deterministic": false,
|
|
917
|
+
"attack_ref": "T1195.001",
|
|
918
|
+
"atlas_ref": "AML.T0010",
|
|
919
|
+
"cve_ref": "MAL-2026-MOIKA-DEPCONFUSION",
|
|
920
|
+
"false_positive_checks_required": [
|
|
921
|
+
"Confirm the @scope is genuinely an INTERNAL/private namespace for this org (present in a private-registry manifest, .npmrc scope-to-registry map, or internal pip index), not a public org scope the org legitimately consumes from npmjs (@types, @babel, @aws-sdk are public-by-design and not confusion). Demote when the scope is a known public namespace.",
|
|
922
|
+
"Verify an actual private package of the same name exists that the public one could shadow. A public-only package under a coincidentally org-like scope, with no private counterpart, is not dependency-confusion — there is nothing to confuse it with. Demote to inconclusive absent a private same-name package.",
|
|
923
|
+
"Confirm install config does not already pin the internal scope to the private registry (.npmrc @scope:registry=, always-auth, pip --index-url with no public fallthrough). A correctly-pinned scope cannot be confused; the public squat exists but cannot win resolution. Demote when resolution is pinned.",
|
|
924
|
+
"Distinguish an inflated version that reflects a real (if unusual) upstream release from a squat. Check the package real release history on the AUTHORITATIVE registry for this scope: a genuine 99.x maintained release is not a squat. Demote when the inflated version is the legitimate publisher actual latest on the correct registry.",
|
|
925
|
+
"Confirm the resolved artifact actually came from the public registry for this run (lockfile resolved/resolution URL host = registry.npmjs.org / files.pythonhosted.org), not the private mirror that merely proxies public names. A private proxy serving a vetted copy under the internal scope is not confusion-exposed. Demote when the resolution host is the internal/proxy registry."
|
|
926
|
+
]
|
|
803
927
|
}
|
|
804
928
|
],
|
|
805
929
|
"false_positive_profile": [
|
|
@@ -817,6 +941,31 @@
|
|
|
817
941
|
"indicator_id": "ai-code-no-provenance",
|
|
818
942
|
"benign_pattern": "Org has documented internal commit-trailer convention (Co-Authored-By:, Generated-By:, AI-Model:) but it isn't reflected in machine-readable SBOM.",
|
|
819
943
|
"distinguishing_test": "Sample 20 commits in the repo. If commit-trailer convention is consistently used, downgrade to medium and recommend SBOM integration rather than flagging absence outright."
|
|
944
|
+
},
|
|
945
|
+
{
|
|
946
|
+
"indicator_id": "dependency-capability-creep-across-version-bump",
|
|
947
|
+
"benign_pattern": "A widely-used package legitimately adds a capability in a feature release — e.g. an HTTP client adds optional HTTP/2 (new net surface), an ORM adds a CLI migration runner (new child_process), a telemetry SDK adds an opt-in exporter (new egress host). The capability is documented, the maintainer key and publish provenance are unchanged, and the release follows normal cadence.",
|
|
948
|
+
"distinguishing_test": "Pull the version release notes / CHANGELOG and the npm/PyPI provenance attestation. Benign when ALL hold: (1) the new capability is documented as an intended feature, (2) the signing/maintainer identity is unchanged across the bump, (3) provenance present-before is still present, and (4) publish age exceeds the registry-cooldown threshold (not a sub-72h fresh publish). Malicious-leaning when ANY of: capability is undocumented, maintainer key rotated, provenance regressed from present-to-absent, content changed under an unchanged version string, or the gain is credential-path reads / outbound to a never-before-seen host."
|
|
949
|
+
},
|
|
950
|
+
{
|
|
951
|
+
"indicator_id": "package-capability-creep",
|
|
952
|
+
"benign_pattern": "Native-addon and build-tooling packages (node-gyp, sass-embedded, esbuild, sharp, bcrypt, prebuild-install, electron, playwright, puppeteer) legitimately carry install-script + native-binary + (sometimes) network capability — the prebuilt-binary download is network + install-script by design.",
|
|
953
|
+
"distinguishing_test": "Cross-reference the flagged package against the operator documented build-dependency allowlist and its npm/PyPI provenance attestation. If the package is a known build/native-addon dependency AND its publisher identity matches the expected signed publisher, downgrade to medium/informational. Reserve high confidence for packages whose capability surface is unexplained by their stated function (a date-formatting utility that opens the network and reads env)."
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
"indicator_id": "dependency-name-typosquat",
|
|
957
|
+
"benign_pattern": "A short or generic package name sits within edit-distance of a popular one by coincidence (ms, qs), or the operator deliberately consumes a sanctioned near-name (an internal mirror, a scoped fork), or a legitimately non-Latin package name has no popular ASCII twin.",
|
|
958
|
+
"distinguishing_test": "Typosquat-likely only when ALL hold: the name is within edit-distance 1-2 of (or a homoglyph substitution within) a SPECIFIC high-download package it is NOT, the flagged package is far lower download/younger than that target, and it is not on the operator sanctioned-alias allowlist. Any one failing demotes to medium/informational."
|
|
959
|
+
},
|
|
960
|
+
{
|
|
961
|
+
"indicator_id": "package-content-obfuscation-screen",
|
|
962
|
+
"benign_pattern": "A front-end package ships a minified dist/ with a sourcemap or alongside readable src/; a native-addon/WASM package carries legitimate high-entropy binary blobs; an established framework uses documented dynamic require for plugin loading.",
|
|
963
|
+
"distinguishing_test": "Content-malicious-leaning only when the obfuscation co-occurs with risk: minified-only AND no sourcemap AND no readable source; OR trivial-LOC AND (install-script OR network); OR eval+dynamic-require in a fresh/low-trust package. A minified bundle WITH a sourcemap, or high-entropy in an allowlisted native-addon, or dynamic-require in a known framework, demotes to informational."
|
|
964
|
+
},
|
|
965
|
+
{
|
|
966
|
+
"indicator_id": "dependency-confusion-internal-scope-public-resolution",
|
|
967
|
+
"benign_pattern": "An org legitimately consumes a public scoped package (@types/*, @babel/*, @aws-sdk/*) that superficially resembles an internal naming convention; OR an internal scope is already correctly pinned to the private registry so the public squat cannot win resolution.",
|
|
968
|
+
"distinguishing_test": "Resolve each flagged @scope against (1) the org private-registry internal-scope list and (2) the .npmrc/.yarnrc/pip index scope-to-registry mapping. Dependency-confusion-exposed only when ALL hold: the scope is an internal namespace, a same-name private package exists, the scope is NOT pinned to the private registry, and the lockfile resolved host is the public registry. If any fails, downgrade to medium/informational."
|
|
820
969
|
}
|
|
821
970
|
],
|
|
822
971
|
"minimum_signal": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"rebuild_after_days": 365,
|
|
18
18
|
"note": "Per-entry last_verified governs decay. Skills depending on this catalog must check entry freshness before high-stakes use."
|
|
19
19
|
},
|
|
20
|
-
"entry_count":
|
|
20
|
+
"entry_count": 430
|
|
21
21
|
},
|
|
22
22
|
"CVE-2026-31431": {
|
|
23
23
|
"name": "Copy Fail",
|
|
@@ -24854,5 +24854,255 @@
|
|
|
24854
24854
|
"ai_discovered_zeroday": false,
|
|
24855
24855
|
"ai_discovery_source": "vendor_research",
|
|
24856
24856
|
"ai_assist_factor": "none"
|
|
24857
|
+
},
|
|
24858
|
+
"CVE-2022-23812": {
|
|
24859
|
+
"name": "node-ipc geo-targeted file-wiper protestware (RU/BY heart-emoji overwrite; peacenotwar dropper in 11.0.0+)",
|
|
24860
|
+
"lesson_date": "2026-05-30",
|
|
24861
|
+
"attack_vector": {
|
|
24862
|
+
"description": "A trusted, legitimate package maintainer (node-ipc, ~1M weekly downloads, transitive via Vue CLI) authored a destructive payload directly into published versions 10.1.1/10.1.2 (2022-03-07/08): on require('node-ipc') the code resolved the host's public-IP geolocation and, for Russia/Belarus, recursively overwrote writable files with a heart character; a follow-on bundled the peacenotwar dropper (WITH-LOVE-FROM-AMERICA.txt, NVD-scoped into 11.0.0+). The payload ships in the MAIN MODULE, not a postinstall hook, so --ignore-scripts — the canonical post-Shai-Hulud npm mitigation — does NOT mitigate. Class: insider-maintainer protestware/sabotage, distinct from account-recovery takeover (node-ipc 2026 stealer) and typosquat.",
|
|
24863
|
+
"privileges_required": "downstream consumer runs install resolving 10.1.1/10.1.2 (directly or transitively); payload fires automatically on require('node-ipc')",
|
|
24864
|
+
"complexity": "low for downstream consumers (automatic on module load); trivial for the maintainer (any owner with publish rights can author it)",
|
|
24865
|
+
"ai_factor": "Not AI-discovered, not AI-built. Hand-authored geo-gated wiper; the novelty is the trust-abuse + main-module delivery, not algorithmic."
|
|
24866
|
+
},
|
|
24867
|
+
"defense_chain": {
|
|
24868
|
+
"prevention": {
|
|
24869
|
+
"what_would_have_worked": "Strict-lockfile installs (npm ci) against a lockfile predating 2022-03-07; pinning critical-path dependencies and gating upgrades on a review window; treating high-reach single-maintainer packages as a concentration risk.",
|
|
24870
|
+
"was_this_required": false,
|
|
24871
|
+
"framework_requiring_it": null,
|
|
24872
|
+
"adequacy": "--ignore-scripts does NOT help (main-module payload). Lockfile pinning helps only if the lockfile predates the malicious publish AND npm ci (not npm install) is used."
|
|
24873
|
+
},
|
|
24874
|
+
"detection": {
|
|
24875
|
+
"what_would_have_worked": "Behavioral telemetry on developer/CI hosts: an outbound geolocation lookup followed by recursive filesystem writes within a Node process's lifetime is a near-deterministic IoC for a main-module wiper. File-write telemetry on broad path globs at module-load time catches the class even when --ignore-scripts is set.",
|
|
24876
|
+
"was_this_required": false,
|
|
24877
|
+
"framework_requiring_it": null,
|
|
24878
|
+
"adequacy": "Most endpoints and CI runners do not retain process-level file-write telemetry; the geo-gating means absence of harm on a non-RU/BY host does not prove the payload was absent."
|
|
24879
|
+
},
|
|
24880
|
+
"response": {
|
|
24881
|
+
"what_would_have_worked": "npm removed 10.1.1/10.1.2; consumers upgraded to 10.1.3+. For the 11.0.0+ peacenotwar range, pin to a pre-11.0.0 line or a maintained fork. Restore any overwritten files from backup on affected RU/BY hosts.",
|
|
24882
|
+
"was_this_required": false,
|
|
24883
|
+
"framework_requiring_it": null,
|
|
24884
|
+
"adequacy": "Yank closes new exposure but does not restore wiped files; the destructive action is irreversible without backups."
|
|
24885
|
+
}
|
|
24886
|
+
},
|
|
24887
|
+
"framework_coverage": {
|
|
24888
|
+
"NIST-800-218-SSDF": {
|
|
24889
|
+
"covered": true,
|
|
24890
|
+
"adequate": false,
|
|
24891
|
+
"gap": "Reused-component controls assume maintainer good-faith; no coverage of trusted-maintainer sabotage."
|
|
24892
|
+
},
|
|
24893
|
+
"EU-CRA-Art13": {
|
|
24894
|
+
"covered": true,
|
|
24895
|
+
"adequate": false,
|
|
24896
|
+
"gap": "SBOM records what resolved; a pinned malicious version is SBOM-compliant."
|
|
24897
|
+
},
|
|
24898
|
+
"NIS2-Art21-supply-chain": {
|
|
24899
|
+
"covered": true,
|
|
24900
|
+
"adequate": false,
|
|
24901
|
+
"gap": "No main-module-vs-postinstall payload distinction; no maintainer-intent risk."
|
|
24902
|
+
},
|
|
24903
|
+
"UK-CAF-B4-supply-chain": {
|
|
24904
|
+
"covered": true,
|
|
24905
|
+
"adequate": false,
|
|
24906
|
+
"gap": "Outcome-based principle does not prescribe insider-maintainer-sabotage defense."
|
|
24907
|
+
},
|
|
24908
|
+
"ANY-FRAMEWORK": {
|
|
24909
|
+
"covered": false,
|
|
24910
|
+
"adequate": false,
|
|
24911
|
+
"gap": "No major framework treats a trusted maintainer shipping a geo-targeted destructive main-module payload as a distinct supply-chain class. NEW-CTRL-114 is the headline novel control."
|
|
24912
|
+
}
|
|
24913
|
+
},
|
|
24914
|
+
"new_control_requirements": [
|
|
24915
|
+
{
|
|
24916
|
+
"id": "NEW-CTRL-114",
|
|
24917
|
+
"name": "MAIN-MODULE-PAYLOAD-DETECTION",
|
|
24918
|
+
"description": "Supply-chain controls must not assume --ignore-scripts mitigates malicious dependencies. A payload shipped in the package main module (executing on require/import, not via a lifecycle hook) bypasses --ignore-scripts entirely. Detect via process-level behavioral telemetry: geolocation/IP lookups, broad recursive filesystem writes, or credential-path reads occurring at module-load time within a dependency. Pair with strict-lockfile installs (npm ci) and a publish-to-consume review window on high-reach single-maintainer packages.",
|
|
24919
|
+
"evidence": "CVE-2022-23812 — node-ipc 10.1.1/10.1.2 shipped a geo-targeted file-wiper in the MAIN MODULE; --ignore-scripts (the canonical npm-supply-chain mitigation since Shai-Hulud) did not mitigate because the payload was not in a postinstall hook.",
|
|
24920
|
+
"gap_closes": [
|
|
24921
|
+
"NIST-800-218-SSDF",
|
|
24922
|
+
"NIS2-Art21-supply-chain",
|
|
24923
|
+
"EU-CRA-Art13",
|
|
24924
|
+
"ANY-FRAMEWORK"
|
|
24925
|
+
]
|
|
24926
|
+
}
|
|
24927
|
+
],
|
|
24928
|
+
"compliance_exposure_score": {
|
|
24929
|
+
"percent_audit_passing_orgs_still_exposed": 90,
|
|
24930
|
+
"basis": "Most npm-hygiene programs lean on --ignore-scripts and SBOM, neither of which addresses a main-module payload from a trusted maintainer. Lockfile pinning helps only with npm ci and a pre-2022-03-07 lockfile.",
|
|
24931
|
+
"theater_pattern": "ignore_scripts_assumed_sufficient_for_all_supply_chain_payloads"
|
|
24932
|
+
},
|
|
24933
|
+
"ai_discovered_zeroday": false,
|
|
24934
|
+
"ai_discovery_source": "human_researcher",
|
|
24935
|
+
"ai_discovery_date": "2022-03-15",
|
|
24936
|
+
"ai_assist_factor": "low"
|
|
24937
|
+
},
|
|
24938
|
+
"MAL-2026-TRAPDOOR-CROSS-ECOSYSTEM": {
|
|
24939
|
+
"name": "TrapDoor cross-ecosystem crypto-stealer + AI-assistant poisoning campaign (npm/PyPI/crates.io)",
|
|
24940
|
+
"lesson_date": "2026-05-30",
|
|
24941
|
+
"attack_vector": {
|
|
24942
|
+
"description": "Cross-ecosystem (npm/PyPI/crates.io) coordinated malicious-package campaign (\"TrapDoor\", anchor OSV MAL-2026-4207 / GHSA-7r6r-hqg7-f6mq) using lure packages themed as security/crypto/AI tooling. npm: postinstall executes trap-core.js. PyPI: import-time node -e pulls JS from attacker GitHub Pages. crates.io: build.rs runs at compile time. All variants harvest cloud/SSH/GitHub credentials + crypto-wallet data and exfiltrate via a dynamic GitHub-Pages C2. The genuinely novel vector: the payload plants .cursorrules and CLAUDE.md files containing zero-width-Unicode-hidden instructions designed to trick AI coding assistants (Cursor, Claude Code) into running a fake \"security scan\" that self-exfiltrates local secrets — AI-assistant prompt-injection as a supply-chain weaponization channel.",
|
|
24943
|
+
"privileges_required": "developer/CI runs install (npm postinstall), import (PyPI), or compile (crates build.rs); AI-assistant poisoning additionally fires when a developer opens the repo in an LLM coding assistant",
|
|
24944
|
+
"complexity": "low — consumer-side execution is automatic on install/import/compile; --ignore-scripts mitigates only the npm postinstall path, not PyPI import-time or crates build-time triggers",
|
|
24945
|
+
"ai_factor": "AI-ASSISTED WEAPONIZATION (not AI-discovered). The .cursorrules/CLAUDE.md zero-width-Unicode prompt-injection that subverts an AI coding assistant into exfiltrating secrets is the differentiator — maps ATLAS AML.T0051 (LLM prompt injection). Ties directly to the Trojan-Source codepoint-threat surface (CVE-2021-42574) the catalog already tracks."
|
|
24946
|
+
},
|
|
24947
|
+
"defense_chain": {
|
|
24948
|
+
"prevention": {
|
|
24949
|
+
"what_would_have_worked": "Scope-pinning + provenance on dependencies; --ignore-scripts (npm path only); scanning .cursorrules/CLAUDE.md and other AI-assistant config files for zero-width Unicode before an assistant reads them; treating AI-assistant config as untrusted input.",
|
|
24950
|
+
"was_this_required": false,
|
|
24951
|
+
"framework_requiring_it": null,
|
|
24952
|
+
"adequacy": "No framework treats AI-coding-assistant config files as a prompt-injection attack surface. --ignore-scripts is partial (misses import-time + compile-time triggers)."
|
|
24953
|
+
},
|
|
24954
|
+
"detection": {
|
|
24955
|
+
"what_would_have_worked": "Install/import/compile-time egress monitoring (the GitHub-Pages C2 + Gist exfil); a zero-width-Unicode scan over repo config files (.cursorrules, CLAUDE.md) — the same codepoint-class the catalog flags for Trojan-Source; behavioral detection of a credential scan over ~/.aws, ~/.ssh, browser wallet stores following a package resolution.",
|
|
24956
|
+
"was_this_required": false,
|
|
24957
|
+
"framework_requiring_it": null,
|
|
24958
|
+
"adequacy": "PyPI variant pulls live JS at import time, so the on-disk package may not contain the final payload — network/DNS capture to the C2 is required to prove exfil. Zero-width Unicode in repo config is invisible in normal review."
|
|
24959
|
+
},
|
|
24960
|
+
"response": {
|
|
24961
|
+
"what_would_have_worked": "Remove the named packages; rotate ALL credentials reachable from any host that installed/imported/compiled them; audit .cursorrules/CLAUDE.md for hidden instructions; block egress to the C2.",
|
|
24962
|
+
"was_this_required": false,
|
|
24963
|
+
"framework_requiring_it": null,
|
|
24964
|
+
"adequacy": "Registry takedown was partial (some versions live at reporting time); crates.io ids not yet in OSV. Rotation is the only reliable response once secrets are harvested."
|
|
24965
|
+
}
|
|
24966
|
+
},
|
|
24967
|
+
"framework_coverage": {
|
|
24968
|
+
"EU-AI-Act-Art15": {
|
|
24969
|
+
"covered": true,
|
|
24970
|
+
"adequate": false,
|
|
24971
|
+
"gap": "AI robustness/accuracy controls do not address adversarial prompt-injection planted in repo config files that subvert a developer AI assistant."
|
|
24972
|
+
},
|
|
24973
|
+
"NIS2-Art21-supply-chain": {
|
|
24974
|
+
"covered": true,
|
|
24975
|
+
"adequate": false,
|
|
24976
|
+
"gap": "No coverage of cross-ecosystem coordinated campaigns or AI-assistant config-file poisoning as a supply-chain vector."
|
|
24977
|
+
},
|
|
24978
|
+
"DORA-ICT-third-party": {
|
|
24979
|
+
"covered": true,
|
|
24980
|
+
"adequate": false,
|
|
24981
|
+
"gap": "ICT third-party risk assumes vendor relationships, not transitive OSS lure-packages auto-executing on import/build."
|
|
24982
|
+
},
|
|
24983
|
+
"UK-CAF-B4-supply-chain": {
|
|
24984
|
+
"covered": true,
|
|
24985
|
+
"adequate": false,
|
|
24986
|
+
"gap": "Does not cover build-time (build.rs) or import-time auto-execution distinct from install-time hooks."
|
|
24987
|
+
},
|
|
24988
|
+
"ANY-FRAMEWORK": {
|
|
24989
|
+
"covered": false,
|
|
24990
|
+
"adequate": false,
|
|
24991
|
+
"gap": "No framework treats AI-coding-assistant config-file prompt-injection as a supply-chain exfiltration vector. NEW-CTRL-115/116 are the headline novel controls."
|
|
24992
|
+
}
|
|
24993
|
+
},
|
|
24994
|
+
"new_control_requirements": [
|
|
24995
|
+
{
|
|
24996
|
+
"id": "NEW-CTRL-115",
|
|
24997
|
+
"name": "AI-ASSISTANT-CONFIG-POISONING-DETECTION",
|
|
24998
|
+
"description": "Scan AI-coding-assistant configuration files (.cursorrules, CLAUDE.md, .github/copilot-instructions.md, and equivalents) for zero-width / bidirectional / invisible Unicode codepoints and for instructions that direct the assistant to read credential paths, run \"security scans\", or perform network egress. Treat AI-assistant config as untrusted input on dependency intake; render it through a codepoint-normalizer (the Trojan-Source class, CVE-2021-42574) before any assistant consumes it.",
|
|
24999
|
+
"evidence": "MAL-2026-TRAPDOOR-CROSS-ECOSYSTEM — the payload planted .cursorrules and CLAUDE.md with zero-width-Unicode-hidden instructions to trick Cursor/Claude Code into discovering and exfiltrating local secrets.",
|
|
25000
|
+
"gap_closes": [
|
|
25001
|
+
"EU-AI-Act-Art15",
|
|
25002
|
+
"NIS2-Art21-supply-chain",
|
|
25003
|
+
"ANY-FRAMEWORK"
|
|
25004
|
+
]
|
|
25005
|
+
},
|
|
25006
|
+
{
|
|
25007
|
+
"id": "NEW-CTRL-116",
|
|
25008
|
+
"name": "MULTI-TRIGGER-DEPENDENCY-EXECUTION-POLICY",
|
|
25009
|
+
"description": "Dependency-execution policy must cover install-time (npm postinstall), import-time (PyPI module load / node -e), AND compile-time (crates build.rs) execution — not install hooks alone. --ignore-scripts addresses only the npm postinstall path. Enforce egress allowlists during install/import/compile in CI and monitor for second-stage fetches from dynamic config hosts.",
|
|
25010
|
+
"evidence": "MAL-2026-TRAPDOOR-CROSS-ECOSYSTEM — npm postinstall, PyPI import-time node -e, and crates.io build.rs were three distinct execution triggers; --ignore-scripts mitigated only the first.",
|
|
25011
|
+
"gap_closes": [
|
|
25012
|
+
"NIS2-Art21-supply-chain",
|
|
25013
|
+
"UK-CAF-B4-supply-chain",
|
|
25014
|
+
"DORA-ICT-third-party"
|
|
25015
|
+
]
|
|
25016
|
+
}
|
|
25017
|
+
],
|
|
25018
|
+
"compliance_exposure_score": {
|
|
25019
|
+
"percent_audit_passing_orgs_still_exposed": 96,
|
|
25020
|
+
"basis": "AI-coding-assistant config files are not in scope for any current supply-chain or AI-governance control; near-total exposure. Multi-trigger execution (import/compile) is uncovered by the install-hook-centric --ignore-scripts mitigation most programs rely on.",
|
|
25021
|
+
"theater_pattern": "ai_assistant_config_treated_as_trusted"
|
|
25022
|
+
},
|
|
25023
|
+
"ai_discovered_zeroday": false,
|
|
25024
|
+
"ai_discovery_source": "vendor_research",
|
|
25025
|
+
"ai_discovery_date": "2026-05-25",
|
|
25026
|
+
"ai_assist_factor": "high"
|
|
25027
|
+
},
|
|
25028
|
+
"MAL-2026-MOIKA-DEPCONFUSION": {
|
|
25029
|
+
"name": "oob.moika.tech dependency-confusion credential-exfiltration campaign (internal-scope namespace squat)",
|
|
25030
|
+
"lesson_date": "2026-05-30",
|
|
25031
|
+
"attack_vector": {
|
|
25032
|
+
"description": "Dependency-confusion / internal-scope namespace-squat campaign (C2 oob.moika.tech, OSV MAL-2026-4952/4978/5032). The attacker published public npm packages under organizational scopes that mirror targets internal namespaces (@cloudplatform-single-spa, @mlspace, @sber-ecom-core, etc.) at inflated semver (99.99.99 etc.) so default npm resolution prefers the public package when no scope->registry pin exists. A 7-13 KB obfuscated postinstall stager fetches a second stage from oob.moika.tech/payload (header X-Secret: l95HdDaz3kQx1Zsg3WxH6HvKANf51RY1) and POSTs the full process.env (NPM_TOKEN, AWS keys, GITHUB_TOKEN, DB URLs) + host fingerprint to oob.moika.tech/report. Microsoft documents a server-side toggle gating second-stage escalation, but the /report env-exfil is live. Class: dependency-confusion with postinstall second-stage staging — the catalog had zero dependency-confusion entries before this.",
|
|
25033
|
+
"privileges_required": "developer/CI runs npm install against a project referencing a squatted internal scope with no scope->registry pin; postinstall fires automatically",
|
|
25034
|
+
"complexity": "low — automatic on install; --ignore-scripts DOES mitigate (postinstall-gated, unlike the node-ipc main-module class). Attacker-side precondition (knowing internal scope names) is low: scope names leak via public source, CI logs, and job postings.",
|
|
25035
|
+
"ai_factor": "Not AI-discovered, not AI-built. Targets ML-tooling scopes (@mlspace, @data-science) among others, but the payload is conventional obfuscated JavaScript."
|
|
25036
|
+
},
|
|
25037
|
+
"defense_chain": {
|
|
25038
|
+
"prevention": {
|
|
25039
|
+
"what_would_have_worked": "Scope->registry pinning in .npmrc (@scope:registry=) for every internal scope — the structural fix for the entire dependency-confusion class; this prevents public-registry resolution regardless of version inflation. --ignore-scripts blocks the postinstall stager specifically.",
|
|
25040
|
+
"was_this_required": false,
|
|
25041
|
+
"framework_requiring_it": null,
|
|
25042
|
+
"adequacy": "Scope-pinning fully prevents the class but must be configured BEFORE exposure. No framework mandates it; default npm resolution silently prefers the higher public version."
|
|
25043
|
+
},
|
|
25044
|
+
"detection": {
|
|
25045
|
+
"what_would_have_worked": "Install-time egress monitoring (the oob.moika.tech C2); detection of a raw process.env serialization immediately followed by an outbound POST (the credential-harvest fingerprint); lockfile audit for any internal scope resolved to a PUBLIC-registry tarball at an inflated version (99.99.99 / 5.7.1 / 99.5.x).",
|
|
25046
|
+
"was_this_required": false,
|
|
25047
|
+
"framework_requiring_it": null,
|
|
25048
|
+
"adequacy": "A postinstall POST to /report proves exfil; postinstall execution alone does not. Most CI runners do not monitor install-time egress."
|
|
25049
|
+
},
|
|
25050
|
+
"response": {
|
|
25051
|
+
"what_would_have_worked": "Pin internal scopes to the private registry in project + CI .npmrc; rotate every credential exposed as an env var on any host that ran an affected install during the exposure windows; block egress to oob.moika.tech and the t-in-one lure domains.",
|
|
25052
|
+
"was_this_required": false,
|
|
25053
|
+
"framework_requiring_it": null,
|
|
25054
|
+
"adequacy": "Per-package yank is reactive — any attacker can re-publish an inflated version under an unpinned internal scope. Scope-pinning is the only durable fix."
|
|
25055
|
+
}
|
|
25056
|
+
},
|
|
25057
|
+
"framework_coverage": {
|
|
25058
|
+
"NIST-800-218-SSDF": {
|
|
25059
|
+
"covered": true,
|
|
25060
|
+
"adequate": false,
|
|
25061
|
+
"gap": "Component-provenance controls assume the consumed package is the intended one; SSDF does not mandate scope->registry pinning, so a confused resolution is SSDF-conformant."
|
|
25062
|
+
},
|
|
25063
|
+
"EU-CRA-Art13": {
|
|
25064
|
+
"covered": true,
|
|
25065
|
+
"adequate": false,
|
|
25066
|
+
"gap": "SBOM records what resolved, not whether resolution should have gone to a private registry; a confused public package is SBOM-listed and CRA-compliant."
|
|
25067
|
+
},
|
|
25068
|
+
"NIS2-Art21-supply-chain": {
|
|
25069
|
+
"covered": true,
|
|
25070
|
+
"adequate": false,
|
|
25071
|
+
"gap": "No requirement for scope-registry pinning, postinstall-hook policy, or internal-namespace-squat monitoring on public registries."
|
|
25072
|
+
},
|
|
25073
|
+
"SLSA-v1.0-Provenance": {
|
|
25074
|
+
"covered": true,
|
|
25075
|
+
"adequate": false,
|
|
25076
|
+
"gap": "Provenance attests who built an artifact; it cannot assert the resolver selected the artifact the org intended — orthogonal to dependency confusion."
|
|
25077
|
+
},
|
|
25078
|
+
"ANY-FRAMEWORK": {
|
|
25079
|
+
"covered": false,
|
|
25080
|
+
"adequate": false,
|
|
25081
|
+
"gap": "No major framework mandates internal-scope->registry pinning as a dependency-confusion control. NEW-CTRL-117 is the headline novel control."
|
|
25082
|
+
}
|
|
25083
|
+
},
|
|
25084
|
+
"new_control_requirements": [
|
|
25085
|
+
{
|
|
25086
|
+
"id": "NEW-CTRL-117",
|
|
25087
|
+
"name": "PACKAGE-INTERNAL-SCOPE-REGISTRY-PINNING",
|
|
25088
|
+
"description": "Every internal/private package scope must be pinned to its private registry in project and CI .npmrc (@scope:registry=https://private-registry/), so public-registry resolution can never win regardless of version inflation. Continuously monitor public registries for newly published packages under the organization internal scope names (namespace-squat detection). Lockfile-audit for any internal scope resolved to a public-registry tarball. This is the structural fix for the dependency-confusion class, which no current framework prescribes.",
|
|
25089
|
+
"evidence": "MAL-2026-MOIKA-DEPCONFUSION — ~164-179 public npm packages were published under squatted internal scopes at inflated versions (99.99.99 etc.) so default resolution preferred them; a postinstall stager exfiltrated full process.env. Scope->registry pinning would have prevented every install.",
|
|
25090
|
+
"gap_closes": [
|
|
25091
|
+
"NIST-800-218-SSDF",
|
|
25092
|
+
"NIS2-Art21-supply-chain",
|
|
25093
|
+
"EU-CRA-Art13",
|
|
25094
|
+
"ANY-FRAMEWORK"
|
|
25095
|
+
]
|
|
25096
|
+
}
|
|
25097
|
+
],
|
|
25098
|
+
"compliance_exposure_score": {
|
|
25099
|
+
"percent_audit_passing_orgs_still_exposed": 88,
|
|
25100
|
+
"basis": "Scope->registry pinning is a non-default npm configuration that no framework mandates; most organizations using internal scopes have not pinned them, leaving the public registry able to win resolution on an inflated version.",
|
|
25101
|
+
"theater_pattern": "default_registry_resolution_assumed_safe"
|
|
25102
|
+
},
|
|
25103
|
+
"ai_discovered_zeroday": false,
|
|
25104
|
+
"ai_discovery_source": "vendor_research",
|
|
25105
|
+
"ai_discovery_date": "2026-05-28",
|
|
25106
|
+
"ai_assist_factor": "low"
|
|
24857
25107
|
}
|
|
24858
25108
|
}
|
package/lib/prefetch.js
CHANGED
|
@@ -696,8 +696,8 @@ async function main() {
|
|
|
696
696
|
// contractually correct but visibly noisy. Letting the event loop
|
|
697
697
|
// drain naturally — via exitCode + return — lets undici's connection
|
|
698
698
|
// pool and the AbortController signal listeners finish teardown
|
|
699
|
-
// before the process exits, eliminating the assertion. Same pattern
|
|
700
|
-
//
|
|
699
|
+
// before the process exits, eliminating the assertion. Same pattern as
|
|
700
|
+
// the `ci` #100 stdout-flush regression.
|
|
701
701
|
try {
|
|
702
702
|
const result = await prefetch(opts);
|
|
703
703
|
process.exitCode = result.errors > 0 ? 1 : 0;
|