@blamejs/exceptd-skills 0.15.51 → 0.15.53

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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.15.53 — 2026-05-30
4
+
5
+ Catalog: package-class entries can now carry an optional package-confidence signal — a 0–100 supply-chain trustworthiness score with inverse polarity to RWEP (high = trustworthy provenance, low = behaves like malware), derived from maintainer, code-quality, behavioral, and provenance sub-signals. It is surfaced alongside RWEP and never feeds or replaces it: RWEP answers "how urgently must I act on this known vulnerability," while package-confidence answers "how much do I trust this package's provenance." Populated on the supply-chain malware entries (the MOIKA dependency-confusion campaign, TrapDoor, Shai-Hulud, the node-ipc credential-stealer, and the node-ipc protestware incident).
6
+
7
+ The Shai-Hulud worm entry now also covers the PyTorch Lightning PyPI compromise (CVE-2026-44484 / `lightning` 2.6.2–2.6.3) as the PyPI side of the same Mini Shai-Hulud wave, with its Bun-runtime infostealer indicators and disclosure sources.
8
+
9
+ ## 0.15.52 — 2026-05-30
10
+
11
+ Supply-chain playbook: three new detection-depth checks. Typosquat / homoglyph detection flags a direct dependency whose name impersonates a popular package by edit-distance or a visually-confusable Unicode substitution (reusing the Trojan-Source codepoint tables) — a lure signal that precedes any payload. A static content red-flag screen flags packages that ship only minified/obfuscated source, carry a high ratio of high-entropy strings, are a trivial shell that nonetheless runs an install script or opens the network, or combine dynamic `eval` with dynamic `require` (CWE/ATT&CK T1027) — orthogonal to the capability screens. A dependency-confusion resolution check flags an internal-looking package name served from the public registry, or an inflated-version public squat that wins resolution over the private package — the resolution-source signature that precedes execution, correlated to the MOIKA campaign. Each ships with a paired evidence artifact and false-positive profile.
12
+
3
13
  ## 0.15.51 — 2026-05-30
4
14
 
5
15
  Catalog: three new supply-chain entries. CVE-2022-23812 — the node-ipc "peacenotwar" protestware incident, where a trusted maintainer shipped a geo-targeted file-wiper in the package main module, so `--ignore-scripts` (the usual npm-supply-chain mitigation) does not stop it. TrapDoor — a cross-ecosystem (npm / PyPI / crates.io) credential-stealer campaign whose novel vector plants zero-width-Unicode instructions in `.cursorrules` / `CLAUDE.md` files to subvert AI coding assistants into discovering and exfiltrating local secrets. MOIKA — the catalog's first dependency-confusion entry: public packages published under squatted internal scopes at inflated versions, with a postinstall stager that exfiltrates the full process environment. Each carries its paired zero-day lesson and new framework-lag controls (main-module-payload detection, AI-assistant config-file poisoning detection, internal-scope→registry pinning).
package/NOTICE CHANGED
@@ -45,7 +45,7 @@ URL: https://atlas.mitre.org
45
45
  Version: v5.1.0 (November 2025)
46
46
  Used for: Adversarial Threat Landscape for AI Systems — TTP IDs cited in
