@blamejs/exceptd-skills 0.10.0 → 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/CHANGELOG.md +54 -0
- package/bin/exceptd.js +303 -9
- package/data/_indexes/_meta.json +2 -2
- package/data/playbooks/ai-api.json +400 -90
- package/data/playbooks/containers.json +406 -94
- package/data/playbooks/cred-stores.json +374 -89
- package/data/playbooks/crypto.json +369 -87
- package/data/playbooks/framework.json +376 -86
- package/data/playbooks/hardening.json +357 -84
- package/data/playbooks/kernel.json +324 -77
- package/data/playbooks/mcp.json +407 -92
- package/data/playbooks/runtime.json +345 -81
- package/data/playbooks/sbom.json +497 -111
- package/data/playbooks/secrets.json +352 -83
- package/lib/playbook-runner.js +77 -7
- package/lib/schemas/playbook.schema.json +5 -0
- package/manifest-snapshot.json +1 -1
- package/manifest.json +39 -39
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
|
@@ -9,11 +9,17 @@
|
|
|
9
9
|
"version": "1.0.0",
|
|
10
10
|
"date": "2026-05-11",
|
|
11
11
|
"summary": "Initial seven-phase credential-store inventory playbook. Inspects ~/.aws/credentials, ~/.kube/config, ~/.config/gcloud/credentials.db, ~/.docker/config.json, ~/.npmrc, ~/.pypirc, GPG keychain. Distinguishes long-lived static credentials from short-lived federated (SSO-issued kube tokens, IAM Identity Center / Workforce Identity / OIDC-issued service account tokens). Reports identity-assurance posture per store with framework-gap mapping.",
|
|
12
|
-
"framework_gaps_updated": [
|
|
12
|
+
"framework_gaps_updated": [
|
|
13
|
+
"nist-800-53-IA-5",
|
|
14
|
+
"nist-800-63b-aal",
|
|
15
|
+
"iso-27001-2022-A.5.16",
|
|
16
|
+
"nis2-art21-2j"
|
|
17
|
+
]
|
|
13
18
|
}
|
|
14
19
|
],
|
|
15
20
|
"owner": "@blamejs/identity",
|
|
16
21
|
"air_gap_mode": true,
|
|
22
|
+
"scope": "system",
|
|
17
23
|
"preconditions": [
|
|
18
24
|
{
|
|
19
25
|
"id": "home-dir-readable",
|
|
@@ -34,23 +40,42 @@
|
|
|
34
40
|
}
|
|
35
41
|
]
|
|
36
42
|
},
|
|
37
|
-
|
|
38
43
|
"domain": {
|
|
39
44
|
"name": "Per-user credential store inventory + identity-assurance posture",
|
|
40
45
|
"attack_class": "identity-abuse",
|
|
41
46
|
"atlas_refs": [],
|
|
42
|
-
"attack_refs": [
|
|
47
|
+
"attack_refs": [
|
|
48
|
+
"T1078",
|
|
49
|
+
"T1552.001",
|
|
50
|
+
"T1552.004",
|
|
51
|
+
"T1528",
|
|
52
|
+
"T1606.001"
|
|
53
|
+
],
|
|
43
54
|
"cve_refs": [],
|
|
44
|
-
"cwe_refs": [
|
|
45
|
-
|
|
55
|
+
"cwe_refs": [
|
|
56
|
+
"CWE-522",
|
|
57
|
+
"CWE-256",
|
|
58
|
+
"CWE-798",
|
|
59
|
+
"CWE-732"
|
|
60
|
+
],
|
|
61
|
+
"d3fend_refs": [
|
|
62
|
+
"D3-CAA",
|
|
63
|
+
"D3-FCR",
|
|
64
|
+
"D3-ANCI"
|
|
65
|
+
],
|
|
46
66
|
"frameworks_in_scope": [
|
|
47
|
-
"nist-800-53",
|
|
48
|
-
"
|
|
67
|
+
"nist-800-53",
|
|
68
|
+
"iso-27001-2022",
|
|
69
|
+
"soc2",
|
|
70
|
+
"pci-dss-4",
|
|
71
|
+
"nis2",
|
|
72
|
+
"dora",
|
|
73
|
+
"uk-caf",
|
|
74
|
+
"au-ism",
|
|
75
|
+
"au-essential-8"
|
|
49
76
|
]
|
|
50
77
|
},
|
|
51
|
-
|
|
52
78
|
"phases": {
|
|
53
|
-
|
|
54
79
|
"govern": {
|
|
55
80
|
"jurisdiction_obligations": [
|
|
56
81
|
{
|
|
@@ -59,7 +84,11 @@
|
|
|
59
84
|
"obligation": "notify_regulator",
|
|
60
85
|
"window_hours": 24,
|
|
61
86
|
"clock_starts": "detect_confirmed",
|
|
62
|
-
"evidence_required": [
|
|
87
|
+
"evidence_required": [
|
|
88
|
+
"credential_inventory",
|
|
89
|
+
"exposure_window_estimate",
|
|
90
|
+
"rotation_status"
|
|
91
|
+
]
|
|
63
92
|
},
|
|
64
93
|
{
|
|
65
94
|
"jurisdiction": "EU",
|
|
@@ -67,7 +96,10 @@
|
|
|
67
96
|
"obligation": "notify_regulator",
|
|
68
97
|
"window_hours": 4,
|
|
69
98
|
"clock_starts": "detect_confirmed",
|
|
70
|
-
"evidence_required": [
|
|
99
|
+
"evidence_required": [
|
|
100
|
+
"initial_notification",
|
|
101
|
+
"ict_third_party_dependencies"
|
|
102
|
+
]
|
|
71
103
|
},
|
|
72
104
|
{
|
|
73
105
|
"jurisdiction": "EU",
|
|
@@ -75,7 +107,10 @@
|
|
|
75
107
|
"obligation": "notify_regulator",
|
|
76
108
|
"window_hours": 72,
|
|
77
109
|
"clock_starts": "detect_confirmed",
|
|
78
|
-
"evidence_required": [
|
|
110
|
+
"evidence_required": [
|
|
111
|
+
"credential_inventory",
|
|
112
|
+
"data_subject_impact_assessment"
|
|
113
|
+
]
|
|
79
114
|
},
|
|
80
115
|
{
|
|
81
116
|
"jurisdiction": "AU",
|
|
@@ -83,7 +118,10 @@
|
|
|
83
118
|
"obligation": "notify_regulator",
|
|
84
119
|
"window_hours": 72,
|
|
85
120
|
"clock_starts": "validate_complete",
|
|
86
|
-
"evidence_required": [
|
|
121
|
+
"evidence_required": [
|
|
122
|
+
"materiality_assessment",
|
|
123
|
+
"remediation_completed_evidence"
|
|
124
|
+
]
|
|
87
125
|
}
|
|
88
126
|
],
|
|
89
127
|
"theater_fingerprints": [
|
|
@@ -91,25 +129,41 @@
|
|
|
91
129
|
"pattern_id": "sso-attested-but-aws-static-keys-live",
|
|
92
130
|
"claim": "We use SSO / Identity Center / Workforce Identity for all cloud access — no static keys.",
|
|
93
131
|
"fast_detection_test": "Cat ~/.aws/credentials. If file is non-empty AND contains an [profile] block with aws_access_key_id (not just aws_sso_session-shaped config), SSO attestation is incomplete. Repeat for ~/.config/gcloud/credentials.db (sqlite query for service_account-shaped rows) and ~/.kube/config (token: field with no exec: federated flow).",
|
|
94
|
-
"implicated_controls": [
|
|
132
|
+
"implicated_controls": [
|
|
133
|
+
"nist-800-53-IA-2",
|
|
134
|
+
"iso-27001-2022-A.5.16",
|
|
135
|
+
"soc2-cc6.1"
|
|
136
|
+
]
|
|
95
137
|
},
|
|
96
138
|
{
|
|
97
139
|
"pattern_id": "mfa-attested-but-pat-bypasses",
|
|
98
140
|
"claim": "MFA is enforced for all developer access.",
|
|
99
141
|
"fast_detection_test": "GitHub PATs, NPM tokens, PyPI tokens, container registry tokens bypass MFA by design — they are long-lived bearer tokens. Inspect ~/.npmrc, ~/.pypirc, ~/.docker/config.json for raw tokens. Any non-OAuth-flow token is an MFA bypass primitive whose existence undermines the MFA attestation.",
|
|
100
|
-
"implicated_controls": [
|
|
142
|
+
"implicated_controls": [
|
|
143
|
+
"nist-800-53-IA-2(1)",
|
|
144
|
+
"iso-27001-2022-A.5.17",
|
|
145
|
+
"pci-dss-4-req-8.4"
|
|
146
|
+
]
|
|
101
147
|
},
|
|
102
148
|
{
|
|
103
149
|
"pattern_id": "passkey-attested-but-ssh-keys-stale",
|
|
104
150
|
"claim": "Passkeys / FIDO2 are deployed for all auth — phishing-resistant.",
|
|
105
151
|
"fast_detection_test": "ls -la ~/.ssh/ and check mtime on private keys. SSH keys older than 12 months on a workstation that's been online during that time are stale credentials that exist outside the FIDO2 trust model. Any FIDO2 / Passkey attestation that does not also address legacy SSH key lifecycle is incomplete.",
|
|
106
|
-
"implicated_controls": [
|
|
152
|
+
"implicated_controls": [
|
|
153
|
+
"nist-800-63b-aal3",
|
|
154
|
+
"iso-27001-2022-A.5.16",
|
|
155
|
+
"uk-caf-b2"
|
|
156
|
+
]
|
|
107
157
|
},
|
|
108
158
|
{
|
|
109
159
|
"pattern_id": "credential-age-not-tracked",
|
|
110
160
|
"claim": "Credentials are rotated quarterly per policy.",
|
|
111
161
|
"fast_detection_test": "Stat each credential file: mtime is the upper bound on credential age. If any ~/.aws/credentials or ~/.kube/config or ~/.docker/config.json mtime is > 90 days, the policy is not enforced on this workstation. Compare against the policy text: if policy says 'rotated' but no rotation cadence is technically enforced, it is theater.",
|
|
112
|
-
"implicated_controls": [
|
|
162
|
+
"implicated_controls": [
|
|
163
|
+
"nist-800-53-IA-5(1)",
|
|
164
|
+
"iso-27001-2022-A.5.17",
|
|
165
|
+
"pci-dss-4-req-8.3.7"
|
|
166
|
+
]
|
|
113
167
|
}
|
|
114
168
|
],
|
|
115
169
|
"framework_context": {
|
|
@@ -148,9 +202,13 @@
|
|
|
148
202
|
}
|
|
149
203
|
]
|
|
150
204
|
},
|
|
151
|
-
"skill_preload": [
|
|
205
|
+
"skill_preload": [
|
|
206
|
+
"identity-assurance",
|
|
207
|
+
"framework-gap-analysis",
|
|
208
|
+
"compliance-theater",
|
|
209
|
+
"policy-exception-gen"
|
|
210
|
+
]
|
|
152
211
|
},
|
|
153
|
-
|
|
154
212
|
"direct": {
|
|
155
213
|
"threat_context": "Developer workstation credential harvest is the dominant 2025-2026 cloud-account-compromise vector. Mandiant / CrowdStrike Q1 2026 IR data: ~38% of cloud account intrusions traced to harvest of ~/.aws/credentials, ~/.kube/config, ~/.config/gcloud, or ~/.npmrc on a developer endpoint. Stealer malware families (Lumma, Rhadamanthys, Stealc) ship dedicated parsers for these files. AI-coding-assistant agentic flows expand the attack surface: any tool a developer trusts to read their workspace can be prompt-injected to exfiltrate these stores. Identity-assurance posture matters: workstations using exclusively federated short-lived credentials (SSO → STS:AssumeRoleWithWebIdentity → 1h credentials in ~/.aws/cli/cache) deny the harvest its target; workstations with long-lived static credentials provide it directly. Frameworks attest AAL3 + SSO without requiring the workstation-inventory evidence that distinguishes the two postures.",
|
|
156
214
|
"rwep_threshold": {
|
|
@@ -160,10 +218,27 @@
|
|
|
160
218
|
},
|
|
161
219
|
"framework_lag_declaration": "NIST 800-63b AAL specs, NIST 800-53 IA-2/IA-5, ISO A.5.16/A.5.17, NIS2 Art.21(2)(j), PCI-DSS Req.8 collectively specify target authenticator properties (phishing-resistant, short-lived, MFA-gated). They do not require enumeration of credentials actually present on developer endpoints. SSO + MFA attestation is accepted as evidence even when local credential stores hold long-lived bearer tokens that bypass SSO. Gap = ~25 days between credential-store drift (new PAT issued, new long-lived AWS key created) and any framework's review cadence.",
|
|
162
220
|
"skill_chain": [
|
|
163
|
-
{
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
221
|
+
{
|
|
222
|
+
"skill": "identity-assurance",
|
|
223
|
+
"purpose": "Score each inventoried credential against AAL/IAL/FAL framework. Distinguish AAL3 short-lived from AAL1 long-lived. Map to NIST 800-63 obligations.",
|
|
224
|
+
"required": true
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"skill": "framework-gap-analysis",
|
|
228
|
+
"purpose": "Map credential-store findings to which IA/A.5 controls claim to cover them and where the gap is.",
|
|
229
|
+
"required": true
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"skill": "compliance-theater",
|
|
233
|
+
"purpose": "Run the theater test on the org's SSO / MFA / passkey attestation against the live credential-store state.",
|
|
234
|
+
"required": true
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"skill": "policy-exception-gen",
|
|
238
|
+
"purpose": "Generate auditor-ready exception language for long-lived credentials that cannot be eliminated (e.g. CI bootstrap secrets, legacy SaaS without OIDC).",
|
|
239
|
+
"skip_if": "close.exception_generation.trigger_condition == false",
|
|
240
|
+
"required": false
|
|
241
|
+
}
|
|
167
242
|
],
|
|
168
243
|
"token_budget": {
|
|
169
244
|
"estimated_total": 18000,
|
|
@@ -178,7 +253,6 @@
|
|
|
178
253
|
}
|
|
179
254
|
}
|
|
180
255
|
},
|
|
181
|
-
|
|
182
256
|
"look": {
|
|
183
257
|
"artifacts": [
|
|
184
258
|
{
|
|
@@ -288,18 +362,53 @@
|
|
|
288
362
|
}
|
|
289
363
|
],
|
|
290
364
|
"fallback_if_unavailable": [
|
|
291
|
-
{
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
{
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
365
|
+
{
|
|
366
|
+
"artifact_id": "aws-credentials",
|
|
367
|
+
"fallback_action": "mark_inconclusive",
|
|
368
|
+
"confidence_impact": "high"
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
"artifact_id": "kube-config",
|
|
372
|
+
"fallback_action": "mark_inconclusive",
|
|
373
|
+
"confidence_impact": "high"
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
"artifact_id": "gcloud-credentials",
|
|
377
|
+
"fallback_action": "mark_inconclusive",
|
|
378
|
+
"confidence_impact": "high"
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
"artifact_id": "docker-config",
|
|
382
|
+
"fallback_action": "mark_inconclusive",
|
|
383
|
+
"confidence_impact": "medium"
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
"artifact_id": "npmrc",
|
|
387
|
+
"fallback_action": "mark_inconclusive",
|
|
388
|
+
"confidence_impact": "medium"
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
"artifact_id": "pypirc",
|
|
392
|
+
"fallback_action": "mark_inconclusive",
|
|
393
|
+
"confidence_impact": "medium"
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
"artifact_id": "gpg-keys",
|
|
397
|
+
"fallback_action": "use_compensating_artifact",
|
|
398
|
+
"confidence_impact": "low"
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
"artifact_id": "ssh-keys-inventory",
|
|
402
|
+
"fallback_action": "escalate_to_human",
|
|
403
|
+
"confidence_impact": "high"
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
"artifact_id": "keychain-inventory",
|
|
407
|
+
"fallback_action": "mark_inconclusive",
|
|
408
|
+
"confidence_impact": "low"
|
|
409
|
+
}
|
|
300
410
|
]
|
|
301
411
|
},
|
|
302
|
-
|
|
303
412
|
"detect": {
|
|
304
413
|
"indicators": [
|
|
305
414
|
{
|
|
@@ -431,27 +540,91 @@
|
|
|
431
540
|
"not_detected": "All credential stores inspected AND no deterministic indicators fired AND all-stores-empty-or-federated fires positively AND all credential files have mode 0600."
|
|
432
541
|
}
|
|
433
542
|
},
|
|
434
|
-
|
|
435
543
|
"analyze": {
|
|
436
544
|
"rwep_inputs": [
|
|
437
|
-
{
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
{
|
|
444
|
-
|
|
445
|
-
|
|
545
|
+
{
|
|
546
|
+
"signal_id": "aws-static-key-present",
|
|
547
|
+
"rwep_factor": "active_exploitation",
|
|
548
|
+
"weight": 30,
|
|
549
|
+
"notes": "Stealer malware ships dedicated AWS-credentials parsers. Treat as exploitation-imminent on any compromised endpoint."
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
"signal_id": "kube-static-token",
|
|
553
|
+
"rwep_factor": "active_exploitation",
|
|
554
|
+
"weight": 25,
|
|
555
|
+
"notes": "Static kube tokens enable cluster pivot; ATT&CK T1528 in published threat actor playbooks."
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
"signal_id": "gcp-service-account-json-adc",
|
|
559
|
+
"rwep_factor": "active_exploitation",
|
|
560
|
+
"weight": 30,
|
|
561
|
+
"notes": "Service account JSON = same risk class as AWS access keys."
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
"signal_id": "docker-cleartext-auth",
|
|
565
|
+
"rwep_factor": "blast_radius",
|
|
566
|
+
"weight": 15,
|
|
567
|
+
"notes": "Registry compromise = supply-chain pivot."
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
"signal_id": "npm-pat-present",
|
|
571
|
+
"rwep_factor": "active_exploitation",
|
|
572
|
+
"weight": 25,
|
|
573
|
+
"notes": "Publish-capable NPM PAT = supply-chain attack primitive (event-stream, ua-parser-js, chalk classes of incidents)."
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
"signal_id": "pypi-token-present",
|
|
577
|
+
"rwep_factor": "active_exploitation",
|
|
578
|
+
"weight": 25,
|
|
579
|
+
"notes": "PyPI publish token = same supply-chain pattern as NPM."
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
"signal_id": "ssh-key-rsa-short-bits",
|
|
583
|
+
"rwep_factor": "ai_weaponization",
|
|
584
|
+
"weight": 5,
|
|
585
|
+
"notes": "AI-accelerated cryptanalysis is not yet a practical threat to RSA-2048, but DSA + RSA-1024 cracking is operational."
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
"signal_id": "ssh-key-old",
|
|
589
|
+
"rwep_factor": "blast_radius",
|
|
590
|
+
"weight": 10,
|
|
591
|
+
"notes": "Stale key = unknown exposure history."
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
"signal_id": "credentials-file-bad-perms",
|
|
595
|
+
"rwep_factor": "active_exploitation",
|
|
596
|
+
"weight": 20,
|
|
597
|
+
"notes": "Permissive perms on a creds file = same-host LPE chains to credential theft."
|
|
598
|
+
}
|
|
446
599
|
],
|
|
447
600
|
"blast_radius_model": {
|
|
448
601
|
"scope_question": "Given the inventoried credential stores, what is the realistic blast radius if this endpoint is compromised by stealer malware or a prompt-injected agent?",
|
|
449
602
|
"scoring_rubric": [
|
|
450
|
-
{
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
603
|
+
{
|
|
604
|
+
"condition": "All stores empty or federated (all-stores-empty-or-federated fires), all permissions correct, SSH keys ed25519 + recent + agent-mediated",
|
|
605
|
+
"blast_radius_score": 1,
|
|
606
|
+
"description": "Endpoint compromise yields no useful long-lived credentials. AAL3-equivalent."
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
"condition": "One static credential present in a single bounded scope (e.g. one read-only PAT) AND federated paths exist for all primary providers",
|
|
610
|
+
"blast_radius_score": 2,
|
|
611
|
+
"description": "Limited bounded scope from one static credential. Cleanup feasible without org-wide impact."
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
"condition": "Multiple static credentials across one provider OR a single static credential with broad scope (admin / wildcard / cross-account-trust)",
|
|
615
|
+
"blast_radius_score": 3,
|
|
616
|
+
"description": "Provider-account-level pivot from a single endpoint compromise."
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
"condition": "Static credentials across multiple providers (cloud + SCM + registry) OR publish-capable supply-chain token (NPM/PyPI/container) present",
|
|
620
|
+
"blast_radius_score": 4,
|
|
621
|
+
"description": "Cross-provider pivot + supply-chain attack primitive."
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
"condition": "All of: static cloud admin key + publish-capable supply-chain token + permissive permissions + stale SSH keys",
|
|
625
|
+
"blast_radius_score": 5,
|
|
626
|
+
"description": "Maximum endpoint-derived blast radius. Identity boundary collapse from this single compromise."
|
|
627
|
+
}
|
|
455
628
|
]
|
|
456
629
|
},
|
|
457
630
|
"compliance_theater_check": {
|
|
@@ -491,59 +664,98 @@
|
|
|
491
664
|
}
|
|
492
665
|
],
|
|
493
666
|
"escalation_criteria": [
|
|
494
|
-
{
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
667
|
+
{
|
|
668
|
+
"condition": "rwep >= 90 AND credential_is_publish_capable == true",
|
|
669
|
+
"action": "page_on_call"
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
"condition": "blast_radius_score >= 4",
|
|
673
|
+
"action": "trigger_playbook",
|
|
674
|
+
"target_playbook": "secrets"
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
"condition": "compliance_theater_check.verdict == 'theater' AND jurisdiction_obligations contains 'EU'",
|
|
678
|
+
"action": "notify_legal"
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
"condition": "any_static_admin_credential == true",
|
|
682
|
+
"action": "raise_severity"
|
|
683
|
+
}
|
|
498
684
|
]
|
|
499
685
|
},
|
|
500
|
-
|
|
501
686
|
"validate": {
|
|
502
687
|
"remediation_paths": [
|
|
503
688
|
{
|
|
504
689
|
"id": "migrate-aws-to-sso",
|
|
505
690
|
"description": "Replace static aws_access_key_id entries with sso_session profiles using IAM Identity Center (or AWS SSO). Remove old key from ~/.aws/credentials AND deactivate at IAM console.",
|
|
506
|
-
"preconditions": [
|
|
691
|
+
"preconditions": [
|
|
692
|
+
"org_has_identity_center == true",
|
|
693
|
+
"user_enrolled_in_sso == true"
|
|
694
|
+
],
|
|
507
695
|
"priority": 1,
|
|
508
|
-
"compensating_controls": [
|
|
696
|
+
"compensating_controls": [
|
|
697
|
+
"iam-key-deactivated",
|
|
698
|
+
"cloudtrail-monitor-on-old-key-for-residual-use"
|
|
699
|
+
],
|
|
509
700
|
"estimated_time_hours": 1
|
|
510
701
|
},
|
|
511
702
|
{
|
|
512
703
|
"id": "migrate-gcp-to-workforce-identity",
|
|
513
704
|
"description": "Replace ~/.config/gcloud/application_default_credentials.json service_account with workforce identity (gcloud auth login or workforce-pool federation). Delete the service account key at GCP console.",
|
|
514
|
-
"preconditions": [
|
|
705
|
+
"preconditions": [
|
|
706
|
+
"org_has_workforce_identity_pool == true OR user_has_authorized_user_credentials == true"
|
|
707
|
+
],
|
|
515
708
|
"priority": 1,
|
|
516
|
-
"compensating_controls": [
|
|
709
|
+
"compensating_controls": [
|
|
710
|
+
"gcp-key-deleted",
|
|
711
|
+
"gcp-audit-log-monitor-on-old-key"
|
|
712
|
+
],
|
|
517
713
|
"estimated_time_hours": 1
|
|
518
714
|
},
|
|
519
715
|
{
|
|
520
716
|
"id": "migrate-kube-to-oidc",
|
|
521
717
|
"description": "Replace static token: kubeconfig entries with exec: hooks (oidc-login, aws eks get-token, gke-gcloud-auth-plugin). Revoke old service-account token via kubectl delete secret.",
|
|
522
|
-
"preconditions": [
|
|
718
|
+
"preconditions": [
|
|
719
|
+
"cluster_supports_oidc == true OR cluster_is_managed_cloud_k8s == true"
|
|
720
|
+
],
|
|
523
721
|
"priority": 1,
|
|
524
|
-
"compensating_controls": [
|
|
722
|
+
"compensating_controls": [
|
|
723
|
+
"kube-token-revoked",
|
|
724
|
+
"k8s-audit-log-monitor-on-old-token"
|
|
725
|
+
],
|
|
525
726
|
"estimated_time_hours": 1
|
|
526
727
|
},
|
|
527
728
|
{
|
|
528
729
|
"id": "migrate-docker-to-cred-helpers",
|
|
529
730
|
"description": "Replace ~/.docker/config.json auths.auth with credHelpers/credsStore directives routing to OS keychain or cloud-IAM (ecr-login, gcloud, acr).",
|
|
530
|
-
"preconditions": [
|
|
731
|
+
"preconditions": [
|
|
732
|
+
"target_registry_supports_cred_helper == true"
|
|
733
|
+
],
|
|
531
734
|
"priority": 1,
|
|
532
|
-
"compensating_controls": [
|
|
735
|
+
"compensating_controls": [
|
|
736
|
+
"docker-token-rotated"
|
|
737
|
+
],
|
|
533
738
|
"estimated_time_hours": 0.5
|
|
534
739
|
},
|
|
535
740
|
{
|
|
536
741
|
"id": "rotate-supply-chain-tokens",
|
|
537
742
|
"description": "For ~/.npmrc / ~/.pypirc tokens: rotate AND scope down (read-only where possible) AND move to OS keychain (npm config set _authToken in keychain-backed config OR pypi via keyring backend).",
|
|
538
|
-
"preconditions": [
|
|
743
|
+
"preconditions": [
|
|
744
|
+
"org_authority_to_rotate == true"
|
|
745
|
+
],
|
|
539
746
|
"priority": 2,
|
|
540
|
-
"compensating_controls": [
|
|
747
|
+
"compensating_controls": [
|
|
748
|
+
"token-scope-tightened",
|
|
749
|
+
"publish-mfa-required"
|
|
750
|
+
],
|
|
541
751
|
"estimated_time_hours": 1
|
|
542
752
|
},
|
|
543
753
|
{
|
|
544
754
|
"id": "fix-credentials-perms",
|
|
545
755
|
"description": "chmod 0600 on all credential files; chmod 0700 on ~/.config/gcloud/, ~/.aws/, ~/.ssh/.",
|
|
546
|
-
"preconditions": [
|
|
756
|
+
"preconditions": [
|
|
757
|
+
"file_owner_is_current_user"
|
|
758
|
+
],
|
|
547
759
|
"priority": 2,
|
|
548
760
|
"compensating_controls": [],
|
|
549
761
|
"estimated_time_hours": 0.25
|
|
@@ -551,17 +763,27 @@
|
|
|
551
763
|
{
|
|
552
764
|
"id": "modernize-ssh-keys",
|
|
553
765
|
"description": "Generate fresh ed25519 SSH key; deploy public key to all currently-authorized hosts; remove old RSA/DSA private keys from disk. Optionally use ssh-agent + Yubikey for hardware backing.",
|
|
554
|
-
"preconditions": [
|
|
766
|
+
"preconditions": [
|
|
767
|
+
"all_authorized_hosts_known == true"
|
|
768
|
+
],
|
|
555
769
|
"priority": 2,
|
|
556
|
-
"compensating_controls": [
|
|
770
|
+
"compensating_controls": [
|
|
771
|
+
"ssh-key-inventory-updated"
|
|
772
|
+
],
|
|
557
773
|
"estimated_time_hours": 2
|
|
558
774
|
},
|
|
559
775
|
{
|
|
560
776
|
"id": "policy-exception",
|
|
561
777
|
"description": "If static credential cannot be eliminated (e.g. legacy SaaS without OIDC), generate exception with bounded compensating controls and time-bound rotation cadence.",
|
|
562
|
-
"preconditions": [
|
|
778
|
+
"preconditions": [
|
|
779
|
+
"federated_alternative_unavailable == true"
|
|
780
|
+
],
|
|
563
781
|
"priority": 4,
|
|
564
|
-
"compensating_controls": [
|
|
782
|
+
"compensating_controls": [
|
|
783
|
+
"credential-vaulted",
|
|
784
|
+
"rotation-cadence-30d",
|
|
785
|
+
"audit-log-monitor-on-credential"
|
|
786
|
+
],
|
|
565
787
|
"estimated_time_hours": 4
|
|
566
788
|
}
|
|
567
789
|
],
|
|
@@ -601,46 +823,84 @@
|
|
|
601
823
|
"risk": "Even after migration to federated identity, transient short-lived tokens (~/.aws/cli/cache/*.json, ~/.config/gcloud/access_tokens.db, ~/.kube/cache/) remain on disk during their TTL. Stealer malware that runs during a session window can still harvest live federated tokens.",
|
|
602
824
|
"why_remains": "Short-lived tokens still exist; the trade-off is shorter exposure window (1h vs. perpetual) but not zero. The compensating control is endpoint protection + IdP risk-based authentication (impossible-travel, device-binding), not absence of tokens-on-disk.",
|
|
603
825
|
"acceptance_level": "manager",
|
|
604
|
-
"compensating_controls_in_place": [
|
|
826
|
+
"compensating_controls_in_place": [
|
|
827
|
+
"all-federated-paths",
|
|
828
|
+
"edr-active-on-endpoint",
|
|
829
|
+
"idp-risk-based-authn",
|
|
830
|
+
"quarterly-credential-inventory-cadence"
|
|
831
|
+
]
|
|
605
832
|
},
|
|
606
833
|
"evidence_requirements": [
|
|
607
834
|
{
|
|
608
835
|
"evidence_type": "scan_report",
|
|
609
836
|
"description": "Pre-remediation credential-store inventory + post-remediation rescan.",
|
|
610
837
|
"retention_period": "1_year",
|
|
611
|
-
"framework_satisfied": [
|
|
838
|
+
"framework_satisfied": [
|
|
839
|
+
"nist-800-53-IA-2",
|
|
840
|
+
"nist-800-53-IA-5",
|
|
841
|
+
"iso-27001-2022-A.5.16"
|
|
842
|
+
]
|
|
612
843
|
},
|
|
613
844
|
{
|
|
614
845
|
"evidence_type": "log_excerpt",
|
|
615
846
|
"description": "Provider audit log excerpt for the lifetime of each rotated credential.",
|
|
616
847
|
"retention_period": "7_years",
|
|
617
|
-
"framework_satisfied": [
|
|
848
|
+
"framework_satisfied": [
|
|
849
|
+
"nis2-art21-2c",
|
|
850
|
+
"soc2-cc7.2"
|
|
851
|
+
]
|
|
618
852
|
},
|
|
619
853
|
{
|
|
620
854
|
"evidence_type": "ticket_reference",
|
|
621
855
|
"description": "Credential rotation tickets at each provider; SSO/Workforce Identity migration approvals.",
|
|
622
856
|
"retention_period": "7_years",
|
|
623
|
-
"framework_satisfied": [
|
|
857
|
+
"framework_satisfied": [
|
|
858
|
+
"soc2-cc8.1",
|
|
859
|
+
"iso-27001-2022-A.8.32"
|
|
860
|
+
]
|
|
624
861
|
},
|
|
625
862
|
{
|
|
626
863
|
"evidence_type": "attestation",
|
|
627
864
|
"description": "Signed exceptd attestation: user identity, endpoint identity, inventory hash, RWEP at detection, RWEP post-remediation, AAL claimed/supported by evidence.",
|
|
628
865
|
"retention_period": "7_years",
|
|
629
|
-
"framework_satisfied": [
|
|
866
|
+
"framework_satisfied": [
|
|
867
|
+
"nist-800-63b-aal",
|
|
868
|
+
"nist-800-53-CA-7",
|
|
869
|
+
"iso-27001-2022-A.5.36"
|
|
870
|
+
]
|
|
630
871
|
}
|
|
631
872
|
],
|
|
632
873
|
"regression_trigger": [
|
|
633
|
-
{
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
874
|
+
{
|
|
875
|
+
"condition": "monthly",
|
|
876
|
+
"interval": "30d"
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
"condition": "post_new_provider_onboarded",
|
|
880
|
+
"interval": "on_event"
|
|
881
|
+
},
|
|
882
|
+
{
|
|
883
|
+
"condition": "post_employee_role_change",
|
|
884
|
+
"interval": "on_event"
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
"condition": "post_credential_compromise_incident",
|
|
888
|
+
"interval": "on_event"
|
|
889
|
+
}
|
|
637
890
|
]
|
|
638
891
|
},
|
|
639
|
-
|
|
640
892
|
"close": {
|
|
641
893
|
"evidence_package": {
|
|
642
894
|
"bundle_format": "json",
|
|
643
|
-
"contents": [
|
|
895
|
+
"contents": [
|
|
896
|
+
"scan_report",
|
|
897
|
+
"log_excerpt",
|
|
898
|
+
"ticket_reference",
|
|
899
|
+
"attestation",
|
|
900
|
+
"framework_gap_mapping",
|
|
901
|
+
"compliance_theater_verdict",
|
|
902
|
+
"residual_risk_statement"
|
|
903
|
+
],
|
|
644
904
|
"destination": "local_only",
|
|
645
905
|
"signed": true
|
|
646
906
|
},
|
|
@@ -652,28 +912,42 @@
|
|
|
652
912
|
"framework_gap": "NIST 800-63 AAL specifies target authenticators, not bypass-credential exclusion. NIST 800-53 IA family names password authenticators, not all bearer-token authenticators. ISO and NIS2 are policy-shaped on identity lifecycle.",
|
|
653
913
|
"new_control_requirement": "Quarterly endpoint-credential-store inventory required for all users in scope of an AAL3 attestation. Static-credential presence on an AAL3-attested user's endpoint = automatic finding requiring remediation OR documented exception."
|
|
654
914
|
},
|
|
655
|
-
"feeds_back_to_skills": [
|
|
915
|
+
"feeds_back_to_skills": [
|
|
916
|
+
"identity-assurance",
|
|
917
|
+
"framework-gap-analysis",
|
|
918
|
+
"compliance-theater"
|
|
919
|
+
]
|
|
656
920
|
},
|
|
657
921
|
"notification_actions": [
|
|
658
922
|
{
|
|
659
923
|
"obligation_ref": "EU/NIS2 Art.23 24h",
|
|
660
924
|
"deadline": "computed_at_runtime",
|
|
661
925
|
"recipient": "internal_legal",
|
|
662
|
-
"evidence_attached": [
|
|
926
|
+
"evidence_attached": [
|
|
927
|
+
"credential_inventory",
|
|
928
|
+
"exposure_window_estimate",
|
|
929
|
+
"rotation_status"
|
|
930
|
+
],
|
|
663
931
|
"draft_notification": "NIS2 Art.23 early-warning notification: Credential-store finding on ${affected_user_count} user endpoint(s); static long-lived credentials identified across ${provider_count} provider(s). Credentials rotated to federated equivalents. Exposure window: ${exposure_window}. Full incident assessment within 72h per Art.23(4)."
|
|
664
932
|
},
|
|
665
933
|
{
|
|
666
934
|
"obligation_ref": "EU/DORA Art.19 4h",
|
|
667
935
|
"deadline": "computed_at_runtime",
|
|
668
936
|
"recipient": "internal_legal",
|
|
669
|
-
"evidence_attached": [
|
|
937
|
+
"evidence_attached": [
|
|
938
|
+
"initial_notification",
|
|
939
|
+
"ict_third_party_dependencies"
|
|
940
|
+
],
|
|
670
941
|
"draft_notification": "DORA Art.19 initial notification: Major ICT-related incident — endpoint credential exposure on financial-entity user endpoint(s). ICT third-party dependencies: ${ict_dependencies}. Full classification + impact assessment to follow within statutory windows."
|
|
671
942
|
},
|
|
672
943
|
{
|
|
673
944
|
"obligation_ref": "EU/GDPR Art.33 72h",
|
|
674
945
|
"deadline": "computed_at_runtime",
|
|
675
946
|
"recipient": "internal_legal",
|
|
676
|
-
"evidence_attached": [
|
|
947
|
+
"evidence_attached": [
|
|
948
|
+
"credential_inventory",
|
|
949
|
+
"data_subject_impact_assessment"
|
|
950
|
+
],
|
|
677
951
|
"draft_notification": "GDPR Art.33 72-hour notification: Endpoint credential exposure event affecting credentials with potential access to systems processing personal data. Categories of data subjects potentially affected: ${data_subject_categories}. Containment: credentials rotated; provider audit logs reviewed for misuse window."
|
|
678
952
|
}
|
|
679
953
|
],
|
|
@@ -682,7 +956,13 @@
|
|
|
682
956
|
"exception_template": {
|
|
683
957
|
"scope": "Static credential of class ${credential_class} for provider ${provider_name} on user endpoint(s) ${affected_endpoint_count}; federated alternative unavailable due to ${blocking_reason}.",
|
|
684
958
|
"duration": "until_next_audit",
|
|
685
|
-
"compensating_controls": [
|
|
959
|
+
"compensating_controls": [
|
|
960
|
+
"credential-vaulted-with-passphrase",
|
|
961
|
+
"rotation-cadence-30d",
|
|
962
|
+
"audit-log-monitor-on-credential",
|
|
963
|
+
"edr-alert-on-credential-file-read",
|
|
964
|
+
"ip-allowlist-tightened"
|
|
965
|
+
],
|
|
686
966
|
"risk_acceptance_owner": "ciso",
|
|
687
967
|
"auditor_ready_language": "Pursuant to ${framework_id} ${control_id}, the organization documents a time-bound risk acceptance for static credential ${credential_class} on ${affected_endpoint_count} endpoint(s). Provider: ${provider_name}. Blocking reason for federation: ${blocking_reason_narrative}. Compensating controls in place: credential vaulted with passphrase-on-use; ${rotation_cadence_days}-day forced rotation; provider audit log monitored daily for anomalous use; EDR alerts on read of the credential file outside the documented use pattern. Residual exposure: static credential remains valid during its rotation window; vaulting + passphrase reduces but does not eliminate stealer-malware harvest if endpoint is fully compromised. Risk accepted by ${ciso_name} on ${acceptance_date}. Time-bound until ${duration_expiry} OR provider OIDC availability, whichever is first. The exception will be re-evaluated on (a) provider OIDC availability, (b) listed expiry date, (c) any audit-log anomaly on the credential — whichever is first."
|
|
688
968
|
}
|
|
@@ -694,22 +974,27 @@
|
|
|
694
974
|
}
|
|
695
975
|
}
|
|
696
976
|
},
|
|
697
|
-
|
|
698
977
|
"directives": [
|
|
699
978
|
{
|
|
700
979
|
"id": "full-credential-store-inventory",
|
|
701
980
|
"title": "Full per-user credential-store inventory across cloud, k8s, container registry, package managers, SSH, GPG",
|
|
702
|
-
"applies_to": {
|
|
981
|
+
"applies_to": {
|
|
982
|
+
"always": true
|
|
983
|
+
}
|
|
703
984
|
},
|
|
704
985
|
{
|
|
705
986
|
"id": "cloud-static-keys",
|
|
706
987
|
"title": "Targeted directive — long-lived cloud credentials (AWS access keys, GCP service account JSON, Azure SP secrets)",
|
|
707
|
-
"applies_to": {
|
|
988
|
+
"applies_to": {
|
|
989
|
+
"attack_technique": "T1552.001"
|
|
990
|
+
}
|
|
708
991
|
},
|
|
709
992
|
{
|
|
710
993
|
"id": "supply-chain-tokens",
|
|
711
994
|
"title": "Targeted directive — publish-capable package-manager tokens (NPM, PyPI, container registry)",
|
|
712
|
-
"applies_to": {
|
|
995
|
+
"applies_to": {
|
|
996
|
+
"attack_technique": "T1528"
|
|
997
|
+
}
|
|
713
998
|
}
|
|
714
999
|
]
|
|
715
1000
|
}
|