penthera 1.7.0 → 1.8.0

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.
Files changed (163) hide show
  1. package/CHANGELOG.md +315 -0
  2. package/README.md +139 -76
  3. package/SECURITY.md +8 -6
  4. package/docs/examples/pr-dependency-review.yml +1 -1
  5. package/lib/adaptive-probes.js +3 -3
  6. package/lib/advisories/evidence.js +248 -0
  7. package/lib/advisories/index.js +228 -0
  8. package/lib/advisories/products.js +83 -0
  9. package/lib/advisories/versions.js +65 -0
  10. package/lib/agents/hidden-content.js +122 -11
  11. package/lib/agents/index.js +26 -6
  12. package/lib/agents/lock.js +33 -5
  13. package/lib/agents/rules.js +33 -4
  14. package/lib/compliance/index.js +4 -4
  15. package/lib/crawler.js +12 -15
  16. package/lib/deps/capabilities.js +21 -5
  17. package/lib/deps/findings.js +309 -0
  18. package/lib/deps/index.js +13 -272
  19. package/lib/deps/iocs.js +74 -0
  20. package/lib/deps/lockfile.js +3 -8
  21. package/lib/deps/online.js +8 -3
  22. package/lib/deps/popular-packages.json.gz +0 -0
  23. package/lib/deps/popular-pypi.json.gz +0 -0
  24. package/lib/deps/python/capabilities.js +16 -3
  25. package/lib/deps/python/index.js +5 -1
  26. package/lib/deps/python/lockfiles.js +11 -5
  27. package/lib/deps/python/source.js +75 -26
  28. package/lib/deps/python/toml.js +15 -3
  29. package/lib/deps/source.js +28 -9
  30. package/lib/deps/typosquat.js +30 -5
  31. package/lib/fix/apply.js +253 -34
  32. package/lib/fix/index.js +7 -4
  33. package/lib/fix/model.js +25 -5
  34. package/lib/fix/util.js +6 -2
  35. package/lib/fuzzer.js +3 -3
  36. package/lib/git.js +65 -0
  37. package/lib/hook/index.js +157 -73
  38. package/lib/hook/policy.js +10 -4
  39. package/lib/install/index.js +4 -7
  40. package/lib/machine.js +0 -13
  41. package/lib/memory/store.js +11 -4
  42. package/lib/nuclei-loader.js +0 -25
  43. package/lib/owasp-wstg.js +4 -0
  44. package/lib/plugins.js +1 -1
  45. package/lib/provenance.js +1 -0
  46. package/lib/recon.js +12 -31
  47. package/lib/references.json +224 -0
  48. package/lib/retirejs.js +4 -4
  49. package/lib/rules/advisories/advisories.json +186 -0
  50. package/lib/rules/advisories/eol.json +413 -0
  51. package/lib/rules/advisories/pack.json +20 -0
  52. package/lib/rules/advisories/pack.json.sig +6 -0
  53. package/lib/rules/allowed_signers +13 -0
  54. package/lib/rules/core/agent-exec.json +29 -0
  55. package/lib/rules/core/agent-instructions.json +17 -0
  56. package/lib/rules/core/pack.json +30 -0
  57. package/lib/rules/core/pack.json.sig +6 -0
  58. package/lib/rules/core/secrets.json +161 -0
  59. package/lib/rules/core/templates.json +52 -0
  60. package/lib/rules/index.js +107 -0
  61. package/lib/rules/ioc-2025-2026/files.json +43 -0
  62. package/lib/rules/ioc-2025-2026/hosts.json +35 -0
  63. package/lib/rules/ioc-2025-2026/pack.json +25 -0
  64. package/lib/rules/ioc-2025-2026/pack.json.sig +6 -0
  65. package/lib/rules/ioc-2025-2026/packages.json +1048 -0
  66. package/lib/rules/pack.js +452 -0
  67. package/lib/rules/regex-guard.js +81 -9
  68. package/lib/rules/sshsig.js +205 -0
  69. package/lib/templates.js +24 -20
  70. package/lib/tmp.js +21 -0
  71. package/lib/whitebox/frameworks.js +1 -1
  72. package/lib/whitebox/secrets.js +117 -41
  73. package/package.json +12 -4
  74. package/scripts/rules/build-pack.mjs +80 -0
  75. package/scripts/rules/check-bundled.mjs +30 -0
  76. package/scripts/rules/sign-pack.sh +32 -0
  77. package/skills/penthera/SKILL.md +16 -15
  78. package/src/agent/git.js +147 -14
  79. package/src/agent/loop.js +165 -46
  80. package/src/cli/agent.js +11 -5
  81. package/src/cli/deps.js +0 -2
  82. package/src/cli/detect.js +14 -7
  83. package/src/cli/fix.js +26 -8
  84. package/src/cli/hook.js +2 -1
  85. package/src/cli/onboarding.js +14 -6
  86. package/src/cli/report-outputs.js +1 -0
  87. package/src/cli/rules.js +144 -0
  88. package/src/cli/ui.js +43 -10
  89. package/src/cli.js +24 -1
  90. package/src/report/evidence.js +105 -21
  91. package/src/report/footer.js +1 -1
  92. package/src/report/md-escape.js +8 -0
  93. package/src/scan-repo.js +6 -0
  94. package/src/scan-url.js +11 -3
  95. package/src/ui/api/data.js +39 -6
  96. package/src/ui/api/routes.js +44 -11
  97. package/src/ui/api/runs.js +32 -7
  98. package/src/ui/security.js +20 -23
  99. package/src/ui/server.js +52 -27
  100. package/src/ui/static/app.css +583 -213
  101. package/src/ui/static/favicon.svg +1 -1
  102. package/src/ui/static/fonts/DepartureMono-LICENSE.txt +93 -0
  103. package/src/ui/static/fonts/DepartureMono-Regular.woff2 +0 -0
  104. package/src/ui/static/index.html +5 -4
  105. package/src/ui/static/js/api.js +114 -6
  106. package/src/ui/static/js/app.js +81 -42
  107. package/src/ui/static/js/center.js +246 -133
  108. package/src/ui/static/js/charts.js +99 -18
  109. package/src/ui/static/js/copy.js +68 -9
  110. package/src/ui/static/js/dialog.js +152 -0
  111. package/src/ui/static/js/left.js +23 -60
  112. package/src/ui/static/js/menubar.js +170 -0
  113. package/src/ui/static/js/onboarding.js +104 -0
  114. package/src/ui/static/js/panels.js +186 -0
  115. package/src/ui/static/js/right.js +69 -188
  116. package/src/ui/static/js/state.js +36 -5
  117. package/src/ui/static/logo.svg +10 -0
  118. package/src/ui/static/mark.svg +3 -0
  119. package/src/utils/http.js +162 -13
  120. package/docs/METHODOLOGY.md +0 -375
  121. package/docs/commands/README.md +0 -24
  122. package/docs/commands/action.md +0 -21
  123. package/docs/commands/agents.md +0 -38
  124. package/docs/commands/deps.md +0 -115
  125. package/docs/commands/fix.md +0 -53
  126. package/docs/commands/hook.md +0 -69
  127. package/docs/commands/init.md +0 -114
  128. package/docs/commands/install.md +0 -53
  129. package/docs/commands/scan.md +0 -100
  130. package/docs/commands/skill.md +0 -58
  131. package/docs/compare/README.md +0 -25
  132. package/docs/compare/github.md +0 -60
  133. package/docs/compare/semgrep.md +0 -54
  134. package/docs/compare/snyk.md +0 -62
  135. package/docs/compare/socket.md +0 -78
  136. package/docs/compare/zap.md +0 -59
  137. package/docs/compliance-cra.md +0 -141
  138. package/docs/compliance-nis2.md +0 -168
  139. package/docs/dashboard.md +0 -187
  140. package/docs/evidence.md +0 -162
  141. package/docs/funding/due-diligence-checklist.md +0 -79
  142. package/docs/funding/nlnet-ngi-zero-draft.md +0 -273
  143. package/docs/funding/partner-one-pager.md +0 -84
  144. package/docs/funding/sponsor-tiers.md +0 -81
  145. package/docs/index.md +0 -81
  146. package/docs/internal-audit.md +0 -83
  147. package/docs/local-loop.md +0 -276
  148. package/docs/marketing.md +0 -151
  149. package/docs/owasp-wstg-coverage.md +0 -79
  150. package/docs/plan.md +0 -102
  151. package/docs/privacy.md +0 -38
  152. package/docs/references.bib +0 -559
  153. package/docs/references.md +0 -119
  154. package/docs/release-plan.md +0 -228
  155. package/docs/research/socket-positioning-notes.md +0 -183
  156. package/docs/use-cases/README.md +0 -14
  157. package/docs/use-cases/agencies.md +0 -71
  158. package/docs/use-cases/ai-coding-teams.md +0 -81
  159. package/docs/use-cases/eu-sme-compliance.md +0 -68
  160. package/docs/use-cases/open-source-maintainers.md +0 -75
  161. package/docs/use-cases/solo-founder.md +0 -65
  162. package/lib/deps/popular-packages.json +0 -1
  163. package/lib/deps/popular-pypi.json +0 -1
