@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,274 @@
1
+ # Exposed-Files Remediation Playbook
2
+
3
+ ## Pattern 1 — Block dot-directories at the web server
4
+
5
+ ### nginx (location blocks)
6
+
7
+ ```nginx
8
+ # Deny all dot-files / dot-directories
9
+ location ~ /\. {
10
+ deny all;
11
+ access_log off;
12
+ log_not_found off;
13
+ }
14
+
15
+ # Belt-and-suspenders for specific high-value paths
16
+ location ~ ^/(\.git|\.svn|\.hg|\.bzr|\.env|\.aws|\.ssh) {
17
+ deny all;
18
+ return 404;
19
+ }
20
+ ```
21
+
22
+ ### Apache (vhost or .htaccess)
23
+
24
+ ```apache
25
+ <DirectoryMatch "^\.|/\.">
26
+ Require all denied
27
+ </DirectoryMatch>
28
+
29
+ <Files ~ "^\.">
30
+ Require all denied
31
+ </Files>
32
+ ```
33
+
34
+ ### Caddy
35
+
36
+ Caddy denies dot-directories by default since v2.0. To explicitly
37
+ assert:
38
+
39
+ ```caddy
40
+ example.com {
41
+ @dot path */.git/* */.env */.aws/* */.ssh/* */.svn/*
42
+ respond @dot 404
43
+ file_server
44
+ }
45
+ ```
46
+
47
+ ### AWS ALB / WAF
48
+
49
+ Add a managed-rule-group or custom rule:
50
+
51
+ ```json
52
+ {
53
+ "Name": "BlockDotPaths",
54
+ "Statement": {
55
+ "RegexMatchStatement": {
56
+ "FieldToMatch": {"UriPath": {}},
57
+ "RegexString": "/\\.(git|env|aws|ssh|svn|hg|bzr|idea|vscode)(\\b|/)",
58
+ "TextTransformations": [{"Priority": 0, "Type": "LOWERCASE"}]
59
+ }
60
+ },
61
+ "Action": {"Block": {}}
62
+ }
63
+ ```
64
+
65
+ ### Cloudflare WAF
66
+
67
+ ```
68
+ http.request.uri.path matches "/\\.(git|env|aws|ssh|svn|hg|bzr)"
69
+ ```
70
+
71
+ Action: Block.
72
+
73
+ ## Pattern 2 — Block backup / dump file extensions
74
+
75
+ ### nginx
76
+
77
+ ```nginx
78
+ location ~* \.(sql|bak|dump|swp|swo|orig|backup|tar\.gz|tar\.bz2|zip|7z|rar)$ {
79
+ deny all;
80
+ return 404;
81
+ }
82
+ ```
83
+
84
+ ### Apache
85
+
86
+ ```apache
87
+ <FilesMatch "\.(sql|bak|dump|swp|swo|orig|backup|tar\.gz|tar\.bz2|zip|7z|rar)$">
88
+ Require all denied
89
+ </FilesMatch>
90
+ ```
91
+
92
+ ### Caddy
93
+
94
+ ```caddy
95
+ @backups path *.sql *.bak *.dump *.swp *.tar.gz *.zip
96
+ respond @backups 404
97
+ ```
98
+
99
+ ## Pattern 3 — Block private key extensions
100
+
101
+ ### nginx
102
+
103
+ ```nginx
104
+ location ~* \.(pem|key|p12|pfx|jks|crt|cer|csr|kdb|kbx)$ {
105
+ deny all;
106
+ return 404;
107
+ }
108
+ ```
109
+
110
+ These extensions should never be reachable via the web; if a key needs
111
+ to be served (e.g., a JWKS for OAuth), serve it at an explicit path
112
+ with a content-type check rather than relying on the extension.
113
+
114
+ ## Pattern 4 — Block development metadata
115
+
116
+ ### nginx
117
+
118
+ ```nginx
119
+ location ~* /(phpinfo|info|test)\.php$ {
120
+ deny all;
121
+ return 404;
122
+ }
123
+
124
+ location ~* /(composer\.json|package\.json|Dockerfile|docker-compose\.yml|requirements\.txt|Gemfile|build\.gradle)$ {
125
+ deny all;
126
+ return 404;
127
+ }
128
+ ```
129
+
130
+ `phpinfo.php` should be removed from any production environment.
131
+ Detection of an active `phpinfo.php` on prod is an audit-flag-worthy
132
+ operational gap; the fix is removal, not access control.
133
+
134
+ ## Pattern 5 — Block at the build / deploy layer
135
+
136
+ The cleanest fix is preventing the files from getting deployed in
137
+ the first place.
138
+
139
+ ### Dockerfile — multi-stage build with explicit copies
140
+
141
+ ```dockerfile
142
+ # Build stage
143
+ FROM node:20 AS build
144
+ WORKDIR /build
145
+ COPY package.json package-lock.json ./
146
+ RUN npm ci
147
+ COPY src/ ./src/
148
+ RUN npm run build
149
+
150
+ # Runtime stage — only copy the built artifact, NOT the build context
151
+ FROM nginx:alpine
152
+ COPY --from=build /build/dist /usr/share/nginx/html
153
+ ```
154
+
155
+ Notice: no `COPY . .` anywhere. The build stage gets only what it
156
+ needs; the runtime stage gets only the `dist/` artifact. `.git/`,
157
+ `.env`, `node_modules/`, etc., never reach the runtime image.
158
+
159
+ ### .dockerignore
160
+
161
+ ```
162
+ .git
163
+ .env*
164
+ .aws
165
+ .ssh
166
+ *.pem
167
+ *.key
168
+ node_modules
169
+ .DS_Store
170
+ .idea
171
+ .vscode
172
+ backup.sql
173
+ *.sql
174
+ *.dump
175
+ ```
176
+
177
+ ### .gitlab-ci.yml / GitHub Actions — exclude sensitive paths from artifacts
178
+
179
+ ```yaml
180
+ artifacts:
181
+ paths:
182
+ - dist/
183
+ exclude:
184
+ - "**/.git*"
185
+ - "**/.env*"
186
+ - "**/.aws/*"
187
+ - "**/.ssh/*"
188
+ - "**/*.pem"
189
+ - "**/*.key"
190
+ - "**/backup.*"
191
+ - "**/*.sql"
192
+ ```
193
+
194
+ ## Pattern 6 — Cloud Run / Lambda artifact builds
195
+
196
+ Cloud Run and Lambda build the deployment artifact from your repo by
197
+ default. To exclude:
198
+
199
+ ### Cloud Run via Cloud Build
200
+
201
+ ```yaml
202
+ # cloudbuild.yaml
203
+ steps:
204
+ - name: 'gcr.io/cloud-builders/docker'
205
+ args: ['build', '-t', 'gcr.io/$PROJECT_ID/app', '.']
206
+ options:
207
+ ignoreFile: '.dockerignore' # honored by Cloud Build
208
+ ```
209
+
210
+ Combined with the `.dockerignore` above, this excludes the files.
211
+
212
+ ### Lambda via SAM / Serverless Framework
213
+
214
+ ```yaml
215
+ package:
216
+ patterns:
217
+ - '!.git/**'
218
+ - '!.env*'
219
+ - '!.aws/**'
220
+ - '!.ssh/**'
221
+ - '!**/*.pem'
222
+ - '!**/*.key'
223
+ - '!backup.*'
224
+ - '!**/*.sql'
225
+ ```
226
+
227
+ ## Auditing existing deployments
228
+
229
+ Run the scanner on every production endpoint:
230
+
231
+ ```bash
232
+ for ENDPOINT in $(cat production-urls.txt); do
233
+ python3 plugins/security/penetration-tester/skills/detecting-exposed-secrets-files/scripts/probe_secrets.py \
234
+ "$ENDPOINT" --authorized --min-severity critical \
235
+ --format jsonl --output /dev/stdout
236
+ done > exposure-audit.jsonl
237
+ ```
238
+
239
+ Treat any CRITICAL finding as ship-same-hour. Document the
240
+ remediation in the same PR as the audit (so the audit's grep target
241
+ gets re-verified on commit).
242
+
243
+ ## After remediation — assume compromise
244
+
245
+ If `.git/` was exposed, assume:
246
+
247
+ - Every credential ever committed to the repo, including ones in past
248
+ commits that were later removed, is compromised. Rotate them all.
249
+ - The full source code is in the attacker's hands. Treat any
250
+ authentication / authorization logic as if it had been read.
251
+
252
+ If `.env` was exposed:
253
+
254
+ - Rotate every credential in the file
255
+ - Audit logs for any API call against those credentials in the
256
+ past window from when the deploy happened to when you rotated
257
+ - Notify partners whose API keys you held
258
+
259
+ If a backup `.sql` was exposed:
260
+
261
+ - Assume the database is compromised in the state it was in when
262
+ the backup was taken
263
+ - Trigger your data-breach response: regulator notification,
264
+ customer notification, credential rotation for anyone whose
265
+ data was in the dump
266
+
267
+ ## Verification after remediation
268
+
269
+ ```bash
270
+ python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-exposed-secrets-files/scripts/probe_secrets.py \
271
+ https://example.com --authorized --min-severity info
272
+ ```
273
+
274
+ Expected: exit 0, zero findings of any severity.
@@ -0,0 +1,174 @@
1
+ # Exposed-Files Theory
2
+
3
+ ## Why this probe class is the highest-value pentest call
4
+
5
+ Most pentest findings are about chained conditions and threshold judgments.
6
+ This one is binary: either the `.git/HEAD` is reachable or it's not. A
7
+ single 200 response answers the question. The consequence of a true
8
+ positive is direct: full repo history (with embedded credentials in old
9
+ commits), live API keys (in `.env`), and direct database access (in
10
+ `backup.sql`). No CVE chaining, no exploitation primitive needed.
11
+
12
+ The reason these exposures keep happening is operational: web servers
13
+ are designed to serve files from a directory tree, and deployment
14
+ processes routinely deploy the whole tree, including dot-directories
15
+ and config files the application never intended to expose. Modern
16
+ frameworks ship "deny" defaults (Caddy, nginx with a security baseline,
17
+ Cloud Run) but legacy stacks (LAMP on a shared host, hand-rolled
18
+ nginx vhosts on a VM) default to "allow."
19
+
20
+ ## File-by-file: why each one matters
21
+
22
+ ### `.git/` — version control directory
23
+
24
+ The `.git/` subdirectory contains the entire history of the repository.
25
+ Exposing it allows an attacker to clone the repo without needing
26
+ authentication:
27
+
28
+ ```
29
+ git clone https://example.com/.git/ ./reconstructed-source
30
+ ```
31
+
32
+ History reveals:
33
+
34
+ - API keys in old commits (before the `.env` was added to `.gitignore`)
35
+ - Hardcoded credentials someone removed but didn't rebase out
36
+ - Repo URL + branch names + commit messages → understanding the
37
+ deployment process
38
+ - Database schemas if migrations are checked in
39
+ - Source code (obviously) → static analysis targets
40
+
41
+ Three minimal probes that confirm exposure:
42
+
43
+ - `.git/HEAD` — should be a small text file starting with `ref:` or a
44
+ 40-char hex SHA
45
+ - `.git/config` — `[remote "origin"]` block reveals the upstream
46
+ - `.git/index` — binary file starting with `DIRC` magic
47
+
48
+ The `.git/` exposure is so common that there are off-the-shelf tools
49
+ (GitDumper, git-dumper) that walk the exposed directory and reconstruct
50
+ the repo. Once exposed, assume the full history is compromised.
51
+
52
+ ### `.env` — dotenv credentials
53
+
54
+ Most modern stacks (Node.js with dotenv, Python with python-dotenv,
55
+ Ruby with dotenv-rails, Laravel) load environment variables from a
56
+ `.env` file at startup. The file's contents are typically the most
57
+ sensitive thing the application has: API keys, database connection
58
+ strings, signing secrets, third-party credentials.
59
+
60
+ A leaked `.env`:
61
+
62
+ - Lets an attacker authenticate as the app to upstream services
63
+ (Stripe, Twilio, OpenAI, etc.)
64
+ - Reveals database credentials if `DATABASE_URL` is present
65
+ - Discloses signing secrets for JWT / session tokens, enabling token
66
+ forgery without further effort
67
+
68
+ Fingerprint: `KEY=VALUE` pattern on each line, often `[A-Z_]` keys.
69
+
70
+ ### `.aws/credentials`, `.aws/config`
71
+
72
+ AWS SDK and CLI look in `~/.aws/credentials` by default. If a deploy
73
+ process accidentally copies the user's home directory into the web
74
+ root, AWS credentials are reachable.
75
+
76
+ `[default]\naws_access_key_id = AKIA...` is the fingerprint. Once
77
+ leaked, the credentials grant whatever permissions the IAM principal
78
+ had — historically that's "Administrator" on dev/test accounts
79
+ because the principle of least privilege is not the default.
80
+
81
+ ### Private keys (`id_rsa`, `*.pem`, `*.key`)
82
+
83
+ SSH private keys, TLS private keys, or signing keys. Exposure means:
84
+
85
+ - An attacker can authenticate as the server to other systems (lateral
86
+ movement)
87
+ - An attacker can MITM TLS connections by presenting the leaked cert +
88
+ key combination
89
+ - An attacker can sign tokens / artifacts as the system
90
+
91
+ Fingerprint: any of `-----BEGIN RSA PRIVATE KEY-----`,
92
+ `-----BEGIN OPENSSH PRIVATE KEY-----`, `-----BEGIN PRIVATE KEY-----`,
93
+ `-----BEGIN EC PRIVATE KEY-----`, `-----BEGIN DSA PRIVATE KEY-----`.
94
+
95
+ ### Backup files (`backup.sql`, `dump.sql`, `*.tar.gz`)
96
+
97
+ SQL dumps contain the entire database — schema and rows. A leaked
98
+ backup is functionally equivalent to RCE on the database server.
99
+ Common origins:
100
+
101
+ - Operator made a backup before a migration and left it in the web root
102
+ - Deploy script copies a tarball of the previous release into the web
103
+ root for rollback purposes
104
+ - Cron job dumps a backup to a path the web server happens to serve
105
+
106
+ Fingerprint: SQL dumps contain `CREATE TABLE`, `INSERT INTO`. Archive
107
+ files (binary) get a no-fingerprint check — the path being reachable
108
+ at 200 is the finding.
109
+
110
+ ### `.DS_Store`
111
+
112
+ macOS Finder creates a `.DS_Store` in every directory it views,
113
+ recording metadata about how the directory is displayed. The binary
114
+ format includes the filenames of every file in the directory.
115
+
116
+ Exposure is medium severity because it doesn't directly leak credentials
117
+ or source code, but it enumerates the directory structure, including
118
+ hidden files that wouldn't otherwise be discoverable by URL probing.
119
+
120
+ Fingerprint: binary blob with `Bud1` magic at offset 4 or 0.
121
+
122
+ ### `phpinfo()` output
123
+
124
+ PHP's `phpinfo()` function dumps the full PHP environment — every
125
+ configuration directive, every environment variable, every loaded
126
+ module. Common in `phpinfo.php`, `info.php`, `test.php` files left
127
+ behind from initial server setup.
128
+
129
+ Includes:
130
+
131
+ - Document root path (informs directory traversal)
132
+ - Loaded extensions (informs exploit selection)
133
+ - Often: `SERVER` variables including request headers and cookies
134
+ - Sometimes: `ENV` variables including secrets
135
+
136
+ Fingerprint: HTML body containing `PHP Version` heading.
137
+
138
+ ### IDE configs (`.idea/`, `.vscode/`)
139
+
140
+ Per-project IDE settings. Low severity but information disclosure:
141
+
142
+ - Run configurations (database connection strings, env vars used during dev)
143
+ - Recent file lists (informs which files the dev was working on)
144
+ - Inspection scope (informs which directories have application code)
145
+
146
+ ### Dependency manifests on production root (`package.json`, etc.)
147
+
148
+ Information disclosure: exposes exact versions of every dependency,
149
+ enabling targeted CVE lookup. Not a vulnerability in itself, but a
150
+ recon enabler.
151
+
152
+ ## Why fingerprint-checking matters
153
+
154
+ SPAs (Single-Page Applications) using client-side routing return the
155
+ app's `index.html` for any unknown route — including `/.git/HEAD`.
156
+ Without fingerprint verification, every `/.git/*` probe returns 200
157
+ and every `/.env` probe returns 200, all of them false positives.
158
+
159
+ The fingerprint check inspects the response body. If a request for
160
+ `.git/HEAD` returns 200 with body `<!DOCTYPE html>`, it's the SPA
161
+ catching the route, not a real `.git/HEAD`. If the body starts with
162
+ `ref:` or matches a 40-char hex SHA, it's the real file.
163
+
164
+ The skill's `--check-only` mode skips fingerprint verification for
165
+ cases where the operator wants to know about every 200, including
166
+ the SPA false positives, and accepts noise as the cost.
167
+
168
+ ## Primary sources
169
+
170
+ - [OWASP WSTG-INFO-02 — Fingerprint Web Server](https://owasp.org/www-project-web-security-testing-guide/v42/4-Web_Application_Security_Testing/01-Information_Gathering/02-Fingerprint_Web_Server)
171
+ - [OWASP WSTG-CONF-04 — Review Old Backup and Unreferenced Files](https://owasp.org/www-project-web-security-testing-guide/v42/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/04-Review_Old_Backup_and_Unreferenced_Files_for_Sensitive_Information)
172
+ - [CWE-538 — File and Directory Information Exposure](https://cwe.mitre.org/data/definitions/538.html)
173
+ - [CWE-200 — Information Exposure](https://cwe.mitre.org/data/definitions/200.html)
174
+ - [NIST SP 800-53 SC-28 — Protection of Information at Rest](https://nvd.nist.gov/800-53/Rev5/control/SC-28)
@@ -0,0 +1,207 @@
1
+ #!/usr/bin/env python3
2
+ """Probe for accidentally-served secret-bearing files in the web root.
3
+
4
+ Companion to skill `detecting-exposed-secrets-files`. Sends a GET for
5
+ each path in a curated 40+ probe set. For 200 responses, fingerprints
6
+ the body to distinguish a real file from an SPA index page that 200s
7
+ on any route.
8
+
9
+ References:
10
+ OWASP WSTG v4.2 § 4.2.4 Enumerate Infrastructure / Application
11
+ NIST SP 800-53 SC-28 Protection of Information at Rest
12
+ CWE-538 File and Directory Information Exposure
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import re
19
+ import sys
20
+ from pathlib import Path
21
+
22
+ _PLUGIN_ROOT = Path(__file__).resolve().parents[3]
23
+ if str(_PLUGIN_ROOT) not in sys.path:
24
+ sys.path.insert(0, str(_PLUGIN_ROOT))
25
+
26
+ from lib.authz_check import require_authorization # noqa: E402
27
+ from lib.finding import Finding, Severity # noqa: E402
28
+ from lib.http_client import make_session, safe_get # noqa: E402
29
+ from lib.report import emit, exit_code # noqa: E402
30
+
31
+ SKILL_ID = "detecting-exposed-secrets-files"
32
+
33
+
34
+ # Probe set. Each entry: (path, label, severity, body_fingerprint_regex_or_None, control)
35
+ # fingerprint_regex applied case-insensitively against first 2 KiB of body.
36
+ PROBES = [
37
+ # Critical - direct credential / source / data exposure
38
+ (
39
+ ".git/HEAD",
40
+ "Git repository .git/HEAD exposed",
41
+ Severity.CRITICAL,
42
+ r"^(ref:\s*refs/|[0-9a-f]{40})",
43
+ "NIST 800-53 SC-28",
44
+ ),
45
+ (
46
+ ".git/config",
47
+ "Git repository .git/config exposed (may include remote credentials)",
48
+ Severity.CRITICAL,
49
+ r"\[remote\b",
50
+ "NIST 800-53 SC-28",
51
+ ),
52
+ (".git/index", "Git repository .git/index exposed", Severity.CRITICAL, r"^DIRC", "NIST 800-53 SC-28"),
53
+ (
54
+ ".git/logs/HEAD",
55
+ "Git repository ref log exposed",
56
+ Severity.CRITICAL,
57
+ r"[0-9a-f]{40}\s+[0-9a-f]{40}",
58
+ "NIST 800-53 SC-28",
59
+ ),
60
+ (
61
+ ".env",
62
+ ".env file exposed (likely contains API keys, DB credentials)",
63
+ Severity.CRITICAL,
64
+ r"^[A-Z_][A-Z0-9_]*\s*=",
65
+ "OWASP A05:2021",
66
+ ),
67
+ (".env.production", ".env.production exposed", Severity.CRITICAL, r"^[A-Z_][A-Z0-9_]*\s*=", "OWASP A05:2021"),
68
+ (".env.local", ".env.local exposed", Severity.CRITICAL, r"^[A-Z_][A-Z0-9_]*\s*=", "OWASP A05:2021"),
69
+ (
70
+ ".aws/credentials",
71
+ "AWS credentials file exposed",
72
+ Severity.CRITICAL,
73
+ r"\[default\]|aws_access_key_id",
74
+ "CWE-200",
75
+ ),
76
+ (".aws/config", "AWS config file exposed", Severity.CRITICAL, r"\[default\]|region\s*=", "CWE-200"),
77
+ ("id_rsa", "SSH private key exposed", Severity.CRITICAL, r"BEGIN\s+(RSA|OPENSSH|EC|DSA)?\s*PRIVATE KEY", "CWE-321"),
78
+ ("id_ed25519", "SSH ed25519 private key exposed", Severity.CRITICAL, r"BEGIN\s+OPENSSH\s+PRIVATE KEY", "CWE-321"),
79
+ ("server.pem", "Server PEM key exposed", Severity.CRITICAL, r"BEGIN\s+(RSA\s+)?PRIVATE KEY", "CWE-321"),
80
+ ("backup.sql", "SQL backup exposed", Severity.CRITICAL, r"CREATE\s+TABLE|INSERT\s+INTO", "CWE-538"),
81
+ ("dump.sql", "SQL dump exposed", Severity.CRITICAL, r"CREATE\s+TABLE|INSERT\s+INTO", "CWE-538"),
82
+ ("database.sql", "Database SQL exposed", Severity.CRITICAL, r"CREATE\s+TABLE|INSERT\s+INTO", "CWE-538"),
83
+ ("backup.zip", "Archive backup.zip exposed", Severity.CRITICAL, None, "CWE-538"),
84
+ ("backup.tar.gz", "Archive backup.tar.gz exposed", Severity.CRITICAL, None, "CWE-538"),
85
+ ("dump.tar.gz", "Archive dump.tar.gz exposed", Severity.CRITICAL, None, "CWE-538"),
86
+ # High - VCS metadata (less direct than .git but still source-of-truth-leaking)
87
+ (".svn/entries", "Subversion .svn/entries exposed", Severity.HIGH, r"^\d+\s|^svn:", "NIST 800-53 SC-28"),
88
+ (".svn/wc.db", "Subversion working copy DB exposed", Severity.HIGH, r"^SQLite", "NIST 800-53 SC-28"),
89
+ (".hg/store/00manifest.i", "Mercurial repo manifest exposed", Severity.HIGH, None, "NIST 800-53 SC-28"),
90
+ (".bzr/branch-format", "Bazaar branch format exposed", Severity.HIGH, r"Bazaar", "NIST 800-53 SC-28"),
91
+ # Medium - useful enumeration for attacker
92
+ (
93
+ ".DS_Store",
94
+ "macOS .DS_Store exposed (reveals directory structure)",
95
+ Severity.MEDIUM,
96
+ r"^Bud1|^\x00\x00\x00\x01Bud1",
97
+ "CWE-538",
98
+ ),
99
+ ("Thumbs.db", "Windows Thumbs.db exposed", Severity.MEDIUM, None, "CWE-538"),
100
+ ("phpinfo.php", "phpinfo() output exposed", Severity.MEDIUM, r"PHP Version|phpinfo\(\)", "CWE-200"),
101
+ ("info.php", "PHP info exposed", Severity.MEDIUM, r"PHP Version|phpinfo\(\)", "CWE-200"),
102
+ ("test.php", "Test PHP file exposed", Severity.MEDIUM, r"PHP Version|phpinfo\(\)", "CWE-200"),
103
+ # Low - infrastructure metadata
104
+ (".idea/workspace.xml", "JetBrains IDE config exposed", Severity.LOW, r"<project", "CWE-200"),
105
+ (".vscode/settings.json", "VS Code config exposed", Severity.LOW, r"^\s*\{", "CWE-200"),
106
+ (".gitlab-ci.yml", "GitLab CI config exposed", Severity.LOW, r"stages:|script:", "CWE-200"),
107
+ (".github/workflows/", "GitHub Actions workflows dir exposed", Severity.LOW, None, "CWE-200"),
108
+ ("Dockerfile", "Dockerfile exposed", Severity.LOW, r"^FROM\s+", "CWE-200"),
109
+ ("docker-compose.yml", "docker-compose.yml exposed", Severity.LOW, r"^version:|services:", "CWE-200"),
110
+ ("composer.json", "PHP composer.json exposed on web root", Severity.LOW, r'"name":\s*"', "CWE-200"),
111
+ ("package.json", "Node package.json exposed on web root", Severity.LOW, r'"name":\s*"', "CWE-200"),
112
+ ("requirements.txt", "Python requirements.txt exposed", Severity.LOW, r"^[a-zA-Z][a-zA-Z0-9_.-]*[=<>]", "CWE-200"),
113
+ ("Gemfile", "Ruby Gemfile exposed", Severity.LOW, r"^source\s+['\"]https", "CWE-200"),
114
+ ("config.yml", "Generic config.yml exposed", Severity.LOW, None, "CWE-200"),
115
+ ("config.json", "Generic config.json exposed", Severity.LOW, r"^\s*\{", "CWE-200"),
116
+ ("README.md", "README.md exposed on production web root", Severity.LOW, r"^#\s+", "CWE-200"),
117
+ ]
118
+
119
+
120
+ def _verify_fingerprint(body_text: str, fingerprint_re: str | None, content_type: str) -> bool:
121
+ """Return True if response body looks like the expected file type."""
122
+ if fingerprint_re is None:
123
+ # No fingerprint check requested (e.g. binary archives) — trust the 200
124
+ return True
125
+ # Inspect first 2 KiB
126
+ sample = body_text[:2048]
127
+ if re.search(fingerprint_re, sample, re.MULTILINE | re.IGNORECASE):
128
+ return True
129
+ # If server claimed it's HTML / SPA, treat as false positive
130
+ if "text/html" in content_type.lower():
131
+ return False
132
+ return False
133
+
134
+
135
+ def main(argv: list[str] | None = None) -> int:
136
+ parser = argparse.ArgumentParser(description="Probe for exposed secrets files")
137
+ parser.add_argument("url")
138
+ parser.add_argument("--authorized", action="store_true")
139
+ parser.add_argument("--output", default=None)
140
+ parser.add_argument("--format", choices=("json", "jsonl", "markdown"), default="markdown")
141
+ parser.add_argument("--min-severity", choices=("critical", "high", "medium", "low", "info"), default="info")
142
+ parser.add_argument("--timeout", type=float, default=10.0)
143
+ parser.add_argument("--paths-file", default=None, help="Custom probe set (one path per line); replaces default")
144
+ parser.add_argument(
145
+ "--check-only", action="store_true", help="Skip body fingerprint check (treat any 200 as a finding)"
146
+ )
147
+ args = parser.parse_args(argv)
148
+
149
+ require_authorization(args.url, args.authorized)
150
+
151
+ base = args.url.rstrip("/") + "/"
152
+ sess = make_session(timeout=args.timeout)
153
+ findings: list[Finding] = []
154
+
155
+ if args.paths_file:
156
+ paths = Path(args.paths_file).read_text().splitlines()
157
+ probe_set = [
158
+ (p.strip(), f"Custom path exposed: {p.strip()}", Severity.MEDIUM, None, "custom")
159
+ for p in paths
160
+ if p.strip()
161
+ ]
162
+ else:
163
+ probe_set = PROBES
164
+
165
+ for path, title, sev, fingerprint, control in probe_set:
166
+ url = base + path.lstrip("/")
167
+ resp = safe_get(sess, url, timeout=args.timeout, allow_redirects=False)
168
+ if resp is None or resp.status_code != 200:
169
+ continue
170
+ body = resp.text or ""
171
+ ctype = resp.headers.get("Content-Type", "")
172
+ if not args.check_only and not _verify_fingerprint(body, fingerprint, ctype):
173
+ continue
174
+ evidence = (("status_code", 200), ("content_length", len(resp.content or b"")), ("content_type", ctype))
175
+ findings.append(
176
+ Finding(
177
+ skill_id=SKILL_ID,
178
+ title=title,
179
+ severity=sev,
180
+ target=url,
181
+ detail=(
182
+ f"GET {url} returned 200 with content matching the expected "
183
+ f"signature of {path!r}. The file is publicly reachable "
184
+ "and likely contains sensitive data."
185
+ ),
186
+ remediation=(
187
+ f"Configure the web server to deny requests to {path!r} and "
188
+ "the directory it lives in. See references/PLAYBOOK.md for "
189
+ "nginx / Apache / Caddy / ALB snippets per category."
190
+ ),
191
+ cwe_id=None,
192
+ affected_control=control,
193
+ evidence=evidence,
194
+ )
195
+ )
196
+
197
+ # Severity floor
198
+ floor = Severity(args.min_severity)
199
+ findings = [f for f in findings if f.severity.numeric >= floor.numeric]
200
+
201
+ target_display = args.url
202
+ emit(findings, args.output, args.format, target_display)
203
+ return exit_code(findings)
204
+
205
+
206
+ if __name__ == "__main__":
207
+ sys.exit(main())