@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.
Files changed (112) hide show
  1. package/.claude-plugin/plugin.json +8 -3
  2. package/README.md +8 -0
  3. package/commands/pentest.md +5 -0
  4. package/package.json +8 -3
  5. package/skills/analyzing-tls-config/SKILL.md +221 -0
  6. package/skills/analyzing-tls-config/references/AUTHORIZATION.md +133 -0
  7. package/skills/analyzing-tls-config/references/PLAYBOOK.md +267 -0
  8. package/skills/analyzing-tls-config/references/THEORY.md +128 -0
  9. package/skills/analyzing-tls-config/scripts/analyze_tls.py +415 -0
  10. package/skills/auditing-cors-policy/SKILL.md +186 -0
  11. package/skills/auditing-cors-policy/references/PLAYBOOK.md +220 -0
  12. package/skills/auditing-cors-policy/references/THEORY.md +142 -0
  13. package/skills/auditing-cors-policy/scripts/audit_cors.py +350 -0
  14. package/skills/auditing-npm-dependencies/SKILL.md +254 -0
  15. package/skills/auditing-npm-dependencies/references/PLAYBOOK.md +175 -0
  16. package/skills/auditing-npm-dependencies/references/THEORY.md +122 -0
  17. package/skills/auditing-npm-dependencies/scripts/audit_npm.py +408 -0
  18. package/skills/auditing-python-dependencies/SKILL.md +251 -0
  19. package/skills/auditing-python-dependencies/references/PLAYBOOK.md +193 -0
  20. package/skills/auditing-python-dependencies/references/THEORY.md +122 -0
  21. package/skills/auditing-python-dependencies/scripts/audit_python.py +459 -0
  22. package/skills/checking-http-security-headers/SKILL.md +176 -0
  23. package/skills/checking-http-security-headers/references/PLAYBOOK.md +212 -0
  24. package/skills/checking-http-security-headers/references/THEORY.md +137 -0
  25. package/skills/checking-http-security-headers/scripts/check_headers.py +362 -0
  26. package/skills/checking-license-compliance/SKILL.md +225 -0
  27. package/skills/checking-license-compliance/references/PLAYBOOK.md +161 -0
  28. package/skills/checking-license-compliance/references/THEORY.md +152 -0
  29. package/skills/checking-license-compliance/scripts/check_licenses.py +461 -0
  30. package/skills/composing-vulnerability-report/SKILL.md +212 -0
  31. package/skills/composing-vulnerability-report/references/PLAYBOOK.md +180 -0
  32. package/skills/composing-vulnerability-report/references/THEORY.md +178 -0
  33. package/skills/composing-vulnerability-report/scripts/compose_report.py +396 -0
  34. package/skills/confirming-pentest-authorization/SKILL.md +247 -0
  35. package/skills/confirming-pentest-authorization/references/PLAYBOOK.md +189 -0
  36. package/skills/confirming-pentest-authorization/references/THEORY.md +167 -0
  37. package/skills/confirming-pentest-authorization/scripts/check_authorization.py +457 -0
  38. package/skills/defining-pentest-scope/SKILL.md +227 -0
  39. package/skills/defining-pentest-scope/references/PLAYBOOK.md +238 -0
  40. package/skills/defining-pentest-scope/references/THEORY.md +170 -0
  41. package/skills/defining-pentest-scope/scripts/define_scope.py +472 -0
  42. package/skills/detecting-command-injection-patterns/SKILL.md +144 -0
  43. package/skills/detecting-command-injection-patterns/references/PLAYBOOK.md +302 -0
  44. package/skills/detecting-command-injection-patterns/references/THEORY.md +206 -0
  45. package/skills/detecting-command-injection-patterns/scripts/scan_cmdi.py +290 -0
  46. package/skills/detecting-debug-endpoints/SKILL.md +207 -0
  47. package/skills/detecting-debug-endpoints/references/PLAYBOOK.md +402 -0
  48. package/skills/detecting-debug-endpoints/references/THEORY.md +218 -0
  49. package/skills/detecting-debug-endpoints/scripts/probe_debug.py +518 -0
  50. package/skills/detecting-directory-listing/SKILL.md +206 -0
  51. package/skills/detecting-directory-listing/references/PLAYBOOK.md +277 -0
  52. package/skills/detecting-directory-listing/references/THEORY.md +203 -0
  53. package/skills/detecting-directory-listing/scripts/probe_directory_listing.py +180 -0
  54. package/skills/detecting-eval-exec-usage/SKILL.md +128 -0
  55. package/skills/detecting-eval-exec-usage/references/PLAYBOOK.md +306 -0
  56. package/skills/detecting-eval-exec-usage/references/THEORY.md +159 -0
  57. package/skills/detecting-eval-exec-usage/scripts/scan_eval.py +223 -0
  58. package/skills/detecting-exposed-secrets-files/SKILL.md +179 -0
  59. package/skills/detecting-exposed-secrets-files/references/PLAYBOOK.md +274 -0
  60. package/skills/detecting-exposed-secrets-files/references/THEORY.md +174 -0
  61. package/skills/detecting-exposed-secrets-files/scripts/probe_secrets.py +207 -0
  62. package/skills/detecting-insecure-deserialization/SKILL.md +148 -0
  63. package/skills/detecting-insecure-deserialization/references/PLAYBOOK.md +333 -0
  64. package/skills/detecting-insecure-deserialization/references/THEORY.md +199 -0
  65. package/skills/detecting-insecure-deserialization/scripts/scan_deserialization.py +250 -0
  66. package/skills/detecting-sql-injection-patterns/SKILL.md +161 -0
  67. package/skills/detecting-sql-injection-patterns/references/PLAYBOOK.md +317 -0
  68. package/skills/detecting-sql-injection-patterns/references/THEORY.md +261 -0
  69. package/skills/detecting-sql-injection-patterns/scripts/scan_sqli.py +354 -0
  70. package/skills/detecting-ssl-cert-issues/SKILL.md +182 -0
  71. package/skills/detecting-ssl-cert-issues/references/PLAYBOOK.md +203 -0
  72. package/skills/detecting-ssl-cert-issues/references/THEORY.md +133 -0
  73. package/skills/detecting-ssl-cert-issues/scripts/check_cert_chain.py +481 -0
  74. package/skills/detecting-weak-cryptography/SKILL.md +147 -0
  75. package/skills/detecting-weak-cryptography/references/PLAYBOOK.md +466 -0
  76. package/skills/detecting-weak-cryptography/references/THEORY.md +194 -0
  77. package/skills/detecting-weak-cryptography/scripts/scan_weak_crypto.py +417 -0
  78. package/skills/fingerprinting-server-software/SKILL.md +191 -0
  79. package/skills/fingerprinting-server-software/references/PLAYBOOK.md +337 -0
  80. package/skills/fingerprinting-server-software/references/THEORY.md +183 -0
  81. package/skills/fingerprinting-server-software/scripts/fingerprint_server.py +347 -0
  82. package/skills/generating-executive-summary/SKILL.md +261 -0
  83. package/skills/generating-executive-summary/references/PLAYBOOK.md +201 -0
  84. package/skills/generating-executive-summary/references/THEORY.md +195 -0
  85. package/skills/generating-executive-summary/scripts/exec_summary.py +538 -0
  86. package/skills/mapping-findings-to-owasp-top10/SKILL.md +235 -0
  87. package/skills/mapping-findings-to-owasp-top10/references/PLAYBOOK.md +193 -0
  88. package/skills/mapping-findings-to-owasp-top10/references/THEORY.md +160 -0
  89. package/skills/mapping-findings-to-owasp-top10/scripts/map_owasp.py +540 -0
  90. package/skills/performing-penetration-testing/SKILL.md +282 -190
  91. package/skills/performing-penetration-testing/references/OWASP_TOP_10.md +22 -0
  92. package/skills/performing-penetration-testing/references/REMEDIATION_PLAYBOOK.md +46 -0
  93. package/skills/performing-penetration-testing/references/SECURITY_HEADERS.md +41 -0
  94. package/skills/performing-penetration-testing/scripts/code_security_scanner.py +144 -79
  95. package/skills/performing-penetration-testing/scripts/dependency_auditor.py +116 -93
  96. package/skills/performing-penetration-testing/scripts/security_scanner.py +574 -446
  97. package/skills/probing-dangerous-http-methods/SKILL.md +182 -0
  98. package/skills/probing-dangerous-http-methods/references/PLAYBOOK.md +234 -0
  99. package/skills/probing-dangerous-http-methods/references/THEORY.md +145 -0
  100. package/skills/probing-dangerous-http-methods/scripts/probe_methods.py +263 -0
  101. package/skills/recording-pentest-engagement/SKILL.md +253 -0
  102. package/skills/recording-pentest-engagement/references/PLAYBOOK.md +203 -0
  103. package/skills/recording-pentest-engagement/references/THEORY.md +195 -0
  104. package/skills/recording-pentest-engagement/scripts/record_engagement.py +461 -0
  105. package/skills/scanning-for-hardcoded-secrets/SKILL.md +215 -0
  106. package/skills/scanning-for-hardcoded-secrets/references/PLAYBOOK.md +325 -0
  107. package/skills/scanning-for-hardcoded-secrets/references/THEORY.md +175 -0
  108. package/skills/scanning-for-hardcoded-secrets/scripts/scan_secrets.py +395 -0
  109. package/skills/tracing-transitive-vulnerabilities/SKILL.md +235 -0
  110. package/skills/tracing-transitive-vulnerabilities/references/PLAYBOOK.md +233 -0
  111. package/skills/tracing-transitive-vulnerabilities/references/THEORY.md +138 -0
  112. package/skills/tracing-transitive-vulnerabilities/scripts/trace_vulns.py +484 -0
