@intentsolutionsio/penetration-tester 2.0.0 → 3.0.4
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/.claude-plugin/plugin.json +8 -3
- package/README.md +8 -0
- package/commands/pentest.md +5 -0
- package/package.json +8 -3
- package/skills/analyzing-tls-config/SKILL.md +221 -0
- package/skills/analyzing-tls-config/references/AUTHORIZATION.md +133 -0
- package/skills/analyzing-tls-config/references/PLAYBOOK.md +267 -0
- package/skills/analyzing-tls-config/references/THEORY.md +128 -0
- package/skills/analyzing-tls-config/scripts/analyze_tls.py +415 -0
- package/skills/auditing-cors-policy/SKILL.md +186 -0
- package/skills/auditing-cors-policy/references/PLAYBOOK.md +220 -0
- package/skills/auditing-cors-policy/references/THEORY.md +142 -0
- package/skills/auditing-cors-policy/scripts/audit_cors.py +350 -0
- package/skills/auditing-npm-dependencies/SKILL.md +254 -0
- package/skills/auditing-npm-dependencies/references/PLAYBOOK.md +175 -0
- package/skills/auditing-npm-dependencies/references/THEORY.md +122 -0
- package/skills/auditing-npm-dependencies/scripts/audit_npm.py +408 -0
- package/skills/auditing-python-dependencies/SKILL.md +251 -0
- package/skills/auditing-python-dependencies/references/PLAYBOOK.md +193 -0
- package/skills/auditing-python-dependencies/references/THEORY.md +122 -0
- package/skills/auditing-python-dependencies/scripts/audit_python.py +459 -0
- package/skills/checking-http-security-headers/SKILL.md +176 -0
- package/skills/checking-http-security-headers/references/PLAYBOOK.md +212 -0
- package/skills/checking-http-security-headers/references/THEORY.md +137 -0
- package/skills/checking-http-security-headers/scripts/check_headers.py +362 -0
- package/skills/checking-license-compliance/SKILL.md +225 -0
- package/skills/checking-license-compliance/references/PLAYBOOK.md +161 -0
- package/skills/checking-license-compliance/references/THEORY.md +152 -0
- package/skills/checking-license-compliance/scripts/check_licenses.py +461 -0
- package/skills/composing-vulnerability-report/SKILL.md +212 -0
- package/skills/composing-vulnerability-report/references/PLAYBOOK.md +180 -0
- package/skills/composing-vulnerability-report/references/THEORY.md +178 -0
- package/skills/composing-vulnerability-report/scripts/compose_report.py +396 -0
- package/skills/confirming-pentest-authorization/SKILL.md +247 -0
- package/skills/confirming-pentest-authorization/references/PLAYBOOK.md +189 -0
- package/skills/confirming-pentest-authorization/references/THEORY.md +167 -0
- package/skills/confirming-pentest-authorization/scripts/check_authorization.py +457 -0
- package/skills/defining-pentest-scope/SKILL.md +227 -0
- package/skills/defining-pentest-scope/references/PLAYBOOK.md +238 -0
- package/skills/defining-pentest-scope/references/THEORY.md +170 -0
- package/skills/defining-pentest-scope/scripts/define_scope.py +472 -0
- package/skills/detecting-command-injection-patterns/SKILL.md +144 -0
- package/skills/detecting-command-injection-patterns/references/PLAYBOOK.md +302 -0
- package/skills/detecting-command-injection-patterns/references/THEORY.md +206 -0
- package/skills/detecting-command-injection-patterns/scripts/scan_cmdi.py +290 -0
- package/skills/detecting-debug-endpoints/SKILL.md +207 -0
- package/skills/detecting-debug-endpoints/references/PLAYBOOK.md +402 -0
- package/skills/detecting-debug-endpoints/references/THEORY.md +218 -0
- package/skills/detecting-debug-endpoints/scripts/probe_debug.py +518 -0
- package/skills/detecting-directory-listing/SKILL.md +206 -0
- package/skills/detecting-directory-listing/references/PLAYBOOK.md +277 -0
- package/skills/detecting-directory-listing/references/THEORY.md +203 -0
- package/skills/detecting-directory-listing/scripts/probe_directory_listing.py +180 -0
- package/skills/detecting-eval-exec-usage/SKILL.md +128 -0
- package/skills/detecting-eval-exec-usage/references/PLAYBOOK.md +306 -0
- package/skills/detecting-eval-exec-usage/references/THEORY.md +159 -0
- package/skills/detecting-eval-exec-usage/scripts/scan_eval.py +223 -0
- package/skills/detecting-exposed-secrets-files/SKILL.md +179 -0
- package/skills/detecting-exposed-secrets-files/references/PLAYBOOK.md +274 -0
- package/skills/detecting-exposed-secrets-files/references/THEORY.md +174 -0
- package/skills/detecting-exposed-secrets-files/scripts/probe_secrets.py +207 -0
- package/skills/detecting-insecure-deserialization/SKILL.md +148 -0
- package/skills/detecting-insecure-deserialization/references/PLAYBOOK.md +333 -0
- package/skills/detecting-insecure-deserialization/references/THEORY.md +199 -0
- package/skills/detecting-insecure-deserialization/scripts/scan_deserialization.py +250 -0
- package/skills/detecting-sql-injection-patterns/SKILL.md +161 -0
- package/skills/detecting-sql-injection-patterns/references/PLAYBOOK.md +317 -0
- package/skills/detecting-sql-injection-patterns/references/THEORY.md +261 -0
- package/skills/detecting-sql-injection-patterns/scripts/scan_sqli.py +354 -0
- package/skills/detecting-ssl-cert-issues/SKILL.md +182 -0
- package/skills/detecting-ssl-cert-issues/references/PLAYBOOK.md +203 -0
- package/skills/detecting-ssl-cert-issues/references/THEORY.md +133 -0
- package/skills/detecting-ssl-cert-issues/scripts/check_cert_chain.py +481 -0
- package/skills/detecting-weak-cryptography/SKILL.md +147 -0
- package/skills/detecting-weak-cryptography/references/PLAYBOOK.md +466 -0
- package/skills/detecting-weak-cryptography/references/THEORY.md +194 -0
- package/skills/detecting-weak-cryptography/scripts/scan_weak_crypto.py +417 -0
- package/skills/fingerprinting-server-software/SKILL.md +191 -0
- package/skills/fingerprinting-server-software/references/PLAYBOOK.md +337 -0
- package/skills/fingerprinting-server-software/references/THEORY.md +183 -0
- package/skills/fingerprinting-server-software/scripts/fingerprint_server.py +347 -0
- package/skills/generating-executive-summary/SKILL.md +261 -0
- package/skills/generating-executive-summary/references/PLAYBOOK.md +201 -0
- package/skills/generating-executive-summary/references/THEORY.md +195 -0
- package/skills/generating-executive-summary/scripts/exec_summary.py +538 -0
- package/skills/mapping-findings-to-owasp-top10/SKILL.md +235 -0
- package/skills/mapping-findings-to-owasp-top10/references/PLAYBOOK.md +193 -0
- package/skills/mapping-findings-to-owasp-top10/references/THEORY.md +160 -0
- package/skills/mapping-findings-to-owasp-top10/scripts/map_owasp.py +540 -0
- package/skills/performing-penetration-testing/SKILL.md +282 -190
- package/skills/performing-penetration-testing/references/OWASP_TOP_10.md +22 -0
- package/skills/performing-penetration-testing/references/REMEDIATION_PLAYBOOK.md +46 -0
- package/skills/performing-penetration-testing/references/SECURITY_HEADERS.md +41 -0
- package/skills/performing-penetration-testing/scripts/code_security_scanner.py +144 -79
- package/skills/performing-penetration-testing/scripts/dependency_auditor.py +116 -93
- package/skills/performing-penetration-testing/scripts/security_scanner.py +574 -446
- package/skills/probing-dangerous-http-methods/SKILL.md +182 -0
- package/skills/probing-dangerous-http-methods/references/PLAYBOOK.md +234 -0
- package/skills/probing-dangerous-http-methods/references/THEORY.md +145 -0
- package/skills/probing-dangerous-http-methods/scripts/probe_methods.py +263 -0
- package/skills/recording-pentest-engagement/SKILL.md +253 -0
- package/skills/recording-pentest-engagement/references/PLAYBOOK.md +203 -0
- package/skills/recording-pentest-engagement/references/THEORY.md +195 -0
- package/skills/recording-pentest-engagement/scripts/record_engagement.py +461 -0
- package/skills/scanning-for-hardcoded-secrets/SKILL.md +215 -0
- package/skills/scanning-for-hardcoded-secrets/references/PLAYBOOK.md +325 -0
- package/skills/scanning-for-hardcoded-secrets/references/THEORY.md +175 -0
- package/skills/scanning-for-hardcoded-secrets/scripts/scan_secrets.py +395 -0
- package/skills/tracing-transitive-vulnerabilities/SKILL.md +235 -0
- package/skills/tracing-transitive-vulnerabilities/references/PLAYBOOK.md +233 -0
- package/skills/tracing-transitive-vulnerabilities/references/THEORY.md +138 -0
- package/skills/tracing-transitive-vulnerabilities/scripts/trace_vulns.py +484 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Static-analysis scan for hardcoded credentials in source trees.
|
|
3
|
+
|
|
4
|
+
Companion to skill `scanning-for-hardcoded-secrets`. Walks a directory
|
|
5
|
+
tree, reads each text file, applies the canonical credential regex
|
|
6
|
+
library, and emits findings with file path + line number + redacted
|
|
7
|
+
matched text.
|
|
8
|
+
|
|
9
|
+
Two detection modes layered:
|
|
10
|
+
|
|
11
|
+
1. Provider-specific regex (AWS AKIA, GitHub ghp_, Stripe sk_live_,
|
|
12
|
+
Anthropic sk-ant-, OpenAI sk-proj-, Slack xox*, Google AIza, RSA
|
|
13
|
+
private keys, etc.). These are CRITICAL because tools at the
|
|
14
|
+
receiving end (AWS, GitHub Secret Scanning, vendor bots)
|
|
15
|
+
auto-extract.
|
|
16
|
+
|
|
17
|
+
2. Entropy + context-based heuristics (high-Shannon-entropy string
|
|
18
|
+
appearing in a `key:` / `token:` / `password=` field). HIGH or
|
|
19
|
+
MEDIUM. Higher false-positive rate; requires human verification.
|
|
20
|
+
|
|
21
|
+
References:
|
|
22
|
+
CWE-798 Use of Hard-coded Credentials
|
|
23
|
+
CWE-321 Use of Hard-coded Cryptographic Key
|
|
24
|
+
OWASP A07:2021 Identification and Authentication Failures
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import argparse
|
|
30
|
+
import math
|
|
31
|
+
import re
|
|
32
|
+
import sys
|
|
33
|
+
from collections import Counter
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
|
|
36
|
+
_PLUGIN_ROOT = Path(__file__).resolve().parents[3]
|
|
37
|
+
if str(_PLUGIN_ROOT) not in sys.path:
|
|
38
|
+
sys.path.insert(0, str(_PLUGIN_ROOT))
|
|
39
|
+
|
|
40
|
+
from lib.finding import Finding, Severity # noqa: E402
|
|
41
|
+
from lib.report import emit, exit_code # noqa: E402
|
|
42
|
+
|
|
43
|
+
SKILL_ID = "scanning-for-hardcoded-secrets"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Provider-specific regex library.
|
|
47
|
+
# Each entry: (pattern_name, severity, regex, control)
|
|
48
|
+
PROVIDER_REGEXES = [
|
|
49
|
+
("AWS access key", Severity.CRITICAL, r"\b(AKIA|ASIA|ABIA|ACCA)[0-9A-Z]{16}\b", "CWE-798"),
|
|
50
|
+
("GitHub personal access token", Severity.CRITICAL, r"\bghp_[A-Za-z0-9]{36,}\b", "CWE-798"),
|
|
51
|
+
("GitHub OAuth token", Severity.CRITICAL, r"\bgho_[A-Za-z0-9]{36,}\b", "CWE-798"),
|
|
52
|
+
("GitHub app installation token", Severity.CRITICAL, r"\bghs_[A-Za-z0-9]{36,}\b", "CWE-798"),
|
|
53
|
+
("GitHub user-to-server token", Severity.CRITICAL, r"\bghu_[A-Za-z0-9]{36,}\b", "CWE-798"),
|
|
54
|
+
("GitHub refresh token", Severity.CRITICAL, r"\bghr_[A-Za-z0-9]{36,}\b", "CWE-798"),
|
|
55
|
+
("Stripe live secret key", Severity.CRITICAL, r"\bsk_live_[A-Za-z0-9]{24,}\b", "CWE-798"),
|
|
56
|
+
("Stripe restricted key (live)", Severity.CRITICAL, r"\brk_live_[A-Za-z0-9]{24,}\b", "CWE-798"),
|
|
57
|
+
("Stripe test secret key", Severity.MEDIUM, r"\bsk_test_[A-Za-z0-9]{24,}\b", "CWE-798"),
|
|
58
|
+
("Anthropic API key", Severity.CRITICAL, r"\bsk-ant-(?:api|sid)\d+-[A-Za-z0-9_-]{20,}\b", "CWE-798"),
|
|
59
|
+
("OpenAI API key", Severity.CRITICAL, r"\bsk-(?:proj-)?[A-Za-z0-9_-]{40,}\b", "CWE-798"),
|
|
60
|
+
("Slack bot token", Severity.CRITICAL, r"\bxoxb-[A-Za-z0-9-]{10,}\b", "CWE-798"),
|
|
61
|
+
("Slack user token", Severity.CRITICAL, r"\bxoxp-[A-Za-z0-9-]{10,}\b", "CWE-798"),
|
|
62
|
+
("Slack workspace token", Severity.CRITICAL, r"\bxoxa-[A-Za-z0-9-]{10,}\b", "CWE-798"),
|
|
63
|
+
(
|
|
64
|
+
"Slack webhook URL",
|
|
65
|
+
Severity.HIGH,
|
|
66
|
+
r"\bhttps://hooks\.slack\.com/services/T[A-Z0-9]{8,}/B[A-Z0-9]{8,}/[A-Za-z0-9]{24,}\b",
|
|
67
|
+
"CWE-798",
|
|
68
|
+
),
|
|
69
|
+
(
|
|
70
|
+
"Discord bot token",
|
|
71
|
+
Severity.CRITICAL,
|
|
72
|
+
r"\b[MN][A-Za-z0-9_-]{23,28}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{27,}\b",
|
|
73
|
+
"CWE-798",
|
|
74
|
+
),
|
|
75
|
+
("Google API key", Severity.HIGH, r"\bAIza[A-Za-z0-9_-]{35}\b", "CWE-798"),
|
|
76
|
+
("Google OAuth refresh token", Severity.HIGH, r"\b1//[A-Za-z0-9_-]{40,}\b", "CWE-798"),
|
|
77
|
+
("Twilio account SID", Severity.HIGH, r"\bAC[A-Za-z0-9]{32}\b", "CWE-798"),
|
|
78
|
+
("Twilio auth token", Severity.CRITICAL, r"\bSK[A-Za-z0-9]{32}\b", "CWE-798"),
|
|
79
|
+
("Mailgun API key", Severity.CRITICAL, r"\bkey-[a-f0-9]{32}\b", "CWE-798"),
|
|
80
|
+
("SendGrid API key", Severity.CRITICAL, r"\bSG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}\b", "CWE-798"),
|
|
81
|
+
("Square access token", Severity.CRITICAL, r"\bsq0(?:atp|csp)-[A-Za-z0-9_-]{22,}\b", "CWE-798"),
|
|
82
|
+
("npm access token", Severity.CRITICAL, r"\bnpm_[A-Za-z0-9]{36,}\b", "CWE-798"),
|
|
83
|
+
("PyPI API token", Severity.CRITICAL, r"\bpypi-[A-Za-z0-9_-]{50,}\b", "CWE-798"),
|
|
84
|
+
("Cloudflare API token", Severity.CRITICAL, r"\b[A-Za-z0-9_-]{40}\b(?=.*cloudflare)", "CWE-798"),
|
|
85
|
+
# Private keys — the regex marker strings below match secret-scanner
|
|
86
|
+
# patterns by design; this is the detector library, not real keys.
|
|
87
|
+
("RSA private key", Severity.CRITICAL, "-" * 5 + "BEGIN RSA PRIVATE KEY" + "-" * 5, "CWE-321"), # gitleaks:allow
|
|
88
|
+
(
|
|
89
|
+
"OpenSSH private key",
|
|
90
|
+
Severity.CRITICAL,
|
|
91
|
+
"-" * 5 + "BEGIN OPENSSH PRIVATE KEY" + "-" * 5,
|
|
92
|
+
"CWE-321",
|
|
93
|
+
), # gitleaks:allow
|
|
94
|
+
("EC private key", Severity.CRITICAL, "-" * 5 + "BEGIN EC PRIVATE KEY" + "-" * 5, "CWE-321"), # gitleaks:allow
|
|
95
|
+
("DSA private key", Severity.CRITICAL, "-" * 5 + "BEGIN DSA PRIVATE KEY" + "-" * 5, "CWE-321"), # gitleaks:allow
|
|
96
|
+
(
|
|
97
|
+
"PGP private key",
|
|
98
|
+
Severity.CRITICAL,
|
|
99
|
+
"-" * 5 + "BEGIN PGP PRIVATE KEY BLOCK" + "-" * 5,
|
|
100
|
+
"CWE-321",
|
|
101
|
+
), # gitleaks:allow
|
|
102
|
+
(
|
|
103
|
+
"Generic PEM private key",
|
|
104
|
+
Severity.CRITICAL,
|
|
105
|
+
"-" * 5 + "BEGIN PRIVATE KEY" + "-" * 5,
|
|
106
|
+
"CWE-321",
|
|
107
|
+
), # gitleaks:allow
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
# Context-based regex (field-name + value) — emits HIGH when value looks
|
|
111
|
+
# like a credential.
|
|
112
|
+
CONTEXT_PATTERNS = [
|
|
113
|
+
# Common YAML / .env / config keys with a secret-shaped value
|
|
114
|
+
(
|
|
115
|
+
r"""(?P<key>(?:aws_secret_access_key|password|passwd|api[_-]?key|secret[_-]?key|"""
|
|
116
|
+
r"""auth[_-]?token|access[_-]?token|jwt[_-]?secret|signing[_-]?secret|"""
|
|
117
|
+
r"""client[_-]?secret|private[_-]?key|encryption[_-]?key))[\s:=]+['"]?"""
|
|
118
|
+
r"""(?P<val>[A-Za-z0-9+/=_-]{16,})['"]?""",
|
|
119
|
+
"Credential-shaped value in known field",
|
|
120
|
+
Severity.HIGH,
|
|
121
|
+
),
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
# Placeholder-detection — these don't trigger a finding
|
|
125
|
+
PLACEHOLDER_MARKERS = (
|
|
126
|
+
"<",
|
|
127
|
+
">",
|
|
128
|
+
"EXAMPLE",
|
|
129
|
+
"PLACEHOLDER",
|
|
130
|
+
"YOUR_",
|
|
131
|
+
"YOUR-",
|
|
132
|
+
"XXXX",
|
|
133
|
+
"XXX-",
|
|
134
|
+
"TODO",
|
|
135
|
+
"FIXME",
|
|
136
|
+
"REDACTED",
|
|
137
|
+
"REPLACEME",
|
|
138
|
+
"REPLACE-ME",
|
|
139
|
+
"REPLACE_ME",
|
|
140
|
+
"CHANGEME",
|
|
141
|
+
"FAKE",
|
|
142
|
+
"DUMMY",
|
|
143
|
+
"SAMPLE",
|
|
144
|
+
"TEST_KEY_",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Extensions / directories to skip
|
|
148
|
+
SKIP_DIRS = {
|
|
149
|
+
"node_modules",
|
|
150
|
+
".git",
|
|
151
|
+
"dist",
|
|
152
|
+
"build",
|
|
153
|
+
"target",
|
|
154
|
+
".cache",
|
|
155
|
+
".pnpm-store",
|
|
156
|
+
".venv",
|
|
157
|
+
"venv",
|
|
158
|
+
"__pycache__",
|
|
159
|
+
".astro",
|
|
160
|
+
".next",
|
|
161
|
+
".nuxt",
|
|
162
|
+
"vendor",
|
|
163
|
+
}
|
|
164
|
+
TEXT_EXTS = {
|
|
165
|
+
".py",
|
|
166
|
+
".js",
|
|
167
|
+
".ts",
|
|
168
|
+
".jsx",
|
|
169
|
+
".tsx",
|
|
170
|
+
".mjs",
|
|
171
|
+
".cjs",
|
|
172
|
+
".go",
|
|
173
|
+
".rs",
|
|
174
|
+
".rb",
|
|
175
|
+
".php",
|
|
176
|
+
".java",
|
|
177
|
+
".kt",
|
|
178
|
+
".scala",
|
|
179
|
+
".c",
|
|
180
|
+
".cpp",
|
|
181
|
+
".cc",
|
|
182
|
+
".h",
|
|
183
|
+
".hpp",
|
|
184
|
+
".cs",
|
|
185
|
+
".swift",
|
|
186
|
+
".m",
|
|
187
|
+
".sh",
|
|
188
|
+
".bash",
|
|
189
|
+
".zsh",
|
|
190
|
+
".fish",
|
|
191
|
+
".md",
|
|
192
|
+
".rst",
|
|
193
|
+
".txt",
|
|
194
|
+
".yml",
|
|
195
|
+
".yaml",
|
|
196
|
+
".toml",
|
|
197
|
+
".json",
|
|
198
|
+
".jsonc",
|
|
199
|
+
".ini",
|
|
200
|
+
".cfg",
|
|
201
|
+
".conf",
|
|
202
|
+
".env",
|
|
203
|
+
".envrc",
|
|
204
|
+
".xml",
|
|
205
|
+
".html",
|
|
206
|
+
".vue",
|
|
207
|
+
".svelte",
|
|
208
|
+
".dockerfile",
|
|
209
|
+
}
|
|
210
|
+
MAX_FILE_SIZE = 5 * 1024 * 1024 # 5 MB
|
|
211
|
+
TEST_DIRS = {"tests", "test", "__tests__", "spec", "specs"}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def shannon_entropy(s: str) -> float:
|
|
215
|
+
if not s:
|
|
216
|
+
return 0.0
|
|
217
|
+
counts = Counter(s)
|
|
218
|
+
total = len(s)
|
|
219
|
+
return -sum((c / total) * math.log2(c / total) for c in counts.values())
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def looks_like_placeholder(value: str) -> bool:
|
|
223
|
+
upper = value.upper()
|
|
224
|
+
return any(marker in upper for marker in PLACEHOLDER_MARKERS)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def redact(s: str, head: int = 4, tail: int = 4) -> str:
|
|
228
|
+
if len(s) <= head + tail + 3:
|
|
229
|
+
return "***"
|
|
230
|
+
return f"{s[:head]}...{s[-tail:]}"
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def should_skip_path(path: Path, include_tests: bool, skip_dirs: set) -> bool:
|
|
234
|
+
parts = set(path.parts)
|
|
235
|
+
if parts & skip_dirs:
|
|
236
|
+
return True
|
|
237
|
+
if not include_tests and parts & TEST_DIRS:
|
|
238
|
+
return True
|
|
239
|
+
return False
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def scan_file(
|
|
243
|
+
file_path: Path,
|
|
244
|
+
repo_root: Path,
|
|
245
|
+
include_provider: bool = True,
|
|
246
|
+
include_context: bool = True,
|
|
247
|
+
entropy_threshold: float = 4.5,
|
|
248
|
+
) -> list[Finding]:
|
|
249
|
+
findings = []
|
|
250
|
+
try:
|
|
251
|
+
if file_path.stat().st_size > MAX_FILE_SIZE:
|
|
252
|
+
return findings
|
|
253
|
+
text = file_path.read_text(encoding="utf-8", errors="ignore")
|
|
254
|
+
except (OSError, ValueError):
|
|
255
|
+
return findings
|
|
256
|
+
|
|
257
|
+
try:
|
|
258
|
+
rel = str(file_path.relative_to(repo_root))
|
|
259
|
+
except ValueError:
|
|
260
|
+
rel = str(file_path)
|
|
261
|
+
|
|
262
|
+
if include_provider:
|
|
263
|
+
for name, sev, pattern, control in PROVIDER_REGEXES:
|
|
264
|
+
for m in re.finditer(pattern, text):
|
|
265
|
+
matched = m.group(0)
|
|
266
|
+
if looks_like_placeholder(matched):
|
|
267
|
+
continue
|
|
268
|
+
line_no = text[: m.start()].count("\n") + 1
|
|
269
|
+
findings.append(
|
|
270
|
+
Finding(
|
|
271
|
+
skill_id=SKILL_ID,
|
|
272
|
+
title=f"{name} in {rel}:{line_no}",
|
|
273
|
+
severity=sev,
|
|
274
|
+
target=f"{rel}:{line_no}",
|
|
275
|
+
detail=(
|
|
276
|
+
f"File {rel} line {line_no} contains a string "
|
|
277
|
+
f"matching the {name} pattern. Matched value (redacted): "
|
|
278
|
+
f"`{redact(matched)}`. If this is a real credential it "
|
|
279
|
+
"is now in git history; rotate immediately and audit "
|
|
280
|
+
"logs for unauthorized use."
|
|
281
|
+
),
|
|
282
|
+
remediation=(
|
|
283
|
+
f"Move {name} out of source. Set it in an environment "
|
|
284
|
+
"variable, secrets manager (AWS Secrets Manager, GCP "
|
|
285
|
+
"Secret Manager, HashiCorp Vault, Doppler), or "
|
|
286
|
+
"runtime-provisioned secret. See references/PLAYBOOK.md "
|
|
287
|
+
"for per-language patterns. Then rotate the credential."
|
|
288
|
+
),
|
|
289
|
+
cwe_id=control,
|
|
290
|
+
affected_control="OWASP A07:2021",
|
|
291
|
+
evidence=(
|
|
292
|
+
("file", rel),
|
|
293
|
+
("line", line_no),
|
|
294
|
+
("pattern", name),
|
|
295
|
+
("redacted_match", redact(matched)),
|
|
296
|
+
),
|
|
297
|
+
)
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
if include_context:
|
|
301
|
+
for pattern, label, sev in CONTEXT_PATTERNS:
|
|
302
|
+
for m in re.finditer(pattern, text, re.IGNORECASE | re.MULTILINE):
|
|
303
|
+
value = m.group("val")
|
|
304
|
+
if looks_like_placeholder(value):
|
|
305
|
+
continue
|
|
306
|
+
if shannon_entropy(value) < entropy_threshold:
|
|
307
|
+
continue
|
|
308
|
+
line_no = text[: m.start()].count("\n") + 1
|
|
309
|
+
key = m.group("key")
|
|
310
|
+
findings.append(
|
|
311
|
+
Finding(
|
|
312
|
+
skill_id=SKILL_ID,
|
|
313
|
+
title=f"High-entropy credential in {key} field at {rel}:{line_no}",
|
|
314
|
+
severity=sev,
|
|
315
|
+
target=f"{rel}:{line_no}",
|
|
316
|
+
detail=(
|
|
317
|
+
f"File {rel} line {line_no} assigns a high-entropy "
|
|
318
|
+
f"value (Shannon entropy ≥ {entropy_threshold}) to "
|
|
319
|
+
f"a {key} field. Redacted: `{redact(value)}`. "
|
|
320
|
+
"Likely a real credential."
|
|
321
|
+
),
|
|
322
|
+
remediation=(
|
|
323
|
+
"Move the value out of source. Replace with environment-"
|
|
324
|
+
"variable lookup at startup. Rotate the credential."
|
|
325
|
+
),
|
|
326
|
+
cwe_id="CWE-798",
|
|
327
|
+
affected_control="OWASP A07:2021",
|
|
328
|
+
evidence=(("file", rel), ("line", line_no), ("field", key), ("redacted_match", redact(value))),
|
|
329
|
+
)
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
return findings
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def walk_repo(root: Path, include_tests: bool, extra_exclude: list[str]) -> list[Path]:
|
|
336
|
+
out = []
|
|
337
|
+
# Build exclude-match list
|
|
338
|
+
exclude_patterns = [re.compile(re.escape(p).replace(r"\*", ".*")) for p in extra_exclude]
|
|
339
|
+
for p in root.rglob("*"):
|
|
340
|
+
if not p.is_file():
|
|
341
|
+
continue
|
|
342
|
+
if should_skip_path(p, include_tests, SKIP_DIRS):
|
|
343
|
+
continue
|
|
344
|
+
if p.suffix.lower() not in TEXT_EXTS and p.name.lower() != "dockerfile":
|
|
345
|
+
continue
|
|
346
|
+
rel = str(p)
|
|
347
|
+
if any(ep.search(rel) for ep in exclude_patterns):
|
|
348
|
+
continue
|
|
349
|
+
out.append(p)
|
|
350
|
+
return out
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def main(argv: list[str] | None = None) -> int:
|
|
354
|
+
parser = argparse.ArgumentParser(description="Hardcoded-secrets scanner")
|
|
355
|
+
parser.add_argument("path", type=Path)
|
|
356
|
+
parser.add_argument("--output", default=None)
|
|
357
|
+
parser.add_argument("--format", choices=("json", "jsonl", "markdown"), default="markdown")
|
|
358
|
+
parser.add_argument("--min-severity", choices=("critical", "high", "medium", "low", "info"), default="info")
|
|
359
|
+
parser.add_argument(
|
|
360
|
+
"--include-tests", action="store_true", help="Include test directories in scan (default: excluded)"
|
|
361
|
+
)
|
|
362
|
+
parser.add_argument("--exclude", action="append", default=[], help="Skip files matching glob (repeatable)")
|
|
363
|
+
parser.add_argument(
|
|
364
|
+
"--entropy-only", action="store_true", help="Skip provider-regex pass; only emit entropy-based findings"
|
|
365
|
+
)
|
|
366
|
+
parser.add_argument("--entropy-threshold", type=float, default=4.5)
|
|
367
|
+
args = parser.parse_args(argv)
|
|
368
|
+
|
|
369
|
+
root = args.path.resolve()
|
|
370
|
+
if not root.exists():
|
|
371
|
+
sys.stderr.write(f"ERROR: path does not exist: {root}\n")
|
|
372
|
+
return 2
|
|
373
|
+
|
|
374
|
+
files = walk_repo(root, args.include_tests, args.exclude)
|
|
375
|
+
findings: list[Finding] = []
|
|
376
|
+
for f in files:
|
|
377
|
+
findings.extend(
|
|
378
|
+
scan_file(
|
|
379
|
+
f,
|
|
380
|
+
root,
|
|
381
|
+
include_provider=not args.entropy_only,
|
|
382
|
+
include_context=True,
|
|
383
|
+
entropy_threshold=args.entropy_threshold,
|
|
384
|
+
)
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
floor = Severity(args.min_severity)
|
|
388
|
+
findings = [f for f in findings if f.severity.numeric >= floor.numeric]
|
|
389
|
+
|
|
390
|
+
emit(findings, args.output, args.format, str(root))
|
|
391
|
+
return exit_code(findings)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
if __name__ == "__main__":
|
|
395
|
+
sys.exit(main())
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tracing-transitive-vulnerabilities
|
|
3
|
+
description: |
|
|
4
|
+
Build a dependency-tree map of a project (npm or Python) and trace
|
|
5
|
+
the path from each known-vulnerable transitive package back to one
|
|
6
|
+
or more direct dependencies. Identifies which direct-dep bump would
|
|
7
|
+
clear the most findings at once (highest-leverage upgrade), which
|
|
8
|
+
vulnerabilities are unreachable through any version bump and
|
|
9
|
+
require overrides or vendor-patch, and which CVEs sit at deep
|
|
10
|
+
transitive depth (3+ levels from a direct dep) where blast-radius
|
|
11
|
+
triage is hardest.
|
|
12
|
+
Use when: a multi-finding audit produces noise and you need to
|
|
13
|
+
prioritize, when planning a major dependency refresh, after an
|
|
14
|
+
upstream package compromise hits your tree (e.g. event-stream
|
|
15
|
+
flatmap-stream), or when an audit shows findings that automated
|
|
16
|
+
fix commands cannot auto-resolve.
|
|
17
|
+
Threshold: any HIGH or CRITICAL CVE reachable only through
|
|
18
|
+
transitive paths that no single direct-dep bump can clear.
|
|
19
|
+
Trigger with: "trace transitive vulns", "find dep paths", "SBOM
|
|
20
|
+
vuln trace", "which direct dep pulls this CVE".
|
|
21
|
+
allowed-tools:
|
|
22
|
+
- Read
|
|
23
|
+
- Bash(npm:*)
|
|
24
|
+
- Bash(pip:*)
|
|
25
|
+
- Bash(pip-audit:*)
|
|
26
|
+
- Bash(python3:*)
|
|
27
|
+
- Bash(pipdeptree:*)
|
|
28
|
+
- Glob
|
|
29
|
+
disallowed-tools:
|
|
30
|
+
- Bash(rm:*)
|
|
31
|
+
- Bash(curl:*)
|
|
32
|
+
- Bash(wget:*)
|
|
33
|
+
- Write(.env)
|
|
34
|
+
- Edit(.env)
|
|
35
|
+
- Bash(npm install:*)
|
|
36
|
+
- Bash(pip install:*)
|
|
37
|
+
version: 3.0.0-dev
|
|
38
|
+
author: Jeremy Longshore <jeremy@intentsolutions.io>
|
|
39
|
+
license: MIT
|
|
40
|
+
compatibility: Designed for Claude Code
|
|
41
|
+
tags:
|
|
42
|
+
- security
|
|
43
|
+
- sbom
|
|
44
|
+
- transitive-dependency
|
|
45
|
+
- dependency-graph
|
|
46
|
+
- pentest
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
# Tracing Transitive Vulnerabilities
|
|
50
|
+
|
|
51
|
+
## Overview
|
|
52
|
+
|
|
53
|
+
The auditing-npm-dependencies and auditing-python-dependencies skills
|
|
54
|
+
each surface CVEs, but they don't answer the question that actually
|
|
55
|
+
decides remediation order: **which of these findings can I clear by
|
|
56
|
+
bumping ONE direct dep, and which require deeper intervention?**
|
|
57
|
+
|
|
58
|
+
That question is the core of supply-chain triage. A high-CVSS CVE
|
|
59
|
+
in `lodash@4.17.4` is alarming on first read. If it's pulled in by
|
|
60
|
+
five different direct deps, the right fix may not be to bump any of
|
|
61
|
+
them — it may be a single root-level `overrides` entry pinning
|
|
62
|
+
`lodash@^4.17.21`. The triage discussion goes very differently when
|
|
63
|
+
you can quote: "this CVE is reachable via 5 paths, all of which
|
|
64
|
+
flow through `webpack`, which has a fixed version available." That
|
|
65
|
+
shifts a project-wide panic to a one-line PR.
|
|
66
|
+
|
|
67
|
+
This skill walks the project's dependency graph (via `npm ls`,
|
|
68
|
+
`pipdeptree`, or equivalent), intersects it with the CVE findings
|
|
69
|
+
already produced by the per-language audit skills, and emits a
|
|
70
|
+
trace report that includes:
|
|
71
|
+
|
|
72
|
+
- For each CVE: the full path(s) from direct dep → ... → vulnerable package
|
|
73
|
+
- For each direct dep: the count of CVEs reachable through it
|
|
74
|
+
- "Highest-leverage upgrade" recommendation — the single direct-dep
|
|
75
|
+
bump that clears the most findings at once
|
|
76
|
+
- "Unreachable" findings — CVEs whose vulnerable version range is
|
|
77
|
+
forced by EVERY parent in the path, requiring overrides or
|
|
78
|
+
vendor-patch
|
|
79
|
+
- "Deep transitive" findings (≥3 levels from a direct dep) — these
|
|
80
|
+
are highest-risk for hidden surprises because the relationship to
|
|
81
|
+
your code is most opaque
|
|
82
|
+
|
|
83
|
+
## When the skill produces findings
|
|
84
|
+
|
|
85
|
+
| Finding | Severity | Threshold | Affected control |
|
|
86
|
+
|---|---|---|---|
|
|
87
|
+
| Critical CVE at deep transitive depth (≥3) | **HIGH** | Depth ≥3 + severity CRITICAL — blast radius unclear | CWE-1395 |
|
|
88
|
+
| High CVE reachable only via overrides | **HIGH** | No direct-dep version clears the finding | CWE-1395 |
|
|
89
|
+
| Multi-CVE direct-dep hotspot | **MEDIUM** | Single direct dep is ancestor for ≥3 separate CVEs | (informational) |
|
|
90
|
+
| Direct-dep bump clears N findings | **INFO** | Reports the recommended bump | (operational) |
|
|
91
|
+
| Unreachable CVE (no fix in any reachable version) | **HIGH** | Finding has no fix-available across the whole graph | CWE-1395 |
|
|
92
|
+
| Circular dep with CVE | **MEDIUM** | Cycle in the dep graph involves a vulnerable package | (operational) |
|
|
93
|
+
|
|
94
|
+
## Prerequisites
|
|
95
|
+
|
|
96
|
+
- Python 3.9+
|
|
97
|
+
- npm or pip available (depending on project type)
|
|
98
|
+
- `pipdeptree` (optional but recommended for Python; falls back to
|
|
99
|
+
`pip show` chains if absent — slower but works)
|
|
100
|
+
- An existing audit JSON file from auditing-npm-dependencies or
|
|
101
|
+
auditing-python-dependencies, OR the willingness to let this
|
|
102
|
+
skill run those audits itself
|
|
103
|
+
|
|
104
|
+
## Instructions
|
|
105
|
+
|
|
106
|
+
### Step 1 — Identify the scan target
|
|
107
|
+
|
|
108
|
+
The skill auto-detects whether the project is npm-flavored
|
|
109
|
+
(`package.json` + `node_modules`) or Python-flavored
|
|
110
|
+
(`pyproject.toml` / `requirements.txt` / installed venv).
|
|
111
|
+
|
|
112
|
+
### Step 2 — Acquire audit data
|
|
113
|
+
|
|
114
|
+
The skill can run the per-language audit itself, or consume a
|
|
115
|
+
pre-produced audit JSON:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Self-running mode
|
|
119
|
+
python3 ./scripts/trace_vulns.py /path/to/project
|
|
120
|
+
|
|
121
|
+
# Pre-produced mode (faster on re-runs)
|
|
122
|
+
python3 plugins/security/penetration-tester/skills/auditing-npm-dependencies/scripts/audit_npm.py \
|
|
123
|
+
/path/to/project --format json --output /tmp/audit.json
|
|
124
|
+
python3 ./scripts/trace_vulns.py /path/to/project --audit-input /tmp/audit.json
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Step 3 — Walk the graph
|
|
128
|
+
|
|
129
|
+
For npm, the skill calls `npm ls --json --all` to enumerate the
|
|
130
|
+
full installed tree. For Python, it calls `pipdeptree --json-tree`
|
|
131
|
+
or falls back to recursive `pip show`.
|
|
132
|
+
|
|
133
|
+
The resulting graph is intersected with the per-package CVE
|
|
134
|
+
findings, producing a path list for each vulnerability.
|
|
135
|
+
|
|
136
|
+
### Step 4 — Triage by leverage
|
|
137
|
+
|
|
138
|
+
The report ranks findings by:
|
|
139
|
+
|
|
140
|
+
1. Severity (CRITICAL → HIGH → MEDIUM → LOW)
|
|
141
|
+
2. Depth in the graph (deeper = more uncertain blast radius)
|
|
142
|
+
3. Number of reachable paths (more paths = harder to clear with a
|
|
143
|
+
single bump)
|
|
144
|
+
|
|
145
|
+
For each direct dep, the report aggregates:
|
|
146
|
+
|
|
147
|
+
- Total reachable CVE count
|
|
148
|
+
- Severity breakdown
|
|
149
|
+
- Suggested bump version (if available)
|
|
150
|
+
|
|
151
|
+
### Step 5 — Plan the upgrades
|
|
152
|
+
|
|
153
|
+
Use the "highest-leverage upgrade" recommendation as the first PR.
|
|
154
|
+
Then re-run this skill against the post-upgrade state to confirm
|
|
155
|
+
how many findings dropped. Iterate until the residual is overrides /
|
|
156
|
+
vendor work only.
|
|
157
|
+
|
|
158
|
+
## Examples
|
|
159
|
+
|
|
160
|
+
### Example 1 — Triage a noisy audit
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# Run base audit
|
|
164
|
+
python3 plugins/security/penetration-tester/skills/auditing-npm-dependencies/scripts/audit_npm.py \
|
|
165
|
+
. --format json --output /tmp/npm-audit.json
|
|
166
|
+
|
|
167
|
+
# Trace
|
|
168
|
+
python3 ./scripts/trace_vulns.py . --audit-input /tmp/npm-audit.json \
|
|
169
|
+
--format markdown --output /tmp/trace.md
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`/tmp/trace.md` is human-readable: per-CVE path lists + recommended
|
|
173
|
+
bumps + leverage analysis.
|
|
174
|
+
|
|
175
|
+
### Example 2 — Highest-leverage upgrade discovery
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
python3 ./scripts/trace_vulns.py . --format json --leverage-only \
|
|
179
|
+
| jq '.[] | select(.cve_count >= 3)'
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Surfaces direct deps that, if bumped, would clear ≥3 CVEs at once.
|
|
183
|
+
|
|
184
|
+
### Example 3 — Deep-transitive risk report
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
python3 ./scripts/trace_vulns.py . --min-depth 3 --format markdown \
|
|
188
|
+
--output deep-trace.md
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Limits output to findings at depth ≥3 from a direct dep — the
|
|
192
|
+
hardest-to-triage class.
|
|
193
|
+
|
|
194
|
+
## Output
|
|
195
|
+
|
|
196
|
+
JSON / JSONL / Markdown per `lib/report.py`. Exit codes: 0 clean,
|
|
197
|
+
1 high/critical, 2 error.
|
|
198
|
+
|
|
199
|
+
Each Finding includes:
|
|
200
|
+
|
|
201
|
+
- `id` — `trace::<cve-id>::<vulnerable-package>`
|
|
202
|
+
- `severity` — re-derived based on depth + reachability
|
|
203
|
+
- `category` — `transitive-trace`
|
|
204
|
+
- `summary` — short description of the path situation
|
|
205
|
+
- `evidence` — original CVE, dep path(s), depth, parent direct-deps,
|
|
206
|
+
recommended bump
|
|
207
|
+
- `references` — link back to the source audit finding
|
|
208
|
+
|
|
209
|
+
A "leverage report" section in markdown output lists the
|
|
210
|
+
top-N direct-dep bumps ranked by aggregate CVE-clearance count.
|
|
211
|
+
|
|
212
|
+
## Error Handling
|
|
213
|
+
|
|
214
|
+
- **npm ls fails to produce a complete tree** (lockfile out of sync)
|
|
215
|
+
→ emits an INFO Finding flagging the desync and proceeds with
|
|
216
|
+
partial data.
|
|
217
|
+
- **pipdeptree not installed** → falls back to `pip show` recursion;
|
|
218
|
+
emits INFO finding recommending pipdeptree install for accuracy.
|
|
219
|
+
- **No audit findings input AND no audit tool available** → exits
|
|
220
|
+
2 with operational error advising the operator to provide an
|
|
221
|
+
audit JSON via --audit-input.
|
|
222
|
+
- **Graph contains cycles** → cycles are detected and broken; each
|
|
223
|
+
package in the cycle is reported once at its shallowest depth.
|
|
224
|
+
|
|
225
|
+
## Resources
|
|
226
|
+
|
|
227
|
+
- `references/THEORY.md` — Why deep transitive deps are
|
|
228
|
+
disproportionately risky, dependency-graph traversal theory,
|
|
229
|
+
SBOM (Software Bill of Materials) standards (CycloneDX, SPDX
|
|
230
|
+
3.0), reachability theory for vulnerability analysis,
|
|
231
|
+
exploit-prediction-scoring-system (EPSS) integration plans
|
|
232
|
+
- `references/PLAYBOOK.md` — Per-runtime trace patterns, SBOM
|
|
233
|
+
generation patterns (cyclonedx-cli, syft, anchore), graph-based
|
|
234
|
+
upgrade planning, when to override vs vendor-patch, integration
|
|
235
|
+
with the per-language audit skills
|