47
47
  skills/*, data/atlas-ttps.json, and manifest.json. Pinned per
48
- CLAUDE.md hard rule #12.
48
+ AGENTS.md Hard Rule #12 (external data version pinning).
49
49
  Notice: ATLAS is © The MITRE Corporation, released under the terms at
50
50
  https://atlas.mitre.org/resources/terms-of-use.
51
51
  --------------------------------------------------------------------------------
package/bin/exceptd.js CHANGED
@@ -3031,7 +3031,7 @@ function cmdPlan(runner, args, runOpts, pretty) {
3031
3031
  // in `run --scope nonsense` produced `count: 0` + exit 0 (cmd reports
3032
3032
  // "ran 0 playbooks") and in `ci --scope nonsense` silently ran only the
3033
3033
  // cross-cutting set (the union with `framework` produced a false-positive
3034
- // PASS). Both are operator-intent loss patterns CLAUDE.md flags as the
3034
+ // PASS). Both are operator-intent loss patterns of the
3035
3035
  // "field-present, content-wrong" class.
3036
3036
  const VALID_SCOPES = ["system", "code", "service", "cross-cutting", "all"];
3037
3037
 
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "schema_version": "1.1.0",
3
- "generated_at": "2026-05-31T03:23:30.384Z",
3
+ "generated_at": "2026-05-31T05:03:02.966Z",
4
4
  "generator": "scripts/build-indexes.js",
5
5
  "source_count": 54,
6
6
  "source_hashes": {
7
- "manifest.json": "26f77d56ab70b31946f08cd83c8cd5fc43e807c77955a0e9d4ae9450d32e29c9",
7
+ "manifest.json": "4359446f2ab6bfb0484b716ca15b81755ca4075d941b0c8caa5d2d4ad1c4f0a7",
8
8
  "data/atlas-ttps.json": "878b4a08bb73c8d20396d85cf433a88f2bc5e7a8cbf7f6ab773ce7ede0a11251",
9
9
  "data/attack-techniques.json": "318bf8e9c5aee1d0a4a1dc37c4b211f2fbc937bf332a401a22483cc7d0547252",
10
- "data/cve-catalog.json": "cb5e305b5488a2a02e177f10e913d22f602d6016109f152903093e9614e0b470",
10
+ "data/cve-catalog.json": "1aac7e75eae24ece2ef09d1c63977bdd7a1f81a9f11609ebb966109be316426c",
11
11
  "data/cwe-catalog.json": "b0e4d8f90b655b2b35b1e91c682ee66f2aa51ae5d38efb14f0e1b77f75ec5f7b",
12
12
  "data/d3fend-catalog.json": "9a54bccb9f24f84b32024216cc3f53819a053721ac8ab43c326859e68fc0ffaf",
13
13
  "data/dlp-controls.json": "d2406c482dddd30e49203879999dc4b3a7fd4d0494d6a61d86b91ee76415df19",
@@ -200,7 +200,20 @@
200
200
  "remediation_status": "patched_upstream",
201
201
  "remediation_note": "node-ipc 10.1.3 removed the destructive payload (2022-03-08). The 11.0.0+ peacenotwar protestware remained per NVD scoping. Novel defended class for this catalog: trusted-maintainer protestware / sabotage of a high-reach dependency (the catalog previously had account-takeover and typosquat supply-chain classes, not insider-maintainer sabotage).",
202
202
  "remediation_status_verified_at": "2026-05-30",
203
- "_editorial_note": "CVE-2022-23812 intake: first trusted-maintainer-protestware/sabotage entry in the catalog. Distinct from MAL-2026-NODE-IPC-STEALER (account-recovery takeover, credential-stealer): this is the original maintainer authoring a geo-targeted destructive payload. Paired zeroday-lesson generates NEW-CTRL-114 (main-module-payload detection — the --ignore-scripts blind spot)."
203
+ "_editorial_note": "CVE-2022-23812 intake: first trusted-maintainer-protestware/sabotage entry in the catalog. Distinct from MAL-2026-NODE-IPC-STEALER (account-recovery takeover, credential-stealer): this is the original maintainer authoring a geo-targeted destructive payload. Paired zeroday-lesson generates NEW-CTRL-114 (main-module-payload detection — the --ignore-scripts blind spot).",
204
+ "package_confidence": {
205
+ "score": 31,
206
+ "polarity": "trust",
207
+ "inputs": {
208
+ "maintainer": 25,
209
+ "quality": 60,
210
+ "behavioral": 10,
211
+ "provenance": 30
212
+ },
213
+ "rationale": "node-ipc was a genuine, high-quality, high-reach package (quality high) but the TRUSTED maintainer authored a geo-targeted destructive payload (behavioral collapse); maintainer-intent is the risk, not a takeover. Provenance existed pre-incident.",
214
+ "method": "supplementary-supply-chain-trust (equal-weight mean of present sub-signals; never feeds RWEP)",
215
+ "as_of": "2026-05-30"
216
+ }
204
217
  },
205
218
  "MAL-2026-TRAPDOOR-CROSS-ECOSYSTEM": {
206
219
  "name": "TrapDoor cross-ecosystem crypto-stealer + AI-assistant poisoning campaign (npm/PyPI/crates.io)",
@@ -364,7 +377,20 @@
364
377
  "npm: 19 lure packages across the campaign (10 in the GHSA-7r6r-hqg7-f6mq anchor cluster, by maintainer ddjidd5640, published 2026-05-19..21) — all published versions of the named packages are malicious (e.g. eth-wallet-sentinel@2.1.2, SHA-256 in OSV MAL-2026-4207)",
365
378
  "PyPI: cryptowallet-safety / defi-risk-scanner / eth-security-auditor (earliest eth-security-auditor@0.1.0, 2026-05-22) — all published versions malicious (OSV MAL-2026-4259/4260/4261)",
366
379
  "crates.io: move-* / sui-* build-helper packages — all published versions malicious; no OSV MAL-* assigned as of 2026-05-30 (UNVERIFIED pending OSV coverage)"
367
- ]
380
+ ],
381
+ "package_confidence": {
382
+ "score": 6,
383
+ "polarity": "trust",
384
+ "inputs": {
385
+ "maintainer": 5,
386
+ "quality": 15,
387
+ "behavioral": 3,
388
+ "provenance": 0
389
+ },
390
+ "rationale": "Cross-ecosystem lure packages (security/crypto/AI-tooling themes) from a single actor cluster; install/import/build-time credential stealer + AI-assistant config poisoning; no provenance.",
391
+ "method": "supplementary-supply-chain-trust (equal-weight mean of present sub-signals; never feeds RWEP)",
392
+ "as_of": "2026-05-30"
393
+ }
368
394
  },
369
395
  "MAL-2026-MOIKA-DEPCONFUSION": {
370
396
  "name": "oob.moika.tech dependency-confusion credential-exfiltration campaign (internal-scope namespace squat)",
@@ -538,7 +564,20 @@
538
564
  "_editorial_note": "MOIKA (oob.moika.tech) intake: NOVEL attack class for this catalog — first dependency-confusion / internal-scope-squat entry (catalog previously had zero). Distinct from MAL-2026-NODE-IPC-STEALER (account-recovery, main-module payload, DNS-TXT exfil): MOIKA is namespace-confusion, postinstall stager, HTTPS-POST exfil, --ignore-scripts-mitigable. Likely warrants a new zeroday-lessons control (PACKAGE-INTERNAL-SCOPE-REGISTRY-PINNING). RWEP 43 (exemplar convention: live_patch_available credited -10).js (would be 43 under the exemplar's -10 convention).",
539
565
  "remediation_status": "partially_removed_from_registry",
540
566
  "remediation_note": "Malicious packages were reported and are being removed from npm; OSV/GHSA records published 2026-05-28/29. Removal is per-package and reactive — the structural risk (any attacker can re-publish an inflated version under an unpinned internal scope) persists until consuming orgs pin internal scopes to their private registry. UNVERIFIED: whether all ~179 packages were removed and whether all four publisher accounts were deactivated as of 2026-05-30 (no source confirms complete takedown).",
541
- "remediation_status_verified_at": "2026-05-30"
567
+ "remediation_status_verified_at": "2026-05-30",
568
+ "package_confidence": {
569
+ "score": 5,
570
+ "polarity": "trust",
571
+ "inputs": {
572
+ "maintainer": 5,
573
+ "quality": 10,
574
+ "behavioral": 5,
575
+ "provenance": 0
576
+ },
577
+ "rationale": "Dependency-confusion squat: fresh attacker accounts (mr.4nd3r50n / t-in-one), inflated 99.99.99 semver, obfuscated postinstall stager exfiltrating process.env, no repo link, no provenance attestation.",
578
+ "method": "supplementary-supply-chain-trust (equal-weight mean of present sub-signals; never feeds RWEP)",
579
+ "as_of": "2026-05-30"
580
+ }
542
581
  },
543
582
  "CVE-2025-0282": {
544
583
  "ai_assisted_weaponization": false,
@@ -4880,7 +4919,20 @@
4880
4919
  "_editorial_note": "Cycle 13 intake (v0.12.33): cycle 13 agent C surfaced node-ipc 2026-05-14 publish event in the 24h-window check. Novel attack precondition (expired-domain re-registration + npm password-reset abuse) makes this a distinct supply-chain class from the Shai-Hulud (token-compromise) and elementary-data (typosquat + orphan-commit) precedents; warrants its own NEW-CTRL-047 in zeroday-lessons.json. RWEP factors satisfy Shape B invariant (0 + 20 + 0 + 20 + 28 - 15 - 10 + 0 = 43); discovery_attribution_note cites multiple firms with URLs.",
4881
4920
  "remediation_status": "removed_from_registry",
4882
4921
  "remediation_note": "npm removed all 3 malicious versions (9.1.6, 9.2.3, 12.0.1) within ~2 hours of publication on 2026-05-14. Publisher account atiertant was deactivated. The expired-domain TTP (atlantis-software.net re-registered via Namecheap on 2026-05-07 after Jan 2025 expiry) remains the novel attack class to defend against — see zeroday-lessons NEW-CTRL-047 (PACKAGE-MAINTAINER-DOMAIN-EXPIRY-MONITORING).",
4883
- "remediation_status_verified_at": "2026-05-16"
4922
+ "remediation_status_verified_at": "2026-05-16",
4923
+ "package_confidence": {
4924
+ "score": 11,
4925
+ "polarity": "trust",
4926
+ "inputs": {
4927
+ "maintainer": 8,
4928
+ "quality": 30,
4929
+ "behavioral": 5,
4930
+ "provenance": 0
4931
+ },
4932
+ "rationale": "Account-takeover (expired-domain account-recovery) of a real 3.35M-download package: the package WAS legitimate (quality moderate) but the publish account was attacker-controlled, shipping a main-module credential stealer; provenance absent post-takeover.",
4933
+ "method": "supplementary-supply-chain-trust (equal-weight mean of present sub-signals; never feeds RWEP)",
4934
+ "as_of": "2026-05-30"
4935
+ }
4884
4936
  },
4885
4937
  "CVE-2026-46333": {
4886
4938
  "name": "ssh-keysign-pwn",
@@ -4990,7 +5042,8 @@
4990
5042
  "active_exploitation_notes": "Copycat modifications observed by Ox Security within hours of the 2026-05-12 release. Mini Shai-Hulud wave (Microsoft Security Research, 2026-05-11) compromised 170+ npm packages + 2 PyPI packages across 404 malicious versions. MAL-2026-TANSTACK-MINI in this catalog is an in-the-wild Shai-Hulud-class incident. Continuous active exploitation expected through 2026.",
4991
5043
  "affected": "npm registry (170+ confirmed packages in May 2026 wave), PyPI (2 confirmed), GitHub Actions runners, developer workstations with credentials staged in ~/.aws, ~/.config/gcloud, ~/.kube, ~/.ssh, ~/.cursor, ~/.codeium, ~/.claude, ~/.npmrc. Any package-registry account whose maintainer workstation runs the framework. Any AI-assistant config file with API tokens or MCP server credentials.",
4992
5044
  "affected_versions": [
4993
- "shai-hulud-framework all forks post-2026-05-12"
5045
+ "shai-hulud-framework all forks post-2026-05-12",
5046
+ "PyPI lightning / pytorch-lightning 2.6.2 and 2.6.3 (Mini Shai-Hulud PyPI wave; last clean 2.6.1, 2026-01-30; CVE-2026-44484 / GHSA-w37p-236h-pfx3 / OSV MAL-2026-3201, published 2026-04-30)"
4994
5047
  ],
4995
5048
  "vector": "Self-replicating npm worm with maintainer-account-pivot. Phase 1: credential harvest via package post-install OR require-time activation (variant-dependent) reads cloud + AI-assistant + version-control configs from operator HOME. Phase 2: stolen npm token authenticates to registry as compromised maintainer; enumerates other packages owned by same maintainer; injects malware; publishes new compromised versions. Phase 3: encrypted exfil to attacker-controlled GitHub repos matching the \"A Gift From TeamPCP\" naming pattern + secondary C2 channels. Phase 4 (variant-dependent): local-environment wipe — destructive opt-in by attacker.",
4996
5049
  "complexity": "turnkey post-source-release",
@@ -5050,7 +5103,12 @@
5050
5103
  "https://www.microsoft.com/en-us/security/blog/2025/12/09/shai-hulud-2-0-guidance-for-detecting-investigating-and-defending-against-the-supply-chain-attack/",
5051
5104
  "https://www.cisa.gov/news-events/alerts/2025/09/23/widespread-supply-chain-compromise-impacting-npm-ecosystem",
5052
5105
  "https://www.wiz.io/blog/shai-hulud-2-0-ongoing-supply-chain-attack",
5053
- "https://snyk.io/blog/tanstack-npm-packages-compromised/"
5106
+ "https://snyk.io/blog/tanstack-npm-packages-compromised/",
5107
+ "https://osv.dev/vulnerability/MAL-2026-3201",
5108
+ "https://github.com/Lightning-AI/pytorch-lightning/security/advisories/GHSA-w37p-236h-pfx3",
5109
+ "https://www.stepsecurity.io/blog/lightning-obfuscated-javascript-credential-stealer-bundled-in-pypi-wheel",
5110
+ "https://www.aikido.dev/blog/pytorch-lightning-pypi-compromise-mini-shai-hulud",
5111
+ "https://socket.dev/blog/lightning-pypi-package-compromised"
5054
5112
  ],
5055
5113
  "last_updated": "2026-05-17",
5056
5114
  "discovery_attribution_note": "TeamPCP threat-actor framework, not a vulnerability discovery. The framework was open-sourced 2026-05-12 on GitHub under MIT license by the same actor group responsible for the September 2025 / November 2025 / May 2026 Shai-Hulud npm-worm waves. TeamPCP self-describes the framework as \"vibe coded\" — AI-coding-assistant-mediated authoring. Adoption-side weaponization is accelerated by AI coding assistants + the BreachForums-hosted $1,000 USD bounty contest.",
@@ -5061,8 +5119,33 @@
5061
5119
  "Self-republication attempts from a package-install context and creation of attacker-controlled public repositories for exfiltration (worm propagation via maintainer-account pivot).",
5062
5120
  "Outbound exfiltration of harvested secrets from a CI runner or developer host shortly after an affected dependency install."
5063
5121
  ],
5064
- "_ioc_source_note": "Anchored to NVD/advisory references and the public Shai-Hulud worm analyses cited in verification_sources."
5065
- }
5122
+ "_ioc_source_note": "Anchored to NVD/advisory references and the public Shai-Hulud worm analyses cited in verification_sources.",
5123
+ "pypi_lightning_subincident": [
5124
+ "PyPI lightning 2.6.2/2.6.3 wheel ships _runtime/start.py + _runtime/router_runtime.js (~11 MB obfuscated JS infostealer)",
5125
+ "Import-triggered payload downloads the Bun JavaScript runtime v1.3.13 and executes router_runtime.js (credential harvest of CI/CD, SSH keyrings, crypto wallets, PyPI publish tokens)",
5126
+ "Cross-ecosystem spread: injects into npm package tarballs on the developer machine for downstream npm propagation"
5127
+ ]
5128
+ },
5129
+ "package_confidence": {
5130
+ "score": 3,
5131
+ "polarity": "trust",
5132
+ "inputs": {
5133
+ "maintainer": 3,
5134
+ "quality": 5,
5135
+ "behavioral": 2,
5136
+ "provenance": 0
5137
+ },
5138
+ "rationale": "Self-replicating worm framework (TeamPCP) compromising 170+ npm + PyPI packages; credential harvest + repo poisoning + self-propagation; no legitimate provenance.",
5139
+ "method": "supplementary-supply-chain-trust (equal-weight mean of present sub-signals; never feeds RWEP)",
5140
+ "as_of": "2026-05-30"
5141
+ },
5142
+ "aliases": [
5143
+ "CVE-2026-44484",
5144
+ "MAL-2026-3201",
5145
+ "GHSA-w37p-236h-pfx3"
5146
+ ],
5147
+ "aliases_note": "CVE-2026-44484 / OSV MAL-2026-3201 / GHSA-w37p-236h-pfx3 cover the PyPI lightning (PyTorch Lightning) sub-incident of this Mini Shai-Hulud wave — distinct ids for the same campaign, tracked here rather than as a separate entry (adversarial dedup 2026-05-30 confirmed it is the PyPI side of this wave, not a distinct incident).",
5148
+ "_editorial_note_lightning": "PyPI lightning (PyTorch Lightning) 2.6.2/2.6.3 compromise (disclosed 2026-04-30, CVE-2026-44484) folded into this entry on 2026-05-30: an adversarial dedup found it is the PyPI side of the Mini Shai-Hulud wave already scoped here (shared Bun-runtime/router_runtime.js IoCs, same campaign/timeline), not a distinct incident warranting a separate MAL-* key."
5066
5149
  },
5067
5150
  "CVE-2024-21762": {
5068
5151
  "ai_assisted_weaponization": false,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "_meta": {
3
3
  "id": "sbom",
4
- "version": "1.3.0",
4
+ "version": "1.3.1",
5
5
  "last_threat_review": "2026-05-13",
6
6
  "threat_currency_score": 96,
7
7
  "changelog": [
@@ -53,6 +53,11 @@
53
53
  "version": "1.3.0",
54
54
  "date": "2026-05-30",
55
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."
56
61
  }
57
62
  ],
58
63
  "owner": "@blamejs/supply-chain",
@@ -619,6 +624,27 @@
619
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.",
620
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.",
621
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
622
648
  }
623
649
  ],
624
650
  "collection_scope": {
@@ -849,6 +875,55 @@
849
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.",
850
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)."
851
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
+ ]
852
927
  }
853
928
  ],
854
929
  "false_positive_profile": [
@@ -876,6 +951,21 @@
876
951
  "indicator_id": "package-capability-creep",
877
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.",
878
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."
879
969
  }
880
970
  ],
881
971
  "minimum_signal": {
@@ -128,7 +128,8 @@ async function resolveCve(id, opts = {}) {
128
128
  }
129
129
 
130
130
  // 1. curated catalog (offline, authoritative for the ids it covers)
131
- const entry = cveCatalog()[cveId];
131
+ const catalog = cveCatalog();
132
+ const entry = catalog[cveId];
132
133
  if (entry && typeof entry === "object") {
133
134
  return {
134
135
  ...base,
@@ -141,6 +142,27 @@ async function resolveCve(id, opts = {}) {
141
142
  };
142
143
  }
143
144
 
145
+ // 1b. alias lookup — an id may be carried as an alias of a curated entry
146
+ // (e.g. a CVE for a sub-incident folded into a campaign-level MAL-* key).
147
+ // Catalogued-by-alias must resolve offline too, or `exceptd cve <alias>`
148
+ // would report unknown for an incident the catalog actually covers.
149
+ for (const k of Object.keys(catalog)) {
150
+ if (k === "_meta") continue;
151
+ const e = catalog[k];
152
+ if (e && Array.isArray(e.aliases) && e.aliases.includes(cveId)) {
153
+ return {
154
+ ...base,
155
+ status: e.status || "published",
156
+ cvss: e.cvss_score ?? null,
157
+ kev: e.cisa_kev ?? null,
158
+ product: e.name || e.type || null,
159
+ exploitation: e.active_exploitation ?? null,
160
+ from: "catalog-alias",
161
+ aliased_to: k,
162
+ };
163
+ }
164
+ }
165
+
144
166
  // 2. resolved cache (offline, warmed by a prior agent's lookup)
145
167
  const cached = cacheGet("cve", cveId);
146
168
  if (cached) return { ...cached, from: "cache" };
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
- // documented in CLAUDE.md for v0.11.11's `ci` #100 regression.
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;
@@ -187,6 +187,29 @@
187
187
  "last_updated": {
188
188
  "type": "string",
189
189
  "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
190
+ },
191
+ "package_confidence": {
192
+ "type": "object",
193
+ "description": "Optional supplementary supply-chain trustworthiness signal (Package-Confidence Score), surfaced ALONGSIDE rwep_score and never feeding it. Polarity is INVERSE of RWEP: high score = trustworthy provenance/behaviour, low = behaves like malware. Present only on package-class entries (MAL-*, supply-chain-* CVEs); never participates in the RWEP sum invariant.",
194
+ "required": ["score", "polarity"],
195
+ "properties": {
196
+ "score": { "type": "integer", "minimum": 0, "maximum": 100 },
197
+ "polarity": { "enum": ["trust"] },
198
+ "inputs": {
199
+ "type": "object",
200
+ "properties": {
201
+ "maintainer": { "type": "number", "minimum": 0, "maximum": 100 },
202
+ "quality": { "type": "number", "minimum": 0, "maximum": 100 },
203
+ "behavioral": { "type": "number", "minimum": 0, "maximum": 100 },
204
+ "provenance": { "type": "number", "minimum": 0, "maximum": 100 }
205
+ },
206
+ "additionalProperties": false
207
+ },
208
+ "rationale": { "type": "string" },
209
+ "method": { "type": "string" },
210
+ "as_of": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" }
211
+ },
212
+ "additionalProperties": false
190
213
  }
191
214
  }
192
215
  }
package/lib/scoring.js CHANGED
@@ -498,11 +498,44 @@ function parseCvss31Vector(v) {
498
498
  return { ok: true, version };
499
499
  }
500
500
 
501
+ /**
502
+ * Package-Confidence Score (PCS) — a SUPPLEMENTARY 0-100 supply-chain
503
+ * trustworthiness signal, surfaced ALONGSIDE RWEP, never replacing it.
504
+ *
505
+ * Polarity is the INVERSE of RWEP: high PCS = trustworthy provenance/behaviour;
506
+ * low PCS = behaves like malware. RWEP answers "how urgently must I act on this
507
+ * known vulnerability"; PCS answers "how much do I trust this package's
508
+ * provenance/behaviour, independent of any CVE." The two are different axes and
509
+ * must never be summed or compared numerically (the `package_confidence.polarity:
510
+ * "trust"` const on catalog entries exists to assert direction before display).
511
+ *
512
+ * CRITICAL: this function is NEVER called inside validate() / scoreCustom() /
513
+ * deriveRwepFromFactors(). PCS lives OUTSIDE the RWEP factor key set so the
514
+ * RWEP sum invariant + >5 divergence gate cannot see it — adding it is purely
515
+ * additive and cannot perturb any stored rwep_score.
516
+ *
517
+ * Equal-weight mean of the PRESENT sub-signals (maintainer / quality /
518
+ * behavioral / provenance), each 0-100, clamped to [0,100]. Absent sub-signals
519
+ * are skipped (not treated as 0) so a partially-curated entry isn't punished
520
+ * for un-assessed dimensions. Returns null when no usable input is present.
521
+ */
522
+ function packageConfidence(inputs) {
523
+ if (!inputs || typeof inputs !== 'object') return null;
524
+ const dims = ['maintainer', 'quality', 'behavioral', 'provenance'];
525
+ const present = dims
526
+ .map((d) => inputs[d])
527
+ .filter((v) => typeof v === 'number' && Number.isFinite(v));
528
+ if (!present.length) return null;
529
+ const mean = present.reduce((a, b) => a + b, 0) / present.length;
530
+ return Math.max(0, Math.min(100, Math.round(mean)));
531
+ }
532
+
501
533
  module.exports = {
502
534
  score,
503
535
  scoreCustom,
504
536
  timeline,
505
537
  compare,
538
+ packageConfidence,
506
539
  validate,
507
540
  validateFactors,
508
541
  deriveRwepFromFactors,