@blamejs/exceptd-skills 0.9.5 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +45 -0
- package/CHANGELOG.md +120 -0
- package/README.md +30 -5
- package/bin/exceptd.js +694 -1
- package/data/_indexes/_meta.json +2 -2
- package/data/playbooks/ai-api.json +1073 -0
- package/data/playbooks/containers.json +1078 -0
- package/data/playbooks/cred-stores.json +1000 -0
- package/data/playbooks/crypto.json +1008 -0
- package/data/playbooks/framework.json +1015 -0
- package/data/playbooks/hardening.json +945 -0
- package/data/playbooks/kernel.json +796 -0
- package/data/playbooks/mcp.json +1042 -0
- package/data/playbooks/runtime.json +913 -0
- package/data/playbooks/sbom.json +1279 -0
- package/data/playbooks/secrets.json +959 -0
- package/lib/cross-ref-api.js +224 -0
- package/lib/playbook-runner.js +896 -0
- package/lib/schemas/playbook.schema.json +657 -0
- package/manifest-snapshot.json +1 -1
- package/manifest.json +39 -39
- package/orchestrator/scanner.js +23 -1
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
|
@@ -0,0 +1,959 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"id": "secrets",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"last_threat_review": "2026-05-11",
|
|
6
|
+
"threat_currency_score": 96,
|
|
7
|
+
"changelog": [
|
|
8
|
+
{
|
|
9
|
+
"version": "1.0.0",
|
|
10
|
+
"date": "2026-05-11",
|
|
11
|
+
"summary": "Initial seven-phase repo-scoped secret-exfiltration playbook. Walks the current repository to depth 6 (excluding node_modules, .git/objects, dist, build) for secret-shape regexes (AWS, GCP, GitHub, Slack, Stripe, JWT, SSH private keys, OpenAI, Anthropic), world-writable .env / .ssh files, and IaC files with embedded credentials. Closes the GRC loop with DLP framework-gap mapping + auditor-ready exception generation.",
|
|
12
|
+
"framework_gaps_updated": [
|
|
13
|
+
"nist-800-53-SC-28",
|
|
14
|
+
"iso-27001-2022-A.8.24",
|
|
15
|
+
"gdpr-art-32",
|
|
16
|
+
"pci-dss-4-req-3",
|
|
17
|
+
"soc2-cc6.1"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"owner": "@blamejs/grc",
|
|
22
|
+
"air_gap_mode": true,
|
|
23
|
+
"scope": "code",
|
|
24
|
+
"preconditions": [
|
|
25
|
+
{
|
|
26
|
+
"id": "repo-context",
|
|
27
|
+
"description": "Playbook runs against the current working directory tree. There must be a working directory and the agent must have read access to it.",
|
|
28
|
+
"check": "cwd_readable == true",
|
|
29
|
+
"on_fail": "halt"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "regex-engine",
|
|
33
|
+
"description": "Host AI must support PCRE-equivalent regex for the catalogued secret patterns.",
|
|
34
|
+
"check": "agent_supports_pcre == true",
|
|
35
|
+
"on_fail": "warn"
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"mutex": [],
|
|
39
|
+
"feeds_into": [
|
|
40
|
+
{
|
|
41
|
+
"playbook_id": "cred-stores",
|
|
42
|
+
"condition": "finding.severity >= 'high'"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
"domain": {
|
|
47
|
+
"name": "Repository-scoped secret exfiltration risk",
|
|
48
|
+
"attack_class": "dlp-exfiltration",
|
|
49
|
+
"atlas_refs": [
|
|
50
|
+
"AML.T0055"
|
|
51
|
+
],
|
|
52
|
+
"attack_refs": [
|
|
53
|
+
"T1552.001",
|
|
54
|
+
"T1552.004",
|
|
55
|
+
"T1552.005",
|
|
56
|
+
"T1078.004"
|
|
57
|
+
],
|
|
58
|
+
"cve_refs": [],
|
|
59
|
+
"cwe_refs": [
|
|
60
|
+
"CWE-798",
|
|
61
|
+
"CWE-200",
|
|
62
|
+
"CWE-312",
|
|
63
|
+
"CWE-732"
|
|
64
|
+
],
|
|
65
|
+
"d3fend_refs": [
|
|
66
|
+
"D3-CAA",
|
|
67
|
+
"D3-FCR"
|
|
68
|
+
],
|
|
69
|
+
"frameworks_in_scope": [
|
|
70
|
+
"nist-800-53",
|
|
71
|
+
"iso-27001-2022",
|
|
72
|
+
"soc2",
|
|
73
|
+
"pci-dss-4",
|
|
74
|
+
"nis2",
|
|
75
|
+
"dora",
|
|
76
|
+
"uk-caf",
|
|
77
|
+
"hipaa"
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
"phases": {
|
|
81
|
+
"govern": {
|
|
82
|
+
"jurisdiction_obligations": [
|
|
83
|
+
{
|
|
84
|
+
"jurisdiction": "EU",
|
|
85
|
+
"regulation": "GDPR Art.33",
|
|
86
|
+
"obligation": "notify_regulator",
|
|
87
|
+
"window_hours": 72,
|
|
88
|
+
"clock_starts": "detect_confirmed",
|
|
89
|
+
"evidence_required": [
|
|
90
|
+
"secret_inventory",
|
|
91
|
+
"data_subject_impact_assessment",
|
|
92
|
+
"containment_record"
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"jurisdiction": "EU",
|
|
97
|
+
"regulation": "GDPR Art.34",
|
|
98
|
+
"obligation": "notify_affected_individuals",
|
|
99
|
+
"window_hours": 72,
|
|
100
|
+
"clock_starts": "analyze_complete",
|
|
101
|
+
"evidence_required": [
|
|
102
|
+
"data_subject_categories_affected",
|
|
103
|
+
"containment_record"
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"jurisdiction": "EU",
|
|
108
|
+
"regulation": "NIS2 Art.23",
|
|
109
|
+
"obligation": "notify_regulator",
|
|
110
|
+
"window_hours": 24,
|
|
111
|
+
"clock_starts": "detect_confirmed",
|
|
112
|
+
"evidence_required": [
|
|
113
|
+
"secret_inventory",
|
|
114
|
+
"exposure_window_estimate",
|
|
115
|
+
"rotation_status"
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"jurisdiction": "UK",
|
|
120
|
+
"regulation": "UK GDPR Art.33",
|
|
121
|
+
"obligation": "notify_regulator",
|
|
122
|
+
"window_hours": 72,
|
|
123
|
+
"clock_starts": "detect_confirmed",
|
|
124
|
+
"evidence_required": [
|
|
125
|
+
"secret_inventory",
|
|
126
|
+
"data_subject_impact_assessment"
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"jurisdiction": "US-CA",
|
|
131
|
+
"regulation": "CCPA / CPRA Sec.1798.82",
|
|
132
|
+
"obligation": "notify_affected_individuals",
|
|
133
|
+
"window_hours": 1440,
|
|
134
|
+
"clock_starts": "analyze_complete",
|
|
135
|
+
"evidence_required": [
|
|
136
|
+
"california_resident_records_affected",
|
|
137
|
+
"containment_record"
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
],
|
|
141
|
+
"theater_fingerprints": [
|
|
142
|
+
{
|
|
143
|
+
"pattern_id": "gitleaks-in-ci-as-prevention",
|
|
144
|
+
"claim": "gitleaks / trufflehog runs in CI → no secrets in repo.",
|
|
145
|
+
"fast_detection_test": "Run a current scan against the repo HEAD and against the working tree (uncommitted files). CI catches committed-and-pushed secrets only. Uncommitted .env files, files in .gitignore, secrets in build artifacts under dist/, and IaC files staged-but-not-committed are all outside CI scope. Find any non-empty .env, .env.local, .npmrc, .pypirc, or terraform.tfvars in the working tree to verify.",
|
|
146
|
+
"implicated_controls": [
|
|
147
|
+
"nist-800-53-SA-11",
|
|
148
|
+
"soc2-cc7.1",
|
|
149
|
+
"iso-27001-2022-A.8.28"
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"pattern_id": "vault-attested-but-bypassed",
|
|
154
|
+
"claim": "We use Vault / Secrets Manager / KMS — no static credentials.",
|
|
155
|
+
"fast_detection_test": "Grep the working tree for AWS_ACCESS_KEY_ID, GOOGLE_APPLICATION_CREDENTIALS pointing to a JSON file, ANTHROPIC_API_KEY, OPENAI_API_KEY, GITHUB_TOKEN literals. Vault-attested orgs commonly bypass for 'just this one dev workflow' or 'CI bootstrap secrets'. Any non-templated secret in the working tree is theater regardless of Vault posture.",
|
|
156
|
+
"implicated_controls": [
|
|
157
|
+
"nist-800-53-SC-12",
|
|
158
|
+
"iso-27001-2022-A.8.24",
|
|
159
|
+
"pci-dss-4-req-3"
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"pattern_id": "pre-commit-hook-as-control",
|
|
164
|
+
"claim": "Pre-commit hooks catch secrets before commit.",
|
|
165
|
+
"fast_detection_test": "Check whether the repo's .pre-commit-config.yaml / lefthook.yml / husky config actually invokes a secret-scan tool AND whether the team has ever bypassed via --no-verify (visible in git log for repos with verified commits or in the reflog). Pre-commit is bypassable; pre-receive (server-side) is not. If only pre-commit exists, the control is advisory.",
|
|
166
|
+
"implicated_controls": [
|
|
167
|
+
"soc2-cc7.1",
|
|
168
|
+
"iso-27001-2022-A.8.28"
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
],
|
|
172
|
+
"framework_context": {
|
|
173
|
+
"gap_summary": "Secret scanning frameworks (DLP, GDPR Art.32, ISO A.8.24 cryptographic key management, PCI-DSS 4.0 Req.3, SOC 2 CC6.1) target storage + transit protection of sensitive data. They do not name 'committed/staged secrets in source-controlled repositories' as a distinct control class. NIS2 Art.21(2)(j) names cryptography but not key-material exposure in development artifacts. Real-world: GitGuardian's 2025 report flagged ~24M secrets leaked to public GitHub in 2024 alone — the dominant data-exfil vector is not the storage backend but the development workflow. SCOPE OF THIS SCAN: self-scoped to current working directory tree, depth 6, excluding node_modules, .git/objects, dist, build. Working-tree scan complements (does not replace) git-history scans done by gitleaks/trufflehog in CI. This is a point-in-time triage of what an attacker with read access to the working copy would find.",
|
|
174
|
+
"lag_score": 30,
|
|
175
|
+
"per_framework_gaps": [
|
|
176
|
+
{
|
|
177
|
+
"framework": "nist-800-53",
|
|
178
|
+
"control_id": "SC-28",
|
|
179
|
+
"designed_for": "Protection of information at rest — encryption + access control.",
|
|
180
|
+
"insufficient_because": "Names storage protections; assumes secret material lives in designated stores. Does not contemplate plaintext secret leakage into developer working trees + IaC source files + CI configuration files."
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"framework": "iso-27001-2022",
|
|
184
|
+
"control_id": "A.8.24",
|
|
185
|
+
"designed_for": "Use of cryptography — key lifecycle management.",
|
|
186
|
+
"insufficient_because": "Manages keys-in-stores; silent on key-material-leaked-to-source-code. Audit evidence is typically a key inventory + rotation schedule, neither of which surfaces committed secrets."
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"framework": "pci-dss-4",
|
|
190
|
+
"control_id": "Req.3.5",
|
|
191
|
+
"designed_for": "Cryptographic key protection — keys stored separately from data they protect.",
|
|
192
|
+
"insufficient_because": "Targets cardholder-data-encryption keys; does not cover API tokens / OAuth client secrets / cloud access keys that live in developer working trees."
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"framework": "soc2",
|
|
196
|
+
"control_id": "CC6.1",
|
|
197
|
+
"designed_for": "Logical access controls — restrict logical access to data + system resources.",
|
|
198
|
+
"insufficient_because": "Logical access is about who can authenticate. Says nothing about where credentials are stored on disk in developer workflows."
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"framework": "hipaa",
|
|
202
|
+
"control_id": "164.312(a)(2)(iv)",
|
|
203
|
+
"designed_for": "Encryption + decryption — mechanism to encrypt and decrypt ePHI.",
|
|
204
|
+
"insufficient_because": "Targets encryption mechanism, not key material storage in source artifacts."
|
|
205
|
+
}
|
|
206
|
+
]
|
|
207
|
+
},
|
|
208
|
+
"skill_preload": [
|
|
209
|
+
"dlp-gap-analysis",
|
|
210
|
+
"framework-gap-analysis",
|
|
211
|
+
"compliance-theater",
|
|
212
|
+
"policy-exception-gen"
|
|
213
|
+
]
|
|
214
|
+
},
|
|
215
|
+
"direct": {
|
|
216
|
+
"threat_context": "Secret-in-repo is the #1 cause of cloud account compromise in 2025-2026 IR data. AI-coding-assistant adoption has accelerated the rate: AI assistants generate boilerplate that embeds API keys directly (Anthropic's own published guidance warns against this). GitGuardian Q1 2026 report: ~7.2M secrets leaked to public GitHub in Q1 alone, with OpenAI and Anthropic API keys representing the fastest-growing leak categories. Attack pattern: scraper bots monitor GitHub firehose for committed secrets within minutes of push; AWS keys are exploited within ~5 minutes of public exposure, often before the developer notices the commit. Working-tree scan (this playbook) catches the pre-commit window AND uncommitted secrets that CI scanners miss entirely (build artifacts, .env files in .gitignore, IaC tfvars files, .npmrc auth tokens).",
|
|
217
|
+
"rwep_threshold": {
|
|
218
|
+
"escalate": 85,
|
|
219
|
+
"monitor": 60,
|
|
220
|
+
"close": 30
|
|
221
|
+
},
|
|
222
|
+
"framework_lag_declaration": "GDPR Art.32, ISO A.8.24, PCI-DSS Req.3, SOC 2 CC6.1 collectively cover encryption + key management + access control in production storage backends. None of them name 'secret in source artifact' as a distinct exposure category, despite this being the dominant 2025-2026 cloud-compromise vector. NIS2 Art.21(2)(j) names cryptography but not development-workflow exposure. Gap = ~30 days between developer commit and framework's quarterly access-review cadence; in practice, scraper bots exploit faster than any framework cadence.",
|
|
223
|
+
"skill_chain": [
|
|
224
|
+
{
|
|
225
|
+
"skill": "dlp-gap-analysis",
|
|
226
|
+
"purpose": "Inventory secret-shape findings in the working tree; assess exposure window; map to provider-specific rotation procedures.",
|
|
227
|
+
"required": true
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"skill": "framework-gap-analysis",
|
|
231
|
+
"purpose": "Map findings to which framework controls claim to address secret protection and where the gap is.",
|
|
232
|
+
"required": true
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"skill": "compliance-theater",
|
|
236
|
+
"purpose": "Run the theater test on the org's secret-scanning attestation (CI gitleaks + Vault posture).",
|
|
237
|
+
"required": true
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"skill": "policy-exception-gen",
|
|
241
|
+
"purpose": "Generate auditor-ready exception language for secrets that cannot be rotated immediately (e.g. requires coordinated multi-system rotation).",
|
|
242
|
+
"skip_if": "close.exception_generation.trigger_condition == false",
|
|
243
|
+
"required": false
|
|
244
|
+
}
|
|
245
|
+
],
|
|
246
|
+
"token_budget": {
|
|
247
|
+
"estimated_total": 19000,
|
|
248
|
+
"breakdown": {
|
|
249
|
+
"govern": 2600,
|
|
250
|
+
"direct": 1600,
|
|
251
|
+
"look": 2400,
|
|
252
|
+
"detect": 3000,
|
|
253
|
+
"analyze": 4000,
|
|
254
|
+
"validate": 3000,
|
|
255
|
+
"close": 2400
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
"look": {
|
|
260
|
+
"artifacts": [
|
|
261
|
+
{
|
|
262
|
+
"id": "repo-tree",
|
|
263
|
+
"type": "file",
|
|
264
|
+
"source": "Walk cwd to depth 6, exclude **/node_modules/**, **/.git/objects/**, **/dist/**, **/build/**",
|
|
265
|
+
"description": "Repository file tree within scope. The entry list bounds every subsequent regex scan.",
|
|
266
|
+
"required": true,
|
|
267
|
+
"air_gap_alternative": "Use native filesystem walk; no network required."
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
"id": "env-files",
|
|
271
|
+
"type": "file",
|
|
272
|
+
"source": "Walk for filenames matching .env, .env.*, .envrc, *.env, .env.local, .env.production, .env.development",
|
|
273
|
+
"description": "Environment files. Standard secret-carrier in modern dev workflows.",
|
|
274
|
+
"required": true
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
"id": "auth-config-files",
|
|
278
|
+
"type": "file",
|
|
279
|
+
"source": "Walk for filenames .npmrc, .pypirc, .netrc, .git-credentials, .docker/config.json, .yarnrc.yml, .yarnrc, settings.xml, gradle.properties",
|
|
280
|
+
"description": "Package manager / VCS auth config files commonly containing tokens.",
|
|
281
|
+
"required": true
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
"id": "ssh-private-keys",
|
|
285
|
+
"type": "file",
|
|
286
|
+
"source": "Walk for filenames id_rsa, id_ed25519, id_ecdsa, id_dsa, *.pem, *.key, *.p12, *.pfx (excluding *.pub)",
|
|
287
|
+
"description": "Private key material. Even encrypted private keys are findings (offline brute-force surface).",
|
|
288
|
+
"required": true
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
"id": "iac-credential-bearers",
|
|
292
|
+
"type": "file",
|
|
293
|
+
"source": "Walk for *.tf, *.tfvars, *.tfvars.json, terraform.tfstate, pulumi.*.yaml, *.bicep, *.arm.json, ansible playbooks (*.yml + *.yaml in roles/group_vars/host_vars), helm values.yaml, k8s secret.yaml",
|
|
294
|
+
"description": "Infrastructure-as-code files frequently containing embedded credentials.",
|
|
295
|
+
"required": true
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
"id": "secret-regex-scan-text-files",
|
|
299
|
+
"type": "file",
|
|
300
|
+
"source": "For every text file in scope: grep against the catalogued secret patterns (AWS, GCP, GitHub, Slack, Stripe, JWT, OpenAI, Anthropic) — see detect.indicators for the regex set.",
|
|
301
|
+
"description": "Full-content regex scan against the working tree. Produces match locations + surrounding context for each finding.",
|
|
302
|
+
"required": true
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"id": "world-writable-secret-files",
|
|
306
|
+
"type": "file",
|
|
307
|
+
"source": "For every file matched by env-files OR auth-config-files OR ssh-private-keys: stat for world-writable (perm o+w) OR world-readable (perm o+r) on .ssh contents.",
|
|
308
|
+
"description": "Permission posture on secret-carrier files. .ssh/id_* should be 0600; .env should not be world-readable.",
|
|
309
|
+
"required": true
|
|
310
|
+
}
|
|
311
|
+
],
|
|
312
|
+
"collection_scope": {
|
|
313
|
+
"time_window": "current",
|
|
314
|
+
"asset_scope": "current_repository_working_tree",
|
|
315
|
+
"depth": "deep",
|
|
316
|
+
"sampling": "complete walk of cwd to depth 6 with named exclusions; no sampling — every file in scope is scanned"
|
|
317
|
+
},
|
|
318
|
+
"environment_assumptions": [
|
|
319
|
+
{
|
|
320
|
+
"assumption": "cwd is a development repository (not a deployed production filesystem)",
|
|
321
|
+
"if_false": "If cwd is a deployed-app filesystem, regex set still applies but interpretation changes: secrets at runtime in /etc, /opt, /home are operationally normal (Vault agent caches, kubelet service-account tokens). Mark such findings as 'deployed-runtime' class and defer to cred-stores playbook for proper treatment."
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
"assumption": "agent has read access to all files in cwd",
|
|
325
|
+
"if_false": "Some files unreadable (different UID, ACL). Mark per-file inconclusive rather than treating absence as clean."
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
"assumption": "cwd is under reasonable depth (large monorepos may exceed scope at depth 6)",
|
|
329
|
+
"if_false": "Files at depth > 6 are excluded. Emit a 'scope-truncated' visibility note. Operator may re-run with --depth flag if needed."
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
"assumption": "host filesystem supports POSIX permission bits (not Windows ACL-only)",
|
|
333
|
+
"if_false": "On Windows, world-writable check is replaced with Everyone:Write ACL check; same conceptual finding but different artifact shape."
|
|
334
|
+
}
|
|
335
|
+
],
|
|
336
|
+
"fallback_if_unavailable": [
|
|
337
|
+
{
|
|
338
|
+
"artifact_id": "repo-tree",
|
|
339
|
+
"fallback_action": "escalate_to_human",
|
|
340
|
+
"confidence_impact": "high"
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
"artifact_id": "env-files",
|
|
344
|
+
"fallback_action": "mark_inconclusive",
|
|
345
|
+
"confidence_impact": "medium"
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
"artifact_id": "auth-config-files",
|
|
349
|
+
"fallback_action": "mark_inconclusive",
|
|
350
|
+
"confidence_impact": "medium"
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
"artifact_id": "ssh-private-keys",
|
|
354
|
+
"fallback_action": "mark_inconclusive",
|
|
355
|
+
"confidence_impact": "high"
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"artifact_id": "iac-credential-bearers",
|
|
359
|
+
"fallback_action": "mark_inconclusive",
|
|
360
|
+
"confidence_impact": "medium"
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
"artifact_id": "secret-regex-scan-text-files",
|
|
364
|
+
"fallback_action": "escalate_to_human",
|
|
365
|
+
"confidence_impact": "high"
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
"artifact_id": "world-writable-secret-files",
|
|
369
|
+
"fallback_action": "mark_inconclusive",
|
|
370
|
+
"confidence_impact": "medium"
|
|
371
|
+
}
|
|
372
|
+
]
|
|
373
|
+
},
|
|
374
|
+
"detect": {
|
|
375
|
+
"indicators": [
|
|
376
|
+
{
|
|
377
|
+
"id": "aws-access-key-id",
|
|
378
|
+
"type": "log_pattern",
|
|
379
|
+
"value": "AKIA[0-9A-Z]{16}",
|
|
380
|
+
"description": "AWS Access Key ID. Long-lived IAM user credential. Scraper-bot priority target.",
|
|
381
|
+
"confidence": "deterministic",
|
|
382
|
+
"deterministic": true,
|
|
383
|
+
"attack_ref": "T1552.001"
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
"id": "aws-secret-access-key",
|
|
387
|
+
"type": "log_pattern",
|
|
388
|
+
"value": "(?i)aws_secret_access_key[\"']?\\s*[:=]\\s*[\"']?[A-Za-z0-9/+=]{40}[\"']?",
|
|
389
|
+
"description": "AWS Secret Access Key. Always findable in tandem with AKIA*. Independent finding because both halves are needed.",
|
|
390
|
+
"confidence": "deterministic",
|
|
391
|
+
"deterministic": true,
|
|
392
|
+
"attack_ref": "T1552.001"
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
"id": "gcp-service-account-json",
|
|
396
|
+
"type": "log_pattern",
|
|
397
|
+
"value": "\"type\"\\s*:\\s*\"service_account\"[^{}]*\"private_key\"\\s*:\\s*\"-----BEGIN PRIVATE KEY-----",
|
|
398
|
+
"description": "GCP service account JSON file with embedded private key. Plaintext key material.",
|
|
399
|
+
"confidence": "deterministic",
|
|
400
|
+
"deterministic": true,
|
|
401
|
+
"attack_ref": "T1552.004"
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
"id": "github-personal-access-token",
|
|
405
|
+
"type": "log_pattern",
|
|
406
|
+
"value": "gh[ps]_[A-Za-z0-9]{36}",
|
|
407
|
+
"description": "GitHub Personal Access Token (ghp_) or Server Token (ghs_).",
|
|
408
|
+
"confidence": "deterministic",
|
|
409
|
+
"deterministic": true,
|
|
410
|
+
"attack_ref": "T1552.001"
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
"id": "github-fine-grained-pat",
|
|
414
|
+
"type": "log_pattern",
|
|
415
|
+
"value": "github_pat_[A-Za-z0-9_]{82}",
|
|
416
|
+
"description": "GitHub fine-grained personal access token.",
|
|
417
|
+
"confidence": "deterministic",
|
|
418
|
+
"deterministic": true,
|
|
419
|
+
"attack_ref": "T1552.001"
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
"id": "slack-bot-or-user-token",
|
|
423
|
+
"type": "log_pattern",
|
|
424
|
+
"value": "xox[baprs]-[A-Za-z0-9-]{10,}",
|
|
425
|
+
"description": "Slack bot / user / refresh / app token.",
|
|
426
|
+
"confidence": "deterministic",
|
|
427
|
+
"deterministic": true,
|
|
428
|
+
"attack_ref": "T1552.001"
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
"id": "stripe-secret-key",
|
|
432
|
+
"type": "log_pattern",
|
|
433
|
+
"value": "sk_(live|test)_[A-Za-z0-9]{24,}",
|
|
434
|
+
"description": "Stripe live or test secret key. Live keys are direct financial exposure.",
|
|
435
|
+
"confidence": "deterministic",
|
|
436
|
+
"deterministic": true,
|
|
437
|
+
"attack_ref": "T1552.001"
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
"id": "jwt-token-with-secret-context",
|
|
441
|
+
"type": "log_pattern",
|
|
442
|
+
"value": "eyJ[A-Za-z0-9_-]{10,}\\.eyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}",
|
|
443
|
+
"description": "JWT (header.payload.signature). May or may not be sensitive depending on signing context; surface for triage.",
|
|
444
|
+
"confidence": "medium",
|
|
445
|
+
"deterministic": false,
|
|
446
|
+
"attack_ref": "T1552.004"
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
"id": "ssh-private-key-block",
|
|
450
|
+
"type": "log_pattern",
|
|
451
|
+
"value": "-----BEGIN ((RSA|EC|DSA|OPENSSH|ENCRYPTED) )?PRIVATE KEY-----",
|
|
452
|
+
"description": "Inline SSH/PEM private key material in a text file.",
|
|
453
|
+
"confidence": "deterministic",
|
|
454
|
+
"deterministic": true,
|
|
455
|
+
"attack_ref": "T1552.004"
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
"id": "openai-api-key",
|
|
459
|
+
"type": "log_pattern",
|
|
460
|
+
"value": "sk-(proj-)?[A-Za-z0-9_-]{40,}",
|
|
461
|
+
"description": "OpenAI API key (incl. project-scoped sk-proj-* form). Active key spend exposure.",
|
|
462
|
+
"confidence": "deterministic",
|
|
463
|
+
"deterministic": true,
|
|
464
|
+
"attack_ref": "T1552.001"
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
"id": "anthropic-api-key",
|
|
468
|
+
"type": "log_pattern",
|
|
469
|
+
"value": "sk-ant-(api03|admin01)-[A-Za-z0-9_-]{80,}",
|
|
470
|
+
"description": "Anthropic API key. Active key spend exposure.",
|
|
471
|
+
"confidence": "deterministic",
|
|
472
|
+
"deterministic": true,
|
|
473
|
+
"attack_ref": "T1552.001"
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
"id": "world-writable-env-file",
|
|
477
|
+
"type": "file_path",
|
|
478
|
+
"value": ".env / .env.* / .envrc with mode 0666 or 0664 (group/world writable)",
|
|
479
|
+
"description": "Env file writable by group or world. Tampering primitive.",
|
|
480
|
+
"confidence": "deterministic",
|
|
481
|
+
"deterministic": true,
|
|
482
|
+
"attack_ref": "T1552.001"
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
"id": "ssh-key-bad-perms",
|
|
486
|
+
"type": "file_path",
|
|
487
|
+
"value": "~/.ssh/id_* file with mode != 0600",
|
|
488
|
+
"description": "SSH private key with permissive permissions. ssh-agent refuses to load; user often chmods to 0644 or 0666 'to make it work'.",
|
|
489
|
+
"confidence": "deterministic",
|
|
490
|
+
"deterministic": true,
|
|
491
|
+
"attack_ref": "T1552.004"
|
|
492
|
+
}
|
|
493
|
+
],
|
|
494
|
+
"false_positive_profile": [
|
|
495
|
+
{
|
|
496
|
+
"indicator_id": "aws-access-key-id",
|
|
497
|
+
"benign_pattern": "AKIA-shaped string in test fixtures, documentation, or sample-data files (e.g. AKIAIOSFODNN7EXAMPLE — AWS's own published example).",
|
|
498
|
+
"distinguishing_test": "Check whether the exact string equals one of the published AWS example keys (AKIAIOSFODNN7EXAMPLE is the canonical). If yes, suppress as known-test. Otherwise check whether the secret-half also resolves (call sts:GetCallerIdentity if network available; in air-gap mode, treat as deterministic finding regardless)."
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
"indicator_id": "jwt-token-with-secret-context",
|
|
502
|
+
"benign_pattern": "JWT example token from documentation, OIDC discovery flows, test fixtures.",
|
|
503
|
+
"distinguishing_test": "Decode the JWT header + payload (base64 decode; no signature verification needed). If 'iss' / 'aud' / 'sub' claims point to test/example/localhost issuers OR the token's 'exp' is in the past, downgrade to low confidence. Real production JWTs targeting prod issuers stay high."
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
"indicator_id": "ssh-private-key-block",
|
|
507
|
+
"benign_pattern": "Test fixture private keys used in unit/integration tests (e.g. cryptography library test vectors).",
|
|
508
|
+
"distinguishing_test": "Check filename context (path contains /test/, /__tests__/, /fixtures/, /spec/) AND check whether the key has a corresponding .pub fixture in the same directory. If both heuristics match AND the key bits match a known test-vector hash, downgrade to medium. Otherwise hold deterministic."
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
"indicator_id": "openai-api-key",
|
|
512
|
+
"benign_pattern": "sk- prefix is also used by Stripe (sk_live_, sk_test_) and other vendors; loose regex may collide.",
|
|
513
|
+
"distinguishing_test": "OpenAI keys are sk-[A-Za-z0-9_-]{40,} OR sk-proj-[A-Za-z0-9_-]{40,}; Stripe keys are sk_live_ / sk_test_ with underscores after sk. Pattern boundary on the third character (-/underscore) distinguishes deterministically."
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
"indicator_id": "github-personal-access-token",
|
|
517
|
+
"benign_pattern": "Revoked tokens left in old code or in security training materials.",
|
|
518
|
+
"distinguishing_test": "GitHub PATs have a checksum in the last 6 characters. Verify checksum if doing offline. If checksum valid, the token was real at issuance time; even if revoked, treat as finding because revocation status cannot be verified in air-gap mode. If checksum invalid, downgrade to low."
|
|
519
|
+
}
|
|
520
|
+
],
|
|
521
|
+
"minimum_signal": {
|
|
522
|
+
"detected": "At least one deterministic indicator fires AND the false-positive distinguishing test does not clear it.",
|
|
523
|
+
"inconclusive": "Match found but distinguishing test cannot be performed in air-gap mode (e.g. cannot call sts:GetCallerIdentity to confirm AWS key validity). Treat as detected with the caveat noted.",
|
|
524
|
+
"not_detected": "Full repo walk completed AND zero deterministic patterns matched AND no high-confidence patterns matched AND no world-writable secret files AND .ssh keys (if present) have mode 0600."
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
"analyze": {
|
|
528
|
+
"rwep_inputs": [
|
|
529
|
+
{
|
|
530
|
+
"signal_id": "aws-access-key-id",
|
|
531
|
+
"rwep_factor": "active_exploitation",
|
|
532
|
+
"weight": 30,
|
|
533
|
+
"notes": "Scraper bots monetize within minutes of public exposure. Treat as exploitation-imminent."
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
"signal_id": "aws-access-key-id",
|
|
537
|
+
"rwep_factor": "ai_weaponization",
|
|
538
|
+
"weight": 10,
|
|
539
|
+
"notes": "AI-powered scrapers triage and weaponize at scale."
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
"signal_id": "gcp-service-account-json",
|
|
543
|
+
"rwep_factor": "active_exploitation",
|
|
544
|
+
"weight": 30,
|
|
545
|
+
"notes": "Same as AWS."
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
"signal_id": "github-personal-access-token",
|
|
549
|
+
"rwep_factor": "active_exploitation",
|
|
550
|
+
"weight": 25,
|
|
551
|
+
"notes": "PAT exposure = repo + CI compromise + supply chain pivot."
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
"signal_id": "openai-api-key",
|
|
555
|
+
"rwep_factor": "active_exploitation",
|
|
556
|
+
"weight": 20,
|
|
557
|
+
"notes": "Spend abuse is automatic; AI account compromise extends to fine-tunes / files / projects in account."
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
"signal_id": "anthropic-api-key",
|
|
561
|
+
"rwep_factor": "active_exploitation",
|
|
562
|
+
"weight": 20,
|
|
563
|
+
"notes": "Same as OpenAI."
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
"signal_id": "ssh-private-key-block",
|
|
567
|
+
"rwep_factor": "blast_radius",
|
|
568
|
+
"weight": 20,
|
|
569
|
+
"notes": "Inline private key = lateral movement primitive."
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
"signal_id": "world-writable-env-file",
|
|
573
|
+
"rwep_factor": "blast_radius",
|
|
574
|
+
"weight": 15,
|
|
575
|
+
"notes": "Tampering primitive on dev workstation; supply-chain implications if CI pulls from same path."
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
"signal_id": "ssh-key-bad-perms",
|
|
579
|
+
"rwep_factor": "blast_radius",
|
|
580
|
+
"weight": 10,
|
|
581
|
+
"notes": "Permission flaw enables credential theft by any local user."
|
|
582
|
+
}
|
|
583
|
+
],
|
|
584
|
+
"blast_radius_model": {
|
|
585
|
+
"scope_question": "Given the secret(s) found in the working tree, what is the blast radius if this repo (or its working copy) is read by an unauthorized party?",
|
|
586
|
+
"scoring_rubric": [
|
|
587
|
+
{
|
|
588
|
+
"condition": "Single test/expired/example credential matched with FP distinguishing test passing",
|
|
589
|
+
"blast_radius_score": 1,
|
|
590
|
+
"description": "False-positive shaped match; no live exposure."
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
"condition": "Single non-production credential (test env, sandbox account) confirmed live",
|
|
594
|
+
"blast_radius_score": 2,
|
|
595
|
+
"description": "Sandbox-scope compromise. Cleanup required but limited."
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
"condition": "Single production credential confirmed live (or unconfirmed but production-shaped) on a single-cloud or single-vendor account",
|
|
599
|
+
"blast_radius_score": 3,
|
|
600
|
+
"description": "Production single-account compromise. Spend + data exposure within that account's IAM scope."
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
"condition": "Production credential AND credential has broad IAM scope (admin / wildcard / service-account JSON with project owner)",
|
|
604
|
+
"blast_radius_score": 4,
|
|
605
|
+
"description": "Cloud-account-wide compromise. Data exfil + resource creation + lateral to other tenants."
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
"condition": "Multiple production credentials AND credentials span identity layers (cloud + SaaS + SCM) AND repo is or was public",
|
|
609
|
+
"blast_radius_score": 5,
|
|
610
|
+
"description": "Org-wide identity boundary collapse. Treat as confirmed incident."
|
|
611
|
+
}
|
|
612
|
+
]
|
|
613
|
+
},
|
|
614
|
+
"compliance_theater_check": {
|
|
615
|
+
"claim": "Org runs gitleaks/trufflehog in CI + uses Vault for runtime secrets → no exposed secrets.",
|
|
616
|
+
"audit_evidence": "CI logs showing secret-scanner runs; Vault audit log showing secret retrievals.",
|
|
617
|
+
"reality_test": "Run a working-tree scan today (this playbook). Count: (a) secrets in tracked files that CI missed, (b) secrets in .gitignore'd files that CI cannot see at all, (c) secrets in dist/ build/ artifacts that CI scanned source but not build, (d) world-writable .env files. Any count > 0 is gap between CI-attested clean state and actual working-tree state. Additionally cross-reference any matched OpenAI/Anthropic key against Vault's secret inventory — if the key is not in Vault, the 'Vault for runtime secrets' attestation is incomplete.",
|
|
618
|
+
"theater_verdict_if_gap": "CI-only secret scanning misses the dominant exposure paths (uncommitted, gitignored, build-artifact, IaC tfvars). Either (a) extend scanning to working tree + build output + IaC files, (b) require Vault-templated secret consumption with pre-commit blocking on raw literals, OR (c) generate policy exception documenting the scope gap + compensating controls + remediation timeline."
|
|
619
|
+
},
|
|
620
|
+
"framework_gap_mapping": [
|
|
621
|
+
{
|
|
622
|
+
"finding_id": "secret-in-repo",
|
|
623
|
+
"framework": "nist-800-53",
|
|
624
|
+
"claimed_control": "SC-28 — Protection of Information at Rest",
|
|
625
|
+
"actual_gap": "Targets designated storage. Does not name source-control working trees as 'at-rest' for sensitive data. Permits attestation focused on production storage encryption.",
|
|
626
|
+
"required_control": "Extend SC-28 scope to source artifacts. Require evidence that secrets are not present in working-tree files (env, IaC, package configs) at any time, with continuous attestation."
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
"finding_id": "secret-in-repo",
|
|
630
|
+
"framework": "iso-27001-2022",
|
|
631
|
+
"claimed_control": "A.8.24 — Use of Cryptography",
|
|
632
|
+
"actual_gap": "Key lifecycle management focuses on key-in-store. Silent on key-material-in-source.",
|
|
633
|
+
"required_control": "Add a footnote requiring inventory of credentials present in source artifacts AND evidence of removal prior to commit + at point of working-tree creation."
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
"finding_id": "secret-in-repo",
|
|
637
|
+
"framework": "pci-dss-4",
|
|
638
|
+
"claimed_control": "Req.3.5 — Cryptographic Key Protection",
|
|
639
|
+
"actual_gap": "Cardholder-data-encryption keys only. API tokens / cloud keys handling cardholder-data-adjacent operations outside scope.",
|
|
640
|
+
"required_control": "Extend Req.3 to all credentials granting access to systems within CDE, including cloud-API tokens that could pivot to CDE."
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
"finding_id": "secret-in-repo",
|
|
644
|
+
"framework": "soc2",
|
|
645
|
+
"claimed_control": "CC6.1 — Logical Access Controls",
|
|
646
|
+
"actual_gap": "Authentication-shaped, not credential-storage-shaped.",
|
|
647
|
+
"required_control": "Add a CC6 sub-criterion requiring evidence that credentials granting logical access are not exposed in source-controlled artifacts."
|
|
648
|
+
}
|
|
649
|
+
],
|
|
650
|
+
"escalation_criteria": [
|
|
651
|
+
{
|
|
652
|
+
"condition": "rwep >= 90 AND credential_appears_live == true",
|
|
653
|
+
"action": "page_on_call"
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
"condition": "blast_radius_score >= 4",
|
|
657
|
+
"action": "trigger_playbook",
|
|
658
|
+
"target_playbook": "cred-stores"
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
"condition": "compliance_theater_check.verdict == 'theater' AND jurisdiction_obligations contains 'EU'",
|
|
662
|
+
"action": "notify_legal"
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
"condition": "any_secret_in_committed_history == true",
|
|
666
|
+
"action": "raise_severity"
|
|
667
|
+
}
|
|
668
|
+
]
|
|
669
|
+
},
|
|
670
|
+
"validate": {
|
|
671
|
+
"remediation_paths": [
|
|
672
|
+
{
|
|
673
|
+
"id": "rotate-and-purge",
|
|
674
|
+
"description": "(1) Rotate the credential at the provider (AWS IAM, GCP, GitHub, Slack, Stripe, OpenAI, Anthropic), (2) revoke any sessions tied to the old credential, (3) remove the literal from the working tree + replace with a Vault/Secrets Manager template, (4) if the credential ever entered git history, BFG / git-filter-repo the history AND notify any forks/mirrors.",
|
|
675
|
+
"preconditions": [
|
|
676
|
+
"provider_supports_revocation == true",
|
|
677
|
+
"rotation_ownership_identified == true"
|
|
678
|
+
],
|
|
679
|
+
"priority": 1,
|
|
680
|
+
"compensating_controls": [
|
|
681
|
+
"session-revocation",
|
|
682
|
+
"cloudtrail-or-audit-log-review-for-misuse-window"
|
|
683
|
+
],
|
|
684
|
+
"estimated_time_hours": 2
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
"id": "fix-file-permissions",
|
|
688
|
+
"description": "For world-writable .env / .ssh / auth-config files: chmod 0600 (private keys) or 0640 (env files). For SSH key with bad perms: chmod 0600 and re-load via ssh-add.",
|
|
689
|
+
"preconditions": [
|
|
690
|
+
"file_owner_is_current_user OR has_sudo == true"
|
|
691
|
+
],
|
|
692
|
+
"priority": 2,
|
|
693
|
+
"compensating_controls": [
|
|
694
|
+
"perm-change-recorded"
|
|
695
|
+
],
|
|
696
|
+
"estimated_time_hours": 0.5
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
"id": "tighten-prevention",
|
|
700
|
+
"description": "Install pre-commit hook + pre-receive server-side hook running gitleaks/trufflehog. Add .gitignore entries for env/auth-config files. Add CI step scanning build artifacts under dist/build.",
|
|
701
|
+
"preconditions": [
|
|
702
|
+
"repo_governance_authority == true"
|
|
703
|
+
],
|
|
704
|
+
"priority": 2,
|
|
705
|
+
"compensating_controls": [
|
|
706
|
+
"pre-commit-config-versioned",
|
|
707
|
+
"ci-config-versioned"
|
|
708
|
+
],
|
|
709
|
+
"estimated_time_hours": 2
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
"id": "policy-exception",
|
|
713
|
+
"description": "If rotation requires coordinated multi-system rollout (e.g. credential is consumed by 50+ services), generate exception with bounded duration + compensating audit-log monitoring.",
|
|
714
|
+
"preconditions": [
|
|
715
|
+
"coordinated_rotation_required == true",
|
|
716
|
+
"credential_misuse_monitoring_in_place == true"
|
|
717
|
+
],
|
|
718
|
+
"priority": 4,
|
|
719
|
+
"compensating_controls": [
|
|
720
|
+
"enhanced-audit-log-alerting-on-credential",
|
|
721
|
+
"incident-response-team-on-standby"
|
|
722
|
+
],
|
|
723
|
+
"estimated_time_hours": 24
|
|
724
|
+
}
|
|
725
|
+
],
|
|
726
|
+
"validation_tests": [
|
|
727
|
+
{
|
|
728
|
+
"id": "regex-rescan-clean",
|
|
729
|
+
"test": "Re-run the working-tree regex scan post-remediation. Expect zero deterministic-confidence findings.",
|
|
730
|
+
"expected_result": "No deterministic matches; any remaining medium/low matches passed FP distinguishing tests.",
|
|
731
|
+
"test_type": "negative"
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
"id": "credential-revoked-at-provider",
|
|
735
|
+
"test": "At the provider (AWS IAM list-access-keys / GitHub /user/token endpoints / OpenAI revoke list / etc.), confirm the old credential's status is revoked/inactive AND the new credential is active.",
|
|
736
|
+
"expected_result": "Old credential inactive; new credential active.",
|
|
737
|
+
"test_type": "functional"
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
"id": "audit-log-shows-no-misuse",
|
|
741
|
+
"test": "Review provider audit log (CloudTrail / GitHub Audit / OpenAI usage history) for the credential's full lifetime. Confirm no anomalous activity in the exposure window.",
|
|
742
|
+
"expected_result": "No anomalous activity from unexpected IPs/UAs/regions.",
|
|
743
|
+
"test_type": "functional"
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
"id": "permissions-correct",
|
|
747
|
+
"test": "stat -c '%a' on remediated files. Expect 0600 for private keys, 0640 or 0600 for env files.",
|
|
748
|
+
"expected_result": "All secret-carrier files have correct permissions.",
|
|
749
|
+
"test_type": "negative"
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
"id": "preventive-control-active",
|
|
753
|
+
"test": "Verify pre-commit hook exists, is executable, and the secret scanner is invoked. Attempt to commit a known-test-pattern (e.g. AKIAIOSFODNN7EXAMPLE) and confirm hook blocks.",
|
|
754
|
+
"expected_result": "Pre-commit hook blocks test pattern.",
|
|
755
|
+
"test_type": "functional"
|
|
756
|
+
}
|
|
757
|
+
],
|
|
758
|
+
"residual_risk_statement": {
|
|
759
|
+
"risk": "Even after rotation + purge + hook installation, the prior credential may have been scraped during the exposure window. The credential is revoked, but any data exfiltrated during the window is gone.",
|
|
760
|
+
"why_remains": "Credential exposure is irreversible at the data-exfil level. Revocation prevents future misuse but does not undo prior misuse. Audit log review establishes upper bound on misuse but cannot prove negative for all classes (e.g. provider API calls within normal-looking patterns).",
|
|
761
|
+
"acceptance_level": "ciso",
|
|
762
|
+
"compensating_controls_in_place": [
|
|
763
|
+
"credential-rotated",
|
|
764
|
+
"session-revoked",
|
|
765
|
+
"audit-log-baseline-captured",
|
|
766
|
+
"pre-commit-hook-active",
|
|
767
|
+
"ci-secret-scan-extended-to-build-artifacts"
|
|
768
|
+
]
|
|
769
|
+
},
|
|
770
|
+
"evidence_requirements": [
|
|
771
|
+
{
|
|
772
|
+
"evidence_type": "scan_report",
|
|
773
|
+
"description": "Pre-remediation full working-tree scan output with file paths + line offsets + matched indicator IDs + RWEP per finding. Plus post-remediation rescan showing clean state.",
|
|
774
|
+
"retention_period": "7_years",
|
|
775
|
+
"framework_satisfied": [
|
|
776
|
+
"gdpr-art-32",
|
|
777
|
+
"iso-27001-2022-A.8.24",
|
|
778
|
+
"soc2-cc6.1",
|
|
779
|
+
"pci-dss-4-req-3"
|
|
780
|
+
]
|
|
781
|
+
},
|
|
782
|
+
{
|
|
783
|
+
"evidence_type": "log_excerpt",
|
|
784
|
+
"description": "Provider audit log excerpt for the exposure window for each rotated credential.",
|
|
785
|
+
"retention_period": "7_years",
|
|
786
|
+
"framework_satisfied": [
|
|
787
|
+
"gdpr-art-33",
|
|
788
|
+
"soc2-cc7.2",
|
|
789
|
+
"nis2-art21-2c"
|
|
790
|
+
]
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
"evidence_type": "ticket_reference",
|
|
794
|
+
"description": "Rotation ticket(s) with timestamps, approvers, provider rotation confirmation IDs.",
|
|
795
|
+
"retention_period": "7_years",
|
|
796
|
+
"framework_satisfied": [
|
|
797
|
+
"soc2-cc8.1",
|
|
798
|
+
"iso-27001-2022-A.8.32"
|
|
799
|
+
]
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
"evidence_type": "attestation",
|
|
803
|
+
"description": "Signed exceptd attestation: repo identity, scan timestamp, finding count, RWEP at detection, RWEP post-remediation, rotated credential IDs (hashed).",
|
|
804
|
+
"retention_period": "7_years",
|
|
805
|
+
"framework_satisfied": [
|
|
806
|
+
"nist-800-53-CA-7",
|
|
807
|
+
"iso-27001-2022-A.5.36",
|
|
808
|
+
"nis2-art21-2c"
|
|
809
|
+
]
|
|
810
|
+
}
|
|
811
|
+
],
|
|
812
|
+
"regression_trigger": [
|
|
813
|
+
{
|
|
814
|
+
"condition": "pre_commit",
|
|
815
|
+
"interval": "on_event"
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
"condition": "weekly",
|
|
819
|
+
"interval": "7d"
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
"condition": "post_dependency_update",
|
|
823
|
+
"interval": "on_event"
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
"condition": "new_secret_pattern_published",
|
|
827
|
+
"interval": "on_event"
|
|
828
|
+
}
|
|
829
|
+
]
|
|
830
|
+
},
|
|
831
|
+
"close": {
|
|
832
|
+
"evidence_package": {
|
|
833
|
+
"bundle_format": "json",
|
|
834
|
+
"contents": [
|
|
835
|
+
"scan_report",
|
|
836
|
+
"log_excerpt",
|
|
837
|
+
"ticket_reference",
|
|
838
|
+
"attestation",
|
|
839
|
+
"framework_gap_mapping",
|
|
840
|
+
"compliance_theater_verdict",
|
|
841
|
+
"residual_risk_statement"
|
|
842
|
+
],
|
|
843
|
+
"destination": "local_only",
|
|
844
|
+
"signed": true
|
|
845
|
+
},
|
|
846
|
+
"learning_loop": {
|
|
847
|
+
"enabled": true,
|
|
848
|
+
"lesson_template": {
|
|
849
|
+
"attack_vector": "Credential exposure via $secret_class ($provider) committed/staged in working tree / IaC source / build artifact / world-writable env file.",
|
|
850
|
+
"control_gap": "Secret-scanning controls scoped to git history miss the working-tree, gitignored-file, build-artifact, and IaC-tfvars exposure paths. Vault-attested key management coexists with developer-workflow bypass.",
|
|
851
|
+
"framework_gap": "GDPR Art.32 / ISO A.8.24 / PCI Req.3 / SOC 2 CC6.1 + NIS2 Art.21(2)(j) collectively cover storage backend protection. None name source-artifact exposure as a distinct control class.",
|
|
852
|
+
"new_control_requirement": "Continuous working-tree scanning + pre-commit + pre-receive secret blocking + build-artifact scan + Vault-templated secret enforcement. Source-artifact exposure becomes its own control class with quarterly attestation requirement."
|
|
853
|
+
},
|
|
854
|
+
"feeds_back_to_skills": [
|
|
855
|
+
"dlp-gap-analysis",
|
|
856
|
+
"framework-gap-analysis",
|
|
857
|
+
"compliance-theater"
|
|
858
|
+
]
|
|
859
|
+
},
|
|
860
|
+
"notification_actions": [
|
|
861
|
+
{
|
|
862
|
+
"obligation_ref": "EU/GDPR Art.33 72h",
|
|
863
|
+
"deadline": "computed_at_runtime",
|
|
864
|
+
"recipient": "internal_legal",
|
|
865
|
+
"evidence_attached": [
|
|
866
|
+
"secret_inventory",
|
|
867
|
+
"data_subject_impact_assessment",
|
|
868
|
+
"containment_record"
|
|
869
|
+
],
|
|
870
|
+
"draft_notification": "GDPR Art.33 72-hour notification: Credential exposure event affecting ${credential_count} credential(s) of type ${credential_class}. Exposure window: ${exposure_window}. Affected processing operations: ${processing_operations}. Categories of data subjects potentially affected: ${data_subject_categories}. Containment actions: credentials rotated, sessions revoked, audit logs reviewed for misuse. Risk assessment: ${risk_level}. No notification to data subjects required because ${art34_reasoning} OR notification to data subjects required and being prepared. Contact: ${dpo_contact}."
|
|
871
|
+
},
|
|
872
|
+
{
|
|
873
|
+
"obligation_ref": "EU/GDPR Art.34 72h",
|
|
874
|
+
"deadline": "computed_at_runtime",
|
|
875
|
+
"recipient": "data_subjects",
|
|
876
|
+
"evidence_attached": [
|
|
877
|
+
"data_subject_categories_affected",
|
|
878
|
+
"containment_record"
|
|
879
|
+
],
|
|
880
|
+
"draft_notification": "GDPR Art.34 notification to data subjects (draft for review): We are writing to inform you of a credential exposure incident affecting systems that process your personal data. Affected systems: ${affected_systems}. Exposure window: ${exposure_window}. Containment actions completed: credentials rotated and revoked, audit logs reviewed. Steps you can take: ${recommended_actions}. Our DPO can be reached at ${dpo_contact}."
|
|
881
|
+
},
|
|
882
|
+
{
|
|
883
|
+
"obligation_ref": "EU/NIS2 Art.23 24h",
|
|
884
|
+
"deadline": "computed_at_runtime",
|
|
885
|
+
"recipient": "internal_legal",
|
|
886
|
+
"evidence_attached": [
|
|
887
|
+
"secret_inventory",
|
|
888
|
+
"exposure_window_estimate",
|
|
889
|
+
"rotation_status"
|
|
890
|
+
],
|
|
891
|
+
"draft_notification": "NIS2 Art.23 early-warning notification: Credential exposure ${credential_class} on ${affected_system_count} system(s). Suspected window of exposure: ${exposure_window}. Initial containment: credentials rotated. Full incident assessment within 72h per Art.23(4)."
|
|
892
|
+
},
|
|
893
|
+
{
|
|
894
|
+
"obligation_ref": "UK/UK GDPR Art.33 72h",
|
|
895
|
+
"deadline": "computed_at_runtime",
|
|
896
|
+
"recipient": "internal_legal",
|
|
897
|
+
"evidence_attached": [
|
|
898
|
+
"secret_inventory",
|
|
899
|
+
"data_subject_impact_assessment"
|
|
900
|
+
],
|
|
901
|
+
"draft_notification": "UK GDPR Art.33 72-hour notification to ICO: Credential exposure event. Mirror of GDPR Art.33 notification body, addressed to ICO casework team."
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
"obligation_ref": "US-CA/CCPA / CPRA Sec.1798.82 1440h",
|
|
905
|
+
"deadline": "computed_at_runtime",
|
|
906
|
+
"recipient": "data_subjects",
|
|
907
|
+
"evidence_attached": [
|
|
908
|
+
"california_resident_records_affected",
|
|
909
|
+
"containment_record"
|
|
910
|
+
],
|
|
911
|
+
"draft_notification": "California breach notification: We are writing to notify you of a security incident that may have affected your personal information. ${incident_description}. ${types_of_information_affected}. ${steps_taken}. ${steps_individuals_can_take}. ${contact_information}."
|
|
912
|
+
}
|
|
913
|
+
],
|
|
914
|
+
"exception_generation": {
|
|
915
|
+
"trigger_condition": "coordinated_rotation_required == true OR (credential_class == 'high_blast_radius' AND rotation_eta > obligation_window)",
|
|
916
|
+
"exception_template": {
|
|
917
|
+
"scope": "Credential of class ${credential_class} (${credential_id_hash}) cannot be rotated within ${obligation_window} due to ${blocking_reason}.",
|
|
918
|
+
"duration": "until_vendor_patch",
|
|
919
|
+
"compensating_controls": [
|
|
920
|
+
"enhanced-audit-log-alerting-on-credential-use",
|
|
921
|
+
"incident-response-team-on-standby",
|
|
922
|
+
"ip-allowlist-tightened-on-credential",
|
|
923
|
+
"rate-limit-on-credential-tightened"
|
|
924
|
+
],
|
|
925
|
+
"risk_acceptance_owner": "ciso",
|
|
926
|
+
"auditor_ready_language": "Pursuant to ${framework_id} ${control_id}, the organization documents a time-bound risk acceptance for unrotated credential ${credential_class} (${credential_id_hash}) on ${affected_system_count} system(s). Blocking reason: ${blocking_reason_narrative}. Rotation ETA: ${rotation_eta}. Compensating controls in place: ${compensating_controls}. Residual exposure: credential remains valid; provider audit logs are reviewed daily during the exception window for misuse indicators. Risk accepted by ${ciso_name} on ${acceptance_date}. Time-bound until ${duration_expiry} OR rotation completion, whichever is first. Detection coverage is provided by ${detection_controls}. The exception will be re-evaluated on (a) rotation completion, (b) listed expiry date, (c) any audit-log anomaly on the credential — whichever is first."
|
|
927
|
+
}
|
|
928
|
+
},
|
|
929
|
+
"regression_schedule": {
|
|
930
|
+
"next_run": "computed_at_runtime",
|
|
931
|
+
"trigger": "both",
|
|
932
|
+
"notify_on_skip": true
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
},
|
|
936
|
+
"directives": [
|
|
937
|
+
{
|
|
938
|
+
"id": "full-repo-secret-scan",
|
|
939
|
+
"title": "Full working-tree secret scan with all catalogued patterns + permission checks",
|
|
940
|
+
"applies_to": {
|
|
941
|
+
"always": true
|
|
942
|
+
}
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
"id": "high-velocity-credentials",
|
|
946
|
+
"title": "Targeted directive — AWS / GCP / GitHub / OpenAI / Anthropic high-velocity credentials",
|
|
947
|
+
"applies_to": {
|
|
948
|
+
"attack_technique": "T1552.001"
|
|
949
|
+
}
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
"id": "private-key-material",
|
|
953
|
+
"title": "Targeted directive — SSH/PEM private key material in working tree",
|
|
954
|
+
"applies_to": {
|
|
955
|
+
"attack_technique": "T1552.004"
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
]
|
|
959
|
+
}
|