@@ -0,0 +1,161 @@
1
+ {
2
+ "kind": "secret",
3
+ "rules": [
4
+ {
5
+ "id": "secret.anthropic-api-key",
6
+ "name": "Anthropic API key",
7
+ "pattern": "sk-ant-(?:api|admin)\\d{2}-[A-Za-z0-9_-]{32,}",
8
+ "severity": "critical",
9
+ "source": "https://github.com/danoszz/penthera/blob/afafb24/lib/whitebox/secrets.js",
10
+ "date": "2026-10-19",
11
+ "confidence": "likely",
12
+ "references": [
13
+ "meli2019bad",
14
+ "basak2023secretbench"
15
+ ]
16
+ },
17
+ {
18
+ "id": "secret.openrouter-api-key",
19
+ "name": "OpenRouter API key",
20
+ "pattern": "sk-or-v1-[a-f0-9]{64}",
21
+ "severity": "critical",
22
+ "source": "https://github.com/danoszz/penthera/blob/afafb24/lib/whitebox/secrets.js",
23
+ "date": "2026-10-19",
24
+ "confidence": "likely",
25
+ "references": [
26
+ "meli2019bad",
27
+ "basak2023secretbench"
28
+ ]
29
+ },
30
+ {
31
+ "id": "secret.openai-api-key",
32
+ "name": "OpenAI API key",
33
+ "pattern": "sk-(?:proj|svcacct|admin)-[A-Za-z0-9_-]{20,}",
34
+ "severity": "critical",
35
+ "source": "https://github.com/danoszz/penthera/blob/afafb24/lib/whitebox/secrets.js",
36
+ "date": "2026-10-19",
37
+ "confidence": "likely",
38
+ "references": [
39
+ "meli2019bad",
40
+ "basak2023secretbench"
41
+ ]
42
+ },
43
+ {
44
+ "id": "secret.openai-api-key-legacy-format",
45
+ "name": "OpenAI API key (legacy format)",
46
+ "pattern": "sk-[a-zA-Z0-9]{20,}",
47
+ "severity": "critical",
48
+ "source": "https://github.com/danoszz/penthera/blob/afafb24/lib/whitebox/secrets.js",
49
+ "date": "2026-10-19",
50
+ "confidence": "likely",
51
+ "references": [
52
+ "meli2019bad",
53
+ "basak2023secretbench"
54
+ ]
55
+ },
56
+ {
57
+ "id": "secret.google-api-key-gemini-firebase-maps",
58
+ "name": "Google API key (Gemini / Firebase / Maps)",
59
+ "pattern": "AIza[0-9A-Za-z_-]{35}",
60
+ "severity": "high",
61
+ "source": "https://github.com/danoszz/penthera/blob/afafb24/lib/whitebox/secrets.js",
62
+ "date": "2026-10-19",
63
+ "confidence": "likely",
64
+ "references": [
65
+ "meli2019bad",
66
+ "basak2023secretbench"
67
+ ]
68
+ },
69
+ {
70
+ "id": "secret.groq-api-key",
71
+ "name": "Groq API key",
72
+ "pattern": "gsk_[A-Za-z0-9]{48,}",
73
+ "severity": "critical",
74
+ "source": "https://github.com/danoszz/penthera/blob/afafb24/lib/whitebox/secrets.js",
75
+ "date": "2026-10-19",
76
+ "confidence": "likely",
77
+ "references": [
78
+ "meli2019bad",
79
+ "basak2023secretbench"
80
+ ]
81
+ },
82
+ {
83
+ "id": "secret.hugging-face-token",
84
+ "name": "Hugging Face token",
85
+ "pattern": "hf_[A-Za-z0-9]{34,}",
86
+ "severity": "critical",
87
+ "source": "https://github.com/danoszz/penthera/blob/afafb24/lib/whitebox/secrets.js",
88
+ "date": "2026-10-19",
89
+ "confidence": "likely",
90
+ "references": [
91
+ "meli2019bad",
92
+ "basak2023secretbench"
93
+ ]
94
+ },
95
+ {
96
+ "id": "secret.replicate-api-token",
97
+ "name": "Replicate API token",
98
+ "pattern": "r8_[A-Za-z0-9]{37,}",
99
+ "severity": "critical",
100
+ "source": "https://github.com/danoszz/penthera/blob/afafb24/lib/whitebox/secrets.js",
101
+ "date": "2026-10-19",
102
+ "confidence": "likely",
103
+ "references": [
104
+ "meli2019bad",
105
+ "basak2023secretbench"
106
+ ]
107
+ },
108
+ {
109
+ "id": "secret.xai-api-key",
110
+ "name": "xAI API key",
111
+ "pattern": "xai-[A-Za-z0-9]{70,}",
112
+ "severity": "critical",
113
+ "source": "https://github.com/danoszz/penthera/blob/afafb24/lib/whitebox/secrets.js",
114
+ "date": "2026-10-19",
115
+ "confidence": "likely",
116
+ "references": [
117
+ "meli2019bad",
118
+ "basak2023secretbench"
119
+ ]
120
+ },
121
+ {
122
+ "id": "secret.perplexity-api-key",
123
+ "name": "Perplexity API key",
124
+ "pattern": "pplx-[A-Za-z0-9]{40,}",
125
+ "severity": "critical",
126
+ "source": "https://github.com/danoszz/penthera/blob/afafb24/lib/whitebox/secrets.js",
127
+ "date": "2026-10-19",
128
+ "confidence": "likely",
129
+ "references": [
130
+ "meli2019bad",
131
+ "basak2023secretbench"
132
+ ]
133
+ },
134
+ {
135
+ "id": "secret.langsmith-api-key",
136
+ "name": "LangSmith API key",
137
+ "pattern": "lsv2_(?:pt|sk)_[a-f0-9]{32}_[a-f0-9]{10}",
138
+ "severity": "high",
139
+ "source": "https://github.com/danoszz/penthera/blob/afafb24/lib/whitebox/secrets.js",
140
+ "date": "2026-10-19",
141
+ "confidence": "likely",
142
+ "references": [
143
+ "meli2019bad",
144
+ "basak2023secretbench"
145
+ ]
146
+ },
147
+ {
148
+ "id": "secret.pinecone-api-key",
149
+ "name": "Pinecone API key",
150
+ "pattern": "pcsk_[A-Za-z0-9_]{50,}",
151
+ "severity": "high",
152
+ "source": "https://github.com/danoszz/penthera/blob/afafb24/lib/whitebox/secrets.js",
153
+ "date": "2026-10-19",
154
+ "confidence": "likely",
155
+ "references": [
156
+ "meli2019bad",
157
+ "basak2023secretbench"
158
+ ]
159
+ }
160
+ ]
161
+ }
@@ -0,0 +1,52 @@
1
+ {
2
+ "kind": "nuclei-template",
3
+ "rules": [
4
+ {
5
+ "id": "template.env-file-exposure",
6
+ "template": {
7
+ "id": "env-file-exposure",
8
+ "info": {
9
+ "name": ".env file exposed",
10
+ "severity": "critical",
11
+ "tags": [
12
+ "exposure",
13
+ "config"
14
+ ]
15
+ },
16
+ "http": [
17
+ {
18
+ "method": "GET",
19
+ "path": "/.env",
20
+ "matchers": [
21
+ {
22
+ "type": "status",
23
+ "status": [
24
+ 200
25
+ ]
26
+ },
27
+ {
28
+ "type": "word",
29
+ "words": [
30
+ "DB_",
31
+ "API_KEY",
32
+ "SECRET",
33
+ "PASSWORD",
34
+ "FIREBASE_",
35
+ "STRIPE_"
36
+ ],
37
+ "condition": "or"
38
+ }
39
+ ],
40
+ "matchers-condition": "and"
41
+ }
42
+ ]
43
+ },
44
+ "source": "https://github.com/danoszz/penthera/blob/afafb24/lib/templates.js",
45
+ "date": "2026-06-30",
46
+ "confidence": "confirmed",
47
+ "references": [
48
+ "owaspwstg42"
49
+ ]
50
+ }
51
+ ]
52
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * The rule-pack registry: which packs are active in this process. Bundled
3
+ * packs (lib/rules/<name>/) load at import; packs given with --rules are added
4
+ * by the CLI through addRulePack. Engines read rules with rulesOf(kind).
5
+ * Verification lives in ./pack.js; see docs/rule-packs.md.
6
+ */
7
+ import { readdirSync, existsSync } from "node:fs";
8
+ import { join, basename } from "node:path";
9
+ import { verifyPack, loadSigners, RULES_DIR, PACK_INPUT_CAP } from "./pack.js";
10
+
11
+ export { verifyPack, loadSigners, KINDS, NAMESPACE, RULES_DIR, BUILTIN_SIGNERS, PACK_INPUT_CAP } from "./pack.js";
12
+
13
+ // ── Registry ──────────────────────────────────────────────────────────────
14
+
15
+ const active = [];
16
+ let generation = 0;
17
+
18
+ /** Packs bundled with this release: lib/rules/<name>/pack.json. */
19
+ export function bundledPackDirs() {
20
+ return readdirSync(RULES_DIR, { withFileTypes: true })
21
+ .filter((d) => d.isDirectory() && existsSync(join(RULES_DIR, d.name, "pack.json")))
22
+ .map((d) => join(RULES_DIR, d.name))
23
+ .sort((a, b) => (basename(a) === "core" ? -1 : basename(b) === "core" ? 1 : a.localeCompare(b)));
24
+ }
25
+
26
+ function activate(v, origin) {
27
+ if (active.some((p) => p.name === v.name)) throw new Error(`rule pack "${v.name}" is already loaded`);
28
+ active.push({ name: v.name, version: v.version, dir: v.dir, origin, signature: v.signature, signer: v.signer, rules: v.rules, count: v.count });
29
+ generation++;
30
+ }
31
+
32
+ /**
33
+ * Bundled packs ship inside the npm release, so they carry the same trust as
34
+ * the code beside them; they are hash-checked so a partial edit or corrupted
35
+ * install fails loudly. Their signatures are enforced at release time
36
+ * (scripts/rules/check-bundled.mjs), not on every run.
37
+ */
38
+ function loadBundled() {
39
+ for (const dir of bundledPackDirs()) {
40
+ const v = verifyPack(dir);
41
+ if (!v.ok) throw new Error(`Bundled rule pack ${basename(dir)} failed verification, so this Penthera install is damaged (reinstall it): ${v.errors.join("; ")}`);
42
+ activate(v, "bundled");
43
+ }
44
+ }
45
+ loadBundled();
46
+
47
+ /**
48
+ * Verify and activate a pack given by path (`--rules <dir>`).
49
+ * Refuses unsigned or badly signed packs unless allowUnsigned. Hash, schema
50
+ * and ReDoS failures are refused always.
51
+ * @returns {{ pack: object, warnings: string[] }}
52
+ */
53
+ export function addRulePack(dir, opts = {}) {
54
+ const v = verifyPack(dir, { signers: opts.signers || loadSigners(opts.signersFiles), now: opts.now });
55
+ if (!v.ok) throw new Error(`Rule pack ${dir} refused: ${v.errors.join("; ")}`);
56
+ const warnings = [...v.warnings];
57
+ if (v.signature !== "valid") {
58
+ const why = v.signature === "missing" ? "it is not signed (no pack.json.sig)" : `its signature is invalid: ${v.signatureError}`;
59
+ if (!opts.allowUnsigned) {
60
+ throw new Error(`Rule pack ${dir} refused: ${why}. Only packs signed by a key in allowed_signers load. Pass --allow-unsigned-rules to load it anyway, if you wrote it or have reviewed every rule.`);
61
+ }
62
+ warnings.unshift(`LOADING AN UNTRUSTED RULE PACK: ${v.name}@${v.version} from ${v.dir}: ${why}. --allow-unsigned-rules is set, so it loads anyway. Its rules decide what this scan reports.`);
63
+ }
64
+ activate(v, "path");
65
+ return { pack: active[active.length - 1], warnings };
66
+ }
67
+
68
+ /** Drop packs added by path (tests, and long-lived library callers). */
69
+ export function resetRulePacks() {
70
+ for (let i = active.length - 1; i >= 0; i--) if (active[i].origin !== "bundled") active.splice(i, 1);
71
+ generation++;
72
+ }
73
+
74
+ /** Active packs, for `penthera rules list` and reports. */
75
+ export function activePacks() {
76
+ return active.map(({ rules, ...p }) => ({ ...p }));
77
+ }
78
+
79
+ /** Changes whenever packs are added or removed; engines key caches on it. */
80
+ export function rulesGeneration() {
81
+ return generation;
82
+ }
83
+
84
+ /**
85
+ * Compiled rules of one kind from active packs.
86
+ * @param {string} kind
87
+ * @param {{ pack?: string, exceptPack?: string }} [filter]
88
+ */
89
+ export function rulesOf(kind, filter = {}) {
90
+ const out = [];
91
+ for (const p of active) {
92
+ if (filter.pack && p.name !== filter.pack) continue;
93
+ if (filter.exceptPack && p.name === filter.exceptPack) continue;
94
+ for (const r of p.rules[kind] || []) out.push({ ...r, origin: p.origin });
95
+ }
96
+ return out;
97
+ }
98
+
99
+ /** Test a pack regex, capping input for packs added by path. */
100
+ export function packTest(rule, s) {
101
+ return rule.pattern.test(rule.origin === "bundled" || s.length <= PACK_INPUT_CAP ? s : s.slice(0, PACK_INPUT_CAP));
102
+ }
103
+
104
+ /** Match a pack regex, capping input for packs added by path. */
105
+ export function packMatch(rule, s) {
106
+ return (rule.origin === "bundled" || s.length <= PACK_INPUT_CAP ? s : s.slice(0, PACK_INPUT_CAP)).match(rule.pattern);
107
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "kind": "ioc-file",
3
+ "rules": [
4
+ {
5
+ "id": "shai-hulud-1-bundle",
6
+ "campaign": "Shai-Hulud worm (first wave)",
7
+ "description": "The worm payload. bundle.js is a common file name, so it only matches with this SHA-256.",
8
+ "files": [
9
+ {
10
+ "path": "bundle.js",
11
+ "sha256": "46faab8ab153fae6e80e7cca38eab363075bb524edd79e42269217a083628f09"
12
+ }
13
+ ],
14
+ "source": "https://www.stepsecurity.io/blog/ctrl-tinycolor-and-40-npm-packages-compromised",
15
+ "date": "2025-09-15",
16
+ "confidence": "confirmed",
17
+ "references": [
18
+ "ohm2020backstabber",
19
+ "ladisa2023sok"
20
+ ]
21
+ },
22
+ {
23
+ "id": "shai-hulud-2-loader",
24
+ "campaign": "Shai-Hulud 2.0 (Sha1-Hulud: The Second Coming)",
25
+ "description": "The preinstall loader and payload file names. Matched by name: Wiz publishes SHA-1 hashes only.",
26
+ "files": [
27
+ {
28
+ "path": "setup_bun.js"
29
+ },
30
+ {
31
+ "path": "bun_environment.js"
32
+ }
33
+ ],
34
+ "source": "https://www.wiz.io/blog/shai-hulud-2-0-ongoing-supply-chain-attack",
35
+ "date": "2025-11-24",
36
+ "confidence": "likely",
37
+ "references": [
38
+ "ohm2020backstabber",
39
+ "ladisa2023sok"
40
+ ]
41
+ }
42
+ ]
43
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "kind": "ioc-host",
3
+ "rules": [
4
+ {
5
+ "id": "shai-hulud-1-exfil",
6
+ "campaign": "Shai-Hulud worm (first wave)",
7
+ "description": "The worm's exfiltration endpoint. webhook.site is a legitimate service, so only this exact URL is an indicator.",
8
+ "urls": [
9
+ "https://webhook.site/bb8ca5f6-4175-45d2-b042-fc9ebb8170b7"
10
+ ],
11
+ "source": "https://www.stepsecurity.io/blog/ctrl-tinycolor-and-40-npm-packages-compromised",
12
+ "date": "2025-09-15",
13
+ "confidence": "confirmed",
14
+ "references": [
15
+ "ohm2020backstabber",
16
+ "ladisa2023sok"
17
+ ]
18
+ },
19
+ {
20
+ "id": "postmark-mcp-exfil",
21
+ "campaign": "postmark-mcp backdoor",
22
+ "description": "Attacker domain that received the BCC copies (phan@giftshop.club).",
23
+ "hosts": [
24
+ "giftshop.club"
25
+ ],
26
+ "source": "https://snyk.io/blog/malicious-mcp-server-on-npm-postmark-mcp-harvests-emails/",
27
+ "date": "2025-09-25",
28
+ "confidence": "confirmed",
29
+ "references": [
30
+ "ohm2020backstabber",
31
+ "ladisa2023sok"
32
+ ]
33
+ }
34
+ ]
35
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "schema": 1,
3
+ "name": "ioc-2025-2026",
4
+ "version": "1.0.0",
5
+ "created_at": "2026-10-19T14:09:27Z",
6
+ "min_penthera": "1.2.0",
7
+ "description": "Indicators for named npm supply-chain campaigns of 2025: s1ngularity (Nx), Shai-Hulud and Shai-Hulud 2.0, and the postmark-mcp backdoor. Every entry cites the advisory or vendor list it came from.",
8
+ "rules": [
9
+ {
10
+ "file": "files.json",
11
+ "kind": "ioc-file",
12
+ "sha256": "83ab6ecd29a4ff387ce0a3409a1f8a7ee9e451070fc68fbb7a9e989437e9cdaa"
13
+ },
14
+ {
15
+ "file": "hosts.json",
16
+ "kind": "ioc-host",
17
+ "sha256": "f172e6858de39e41c84f340477dbd403afbaa66730e3c9295a1bad57278377bd"
18
+ },
19
+ {
20
+ "file": "packages.json",
21
+ "kind": "ioc-package",
22
+ "sha256": "9c2d5378917f61894bbb12feba104e49e9cdfb2b7547c19da7ded4eaa1b28ca9"
23
+ }
24
+ ]
25
+ }
@@ -0,0 +1,6 @@
1
+ -----BEGIN SSH SIGNATURE-----
2
+ U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgDgRSZTRaMM36gE32AzSZRuHiqG
3
+ VstB30pUKdNOYLIywAAAAOcGVudGhlcmEtcnVsZXMAAAAAAAAABnNoYTUxMgAAAFMAAAAL
4
+ c3NoLWVkMjU1MTkAAABA0GfNaDjNeTRDBD39zj6gltV0b3fUgPcyQ+5x+HWeI7ReCrpUwv
5
+ jEn40N4V8BqPEtl1odxx3LBUEuOzcSm/KOAw==
6
+ -----END SSH SIGNATURE-----