@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,206 @@
1
+ ---
2
+ name: detecting-directory-listing
3
+ description: |
4
+ Probe a target for directories that return auto-generated index
5
+ listings instead of denying or serving a specific file — exposes
6
+ the full file tree under any reachable directory, including files
7
+ the application never linked to.
8
+ Use when: post-deploy verification on a static-asset host, security
9
+ audit before SOC2, or following up on a finding from skill #6
10
+ (exposed-files) where a backup-file path returned 200 with HTML
11
+ body instead of the expected file content (suggests autoindex
12
+ serving a directory listing).
13
+ Threshold: any directory-shaped path returns 200 with HTML body
14
+ matching the framework-specific autoindex fingerprint (nginx
15
+ fancyindex, Apache mod_autoindex Index of/, Caddy browse, Lighttpd
16
+ mod_dirlisting, etc.).
17
+ Trigger with: "directory listing check", "autoindex detection",
18
+ "open directory scan".
19
+ allowed-tools:
20
+ - Read
21
+ - Bash(python3:*)
22
+ - Bash(curl:*)
23
+ disallowed-tools:
24
+ - Bash(rm:*)
25
+ - Edit(/etc/*)
26
+ version: 3.0.0-dev
27
+ author: Jeremy Longshore <jeremy@intentsolutions.io>
28
+ license: MIT
29
+ compatibility: Designed for Claude Code
30
+ tags:
31
+ - security
32
+ - information-disclosure
33
+ - autoindex
34
+ - pentest
35
+ ---
36
+
37
+ # Detecting Directory Listing
38
+
39
+ ## Overview
40
+
41
+ Web servers can be configured to auto-generate an HTML index page
42
+ when a request hits a directory without a matching default file
43
+ (no `index.html` / `index.php` / etc.). The auto-generated page
44
+ lists every file in the directory. This is by design for static-
45
+ file servers and FTP-style file-sharing setups; it's a misconfiguration
46
+ on application servers where the file tree was never meant to be
47
+ public.
48
+
49
+ The risk compounds in two ways:
50
+
51
+ 1. **File enumeration without prior knowledge.** Attackers find files
52
+ you didn't link to (backup files, log files, old uploads).
53
+ 2. **Chaining with exposed-files (#6).** A `/.git/` request that
54
+ returns an autoindex listing reveals every object file under
55
+ `.git/objects/` and confirms the entire repo is reachable for
56
+ GitDumper-style reconstruction.
57
+
58
+ This skill probes commonly-vulnerable directory paths and grades
59
+ each based on the framework-specific autoindex fingerprint.
60
+
61
+ ## When the skill produces findings
62
+
63
+ | Finding | Severity | Threshold | Affected control |
64
+ |---|---|---|---|
65
+ | Application-root directory listing | **HIGH** | `/` returns autoindex page | OWASP A05:2021 |
66
+ | Backup / upload / log directory listing | **HIGH** | `/backup/`, `/uploads/`, `/logs/`, `/dump/` autoindex | CWE-548 |
67
+ | Asset directory listing (`/assets/`, `/static/`) | **MEDIUM** | autoindex on asset dirs (enables file enumeration) | CWE-548 |
68
+ | Config directory listing | **CRITICAL** | `/config/`, `/conf/`, `/.config/` autoindex | NIST 800-53 SC-28 |
69
+ | VCS subdir listing (chains with skill #6) | **CRITICAL** | `/.git/`, `/.svn/`, `/.hg/` autoindex | NIST 800-53 SC-28 |
70
+ | Generic root reachable via autoindex | **MEDIUM** | `/` or arbitrary path autoindex on app server | OWASP A05:2021 |
71
+
72
+ ## Prerequisites
73
+
74
+ - Python 3.9+ with `requests`
75
+ - Authorization for non-local targets
76
+
77
+ ## Instructions
78
+
79
+ ### Step 1 — Confirm Authorization
80
+
81
+ ```text
82
+ "Do you have authorization to perform directory-listing discovery on
83
+ this target? I need confirmation before proceeding."
84
+ ```
85
+
86
+ ### Step 2 — Run the scanner
87
+
88
+ ```bash
89
+ python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-directory-listing/scripts/probe_directory_listing.py \
90
+ https://target.example.com \
91
+ --authorized
92
+ ```
93
+
94
+ Options:
95
+
96
+ ```
97
+ Usage: probe_directory_listing.py URL [OPTIONS]
98
+
99
+ Options:
100
+ --authorized Attest authorization (required for non-local)
101
+ --output FILE Write findings to FILE
102
+ --format FMT json | jsonl | markdown (default: markdown)
103
+ --min-severity SEV (default: info)
104
+ --timeout SECS Per-probe timeout (default: 10)
105
+ --paths-file FILE Custom probe set (one path per line)
106
+ ```
107
+
108
+ For each candidate directory path, the scanner appends a trailing
109
+ slash and sends a GET. If the response is 200 and the HTML body
110
+ matches one of the canonical autoindex fingerprints (Apache,
111
+ nginx, Caddy, Lighttpd, IIS, Python http.server, Node serve, etc.),
112
+ it's a finding.
113
+
114
+ ### Step 3 — Interpret findings
115
+
116
+ CRITICAL = config or VCS directory listing → direct credential or
117
+ source-code exposure when combined with skill #6's secret-file
118
+ probe. Ship same-hour fix.
119
+
120
+ HIGH = backup / upload / log / app-root listing → significant file
121
+ enumeration. Often reveals backup files (`.bak`, `.swp`, `.orig`),
122
+ log files with embedded credentials in URLs, and orphaned uploads
123
+ from old releases. Ship within sprint.
124
+
125
+ MEDIUM = asset directory listing → file enumeration but bounded
126
+ risk if asset content is genuinely public. Still better to disable
127
+ than to leave on.
128
+
129
+ ### Step 4 — Cross-skill chaining
130
+
131
+ After this skill, suggest:
132
+
133
+ - `detecting-exposed-secrets-files` (#6) — if any of the secret-
134
+ file paths (`.git/`, `.env`) return an autoindex page instead of
135
+ the expected file, that's the autoindex feature confirming repo
136
+ exposure.
137
+ - `detecting-debug-endpoints` (#7) — directory listings under
138
+ framework paths (`/static/`, `/public/`) sometimes expose
139
+ framework-debug artifacts.
140
+
141
+ ## Examples
142
+
143
+ ### Example 1 — Static-asset host audit
144
+
145
+ User: "We just spun up a new S3 + CloudFront. Verify autoindex isn't
146
+ on by accident."
147
+
148
+ ```bash
149
+ python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-directory-listing/scripts/probe_directory_listing.py \
150
+ https://cdn.example.com --authorized --min-severity medium
151
+ ```
152
+
153
+ S3 buckets configured with `ListBucket` permissions return XML
154
+ directory listings; the scanner detects those too.
155
+
156
+ ### Example 2 — Following up on .git exposure
157
+
158
+ User: "Skill #6 found .git/HEAD exposed. Check if the full directory
159
+ is browseable."
160
+
161
+ ```bash
162
+ python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-directory-listing/scripts/probe_directory_listing.py \
163
+ https://app.example.com --authorized --paths-file <(echo .git/)
164
+ ```
165
+
166
+ If the `.git/` probe returns an autoindex listing instead of the
167
+ 404 it should, the whole repository is reachable for reconstruction
168
+ via GitDumper-style tools.
169
+
170
+ ### Example 3 — CI gate against autoindex regression
171
+
172
+ ```yaml
173
+ - name: Directory-listing gate
174
+ run: |
175
+ python3 plugins/security/penetration-tester/skills/detecting-directory-listing/scripts/probe_directory_listing.py \
176
+ "${{ secrets.STAGING_URL }}" \
177
+ --authorized --min-severity high
178
+ ```
179
+
180
+ Exit 1 fails the deploy if any HIGH or CRITICAL autoindex finding
181
+ lands. Catches the regression where a new vhost gets deployed
182
+ without the deny-autoindex directive.
183
+
184
+ ## Output
185
+
186
+ JSON / JSONL / Markdown. Exit codes: 0 clean, 1 high/critical, 2 error.
187
+
188
+ ## Error Handling
189
+
190
+ - **Target returns SPA index for every URL** → the scanner's
191
+ fingerprint check distinguishes a real autoindex from an SPA
192
+ catch-all. SPAs don't generate autoindex-shaped HTML.
193
+ - **WAF blocks the scanner** → expected behavior on
194
+ Cloudflare-fronted hosts. The fingerprint will be of the CDN's
195
+ block page, not the origin.
196
+ - **Connection error** → exit 2.
197
+
198
+ ## Resources
199
+
200
+ - `references/THEORY.md` — Per-server autoindex behavior, fingerprint
201
+ patterns, S3 / GCS / Azure Blob considerations
202
+ - `references/PLAYBOOK.md` — Per-server config to disable
203
+ autoindex (nginx `autoindex off`, Apache `Options -Indexes`,
204
+ Caddy disabling browse, etc.) + cloud-storage equivalents
205
+ - `../analyzing-tls-config/references/AUTHORIZATION.md` — Active-scan
206
+ authorization pattern
@@ -0,0 +1,277 @@
1
+ # Directory-Listing Remediation Playbook
2
+
3
+ ## Apache mod_autoindex
4
+
5
+ ### Disable autoindex globally (preferred)
6
+
7
+ `httpd.conf` or vhost:
8
+
9
+ ```apache
10
+ <Directory /var/www/html>
11
+ Options -Indexes
12
+ AllowOverride None
13
+ Require all granted
14
+ </Directory>
15
+ ```
16
+
17
+ The `Options -Indexes` line is the critical part. Combined with no
18
+ `index.html` in a directory, requests return 403 (per Apache's
19
+ default fall-through) instead of an autoindex page.
20
+
21
+ ### Per-directory override
22
+
23
+ If autoindex is needed in one specific directory (rare):
24
+
25
+ ```apache
26
+ <Directory /var/www/html/public-archive>
27
+ Options +Indexes
28
+ IndexOptions +FancyIndexing +SuppressDescription
29
+ </Directory>
30
+ ```
31
+
32
+ But this should require a deliberate decision, not a default.
33
+
34
+ ### .htaccess (if `AllowOverride` permits)
35
+
36
+ ```apache
37
+ Options -Indexes
38
+ ```
39
+
40
+ In every directory you want to deny. The `AllowOverride` in vhost
41
+ must include `Options` for this to work.
42
+
43
+ ## nginx autoindex
44
+
45
+ nginx defaults to autoindex off. The misconfiguration is when
46
+ someone explicitly enabled it:
47
+
48
+ ```nginx
49
+ # In vhost or location block, REMOVE these lines if present:
50
+ autoindex on;
51
+ autoindex_format html;
52
+ autoindex_exact_size off;
53
+ autoindex_localtime on;
54
+ ```
55
+
56
+ To explicitly assert off:
57
+
58
+ ```nginx
59
+ location / {
60
+ autoindex off;
61
+ try_files $uri $uri/ =404;
62
+ }
63
+ ```
64
+
65
+ The `try_files` directive ensures missing files return 404 instead
66
+ of any fallback behavior.
67
+
68
+ ## Caddy file_server
69
+
70
+ Don't use `browse`:
71
+
72
+ ```caddy
73
+ example.com {
74
+ root * /srv/site
75
+ file_server # NO browse keyword
76
+ }
77
+ ```
78
+
79
+ If browse was previously enabled, remove it:
80
+
81
+ ```caddy
82
+ # BEFORE (vulnerable):
83
+ example.com {
84
+ root * /srv/site
85
+ file_server browse
86
+ }
87
+
88
+ # AFTER:
89
+ example.com {
90
+ root * /srv/site
91
+ file_server
92
+ try_files {path} {path}/index.html =404
93
+ }
94
+ ```
95
+
96
+ ## Python http.server
97
+
98
+ Don't use in production. Period. If you absolutely must serve static
99
+ files with python in a constrained environment, use a real server
100
+ behind it OR a subclass that overrides `list_directory`:
101
+
102
+ ```python
103
+ from http.server import HTTPServer, SimpleHTTPRequestHandler
104
+
105
+ class NoBrowse(SimpleHTTPRequestHandler):
106
+ def list_directory(self, path):
107
+ self.send_error(403, "Directory listing not permitted")
108
+ return None
109
+
110
+ HTTPServer(('0.0.0.0', 8000), NoBrowse).serve_forever()
111
+ ```
112
+
113
+ ## Node serve / http-server
114
+
115
+ ```bash
116
+ # serve — pass --single to disable directory listing
117
+ npx serve --single ./build
118
+
119
+ # http-server — pass -d false
120
+ npx http-server -d false ./build
121
+ ```
122
+
123
+ Both options return 404 for missing files instead of generating a
124
+ listing page.
125
+
126
+ ## IIS
127
+
128
+ `web.config`:
129
+
130
+ ```xml
131
+ <system.webServer>
132
+ <directoryBrowse enabled="false" />
133
+ <defaultDocument>
134
+ <files>
135
+ <add value="index.html" />
136
+ <add value="default.htm" />
137
+ </files>
138
+ </defaultDocument>
139
+ </system.webServer>
140
+ ```
141
+
142
+ ## AWS S3
143
+
144
+ ### Block public ListBucket
145
+
146
+ S3 bucket policy — explicitly deny ListBucket to public:
147
+
148
+ ```json
149
+ {
150
+ "Version": "2012-10-17",
151
+ "Statement": [
152
+ {
153
+ "Sid": "DenyPublicList",
154
+ "Effect": "Deny",
155
+ "Principal": "*",
156
+ "Action": "s3:ListBucket",
157
+ "Resource": "arn:aws:s3:::my-public-cdn-bucket"
158
+ },
159
+ {
160
+ "Sid": "AllowPublicRead",
161
+ "Effect": "Allow",
162
+ "Principal": "*",
163
+ "Action": "s3:GetObject",
164
+ "Resource": "arn:aws:s3:::my-public-cdn-bucket/*"
165
+ }
166
+ ]
167
+ }
168
+ ```
169
+
170
+ Even better: enable S3 Block Public Access at the account level:
171
+
172
+ ```bash
173
+ aws s3api put-public-access-block --bucket my-bucket \
174
+ --public-access-block-configuration \
175
+ BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
176
+ ```
177
+
178
+ This blocks ALL public access at the bucket level. To then serve
179
+ content publicly, do it via CloudFront with Origin Access Control,
180
+ NOT via direct S3 URLs.
181
+
182
+ ## AWS CloudFront in front of S3
183
+
184
+ When using CloudFront with Origin Access Control (recommended
185
+ pattern):
186
+
187
+ 1. S3 bucket policy denies all public access.
188
+ 2. Only the OAC's CloudFront distribution can read objects.
189
+ 3. CloudFront's behavior settings determine what's reachable.
190
+
191
+ ```bash
192
+ # Lock down the bucket
193
+ aws s3api put-bucket-policy --bucket my-bucket --policy '{
194
+ "Version": "2012-10-17",
195
+ "Statement": [{
196
+ "Sid": "AllowCloudFrontOAC",
197
+ "Effect": "Allow",
198
+ "Principal": {"Service": "cloudfront.amazonaws.com"},
199
+ "Action": "s3:GetObject",
200
+ "Resource": "arn:aws:s3:::my-bucket/*",
201
+ "Condition": {
202
+ "StringEquals": {
203
+ "AWS:SourceArn": "arn:aws:cloudfront::ACCOUNT:distribution/DIST_ID"
204
+ }
205
+ }
206
+ }]
207
+ }'
208
+ ```
209
+
210
+ ## GCS
211
+
212
+ ```bash
213
+ # Remove allUsers from objectViewer role
214
+ gsutil iam ch -d allUsers:objectViewer gs://my-bucket
215
+
216
+ # Make individual objects publicly readable instead (per-object ACL)
217
+ gsutil acl ch -u AllUsers:R gs://my-bucket/path/to/specific-file.png
218
+ ```
219
+
220
+ Or use a Cloud CDN + signed URLs for true private serving.
221
+
222
+ ## Azure Blob
223
+
224
+ ```bash
225
+ # Set container access level to Private (not Container)
226
+ az storage container set-permission \
227
+ --account-name myaccount \
228
+ --name mycontainer \
229
+ --public-access off
230
+ ```
231
+
232
+ Then use SAS tokens or Azure CDN with private origin for public-facing
233
+ content.
234
+
235
+ ## CI integration
236
+
237
+ ```yaml
238
+ - name: Directory-listing posture gate
239
+ run: |
240
+ python3 plugins/security/penetration-tester/skills/detecting-directory-listing/scripts/probe_directory_listing.py \
241
+ "${{ secrets.PROD_URL }}" \
242
+ --authorized \
243
+ --min-severity high \
244
+ --format json \
245
+ --output autoindex-report.json
246
+ - run: |
247
+ if jq 'any(.severity == "critical" or .severity == "high")' autoindex-report.json | grep -q true; then
248
+ echo "::error::Directory listing detected"
249
+ exit 1
250
+ fi
251
+ ```
252
+
253
+ ## After remediation: assume enumeration occurred
254
+
255
+ If autoindex was exposed for any period:
256
+
257
+ - For `/backup/` listings: assume all backup files were enumerated.
258
+ Rotate any credentials referenced in those files. Investigate
259
+ whether any backup files contained PII that needs breach
260
+ notification.
261
+ - For `/uploads/` listings: assume the file index was scraped.
262
+ If any uploaded files were private (e.g., user-uploaded
263
+ documents), assume those filenames are known to attackers
264
+ even if the file contents required separate auth to fetch.
265
+ - For `/.git/` listings: assume the entire repository was
266
+ reconstructed. Rotate every credential ever committed to the
267
+ repo, including credentials in past commits that were later
268
+ removed.
269
+
270
+ ## Verification after remediation
271
+
272
+ ```bash
273
+ python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-directory-listing/scripts/probe_directory_listing.py \
274
+ https://example.com --authorized --min-severity info
275
+ ```
276
+
277
+ Expected: exit 0, zero findings of any severity.
@@ -0,0 +1,203 @@
1
+ # Directory-Listing Theory
2
+
3
+ ## The recurring pattern
4
+
5
+ Web servers default-handle a directory request in one of three ways:
6
+
7
+ 1. **Serve the directory's `index` file** (`index.html`, `index.php`,
8
+ etc.) — the safe default.
9
+ 2. **Generate an autoindex** — list every file in the directory as
10
+ HTML. The dangerous default.
11
+ 3. **Return 403 / 404** — deny the listing entirely. The defensive
12
+ default.
13
+
14
+ Which behavior applies depends on the server's configuration and
15
+ whether an index file is present. The risk surface: a directory
16
+ without an `index.html`, where the server is configured to
17
+ fall back to autoindex.
18
+
19
+ This was the default in older Apache configurations. Modern stacks
20
+ (Caddy, nginx defaults) deny by default but legacy configs still
21
+ have it enabled.
22
+
23
+ ## Per-server behavior
24
+
25
+ ### Apache mod_autoindex
26
+
27
+ ```apache
28
+ Options +Indexes # autoindex ENABLED — bad on app servers
29
+ Options -Indexes # autoindex DISABLED — safe default
30
+ ```
31
+
32
+ The default in Apache 2.4 depends on the distribution: Debian /
33
+ Ubuntu have `Indexes` enabled in the default `<Directory /var/www/>`
34
+ block. Red Hat / CentOS disable it by default.
35
+
36
+ Fingerprint: `<title>Index of /<path></title>` + an HTML table of
37
+ file entries with mtime + size columns.
38
+
39
+ ### nginx autoindex
40
+
41
+ ```nginx
42
+ location /uploads/ {
43
+ autoindex on; # enabled — bad
44
+ }
45
+ location /uploads/ {
46
+ autoindex off; # disabled — safe (default)
47
+ }
48
+ ```
49
+
50
+ nginx defaults to autoindex off; you have to explicitly enable it.
51
+ When seen, it's a deliberate config someone added.
52
+
53
+ Fingerprint: HTML body with `<pre>` formatting, lines like
54
+ `<a href="filename">filename</a>` with size + mtime columns.
55
+
56
+ ### Caddy file_server browse
57
+
58
+ ```caddy
59
+ example.com {
60
+ root * /srv/site
61
+ file_server browse # autoindex enabled
62
+ }
63
+ ```
64
+
65
+ vs.
66
+
67
+ ```caddy
68
+ example.com {
69
+ root * /srv/site
70
+ file_server # no browse — disabled (default)
71
+ }
72
+ ```
73
+
74
+ Caddy v2 defaults to NOT browse. The browse mode generates a styled
75
+ HTML directory listing.
76
+
77
+ Fingerprint: `<table class="listing">` with sortable column headers.
78
+
79
+ ### Lighttpd mod_dirlisting
80
+
81
+ ```lighttpd
82
+ dir-listing.activate = "enable"
83
+ ```
84
+
85
+ Less common in modern deployments but still found in IoT firmware
86
+ web UIs and embedded server setups.
87
+
88
+ ### Python http.server (dev only)
89
+
90
+ `python3 -m http.server 8000` — the stdlib dev server defaults to
91
+ autoindex. Should never be in production but sometimes runs on a
92
+ forgotten port.
93
+
94
+ Fingerprint: `<title>Directory listing for /<path></title>`.
95
+
96
+ ### Node `serve` / `http-server`
97
+
98
+ `npx serve` and `http-server` both default to autoindex. Common in
99
+ developer-facing tooling, hosting docs sites, or "I just need a
100
+ quick static server" setups.
101
+
102
+ ### IIS
103
+
104
+ IIS disables directory browsing by default. If enabled in
105
+ `web.config`:
106
+
107
+ ```xml
108
+ <system.webServer>
109
+ <directoryBrowse enabled="true" />
110
+ </system.webServer>
111
+ ```
112
+
113
+ Fingerprint: HTML body with "Directory browsing" title.
114
+
115
+ ### AWS S3 ListBucket
116
+
117
+ S3 buckets aren't web servers in the conventional sense but expose
118
+ similar functionality. A bucket policy that grants `s3:ListBucket`
119
+ to the public lets unauthenticated requestors list every object.
120
+
121
+ Fingerprint: XML response with `<ListBucketResult>` root element,
122
+ listing every key in the bucket.
123
+
124
+ ### Azure Blob Storage list-blob
125
+
126
+ Same pattern: `?restype=container&comp=list` on a public-list
127
+ container returns XML enumeration of every blob.
128
+
129
+ Fingerprint: `<EnumerationResults>` root + blob entries with
130
+ properties.
131
+
132
+ ### GCS bucket "list" permission
133
+
134
+ GCP Cloud Storage equivalent: `storage.objects.list` granted to
135
+ `allUsers` lets anyone enumerate bucket contents.
136
+
137
+ Fingerprint: similar XML / JSON enumeration depending on whether
138
+ the request hit the JSON or XML API.
139
+
140
+ ## Why directory listings escalate other findings
141
+
142
+ Directory listings rarely stand alone. They compound the impact of
143
+ other findings:
144
+
145
+ ### Combined with `.git/` exposure
146
+
147
+ Skill #6 detects `.git/HEAD` reachable. If the parent `.git/`
148
+ directory ALSO has autoindex on, every object in `.git/objects/`
149
+ is enumerable. GitDumper-style tools become unnecessary — you can
150
+ just `wget -r` the directory.
151
+
152
+ ### Combined with backup-file exposure
153
+
154
+ Skill #6 probes specific backup file paths (`backup.sql`,
155
+ `dump.sql`). Autoindex on `/backup/` lets the attacker see every
156
+ backup file the operator ever left, including ones with non-
157
+ canonical names (`db-snapshot-2024-03-15.sql.bz2`,
158
+ `pre-migration-dump.sql`).
159
+
160
+ ### Combined with upload directories
161
+
162
+ If `/uploads/` lists, every user-uploaded file is enumerable
163
+ including potentially-sensitive private uploads that the
164
+ application's auth layer was supposed to gate.
165
+
166
+ ## Why static-asset hosts are a common pitfall
167
+
168
+ A common pattern: front-end is hosted on a CDN (S3 plus CloudFront,
169
+ GCS plus Cloud CDN). The bucket policy is set to "allow public read
170
+ of specific objects" but the BUCKET listing permission is left at
171
+ default, which on S3 with an explicit grant is "public ListBucket."
172
+
173
+ The fix is to:
174
+
175
+ 1. Make individual objects publicly readable (`s3:GetObject`).
176
+ 2. NOT grant `s3:ListBucket` to `*`.
177
+
178
+ Result: clients can fetch `https://cdn.example.com/assets/logo.png`
179
+ if they know the path, but `https://cdn.example.com/assets/`
180
+ returns AccessDenied.
181
+
182
+ ## Why the SPA case isn't autoindex
183
+
184
+ Single-Page Applications using client-side routing return the app's
185
+ `index.html` for any unknown route. That's NOT an autoindex page —
186
+ it's a deliberate routing decision. The fingerprint check
187
+ distinguishes:
188
+
189
+ - Autoindex → HTML body contains "Index of /<path>" or framework
190
+ banner
191
+ - SPA → HTML body is the application's own UI
192
+
193
+ A 200 response with HTML body that doesn't match any autoindex
194
+ fingerprint is an SPA catch-all and is NOT flagged.
195
+
196
+ ## Primary sources
197
+
198
+ - [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)
199
+ - [CWE-548 — Exposure of Information Through Directory Listing](https://cwe.mitre.org/data/definitions/548.html)
200
+ - [nginx autoindex module](https://nginx.org/en/docs/http/ngx_http_autoindex_module.html)
201
+ - [Apache mod_autoindex](https://httpd.apache.org/docs/2.4/mod/mod_autoindex.html)
202
+ - [Caddy file_server directive](https://caddyserver.com/docs/caddyfile/directives/file_server)
203
+ - [AWS S3 — Blocking public access](https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html)