@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,325 @@
1
+ # Hardcoded-Secrets Remediation Playbook
2
+
3
+ ## After detection: the standing procedure
4
+
5
+ 1. **Rotate.** Same hour. The window between detection and rotation
6
+ is dead time for the credential's usefulness to anyone but
7
+ attackers.
8
+ 2. **Audit upstream logs.** Check the provider's API audit log
9
+ (AWS CloudTrail, GitHub audit log, Stripe events, etc.) for any
10
+ request against the credential since the leak commit timestamp.
11
+ 3. **Remove from source.** Replace the literal with an env-var
12
+ lookup or secrets-manager fetch. See per-language patterns
13
+ below.
14
+ 4. **Add a pre-commit gate.** Wire this skill into the pre-commit
15
+ hook so the same engineer doesn't re-introduce the same class
16
+ tomorrow.
17
+ 5. **(Optional) Scrub history.** Only if private repo with
18
+ controlled clones AND credential is non-rotatable AND
19
+ coordination overhead is acceptable.
20
+
21
+ ## Per-language migration patterns
22
+
23
+ ### Python — `python-dotenv`
24
+
25
+ ```python
26
+ # Before (vulnerable):
27
+ STRIPE_KEY = "sk_live_abc123..."
28
+
29
+ # After:
30
+ import os
31
+ from dotenv import load_dotenv
32
+ load_dotenv()
33
+ STRIPE_KEY = os.environ["STRIPE_KEY"] # KeyError on missing
34
+ ```
35
+
36
+ `.env` (gitignored):
37
+
38
+ ```
39
+ STRIPE_KEY=sk_live_abc123...
40
+ ```
41
+
42
+ `.gitignore`:
43
+
44
+ ```
45
+ .env
46
+ .env.local
47
+ .env.production
48
+ ```
49
+
50
+ ### Python — pydantic-settings (typed config)
51
+
52
+ ```python
53
+ from pydantic_settings import BaseSettings, SettingsConfigDict
54
+
55
+ class Settings(BaseSettings):
56
+ model_config = SettingsConfigDict(env_file=".env", case_sensitive=False)
57
+ stripe_key: str
58
+ aws_access_key_id: str
59
+ aws_secret_access_key: str
60
+
61
+ settings = Settings()
62
+ ```
63
+
64
+ ### Node.js — dotenv
65
+
66
+ ```javascript
67
+ // Before:
68
+ const STRIPE_KEY = "sk_live_abc123...";
69
+
70
+ // After:
71
+ require('dotenv').config();
72
+ const STRIPE_KEY = process.env.STRIPE_KEY;
73
+ if (!STRIPE_KEY) throw new Error("STRIPE_KEY not configured");
74
+ ```
75
+
76
+ ### Ruby on Rails — `Rails.application.credentials`
77
+
78
+ ```bash
79
+ # Edit (creates / decrypts encrypted credentials)
80
+ EDITOR=vim rails credentials:edit
81
+ ```
82
+
83
+ In the editor:
84
+
85
+ ```yaml
86
+ stripe:
87
+ api_key: sk_live_abc123...
88
+ ```
89
+
90
+ Then in code:
91
+
92
+ ```ruby
93
+ Rails.application.credentials.dig(:stripe, :api_key)
94
+ ```
95
+
96
+ The encrypted file (`config/credentials.yml.enc`) commits to git;
97
+ the decryption key (`config/master.key`) does NOT. Both are
98
+ required to read the secret at runtime.
99
+
100
+ ### Go — `envconfig`
101
+
102
+ ```go
103
+ package main
104
+
105
+ import (
106
+ "log"
107
+ "github.com/kelseyhightower/envconfig"
108
+ )
109
+
110
+ type Config struct {
111
+ StripeKey string `envconfig:"STRIPE_KEY" required:"true"`
112
+ AWSAccessKeyID string `envconfig:"AWS_ACCESS_KEY_ID" required:"true"`
113
+ AWSSecretKey string `envconfig:"AWS_SECRET_ACCESS_KEY" required:"true"`
114
+ }
115
+
116
+ func main() {
117
+ var c Config
118
+ if err := envconfig.Process("", &c); err != nil {
119
+ log.Fatal(err)
120
+ }
121
+ }
122
+ ```
123
+
124
+ ### Rust — `dotenvy` + `envy`
125
+
126
+ ```rust
127
+ use serde::Deserialize;
128
+
129
+ #[derive(Deserialize)]
130
+ struct Config {
131
+ stripe_key: String,
132
+ aws_access_key_id: String,
133
+ aws_secret_access_key: String,
134
+ }
135
+
136
+ fn main() {
137
+ dotenvy::dotenv().ok();
138
+ let config: Config = envy::from_env().expect("missing env config");
139
+ }
140
+ ```
141
+
142
+ ### Java — Spring Boot `@Value`
143
+
144
+ ```java
145
+ @Component
146
+ public class StripeConfig {
147
+ @Value("${stripe.api.key}")
148
+ private String stripeKey;
149
+ }
150
+ ```
151
+
152
+ `application.yml`:
153
+
154
+ ```yaml
155
+ stripe:
156
+ api:
157
+ key: ${STRIPE_KEY} # interpolated from environment
158
+ ```
159
+
160
+ Production: set `STRIPE_KEY` in the environment (Kubernetes secret,
161
+ ECS task definition, etc.) and Spring picks it up at startup.
162
+
163
+ ## Secrets managers
164
+
165
+ For production, env vars alone aren't sufficient (they leak via
166
+ process listings, container introspection, error reports). Use a
167
+ dedicated secrets manager.
168
+
169
+ ### AWS Secrets Manager
170
+
171
+ ```python
172
+ import boto3, json
173
+ client = boto3.client("secretsmanager")
174
+ secret_value = client.get_secret_value(SecretId="prod/stripe")["SecretString"]
175
+ config = json.loads(secret_value)
176
+ STRIPE_KEY = config["api_key"]
177
+ ```
178
+
179
+ IAM policy grants the runtime role `secretsmanager:GetSecretValue`
180
+ on the specific secret ARN. No literal in source; no literal in
181
+ env-var dumps.
182
+
183
+ ### GCP Secret Manager
184
+
185
+ ```python
186
+ from google.cloud import secretmanager
187
+
188
+ client = secretmanager.SecretManagerServiceClient()
189
+ name = "projects/my-project/secrets/stripe-key/versions/latest"
190
+ response = client.access_secret_version(request={"name": name})
191
+ STRIPE_KEY = response.payload.data.decode("UTF-8")
192
+ ```
193
+
194
+ ### HashiCorp Vault
195
+
196
+ ```bash
197
+ # At runtime, runtime fetches the secret
198
+ vault kv get -field=api_key secret/stripe
199
+ ```
200
+
201
+ Or via the SDK:
202
+
203
+ ```python
204
+ import hvac
205
+ client = hvac.Client(url="https://vault.internal:8200")
206
+ client.auth.approle.login(role_id=..., secret_id=...)
207
+ secret = client.secrets.kv.v2.read_secret_version(path="stripe")
208
+ STRIPE_KEY = secret["data"]["data"]["api_key"]
209
+ ```
210
+
211
+ ### Doppler / 1Password Secrets Automation / Bitwarden Secrets
212
+
213
+ All follow the same pattern: SDK call at startup, no literals in
214
+ source.
215
+
216
+ ## Pre-commit hook integration
217
+
218
+ ### Using `pre-commit` framework
219
+
220
+ `.pre-commit-config.yaml`:
221
+
222
+ ```yaml
223
+ repos:
224
+ - repo: local
225
+ hooks:
226
+ - id: scan-secrets
227
+ name: Scan for hardcoded secrets
228
+ entry: python3 plugins/security/penetration-tester/skills/scanning-for-hardcoded-secrets/scripts/scan_secrets.py
229
+ language: system
230
+ args: ['--min-severity', 'high']
231
+ pass_filenames: false
232
+ ```
233
+
234
+ Install: `pre-commit install`. Now every `git commit` runs the
235
+ scan; commits abort if the scan finds a high/critical credential.
236
+
237
+ ### Husky (Node projects)
238
+
239
+ `package.json`:
240
+
241
+ ```json
242
+ {
243
+ "husky": {
244
+ "hooks": {
245
+ "pre-commit": "python3 plugins/security/penetration-tester/skills/scanning-for-hardcoded-secrets/scripts/scan_secrets.py --min-severity high . || exit 1"
246
+ }
247
+ }
248
+ }
249
+ ```
250
+
251
+ ## Provider rotation procedures
252
+
253
+ ### AWS access key
254
+
255
+ ```bash
256
+ # Create new key (keep old active until apps cut over)
257
+ aws iam create-access-key --user-name myuser
258
+
259
+ # Update app config / secrets manager with new key
260
+ # Verify apps are using new key (CloudTrail will show old key inactive)
261
+
262
+ # Then deactivate + delete old key
263
+ aws iam update-access-key --user-name myuser --access-key-id AKIAOLD --status Inactive
264
+ # After 24h grace:
265
+ aws iam delete-access-key --user-name myuser --access-key-id AKIAOLD
266
+ ```
267
+
268
+ ### GitHub PAT
269
+
270
+ Settings → Developer settings → Personal access tokens → revoke
271
+ old, generate new. Update CI / local config.
272
+
273
+ ### Stripe
274
+
275
+ Dashboard → Developers → API keys → roll secret key. Two-step:
276
+ generate new, deploy, deactivate old.
277
+
278
+ ### Anthropic
279
+
280
+ Console → API keys → revoke old, create new. Update env / secrets
281
+ manager.
282
+
283
+ ### Slack
284
+
285
+ App settings → OAuth & Permissions → "Reinstall to Workspace" with
286
+ admin approval generates fresh bot/user tokens.
287
+
288
+ ## GitHub Secret Scanning integration
289
+
290
+ If your repo is on GitHub:
291
+
292
+ - Settings → Code security → enable "Secret scanning alerts"
293
+ - Settings → Code security → enable "Push protection" (this is the
294
+ game-changer: blocks pushes that contain detected credential
295
+ shapes, BEFORE the commit lands on the remote)
296
+
297
+ GitHub's pattern library is roughly equivalent to this skill's;
298
+ running both is defense-in-depth.
299
+
300
+ ## CI integration
301
+
302
+ ```yaml
303
+ - name: Hardcoded-secrets scan
304
+ run: |
305
+ python3 plugins/security/penetration-tester/skills/scanning-for-hardcoded-secrets/scripts/scan_secrets.py \
306
+ . --min-severity high --format json --output secrets-scan.json
307
+ - name: Fail on findings
308
+ run: |
309
+ if [ "$(jq 'length' secrets-scan.json)" != "0" ]; then
310
+ cat secrets-scan.json
311
+ exit 1
312
+ fi
313
+ ```
314
+
315
+ ## Verification after remediation
316
+
317
+ ```bash
318
+ python3 ${CLAUDE_PLUGIN_ROOT}/skills/scanning-for-hardcoded-secrets/scripts/scan_secrets.py \
319
+ /path/to/repo --min-severity high
320
+ ```
321
+
322
+ Expected: exit 0, zero high/critical findings. MEDIUM entropy-based
323
+ findings may persist if your codebase legitimately contains high-
324
+ entropy literals in test fixtures or build artifacts; verify
325
+ manually.
@@ -0,0 +1,175 @@
1
+ # Hardcoded-Secrets Theory
2
+
3
+ ## Why this class persists despite being well-known
4
+
5
+ The pattern is well-understood: don't hardcode credentials in source.
6
+ Every engineering team knows this. Every framework's getting-started
7
+ guide opens with "set this in your `.env` file, not in code." Yet
8
+ the class remains the #1 root cause of credential compromise year
9
+ over year.
10
+
11
+ Three reasons:
12
+
13
+ 1. **The "just for testing" trap.** An engineer is debugging
14
+ integration with a new API. The right thing is to set
15
+ `STRIPE_KEY=...` in the local env and read from there. The fast
16
+ thing is to paste the key into the integration test file as a
17
+ literal. The test works, the engineer moves on, the literal
18
+ stays.
19
+
20
+ 2. **Migration leftovers.** A codebase migrated from one secrets
21
+ pattern to another (e.g., from `.env` to AWS Secrets Manager)
22
+ often leaves stale literals from the pre-migration state, even if
23
+ the runtime fetches from the new location.
24
+
25
+ 3. **Test fixtures with real keys.** Integration tests need real
26
+ credentials to test against real APIs. Some teams check those
27
+ into a `tests/fixtures/` directory with full intent. The test
28
+ harness is now a permanent credential leak surface.
29
+
30
+ The defensive answer is automated detection: scan on every commit
31
+ (pre-commit hook), every push (CI gate), every release (full audit).
32
+ Tools like gitleaks, trufflehog, and GitHub Secret Scanning all
33
+ implement variations of the same regex library this skill uses.
34
+
35
+ ## Why provider-specific regex is the right pattern
36
+
37
+ The naive approach is entropy detection: "find any long string with
38
+ high randomness." The problem is the false-positive rate. Hash
39
+ digests, base64-encoded image data, minified JavaScript, and
40
+ compiled artifacts all look entropy-shaped.
41
+
42
+ Provider-specific regex works because credential issuers use
43
+ prefixed shapes intentionally — partly for routing, partly so their
44
+ own scanners can find leaks in customer code. Examples:
45
+
46
+ | Provider | Prefix | Why prefixed |
47
+ |---|---|---|
48
+ | AWS | `AKIA`, `ASIA`, `ABIA` | IAM type indicator (`AKIA` = long-term, `ASIA` = STS session) |
49
+ | GitHub | `ghp_`, `gho_`, `ghs_`, `ghu_`, `ghr_` | Token-class router |
50
+ | Stripe | `sk_live_`, `sk_test_`, `rk_live_`, `pk_live_` | Environment + role |
51
+ | Anthropic | `sk-ant-api03-` | Format-version + env |
52
+ | OpenAI | `sk-` and `sk-proj-` | Origin (user vs project) |
53
+ | Slack | `xoxb-`, `xoxp-`, `xoxa-` | Token type |
54
+ | Twilio | `AC`, `SK` | Account SID vs API key SID |
55
+ | Google | `AIza` | Service-account vs user-creds router |
56
+
57
+ The prefix means the provider's own scanner can find the leak the
58
+ moment it lands in a public repo (GitHub Secret Scanning auto-
59
+ notifies the provider). The scanner runs at machine speed: a public
60
+ gist containing `AKIA...` is detected in seconds, and AWS gets a
61
+ push notification.
62
+
63
+ This skill scans the same set on the assumption: if the provider's
64
+ bots will find it within seconds, the defensive posture is to find
65
+ it before the commit lands.
66
+
67
+ ## The 1-minute leak window
68
+
69
+ For public repos, the time between commit landing and credential
70
+ extraction is on the order of seconds. GitHub Secret Scanning is
71
+ roughly real-time; bot operators scraping the public push event
72
+ stream are also real-time. By the time a developer notices the
73
+ credential in their `git log` and force-pushes a rewrite, the
74
+ extraction has happened. The credential must be considered
75
+ compromised from the moment of push, regardless of subsequent
76
+ history-scrub.
77
+
78
+ For private repos: window depends on access posture. If contractors
79
+ can clone, the window is "until you trust every contractor with
80
+ every credential ever committed." If only employees clone, the
81
+ window is "until any employee departs."
82
+
83
+ Either way: rotate is mandatory, history-scrub is optional.
84
+
85
+ ## Entropy as a fallback
86
+
87
+ Provider regex covers known credential shapes. New providers and
88
+ custom internal tokens don't match.
89
+
90
+ Shannon entropy measures information density in a string. Higher
91
+ entropy = less compressible = more "random-looking." Real
92
+ credentials are by design high-entropy (an attacker brute-forcing a
93
+ low-entropy credential succeeds instantly).
94
+
95
+ The threshold of ~4.5 bits/char is empirically calibrated:
96
+
97
+ - English text: ~3.5
98
+ - Base64: ~6.0
99
+ - Hex: ~4.0
100
+ - Random 32-char: ~5.0+
101
+
102
+ Above 4.5 in a field labeled `key:`, `token:`, `secret:`,
103
+ `password=` is a strong signal. Below 4.5 is usually English /
104
+ placeholder / template variable.
105
+
106
+ False positives:
107
+
108
+ - High-entropy hashes (commit SHAs, content hashes) appearing in a
109
+ field labeled `key:` (e.g., `cache_key: a3b9...`). Use context to
110
+ filter.
111
+ - Long base64-encoded test fixtures (PDF content, certificate
112
+ blobs). The entropy check passes; the human-verification step
113
+ rejects.
114
+
115
+ The skill emits these as MEDIUM, not CRITICAL, with explicit
116
+ "requires verification" framing.
117
+
118
+ ## History-scrub decision framework
119
+
120
+ After finding a leaked credential in source:
121
+
122
+ **Always rotate the credential.** Non-negotiable.
123
+
124
+ **History scrub depends on these inputs:**
125
+
126
+ 1. **Is the repo public?** Yes → scrub is roughly futile. Anyone
127
+ who cloned has the history; mirror sites cache it. Public repo
128
+ = "publish a forever-archive of every commit ever made."
129
+ 2. **Is the credential still in the file's current state?** Yes →
130
+ scrub removes the live exposure. No (file's been fixed) → history
131
+ is the only remaining exposure surface.
132
+ 3. **Are clones controlled?** Yes (private repo, employees only) →
133
+ force-push + force-pull on every clone is feasible. No → don't
134
+ bother.
135
+ 4. **How long has the credential been in history?** Days → scrub
136
+ may catch unindexed copies. Months → assume copies exist
137
+ permanently.
138
+
139
+ **Pragmatic default:** rotate, fix the current state, don't scrub
140
+ history. The credential is dead either way; the historical
141
+ disclosure of "we leaked something at this point in time" is mostly
142
+ narrative, not technical risk, once the credential is rotated.
143
+
144
+ **Exception:** if the credential CAN'T be rotated (e.g., it's
145
+ embedded in a customer's deployed binary), scrub becomes the only
146
+ remediation. Plan accordingly.
147
+
148
+ ## Why test directories are excluded by default
149
+
150
+ Test fixtures often contain credential-shaped strings that ARE
151
+ placeholders:
152
+
153
+ ```python
154
+ # tests/fixtures/auth.py
155
+ TEST_API_KEY = "ghp_FAKE0000000000000000000000000000000000"
156
+ TEST_AWS_KEY = "AKIATESTKEY12345678"
157
+ ```
158
+
159
+ These match the regex but are deliberately fake. Scanning tests by
160
+ default produces high false-positive rates that train operators to
161
+ ignore findings.
162
+
163
+ The `--include-tests` flag is for the audit case: when reviewing an
164
+ inherited codebase, you DO want to know whether fixtures contain
165
+ real credentials someone forgot to redact. The flag is opt-in.
166
+
167
+ ## Primary sources
168
+
169
+ - [CWE-798 Use of Hard-coded Credentials](https://cwe.mitre.org/data/definitions/798.html)
170
+ - [CWE-321 Use of Hard-coded Cryptographic Key](https://cwe.mitre.org/data/definitions/321.html)
171
+ - [OWASP A07:2021 Identification and Authentication Failures](https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures/)
172
+ - [GitHub Secret Scanning patterns](https://docs.github.com/en/code-security/secret-scanning/secret-scanning-patterns)
173
+ - [AWS IAM access-key reference](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html)
174
+ - [Trufflehog detector list](https://github.com/trufflesecurity/trufflehog/tree/main/pkg/detectors)
175
+ - [Gitleaks rules](https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.toml)