@@ -0,0 +1,159 @@
1
+ # Eval / Exec Theory
2
+
3
+ ## Why this is the highest-impact injection class
4
+
5
+ SQL injection (skill #11) gives the attacker control over a database
6
+ query. Command injection (skill #12) gives the attacker control over
7
+ a shell process. Eval injection gives the attacker control over the
8
+ application's own interpreter, in the application's own process,
9
+ with the application's own permissions.
10
+
11
+ There's no privilege boundary between "user-supplied string" and
12
+ "arbitrary code in the application." `eval()` collapses them. Any
13
+ filtering, allow-list, or input validation that the application
14
+ does happens BEFORE the eval; the eval's interpreter sees the
15
+ filtered string and executes it. If the attacker's payload uses
16
+ language constructs the filter didn't anticipate, the filter is
17
+ bypassed by definition.
18
+
19
+ This is why "don't eval user input" is the only safe rule. There's
20
+ no "but with these escapes it's safe" version.
21
+
22
+ ## When eval seems necessary
23
+
24
+ Three legitimate use cases drive the temptation:
25
+
26
+ 1. **Formula evaluators.** Spreadsheet-like `=SUM(A1:A5)` cells,
27
+ custom alerting expressions ("alert when latency > 100ms"),
28
+ pricing-rule engines.
29
+
30
+ 2. **Plugin systems.** Users supply small scripts that the
31
+ application runs on their behalf. Examples: Lambda@Edge, Cloudflare
32
+ Workers, custom log processors.
33
+
34
+ 3. **Configuration as code.** Rare but real: a config file that's
35
+ actually a Python / JS source file evaluated at startup.
36
+
37
+ For case 1: use a sandboxed expression library. `simpleeval` for
38
+ Python, `expr-eval` or `mathjs` for JS, `Dentaku` for Ruby. These
39
+ libraries parse a restricted grammar (math + comparison + a curated
40
+ function set) and execute it on a value model that doesn't have
41
+ filesystem / network / process access.
42
+
43
+ For case 2: run user scripts in a real sandbox: WASM
44
+ (WebAssembly with no system imports), V8 isolate (Node's vm
45
+ module with strict no-globals setup), Lua with stripped-down libs,
46
+ or a containerized worker. Don't run untrusted code in the same
47
+ process as the application.
48
+
49
+ For case 3: don't. Use JSON/YAML/TOML config. The "config as code"
50
+ flexibility argument is rarely worth the eval-injection surface.
51
+
52
+ ## Why even allow-list filtering fails
53
+
54
+ A common attempt: "allow only alphanumeric + math operators in the
55
+ evaluated string."
56
+
57
+ ```python
58
+ import re
59
+ if re.match(r"^[\d\+\-\*/\(\)\s\.]+$", user_input):
60
+ result = eval(user_input)
61
+ ```
62
+
63
+ This looks safe. It's not. Python's eval can:
64
+
65
+ - Use `__import__` and `__builtins__` accessors via attribute
66
+ lookup (`().__class__.__bases__[0].__subclasses__()[X]`)
67
+ - Trigger arbitrary code through `__getattr__` on numeric types
68
+ - Call `compile()` on a sub-expression and execute that
69
+
70
+ Attackers have published "polyglot" payloads that look like pure
71
+ math but reach arbitrary functions via Python's metaprogramming.
72
+ The character-class filter is necessary-but-not-sufficient. The
73
+ ONLY safe approach is a separate interpreter that doesn't have
74
+ access to the language's full surface.
75
+
76
+ `ast.literal_eval` is safe — it ONLY parses literals (numbers,
77
+ strings, lists, dicts, tuples, booleans, None). No function calls,
78
+ no name references. Use it when you need to evaluate user-supplied
79
+ literal values; don't use it (or anything like it) for
80
+ expression evaluation more generally.
81
+
82
+ ## Per-language safe patterns
83
+
84
+ ### Python — simpleeval for expressions
85
+
86
+ ```python
87
+ from simpleeval import simple_eval
88
+
89
+ # Safe: only basic math + curated function set
90
+ result = simple_eval("latency * 1.5 + 10", names={"latency": 80})
91
+ ```
92
+
93
+ Or `asteval`:
94
+
95
+ ```python
96
+ from asteval import Interpreter
97
+ aeval = Interpreter()
98
+ result = aeval("a + b * 2", symtable={"a": 1, "b": 2})
99
+ ```
100
+
101
+ ### JavaScript — expr-eval
102
+
103
+ ```javascript
104
+ const { Parser } = require('expr-eval');
105
+ const parser = new Parser();
106
+ const expr = parser.parse('latency * 1.5 + 10');
107
+ const result = expr.evaluate({ latency: 80 });
108
+ ```
109
+
110
+ ### Ruby — Dentaku
111
+
112
+ ```ruby
113
+ require 'dentaku'
114
+ calc = Dentaku::Calculator.new
115
+ calc.evaluate('latency * 1.5 + 10', latency: 80)
116
+ ```
117
+
118
+ ### Java — sandboxed Nashorn / GraalJS
119
+
120
+ ```java
121
+ // GraalJS with restricted permissions
122
+ Context cx = Context.newBuilder("js")
123
+ .allowHostAccess(HostAccess.NONE)
124
+ .allowHostClassLookup(name -> false)
125
+ .build();
126
+ Value result = cx.eval("js", "1 + 2");
127
+ ```
128
+
129
+ ## Avoid plugin-system eval
130
+
131
+ If users need to extend the application with custom logic, the
132
+ right model is:
133
+
134
+ 1. Define a narrow API the plugin can call (e.g., "transform this
135
+ payload, return a transformed version").
136
+ 2. Run the plugin in an isolated sandbox: WASM with no system
137
+ imports, V8 isolate, separate container, separate language
138
+ runtime.
139
+ 3. Apply timeouts, memory limits, syscall whitelists.
140
+
141
+ Don't `eval()` the plugin string in the application process. Even
142
+ "trusted" plugin scripts shouldn't have arbitrary access to the
143
+ host application's memory and modules.
144
+
145
+ ## The pickle / serialization overlap
146
+
147
+ Python `pickle.loads()` is effectively eval-equivalent — pickle can
148
+ execute arbitrary code during deserialization. This is covered in
149
+ depth by skill #14 (`detecting-insecure-deserialization`); this
150
+ skill flags pickle usage as a cross-reference but the remediation
151
+ guidance lives in #14.
152
+
153
+ ## Primary sources
154
+
155
+ - [CWE-95 Eval Injection](https://cwe.mitre.org/data/definitions/95.html)
156
+ - [Python ast.literal_eval docs](https://docs.python.org/3/library/ast.html#ast.literal_eval)
157
+ - [simpleeval — safe Python expression evaluation](https://github.com/danthedeckie/simpleeval)
158
+ - [Bandit B102 (exec_used) / B307 (eval)](https://bandit.readthedocs.io/en/latest/plugins/b102_exec_used.html)
159
+ - [OWASP Code Review Guide — Dynamic code execution](https://owasp.org/www-project-code-review-guide/)
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env python3
2
+ """Static-analysis scan for eval / exec / dynamic-code-execution APIs.
3
+
4
+ References:
5
+ CWE-95 Improper Neutralization of Directives in Dynamically Evaluated Code
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import re
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ _PLUGIN_ROOT = Path(__file__).resolve().parents[3]
16
+ if str(_PLUGIN_ROOT) not in sys.path:
17
+ sys.path.insert(0, str(_PLUGIN_ROOT))
18
+
19
+ from lib.finding import Finding, Severity # noqa: E402
20
+ from lib.report import emit, exit_code # noqa: E402
21
+
22
+ SKILL_ID = "detecting-eval-exec-usage"
23
+
24
+ PY_PATTERNS = [
25
+ ("Python eval()", Severity.CRITICAL, r"\beval\s*\(\s*(?!['\"])", "python"),
26
+ ("Python exec()", Severity.CRITICAL, r"\bexec\s*\(\s*(?!['\"])", "python"),
27
+ ("Python compile() with non-literal", Severity.HIGH, r"\bcompile\s*\(\s*(?!['\"])", "python"),
28
+ ("Python __import__ with variable", Severity.HIGH, r"__import__\s*\(\s*(?!['\"])", "python"),
29
+ (
30
+ "Python pickle.loads (eval-class deserialization)",
31
+ Severity.HIGH,
32
+ r"\bpickle\.loads?\s*\(",
33
+ "python",
34
+ ), # cross-listed; #14 covers in depth
35
+ ]
36
+ JS_PATTERNS = [
37
+ ("JavaScript eval()", Severity.CRITICAL, r"\beval\s*\(", "javascript"),
38
+ (
39
+ "JavaScript new Function() with non-literal",
40
+ Severity.CRITICAL,
41
+ r"new\s+Function\s*\(\s*(?!['\"`])",
42
+ "javascript",
43
+ ),
44
+ ("JavaScript setTimeout with string arg", Severity.HIGH, r"setTimeout\s*\(\s*['\"`]", "javascript"),
45
+ ("JavaScript setInterval with string arg", Severity.HIGH, r"setInterval\s*\(\s*['\"`]", "javascript"),
46
+ ]
47
+ RUBY_PATTERNS = [
48
+ ("Ruby eval()", Severity.CRITICAL, r"\beval\s*\(", "ruby"),
49
+ (
50
+ "Ruby instance_eval / class_eval with non-block",
51
+ Severity.HIGH,
52
+ r"\b(?:instance_eval|class_eval|module_eval)\s*\(\s*['\"]",
53
+ "ruby",
54
+ ),
55
+ ]
56
+ PHP_PATTERNS = [
57
+ ("PHP eval()", Severity.CRITICAL, r"\beval\s*\(", "php"),
58
+ ("PHP assert() with string (legacy eval form)", Severity.CRITICAL, r"\bassert\s*\(\s*['\"$]", "php"),
59
+ ("PHP create_function (deprecated, eval-equivalent)", Severity.CRITICAL, r"\bcreate_function\s*\(", "php"),
60
+ ]
61
+ JAVA_PATTERNS = [
62
+ ("Java ScriptEngine.eval", Severity.HIGH, r"\bScriptEngine[A-Za-z]*\b.*\.eval\s*\(", "java"),
63
+ ("Java GroovyShell.evaluate", Severity.HIGH, r"\bGroovyShell\b.*\.evaluate\s*\(", "java"),
64
+ ]
65
+ CSHARP_PATTERNS = [
66
+ (
67
+ "C# Activator.CreateInstance(Type.GetType(str))",
68
+ Severity.HIGH,
69
+ r"Activator\.CreateInstance\s*\(\s*Type\.GetType\s*\(",
70
+ "csharp",
71
+ ),
72
+ ("C# Reflection.Emit", Severity.MEDIUM, r"\bReflection\.Emit\b", "csharp"),
73
+ ]
74
+
75
+ LANG_EXT_MAP = {
76
+ "python": {".py"},
77
+ "javascript": {".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"},
78
+ "ruby": {".rb"},
79
+ "php": {".php"},
80
+ "java": {".java", ".kt", ".scala"},
81
+ "csharp": {".cs"},
82
+ }
83
+ LANG_PATTERNS = {
84
+ "python": PY_PATTERNS,
85
+ "javascript": JS_PATTERNS,
86
+ "ruby": RUBY_PATTERNS,
87
+ "php": PHP_PATTERNS,
88
+ "java": JAVA_PATTERNS,
89
+ "csharp": CSHARP_PATTERNS,
90
+ }
91
+ SKIP_DIRS = {
92
+ "node_modules",
93
+ ".git",
94
+ "dist",
95
+ "build",
96
+ "target",
97
+ ".cache",
98
+ ".pnpm-store",
99
+ ".venv",
100
+ "venv",
101
+ "__pycache__",
102
+ ".astro",
103
+ ".next",
104
+ ".nuxt",
105
+ "vendor",
106
+ }
107
+ TEST_DIRS = {"tests", "test", "__tests__", "spec", "specs"}
108
+ MAX_FILE_SIZE = 5 * 1024 * 1024
109
+
110
+
111
+ def should_skip_path(path: Path, include_tests: bool) -> bool:
112
+ parts = set(path.parts)
113
+ if parts & SKIP_DIRS:
114
+ return True
115
+ if not include_tests and parts & TEST_DIRS:
116
+ return True
117
+ return False
118
+
119
+
120
+ def detect_language(path: Path, langs: set[str]) -> str | None:
121
+ suf = path.suffix.lower()
122
+ for lang in langs:
123
+ if suf in LANG_EXT_MAP[lang]:
124
+ return lang
125
+ return None
126
+
127
+
128
+ def scan_file(file_path: Path, repo_root: Path, langs: set[str]) -> list[Finding]:
129
+ findings = []
130
+ lang = detect_language(file_path, langs)
131
+ if lang is None:
132
+ return findings
133
+ try:
134
+ if file_path.stat().st_size > MAX_FILE_SIZE:
135
+ return findings
136
+ text = file_path.read_text(encoding="utf-8", errors="ignore")
137
+ except (OSError, ValueError):
138
+ return findings
139
+ try:
140
+ rel = str(file_path.relative_to(repo_root))
141
+ except ValueError:
142
+ rel = str(file_path)
143
+
144
+ for title, sev, pattern, _lang in LANG_PATTERNS[lang]:
145
+ for m in re.finditer(pattern, text, re.MULTILINE):
146
+ line_no = text[: m.start()].count("\n") + 1
147
+ snippet = text.splitlines()[line_no - 1].strip()[:160]
148
+ findings.append(
149
+ Finding(
150
+ skill_id=SKILL_ID,
151
+ title=f"{title} at {rel}:{line_no}",
152
+ severity=sev,
153
+ target=f"{rel}:{line_no}",
154
+ detail=(
155
+ f"File {rel} line {line_no} uses {title}: `{snippet}`. "
156
+ "If the evaluated string is user-reachable, this is "
157
+ "an arbitrary-code-execution vector."
158
+ ),
159
+ remediation=(
160
+ "Replace dynamic code execution with explicit logic "
161
+ "(lookup table, switch statement) or a sandboxed "
162
+ "expression library. Python: simpleeval / ast.literal_eval. "
163
+ "JS: expr-eval / mathjs. Ruby: Dentaku. See "
164
+ "references/PLAYBOOK.md."
165
+ ),
166
+ cwe_id="CWE-95",
167
+ affected_control="OWASP A03:2021",
168
+ evidence=(("file", rel), ("line", line_no), ("language", lang), ("snippet", snippet)),
169
+ )
170
+ )
171
+ return findings
172
+
173
+
174
+ def walk_repo(root: Path, include_tests: bool, langs: set[str]) -> list[Path]:
175
+ out = []
176
+ valid_exts = set()
177
+ for lang in langs:
178
+ valid_exts |= LANG_EXT_MAP[lang]
179
+ for p in root.rglob("*"):
180
+ if not p.is_file():
181
+ continue
182
+ if should_skip_path(p, include_tests):
183
+ continue
184
+ if p.suffix.lower() not in valid_exts:
185
+ continue
186
+ out.append(p)
187
+ return out
188
+
189
+
190
+ def main(argv: list[str] | None = None) -> int:
191
+ parser = argparse.ArgumentParser(description="eval / exec usage scanner")
192
+ parser.add_argument("path", type=Path)
193
+ parser.add_argument("--output", default=None)
194
+ parser.add_argument("--format", choices=("json", "jsonl", "markdown"), default="markdown")
195
+ parser.add_argument("--min-severity", choices=("critical", "high", "medium", "low", "info"), default="info")
196
+ parser.add_argument("--include-tests", action="store_true")
197
+ parser.add_argument("--languages", default="all")
198
+ args = parser.parse_args(argv)
199
+
200
+ if args.languages == "all":
201
+ langs = set(LANG_PATTERNS.keys())
202
+ else:
203
+ langs = {lang.strip() for lang in args.languages.split(",") if lang.strip() in LANG_PATTERNS}
204
+
205
+ root = args.path.resolve()
206
+ if not root.exists():
207
+ sys.stderr.write(f"ERROR: path does not exist: {root}\n")
208
+ return 2
209
+
210
+ files = walk_repo(root, args.include_tests, langs)
211
+ findings: list[Finding] = []
212
+ for f in files:
213
+ findings.extend(scan_file(f, root, langs))
214
+
215
+ floor = Severity(args.min_severity)
216
+ findings = [f for f in findings if f.severity.numeric >= floor.numeric]
217
+
218
+ emit(findings, args.output, args.format, str(root))
219
+ return exit_code(findings)
220
+
221
+
222
+ if __name__ == "__main__":
223
+ sys.exit(main())
@@ -0,0 +1,179 @@
1
+ ---
2
+ name: detecting-exposed-secrets-files
3
+ description: |
4
+ Probe a target for accidentally-served secret-bearing files in the web root
5
+ — `.git/`, `.env`, `.DS_Store`, backup files, database dumps, key files,
6
+ CI configs, IDE configs.
7
+ Use when: post-deploy verification on a new release, or SOC2 auditor asked
8
+ "what's reachable in the web root that shouldn't be," or a bug-bounty
9
+ report hints at a leaked file.
10
+ Threshold: any of the canonical 40+ paths returns 200 OR returns a body
11
+ matching the expected fingerprint of the file type (e.g., `.git/HEAD`
12
+ returns content starting with `ref:` or a 40-char hex SHA).
13
+ Trigger with: "check exposed files", "git directory exposure",
14
+ "env file leak", "backup file scan".
15
+ allowed-tools:
16
+ - Read
17
+ - Bash(python3:*)
18
+ - Bash(curl:*)
19
+ disallowed-tools:
20
+ - Bash(rm:*)
21
+ - Edit(/etc/*)
22
+ version: 3.0.0-dev
23
+ author: Jeremy Longshore <jeremy@intentsolutions.io>
24
+ license: MIT
25
+ compatibility: Designed for Claude Code
26
+ tags:
27
+ - security
28
+ - information-disclosure
29
+ - secrets
30
+ - pentest
31
+ ---
32
+
33
+ # Detecting Exposed Secrets Files
34
+
35
+ ## Overview
36
+
37
+ The single highest-value pentest probe per HTTP request. A `.git/config`
38
+ disclosure leaks repo URL + credentials embedded in remote URLs. A `.env`
39
+ disclosure leaks every API key the app has. A `backup.sql` disclosure
40
+ leaks the entire database. These are not "weak crypto" findings that need
41
+ a chained exploit. They are direct, immediate compromise.
42
+
43
+ The probe set is the canonical 40+ paths web servers commonly expose by
44
+ accident: VCS directories (`.git/`, `.svn/`, `.hg/`), dotenv files,
45
+ OS metadata (`.DS_Store`), database dumps, archive files, IDE configs,
46
+ CI configs, and key files. Each is fingerprinted to distinguish a true
47
+ positive (server returns the file's expected content) from a 200 OK
48
+ that's actually the application's SPA index page catching the route.
49
+
50
+ ## When the skill produces findings
51
+
52
+ | Finding | Severity | Threshold | Affected control |
53
+ |---|---|---|---|
54
+ | `.git/HEAD` reachable + valid content | **CRITICAL** | 200 + body matches `ref:` or 40-char SHA | NIST 800-53 SC-28 |
55
+ | `.git/config` reachable + repo URL leaked | **CRITICAL** | 200 + body matches `[remote` | NIST 800-53 SC-28 |
56
+ | `.env` reachable + dotenv format | **CRITICAL** | 200 + body matches `KEY=VALUE` lines | OWASP A05:2021 |
57
+ | `*.sql` / `*.dump` / `backup.*` reachable | **CRITICAL** | 200 + body looks like SQL or binary dump | CWE-538 |
58
+ | `.aws/credentials` reachable | **CRITICAL** | 200 + body matches `[default]\naws_access_key_id` | CWE-200 |
59
+ | `id_rsa` / `*.pem` / `*.key` reachable | **CRITICAL** | 200 + body matches `BEGIN PRIVATE KEY` or `BEGIN RSA` | CWE-321 |
60
+ | `.svn/entries` / `.hg/store/` reachable | **HIGH** | 200 + body matches VCS format | NIST 800-53 SC-28 |
61
+ | `.DS_Store` reachable | **MEDIUM** | 200 + binary blob with `Bud1` magic | CWE-538 |
62
+ | IDE configs (`.idea/`, `.vscode/`) reachable | **LOW** | 200 + JSON/XML | CWE-200 |
63
+ | `composer.json` / `package.json` reachable on prod | **LOW** | 200 + valid JSON in non-API root | CWE-200 |
64
+
65
+ ## Prerequisites
66
+
67
+ - Python 3.9+ with `requests` library
68
+ - Authorization for non-local targets
69
+
70
+ ## Instructions
71
+
72
+ ### Step 1 — Confirm Authorization
73
+
74
+ ```text
75
+ "Do you have authorization to perform secret-file discovery on this
76
+ target? I need confirmation before proceeding."
77
+ ```
78
+
79
+ ### Step 2 — Run the scanner
80
+
81
+ ```bash
82
+ python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-exposed-secrets-files/scripts/probe_secrets.py \
83
+ https://target.example.com \
84
+ --authorized
85
+ ```
86
+
87
+ Options:
88
+
89
+ ```
90
+ Usage: probe_secrets.py URL [OPTIONS]
91
+
92
+ Options:
93
+ --authorized Attest authorization (required for non-local)
94
+ --output FILE Write findings to FILE
95
+ --format FMT json | jsonl | markdown (default: markdown)
96
+ --min-severity SEV (default: info)
97
+ --timeout SECS Per-probe timeout (default: 10)
98
+ --paths-file FILE Override the canonical probe set with a custom list
99
+ --check-only Skip body-fingerprint verification (faster, more
100
+ false positives — useful when target serves SPA
101
+ index for everything)
102
+ ```
103
+
104
+ The scanner sends a GET for each path in the canonical probe set. For
105
+ every 200 response, it inspects the body to confirm the response really
106
+ is the expected file type (not the app's SPA index catching the route).
107
+
108
+ ### Step 3 — Interpret findings
109
+
110
+ CRITICAL = direct credential / source code / database exposure.
111
+ Ship same-hour fix (configure web server to deny + audit logs for
112
+ anyone who already exfiltrated).
113
+
114
+ ### Step 4 — Cross-skill chaining
115
+
116
+ After this skill, suggest `detecting-debug-endpoints` (#7) — the same
117
+ deploy mistake that exposes `.git/` often exposes `/admin/` and
118
+ `/server-status/`. And `detecting-directory-listing` (#9) — if any of
119
+ the secret-file paths returned a directory listing instead of the file
120
+ itself, autoindex is enabled.
121
+
122
+ ## Examples
123
+
124
+ ### Example 1 — Post-deploy verification
125
+
126
+ User: "We just rolled out v4.2. Make sure we didn't deploy `.env` or
127
+ the `.git/` dir by accident."
128
+
129
+ ```bash
130
+ python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-exposed-secrets-files/scripts/probe_secrets.py \
131
+ https://app.example.com --authorized --min-severity high
132
+ ```
133
+
134
+ ### Example 2 — Bug bounty triage
135
+
136
+ User: "Submission claims our .git/ is exposed."
137
+
138
+ ```bash
139
+ python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-exposed-secrets-files/scripts/probe_secrets.py \
140
+ https://app.example.com --authorized --format json --output exposure.json
141
+ jq '.[] | select(.title | contains(".git"))' exposure.json
142
+ ```
143
+
144
+ The fingerprint check distinguishes real `.git/HEAD` (returns
145
+ `ref: refs/heads/main` or a 40-char hex SHA) from false-positive SPA
146
+ index pages that 200 on any path.
147
+
148
+ ### Example 3 — CI gate against future deploys
149
+
150
+ ```yaml
151
+ - name: Exposed-files gate
152
+ run: |
153
+ python3 plugins/security/penetration-tester/skills/detecting-exposed-secrets-files/scripts/probe_secrets.py \
154
+ "${{ secrets.STAGING_URL }}" \
155
+ --authorized --min-severity critical
156
+ ```
157
+
158
+ Exit 1 fails the deploy if any CRITICAL finding lands.
159
+
160
+ ## Output
161
+
162
+ JSON / JSONL / Markdown. Exit codes: 0 clean, 1 high/critical, 2 error.
163
+
164
+ ## Error Handling
165
+
166
+ - **Target SPA-catches every URL with 200** → use `--check-only` to skip
167
+ body-fingerprint; expect more false positives but get any real exposure.
168
+ - **All probes timeout** → likely DDoS protection blocking the scanner;
169
+ contact the target's security team for an allowlist.
170
+ - **Connection error** → exit 2.
171
+
172
+ ## Resources
173
+
174
+ - `references/THEORY.md` — Why each path matters, fingerprint patterns,
175
+ RFC / OWASP / NIST anchors
176
+ - `references/PLAYBOOK.md` — Per-server config snippets to block each
177
+ category of path (nginx, Apache, Caddy, ALB, GCP LB)
178
+ - `../analyzing-tls-config/references/AUTHORIZATION.md` — Active-scan
179
+ authorization