@prism-d1/cli 1.1.2 → 1.3.0

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 (255) hide show
  1. package/dist/assets/github-workflows/prism-agent-eval.yml +2 -1
  2. package/dist/assets/github-workflows/prism-eval-gate-kiro.yml +12 -2
  3. package/dist/assets/gitlab-workflows/prism-eval-gate.yml +8 -0
  4. package/dist/assets/infra/bin/app.ts +61 -37
  5. package/dist/assets/infra/lib/constructs/otel-collector-construct.ts +96 -0
  6. package/dist/assets/infra/lib/constructs/security-agent-construct.ts +35 -30
  7. package/dist/assets/infra/lib/dashboard-stack.ts +27 -0
  8. package/dist/assets/infra/lib/lambda/otel-receiver.ts +278 -8
  9. package/dist/assets/infra/lib/lambda/pricing/litellm-snapshot.json +3769 -0
  10. package/dist/assets/infra/lib/lambda/pricing/price-source.ts +56 -0
  11. package/dist/assets/infra/lib/lambda/pricing/price-table.ts +240 -0
  12. package/dist/assets/infra/lib/lambda/pricing/prices-key.ts +6 -0
  13. package/dist/assets/infra/lib/lambda/pricing/reprice.ts +108 -0
  14. package/dist/assets/infra/lib/lambda/pricing-refresh.ts +352 -0
  15. package/dist/assets/infra/lib/lambda/velocity-widget.ts +124 -1
  16. package/dist/assets/infra/lib/metrics-pipeline-stack.ts +2 -110
  17. package/dist/assets/infra/lib/security-agent-resources.ts +136 -0
  18. package/dist/assets/infra/lib/security-agent-stack.ts +69 -0
  19. package/dist/assets/infra/package.json +4 -1
  20. package/dist/assets/metric-hooks/prepare-commit-msg +3 -1
  21. package/dist/assets/pentest-cdk/bin/app.ts +12 -0
  22. package/dist/assets/pentest-cdk/cdk.json +7 -0
  23. package/dist/assets/pentest-cdk/lib/pentest-target-stack.ts +206 -0
  24. package/dist/assets/pentest-cdk/package.json +18 -0
  25. package/dist/assets/pentest-cdk/tsconfig.json +12 -0
  26. package/dist/src/codewatch/config.d.ts +17 -0
  27. package/dist/src/codewatch/config.d.ts.map +1 -0
  28. package/dist/src/codewatch/config.js +40 -0
  29. package/dist/src/codewatch/config.js.map +1 -0
  30. package/dist/src/codewatch/gitleaks.d.ts +49 -0
  31. package/dist/src/codewatch/gitleaks.d.ts.map +1 -0
  32. package/dist/src/codewatch/gitleaks.js +160 -0
  33. package/dist/src/codewatch/gitleaks.js.map +1 -0
  34. package/dist/src/codewatch/globalHooks.d.ts +52 -0
  35. package/dist/src/codewatch/globalHooks.d.ts.map +1 -0
  36. package/dist/src/codewatch/globalHooks.js +235 -0
  37. package/dist/src/codewatch/globalHooks.js.map +1 -0
  38. package/dist/src/codewatch/hooks.d.ts +38 -0
  39. package/dist/src/codewatch/hooks.d.ts.map +1 -0
  40. package/dist/src/codewatch/hooks.js +176 -0
  41. package/dist/src/codewatch/hooks.js.map +1 -0
  42. package/dist/src/codewatch/prepush.d.ts +37 -0
  43. package/dist/src/codewatch/prepush.d.ts.map +1 -0
  44. package/dist/src/codewatch/prepush.js +231 -0
  45. package/dist/src/codewatch/prepush.js.map +1 -0
  46. package/dist/src/codewatch/results.d.ts +210 -0
  47. package/dist/src/codewatch/results.d.ts.map +1 -0
  48. package/dist/src/codewatch/results.js +127 -0
  49. package/dist/src/codewatch/results.js.map +1 -0
  50. package/dist/src/codewatch/reviewers/claude.d.ts +9 -0
  51. package/dist/src/codewatch/reviewers/claude.d.ts.map +1 -0
  52. package/dist/src/codewatch/reviewers/claude.js +48 -0
  53. package/dist/src/codewatch/reviewers/claude.js.map +1 -0
  54. package/dist/src/codewatch/reviewers/index.d.ts +39 -0
  55. package/dist/src/codewatch/reviewers/index.d.ts.map +1 -0
  56. package/dist/src/codewatch/reviewers/index.js +97 -0
  57. package/dist/src/codewatch/reviewers/index.js.map +1 -0
  58. package/dist/src/codewatch/reviewers/kiro.d.ts +14 -0
  59. package/dist/src/codewatch/reviewers/kiro.d.ts.map +1 -0
  60. package/dist/src/codewatch/reviewers/kiro.js +32 -0
  61. package/dist/src/codewatch/reviewers/kiro.js.map +1 -0
  62. package/dist/src/codewatch/reviewers/types.d.ts +56 -0
  63. package/dist/src/codewatch/reviewers/types.d.ts.map +1 -0
  64. package/dist/src/codewatch/reviewers/types.js +91 -0
  65. package/dist/src/codewatch/reviewers/types.js.map +1 -0
  66. package/dist/src/codewatch/rules.d.ts +12 -0
  67. package/dist/src/codewatch/rules.d.ts.map +1 -0
  68. package/dist/src/codewatch/rules.js +27 -0
  69. package/dist/src/codewatch/rules.js.map +1 -0
  70. package/dist/src/commands/_top/dashboard.d.ts +17 -0
  71. package/dist/src/commands/_top/dashboard.d.ts.map +1 -0
  72. package/dist/src/commands/_top/dashboard.js +76 -0
  73. package/dist/src/commands/_top/dashboard.js.map +1 -0
  74. package/dist/src/commands/_top/version.d.ts +62 -0
  75. package/dist/src/commands/_top/version.d.ts.map +1 -0
  76. package/dist/src/commands/_top/version.js +175 -0
  77. package/dist/src/commands/_top/version.js.map +1 -0
  78. package/dist/src/commands/bedrock-protection/scan-account.d.ts.map +1 -1
  79. package/dist/src/commands/bedrock-protection/scan-account.js +2 -1
  80. package/dist/src/commands/bedrock-protection/scan-account.js.map +1 -1
  81. package/dist/src/commands/bedrock-protection/scan-org.d.ts.map +1 -1
  82. package/dist/src/commands/bedrock-protection/scan-org.js +16 -4
  83. package/dist/src/commands/bedrock-protection/scan-org.js.map +1 -1
  84. package/dist/src/commands/bootstrapper/install-eval-harness.d.ts.map +1 -1
  85. package/dist/src/commands/bootstrapper/install-eval-harness.js +15 -48
  86. package/dist/src/commands/bootstrapper/install-eval-harness.js.map +1 -1
  87. package/dist/src/commands/bootstrapper/install-git-hooks.d.ts +13 -0
  88. package/dist/src/commands/bootstrapper/install-git-hooks.d.ts.map +1 -1
  89. package/dist/src/commands/bootstrapper/install-git-hooks.js +28 -147
  90. package/dist/src/commands/bootstrapper/install-git-hooks.js.map +1 -1
  91. package/dist/src/commands/bootstrapper/setup-otel-sync.d.ts +12 -6
  92. package/dist/src/commands/bootstrapper/setup-otel-sync.d.ts.map +1 -1
  93. package/dist/src/commands/bootstrapper/setup-otel-sync.js +18 -443
  94. package/dist/src/commands/bootstrapper/setup-otel-sync.js.map +1 -1
  95. package/dist/src/commands/codewatch/pre-commit.d.ts +7 -0
  96. package/dist/src/commands/codewatch/pre-commit.d.ts.map +1 -0
  97. package/dist/src/commands/codewatch/pre-commit.js +30 -0
  98. package/dist/src/commands/codewatch/pre-commit.js.map +1 -0
  99. package/dist/src/commands/codewatch/pre-push.d.ts +16 -0
  100. package/dist/src/commands/codewatch/pre-push.d.ts.map +1 -0
  101. package/dist/src/commands/codewatch/pre-push.js +32 -0
  102. package/dist/src/commands/codewatch/pre-push.js.map +1 -0
  103. package/dist/src/commands/codewatch/report.d.ts +22 -0
  104. package/dist/src/commands/codewatch/report.d.ts.map +1 -0
  105. package/dist/src/commands/codewatch/report.js +64 -0
  106. package/dist/src/commands/codewatch/report.js.map +1 -0
  107. package/dist/src/commands/enable/codeburn.d.ts +59 -0
  108. package/dist/src/commands/enable/codeburn.d.ts.map +1 -0
  109. package/dist/src/commands/enable/codeburn.js +531 -0
  110. package/dist/src/commands/enable/codeburn.js.map +1 -0
  111. package/dist/src/commands/enable/codewatch.d.ts +15 -0
  112. package/dist/src/commands/enable/codewatch.d.ts.map +1 -0
  113. package/dist/src/commands/enable/codewatch.js +186 -0
  114. package/dist/src/commands/enable/codewatch.js.map +1 -0
  115. package/dist/src/commands/enable/security.d.ts +41 -0
  116. package/dist/src/commands/enable/security.d.ts.map +1 -0
  117. package/dist/src/commands/enable/security.js +130 -0
  118. package/dist/src/commands/enable/security.js.map +1 -0
  119. package/dist/src/commands/export/otel.d.ts +31 -0
  120. package/dist/src/commands/export/otel.d.ts.map +1 -0
  121. package/dist/src/commands/export/otel.js +52 -0
  122. package/dist/src/commands/export/otel.js.map +1 -0
  123. package/dist/src/commands/git/claude-session-context.d.ts.map +1 -1
  124. package/dist/src/commands/git/claude-session-context.js +2 -1
  125. package/dist/src/commands/git/claude-session-context.js.map +1 -1
  126. package/dist/src/commands/git/commit-trailers.js +1 -1
  127. package/dist/src/commands/git/commit-trailers.js.map +1 -1
  128. package/dist/src/commands/security/code-review.d.ts +22 -0
  129. package/dist/src/commands/security/code-review.d.ts.map +1 -0
  130. package/dist/src/commands/security/code-review.js +43 -0
  131. package/dist/src/commands/security/code-review.js.map +1 -0
  132. package/dist/src/commands/security/findings.d.ts +15 -0
  133. package/dist/src/commands/security/findings.d.ts.map +1 -0
  134. package/dist/src/commands/security/findings.js +31 -0
  135. package/dist/src/commands/security/findings.js.map +1 -0
  136. package/dist/src/commands/security/pentest.d.ts +18 -0
  137. package/dist/src/commands/security/pentest.d.ts.map +1 -0
  138. package/dist/src/commands/security/pentest.js +32 -0
  139. package/dist/src/commands/security/pentest.js.map +1 -0
  140. package/dist/src/commands/security/threat-model.d.ts +16 -0
  141. package/dist/src/commands/security/threat-model.d.ts.map +1 -0
  142. package/dist/src/commands/security/threat-model.js +24 -0
  143. package/dist/src/commands/security/threat-model.js.map +1 -0
  144. package/dist/src/commands/security/threats.d.ts +12 -0
  145. package/dist/src/commands/security/threats.d.ts.map +1 -0
  146. package/dist/src/commands/security/threats.js +23 -0
  147. package/dist/src/commands/security/threats.js.map +1 -0
  148. package/dist/src/commands/securityagent/setup.d.ts +19 -5
  149. package/dist/src/commands/securityagent/setup.d.ts.map +1 -1
  150. package/dist/src/commands/securityagent/setup.js +25 -189
  151. package/dist/src/commands/securityagent/setup.js.map +1 -1
  152. package/dist/src/commands/workshop/verify-setup.d.ts.map +1 -1
  153. package/dist/src/commands/workshop/verify-setup.js +5 -3
  154. package/dist/src/commands/workshop/verify-setup.js.map +1 -1
  155. package/dist/src/dashboard/codewatch.d.ts +106 -0
  156. package/dist/src/dashboard/codewatch.d.ts.map +1 -0
  157. package/dist/src/dashboard/codewatch.js +120 -0
  158. package/dist/src/dashboard/codewatch.js.map +1 -0
  159. package/dist/src/dashboard/commits.d.ts +96 -0
  160. package/dist/src/dashboard/commits.d.ts.map +1 -0
  161. package/dist/src/dashboard/commits.js +207 -0
  162. package/dist/src/dashboard/commits.js.map +1 -0
  163. package/dist/src/dashboard/page.d.ts +10 -0
  164. package/dist/src/dashboard/page.d.ts.map +1 -0
  165. package/dist/src/dashboard/page.js +470 -0
  166. package/dist/src/dashboard/page.js.map +1 -0
  167. package/dist/src/dashboard/payload.d.ts +114 -0
  168. package/dist/src/dashboard/payload.d.ts.map +1 -0
  169. package/dist/src/dashboard/payload.js +96 -0
  170. package/dist/src/dashboard/payload.js.map +1 -0
  171. package/dist/src/dashboard/report.d.ts +99 -0
  172. package/dist/src/dashboard/report.d.ts.map +1 -0
  173. package/dist/src/dashboard/report.js +300 -0
  174. package/dist/src/dashboard/report.js.map +1 -0
  175. package/dist/src/dashboard/repos.d.ts +58 -0
  176. package/dist/src/dashboard/repos.d.ts.map +1 -0
  177. package/dist/src/dashboard/repos.js +111 -0
  178. package/dist/src/dashboard/repos.js.map +1 -0
  179. package/dist/src/dashboard/security.d.ts +130 -0
  180. package/dist/src/dashboard/security.d.ts.map +1 -0
  181. package/dist/src/dashboard/security.js +144 -0
  182. package/dist/src/dashboard/security.js.map +1 -0
  183. package/dist/src/dashboard/server.d.ts +52 -0
  184. package/dist/src/dashboard/server.d.ts.map +1 -0
  185. package/dist/src/dashboard/server.js +163 -0
  186. package/dist/src/dashboard/server.js.map +1 -0
  187. package/dist/src/dashboard/setup.d.ts +67 -0
  188. package/dist/src/dashboard/setup.d.ts.map +1 -0
  189. package/dist/src/dashboard/setup.js +146 -0
  190. package/dist/src/dashboard/setup.js.map +1 -0
  191. package/dist/src/dashboard/usage.d.ts +43 -0
  192. package/dist/src/dashboard/usage.d.ts.map +1 -0
  193. package/dist/src/dashboard/usage.js +88 -0
  194. package/dist/src/dashboard/usage.js.map +1 -0
  195. package/dist/src/index.d.ts +1 -0
  196. package/dist/src/index.d.ts.map +1 -1
  197. package/dist/src/index.js +48 -2
  198. package/dist/src/index.js.map +1 -1
  199. package/dist/src/security/cfn-diff.d.ts +57 -0
  200. package/dist/src/security/cfn-diff.d.ts.map +1 -0
  201. package/dist/src/security/cfn-diff.js +112 -0
  202. package/dist/src/security/cfn-diff.js.map +1 -0
  203. package/dist/src/security/errors.d.ts +45 -0
  204. package/dist/src/security/errors.d.ts.map +1 -0
  205. package/dist/src/security/errors.js +116 -0
  206. package/dist/src/security/errors.js.map +1 -0
  207. package/dist/src/security/marker.d.ts +46 -0
  208. package/dist/src/security/marker.d.ts.map +1 -0
  209. package/dist/src/security/marker.js +120 -0
  210. package/dist/src/security/marker.js.map +1 -0
  211. package/dist/src/security/pentest-dockerfile.d.ts +8 -0
  212. package/dist/src/security/pentest-dockerfile.d.ts.map +1 -0
  213. package/dist/src/security/pentest-dockerfile.js +163 -0
  214. package/dist/src/security/pentest-dockerfile.js.map +1 -0
  215. package/dist/src/security/pentest.d.ts +233 -0
  216. package/dist/src/security/pentest.d.ts.map +1 -0
  217. package/dist/src/security/pentest.js +1057 -0
  218. package/dist/src/security/pentest.js.map +1 -0
  219. package/dist/src/security/repo.d.ts +25 -0
  220. package/dist/src/security/repo.d.ts.map +1 -0
  221. package/dist/src/security/repo.js +38 -0
  222. package/dist/src/security/repo.js.map +1 -0
  223. package/dist/src/security/review.d.ts +202 -0
  224. package/dist/src/security/review.d.ts.map +1 -0
  225. package/dist/src/security/review.js +706 -0
  226. package/dist/src/security/review.js.map +1 -0
  227. package/dist/src/security/setup.d.ts +193 -0
  228. package/dist/src/security/setup.d.ts.map +1 -0
  229. package/dist/src/security/setup.js +728 -0
  230. package/dist/src/security/setup.js.map +1 -0
  231. package/dist/src/security/threat-model.d.ts +133 -0
  232. package/dist/src/security/threat-model.d.ts.map +1 -0
  233. package/dist/src/security/threat-model.js +653 -0
  234. package/dist/src/security/threat-model.js.map +1 -0
  235. package/dist/src/utils/audit-bedrock.d.ts +1 -0
  236. package/dist/src/utils/audit-bedrock.d.ts.map +1 -1
  237. package/dist/src/utils/audit-bedrock.js +133 -0
  238. package/dist/src/utils/audit-bedrock.js.map +1 -1
  239. package/dist/src/utils/audit-org.d.ts +1 -0
  240. package/dist/src/utils/audit-org.d.ts.map +1 -1
  241. package/dist/src/utils/audit-org.js +155 -0
  242. package/dist/src/utils/audit-org.js.map +1 -1
  243. package/dist/src/utils/exec.d.ts +6 -0
  244. package/dist/src/utils/exec.d.ts.map +1 -1
  245. package/dist/src/utils/exec.js +12 -1
  246. package/dist/src/utils/exec.js.map +1 -1
  247. package/dist/src/utils/otel-sync-schedule.d.ts +41 -0
  248. package/dist/src/utils/otel-sync-schedule.d.ts.map +1 -0
  249. package/dist/src/utils/otel-sync-schedule.js +90 -0
  250. package/dist/src/utils/otel-sync-schedule.js.map +1 -0
  251. package/dist/src/utils/tool-check.d.ts +14 -0
  252. package/dist/src/utils/tool-check.d.ts.map +1 -0
  253. package/dist/src/utils/tool-check.js +42 -0
  254. package/dist/src/utils/tool-check.js.map +1 -0
  255. package/package.json +3 -2
@@ -0,0 +1,136 @@
1
+ /**
2
+ * The Security Agent resources and the CI access to them, shared by the two
3
+ * places they can live:
4
+ *
5
+ * - PrismD1MetricsPipeline, with context enableSecurityAgent=true (the
6
+ * original layout; existing accounts keep it);
7
+ * - PrismD1SecurityAgent, the standalone stack (context
8
+ * securityAgentStandalone=true), for accounts that want Security Agent
9
+ * without the metrics pipeline.
10
+ *
11
+ * Construct ids are relative to the given scope, so the pipeline's logical ids
12
+ * are unchanged by moving this code here. Both layouts use the same fixed names
13
+ * (role, managed policy, bucket, SSM parameters, exports), so CloudFormation
14
+ * refuses a second copy in the same account and region.
15
+ */
16
+ import * as cdk from 'aws-cdk-lib';
17
+ import * as iam from 'aws-cdk-lib/aws-iam';
18
+ import * as kms from 'aws-cdk-lib/aws-kms';
19
+ import { Construct } from 'constructs';
20
+ import { NagSuppressions } from 'cdk-nag';
21
+ import { SecurityAgentConstruct } from './constructs/security-agent-construct';
22
+
23
+ export function addSecurityAgentResources(scope: Construct, kmsKey: kms.IKey): SecurityAgentConstruct {
24
+ const securityAgent = new SecurityAgentConstruct(scope, 'SecurityAgent', {
25
+ agentSpaceName: 'prism-d1-security',
26
+ description: 'PRISM D1 Continuum space for design review, code review, and pen testing',
27
+ kmsKey: kmsKey,
28
+ codeRemediationStrategy: 'DISABLED',
29
+ tags: {
30
+ 'prism:pillar': 'security',
31
+ },
32
+ });
33
+
34
+ // Grant GitHub OIDC roles access to scan bucket and Continuum APIs.
35
+ // The OIDC role is created by `prism-cli bootstrapper setup-github-oidc`
36
+ // with name pattern prism-d1-github-oidc-*.
37
+ securityAgent.scanBucket.addToResourcePolicy(
38
+ new iam.PolicyStatement({
39
+ effect: iam.Effect.ALLOW,
40
+ principals: [new iam.ArnPrincipal(`arn:aws:iam::${cdk.Aws.ACCOUNT_ID}:root`)],
41
+ actions: ['s3:PutObject', 's3:GetObject'],
42
+ resources: [securityAgent.scanBucket.arnForObjects('*')],
43
+ conditions: {
44
+ ArnLike: {
45
+ 'aws:PrincipalArn': `arn:aws:iam::${cdk.Aws.ACCOUNT_ID}:role/prism-d1-github-oidc-*`,
46
+ },
47
+ },
48
+ }),
49
+ );
50
+
51
+ // Create a managed policy for Continuum scan API access.
52
+ // Attached to the OIDC role by the bootstrapper setup script.
53
+ const continuumCiPolicy = new iam.ManagedPolicy(scope, 'ContinuumCiPolicy', {
54
+ managedPolicyName: 'prism-d1-continuum-ci-scan',
55
+ description: 'Grants GitHub Actions workflows access to Continuum diff scan APIs and SSM config',
56
+ statements: [
57
+ new iam.PolicyStatement({
58
+ sid: 'ContinuumScanAPIs',
59
+ effect: iam.Effect.ALLOW,
60
+ actions: [
61
+ 'securityagent:StartCodeReviewJob',
62
+ 'securityagent:BatchGetCodeReviewJobs',
63
+ 'securityagent:BatchGetCodeReviewJobTasks',
64
+ 'securityagent:ListFindings',
65
+ 'securityagent:BatchGetFindings',
66
+ 'securityagent:CreateCodeReview',
67
+ 'securityagent:ListCodeReviews',
68
+ ],
69
+ resources: [
70
+ `arn:aws:securityagent:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:agent-space/*`,
71
+ ],
72
+ }),
73
+ new iam.PolicyStatement({
74
+ sid: 'PassRoleForCodeReview',
75
+ effect: iam.Effect.ALLOW,
76
+ actions: ['iam:PassRole'],
77
+ resources: [securityAgent.serviceRole.roleArn],
78
+ conditions: {
79
+ StringEquals: { 'iam:PassedToService': 'securityagent.amazonaws.com' },
80
+ },
81
+ }),
82
+ new iam.PolicyStatement({
83
+ sid: 'ScanBucketWrite',
84
+ effect: iam.Effect.ALLOW,
85
+ actions: ['s3:PutObject', 's3:GetObject', 's3:ListBucket'],
86
+ resources: [
87
+ securityAgent.scanBucket.bucketArn,
88
+ securityAgent.scanBucket.arnForObjects('*'),
89
+ ],
90
+ }),
91
+ new iam.PolicyStatement({
92
+ sid: 'KMSForScanBucket',
93
+ effect: iam.Effect.ALLOW,
94
+ actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:GenerateDataKey*', 'kms:DescribeKey'],
95
+ resources: [kmsKey.keyArn],
96
+ }),
97
+ new iam.PolicyStatement({
98
+ sid: 'SSMReadConfig',
99
+ effect: iam.Effect.ALLOW,
100
+ actions: ['ssm:GetParameter', 'ssm:GetParameters', 'ssm:PutParameter'],
101
+ resources: [
102
+ `arn:aws:ssm:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:parameter/prism/continuum/*`,
103
+ ],
104
+ }),
105
+ ],
106
+ });
107
+
108
+ NagSuppressions.addResourceSuppressions(
109
+ continuumCiPolicy,
110
+ [
111
+ {
112
+ id: 'AwsSolutions-IAM5',
113
+ reason: 'Agent space ID is dynamic; wildcard scoped to securityagent service resources only',
114
+ appliesTo: [`Resource::arn:aws:securityagent:<AWS::Region>:<AWS::AccountId>:agent-space/*`],
115
+ },
116
+ {
117
+ id: 'AwsSolutions-IAM5',
118
+ reason: 'Scan bucket objects include dynamic diff filenames (SHA-based); wildcard on objects only',
119
+ appliesTo: ['Resource::<SecurityAgentScanBucketB752F4A9.Arn>/*'],
120
+ },
121
+ {
122
+ id: 'AwsSolutions-IAM5',
123
+ reason: 'SSM parameters under /prism/continuum/ are all non-sensitive config values (IDs and bucket names)',
124
+ appliesTo: ['Resource::arn:aws:ssm:<AWS::Region>:<AWS::AccountId>:parameter/prism/continuum/*'],
125
+ },
126
+ ],
127
+ true,
128
+ );
129
+
130
+ new cdk.CfnOutput(scope, 'ContinuumCiPolicyArn', {
131
+ value: continuumCiPolicy.managedPolicyArn,
132
+ description: 'Attach this policy to your GitHub OIDC role for Continuum CI scans',
133
+ });
134
+
135
+ return securityAgent;
136
+ }
@@ -0,0 +1,69 @@
1
+ import * as cdk from 'aws-cdk-lib';
2
+ import * as iam from 'aws-cdk-lib/aws-iam';
3
+ import * as kms from 'aws-cdk-lib/aws-kms';
4
+ import { Construct } from 'constructs';
5
+ import { NagSuppressions } from 'cdk-nag';
6
+ import { SecurityAgentConstruct } from './constructs/security-agent-construct';
7
+ import { addSecurityAgentResources } from './security-agent-resources';
8
+
9
+ export interface SecurityAgentStackProps extends cdk.StackProps {
10
+ /**
11
+ * ARN of an existing KMS key to encrypt the agent space with. Its key policy
12
+ * must already allow securityagent.amazonaws.com to encrypt and decrypt; an
13
+ * imported key's policy cannot be changed from here. Default: a new key.
14
+ */
15
+ kmsKeyArn?: string;
16
+ }
17
+
18
+ /**
19
+ * PrismD1SecurityAgent: AWS Security Agent for PRISM without the metrics
20
+ * pipeline. Same resources, names and exports as the pipeline's
21
+ * enableSecurityAgent=true path, plus its own KMS key. Only for accounts that
22
+ * do not host Security Agent in PrismD1MetricsPipeline; the fixed resource
23
+ * and export names make CloudFormation refuse a second copy.
24
+ *
25
+ * Not here: the security-agent-processor and remediation tracker Lambdas,
26
+ * which read the pipeline's tables and stay in PrismD1MetricsPipeline.
27
+ */
28
+ export class SecurityAgentStack extends cdk.Stack {
29
+ public readonly securityAgent: SecurityAgentConstruct;
30
+
31
+ constructor(scope: Construct, id: string, props: SecurityAgentStackProps = {}) {
32
+ super(scope, id, props);
33
+
34
+ let key: kms.IKey;
35
+ if (props.kmsKeyArn) {
36
+ key = kms.Key.fromKeyArn(this, 'SecurityAgentKey', props.kmsKeyArn);
37
+ } else {
38
+ // The same grants the pipeline's PrismDataKey gives Security Agent.
39
+ const k = new kms.Key(this, 'SecurityAgentKey', {
40
+ alias: 'alias/prism-d1-security-agent-key',
41
+ description: 'Encryption key for the PRISM D1 Security Agent space',
42
+ enableKeyRotation: true,
43
+ removalPolicy: cdk.RemovalPolicy.RETAIN,
44
+ });
45
+ k.grantEncryptDecrypt(new iam.ServicePrincipal('securityagent.amazonaws.com'));
46
+ k.addToResourcePolicy(new iam.PolicyStatement({
47
+ effect: iam.Effect.ALLOW,
48
+ principals: [new iam.ServicePrincipal('logs.amazonaws.com')],
49
+ actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:GenerateDataKey*', 'kms:DescribeKey'],
50
+ resources: ['*'],
51
+ conditions: {
52
+ ArnLike: {
53
+ 'kms:EncryptionContext:aws:logs:arn': `arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/securityagent/*`,
54
+ },
55
+ },
56
+ }));
57
+ key = k;
58
+ }
59
+
60
+ this.securityAgent = addSecurityAgentResources(this, key);
61
+
62
+ // The pipeline stack carries the same suppression stack-wide.
63
+ NagSuppressions.addStackSuppressions(this, [{
64
+ id: 'AwsSolutions-IAM5',
65
+ reason: 'CDK KMS grant generates kms:GenerateDataKey* and kms:ReEncrypt* wildcard actions which are required for envelope encryption',
66
+ appliesTo: ['Action::kms:GenerateDataKey*', 'Action::kms:ReEncrypt*'],
67
+ }]);
68
+ }
69
+ }
@@ -11,7 +11,10 @@
11
11
  "diff": "cdk diff",
12
12
  "check:metrics": "tsx scripts/check-metric-coverage.ts",
13
13
  "check:attribution": "tsx scripts/check-attribution-contract.ts",
14
- "check:transitions": "tsx scripts/check-commit-transitions.ts"
14
+ "check:transitions": "tsx scripts/check-commit-transitions.ts",
15
+ "check:pricing": "tsx scripts/check-pricing.ts",
16
+ "check:tools": "tsx scripts/check-tool-usage.ts",
17
+ "pricing:snapshot": "tsx scripts/refresh-pricing-snapshot.ts"
15
18
  },
16
19
  "dependencies": {
17
20
  "aws-cdk-lib": "^2.170.0",
@@ -6,7 +6,9 @@
6
6
  # (Linux/macOS/Windows Git Bash) with NO dependency on jq, bc, sed, or grep -P.
7
7
  # Token tracking powered by codeburn / prism-cli.
8
8
  #
9
- # Installation: prism-cli bootstrapper install-git-hooks
9
+ # RETIRED: no command installs this hook any more. `prism-cli enable codewatch`
10
+ # (and its deprecated alias `bootstrapper install-git-hooks`) removes it when found.
11
+ # Kept for reference only.
10
12
 
11
13
  set -uo pipefail
12
14
 
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import * as cdk from 'aws-cdk-lib';
3
+ import { PentestTargetStack } from '../lib/pentest-target-stack';
4
+
5
+ const app = new cdk.App();
6
+ const slug = String(app.node.tryGetContext('slug') || '');
7
+ if (!slug) throw new Error('pentest-cdk: context "slug" is required (the adapter sets it)');
8
+ // The adapter addresses this stack by the same name (PrismPentest-<slug>).
9
+ new PentestTargetStack(app, `PrismPentest-${slug}`, {
10
+ env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
11
+ description: 'prism-cli security pentest target: private VPC + Fargate + internal ALB (temporary)',
12
+ });
@@ -0,0 +1,7 @@
1
+ {
2
+ "app": "npx ts-node --prefer-ts-exts bin/app.ts",
3
+ "context": {
4
+ "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
5
+ "@aws-cdk/core:checkSecretUsage": true
6
+ }
7
+ }
@@ -0,0 +1,206 @@
1
+ /**
2
+ * The temporary pentest TARGET environment, deployed per run by
3
+ * `prism-cli security pentest` (dockerfile adapter). Generalized from the
4
+ * hand-run spike stack (removed once this replaced it); see
5
+ * docs/DESIGN-prism-cli-pentest.md. Test target: cli/test/fixtures/pentest-sample.
6
+ *
7
+ * Private VPC only: private isolated subnets, no public subnet, no internet
8
+ * gateway, no NAT, no 0.0.0.0/0 ingress anywhere. The app runs on Fargate
9
+ * behind an INTERNAL ALB; the pentest agent's ENI reaches it over local VPC
10
+ * routing. VPC endpoints (ECR api+dkr, S3 gateway, CloudWatch Logs) let Fargate
11
+ * run with no internet path.
12
+ *
13
+ * Context (all from `cdk -c key=value`, set by the adapter):
14
+ * slug run id; names the stack's resources and the private zone
15
+ * appDir absolute path to the Docker build context -> image asset
16
+ * dockerfile Dockerfile path relative to appDir (default: Dockerfile)
17
+ * port container port the app listens on
18
+ * healthPath ALB health-check path
19
+ * zoneName private hosted-zone name (host is app.<zoneName>)
20
+ * ttlHours hours from synth for the prism:expires-at tag
21
+ */
22
+ import * as cdk from 'aws-cdk-lib';
23
+ import * as ec2 from 'aws-cdk-lib/aws-ec2';
24
+ import * as ecs from 'aws-cdk-lib/aws-ecs';
25
+ import * as ecrAssets from 'aws-cdk-lib/aws-ecr-assets';
26
+ import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
27
+ import * as iam from 'aws-cdk-lib/aws-iam';
28
+ import * as logs from 'aws-cdk-lib/aws-logs';
29
+ import * as route53 from 'aws-cdk-lib/aws-route53';
30
+ import * as targets from 'aws-cdk-lib/aws-route53-targets';
31
+ import * as s3 from 'aws-cdk-lib/aws-s3';
32
+ import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
33
+ import { Construct } from 'constructs';
34
+
35
+ const ALB_PORT = 80;
36
+
37
+ /** A JSON context value (the adapter passes env, secrets and actor ARNs this way); absent = fallback. */
38
+ function ctxJson<T>(scope: Construct, key: string, fallback: T): T {
39
+ const v = scope.node.tryGetContext(key);
40
+ if (v === undefined || v === null || v === '') return fallback;
41
+ return (typeof v === 'string' ? JSON.parse(v) : v) as T;
42
+ }
43
+
44
+ /** A Secrets Manager ARN with its random 6-character suffix is complete; without it, partial. */
45
+ const isCompleteSecretArn = (arn: string) => /-[A-Za-z0-9]{6}$/.test(arn);
46
+
47
+ function ctxStr(scope: Construct, key: string, fallback?: string): string {
48
+ const v = scope.node.tryGetContext(key);
49
+ if (v === undefined || v === null || v === '') {
50
+ if (fallback !== undefined) return fallback;
51
+ throw new Error(`pentest-cdk: required context "${key}" is not set`);
52
+ }
53
+ return String(v);
54
+ }
55
+
56
+ export class PentestTargetStack extends cdk.Stack {
57
+ constructor(scope: Construct, id: string, props: cdk.StackProps) {
58
+ super(scope, id, props);
59
+
60
+ const slug = ctxStr(this, 'slug');
61
+ const appDir = ctxStr(this, 'appDir');
62
+ const port = Number(ctxStr(this, 'port', '8080'));
63
+ const healthPath = ctxStr(this, 'healthPath', '/health');
64
+ const zoneName = ctxStr(this, 'zoneName', `${slug}.prism.internal`);
65
+ const ttlHours = Number(ctxStr(this, 'ttlHours', '24'));
66
+ const logGroupName = `/aws/securityagent/prism-pentest-${slug}`;
67
+
68
+ cdk.Tags.of(this).add('prism:purpose', 'pentest');
69
+ cdk.Tags.of(this).add('prism:pentest-run', slug);
70
+ cdk.Tags.of(this).add('prism:expires-at', new Date(Date.now() + ttlHours * 3_600_000).toISOString());
71
+
72
+ // --- Network: private isolated subnets only (no IGW, no NAT) ---------
73
+ const vpc = new ec2.Vpc(this, 'Vpc', {
74
+ ipAddresses: ec2.IpAddresses.cidr('10.80.0.0/16'),
75
+ maxAzs: 2,
76
+ natGateways: 0,
77
+ subnetConfiguration: [{ name: 'private', subnetType: ec2.SubnetType.PRIVATE_ISOLATED, cidrMask: 24 }],
78
+ restrictDefaultSecurityGroup: true,
79
+ });
80
+ const privateSubnets = { subnetType: ec2.SubnetType.PRIVATE_ISOLATED };
81
+
82
+ const sg = (name: string, description: string) => new ec2.SecurityGroup(this, name, { vpc, description, allowAllOutbound: false });
83
+ const agentSg = sg('AgentSg', 'Pentest agent ENI: may reach the internal ALB on 80 only');
84
+ const albSg = sg('AlbSg', 'Internal ALB: accepts the pentest agent only');
85
+ const appSg = sg('AppSg', 'App tasks: accept the ALB only');
86
+ const endpointSg = sg('EndpointSg', 'VPC interface endpoints: accept the app tasks only');
87
+
88
+ agentSg.addEgressRule(albSg, ec2.Port.tcp(ALB_PORT), 'to the internal ALB');
89
+ albSg.addIngressRule(agentSg, ec2.Port.tcp(ALB_PORT), 'from the pentest agent');
90
+ appSg.addEgressRule(endpointSg, ec2.Port.tcp(443), 'to VPC interface endpoints');
91
+ endpointSg.addIngressRule(appSg, ec2.Port.tcp(443), 'from the app tasks');
92
+
93
+ vpc.addGatewayEndpoint('S3', { service: ec2.GatewayVpcEndpointAwsService.S3, subnets: [privateSubnets] });
94
+ const s3PrefixList = ec2.PrefixList.fromLookup(this, 'S3PrefixList', { prefixListName: `com.amazonaws.${this.region}.s3` });
95
+ appSg.addEgressRule(ec2.Peer.prefixList(s3PrefixList.prefixListId), ec2.Port.tcp(443), 'to S3 (ECR image layers)');
96
+
97
+ const appEnv = ctxJson<Record<string, string>>(this, 'env', {});
98
+ const appSecrets = ctxJson<Record<string, string>>(this, 'secrets', {});
99
+ const actorSecrets = ctxJson<string[]>(this, 'actorSecrets', []);
100
+ const actorLambdas = ctxJson<string[]>(this, 'actorLambdas', []);
101
+
102
+ const endpoints: [string, ec2.InterfaceVpcEndpointAwsService][] = [
103
+ ['EcrApi', ec2.InterfaceVpcEndpointAwsService.ECR],
104
+ ['EcrDkr', ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER],
105
+ ['Logs', ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS],
106
+ ];
107
+ // With no internet path, a task reads its secrets at start through this endpoint.
108
+ if (Object.keys(appSecrets).length) endpoints.push(['SecretsManager', ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER]);
109
+ for (const [cid, service] of endpoints) {
110
+ vpc.addInterfaceEndpoint(cid, { service, subnets: privateSubnets, securityGroups: [endpointSg], open: false, privateDnsEnabled: true });
111
+ }
112
+
113
+ // --- App: Fargate behind an internal ALB -----------------------------
114
+ // dockerfile: path relative to appDir (the build context); may be in a subfolder.
115
+ const dockerfile = ctxStr(this, 'dockerfile', 'Dockerfile');
116
+ const image = new ecrAssets.DockerImageAsset(this, 'Image', { directory: appDir, file: dockerfile, platform: ecrAssets.Platform.LINUX_AMD64 });
117
+ const appLogs = new logs.LogGroup(this, 'AppLogs', { retention: logs.RetentionDays.ONE_WEEK, removalPolicy: cdk.RemovalPolicy.DESTROY });
118
+ const cluster = new ecs.Cluster(this, 'Cluster', { vpc });
119
+ const task = new ecs.FargateTaskDefinition(this, 'Task', {
120
+ cpu: 256, memoryLimitMiB: 512,
121
+ runtimePlatform: { cpuArchitecture: ecs.CpuArchitecture.X86_64, operatingSystemFamily: ecs.OperatingSystemFamily.LINUX },
122
+ });
123
+ task.addContainer('app', {
124
+ image: ecs.ContainerImage.fromDockerImageAsset(image),
125
+ portMappings: [{ containerPort: port }],
126
+ environment: { ...appEnv, PORT: String(port) },
127
+ // Injected by ECS at task start; the execution role is granted read on exactly these secrets.
128
+ secrets: Object.fromEntries(Object.entries(appSecrets).map(([name, arn], i) => [name, ecs.Secret.fromSecretsManager(
129
+ isCompleteSecretArn(arn) ? secretsmanager.Secret.fromSecretCompleteArn(this, `AppSecret${i}`, arn) : secretsmanager.Secret.fromSecretPartialArn(this, `AppSecret${i}`, arn),
130
+ )])),
131
+ logging: ecs.LogDrivers.awsLogs({ logGroup: appLogs, streamPrefix: 'app' }),
132
+ });
133
+ const service = new ecs.FargateService(this, 'Service', {
134
+ cluster, taskDefinition: task, desiredCount: 1, minHealthyPercent: 100,
135
+ assignPublicIp: false, vpcSubnets: privateSubnets, securityGroups: [appSg], circuitBreaker: { rollback: true },
136
+ });
137
+
138
+ const alb = new elbv2.ApplicationLoadBalancer(this, 'Alb', {
139
+ vpc, internetFacing: false, vpcSubnets: privateSubnets, securityGroup: albSg, dropInvalidHeaderFields: true,
140
+ });
141
+ const listener = alb.addListener('Http', { port: ALB_PORT, protocol: elbv2.ApplicationProtocol.HTTP, open: false });
142
+ // Faster than the 5 x 30s default, so a new task turns healthy in ~20s; the
143
+ // command waits for a healthy target before starting the pentest.
144
+ const targetGroup = listener.addTargets('App', {
145
+ port, protocol: elbv2.ApplicationProtocol.HTTP, targets: [service],
146
+ healthCheck: { path: healthPath, interval: cdk.Duration.seconds(10), timeout: cdk.Duration.seconds(5), healthyThresholdCount: 2, unhealthyThresholdCount: 3 },
147
+ deregistrationDelay: cdk.Duration.seconds(10),
148
+ });
149
+
150
+ // --- Private hostname -------------------------------------------------
151
+ const zone = new route53.PrivateHostedZone(this, 'Zone', { zoneName, vpc });
152
+ new route53.ARecord(this, 'AppRecord', { zone, recordName: 'app', target: route53.RecordTarget.fromAlias(new targets.LoadBalancerTarget(alb)) });
153
+
154
+ // --- Source zip bucket and agent log group ---------------------------
155
+ const source = new s3.Bucket(this, 'Source', {
156
+ blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, encryption: s3.BucketEncryption.S3_MANAGED,
157
+ enforceSSL: true, removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true,
158
+ });
159
+ const agentLogs = new logs.LogGroup(this, 'AgentLogs', { logGroupName, retention: logs.RetentionDays.ONE_WEEK, removalPolicy: cdk.RemovalPolicy.DESTROY });
160
+
161
+ // --- Pentest service role (scoped ENI permissions; spike-confirmed) ---
162
+ const subnetArns = vpc.isolatedSubnets.map((s) => this.formatArn({ service: 'ec2', resource: 'subnet', resourceName: s.subnetId }));
163
+ const agentSgArn = this.formatArn({ service: 'ec2', resource: 'security-group', resourceName: agentSg.securityGroupId });
164
+ const eniArn = this.formatArn({ service: 'ec2', resource: 'network-interface', resourceName: '*' });
165
+
166
+ const role = new iam.Role(this, 'ServiceRole', {
167
+ assumedBy: new iam.ServicePrincipal('securityagent.amazonaws.com', { conditions: { StringEquals: { 'aws:SourceAccount': this.account } } }),
168
+ description: 'prism-cli security pentest: Security Agent service role for one private VPC pentest',
169
+ });
170
+ role.addToPolicy(new iam.PolicyStatement({
171
+ sid: 'DescribeNetwork',
172
+ actions: ['ec2:DescribeDhcpOptions', 'ec2:DescribeNetworkInterfaces', 'ec2:DescribeNetworkInterfaceAttribute', 'ec2:DescribeSubnets', 'ec2:DescribeSecurityGroups', 'ec2:DescribeVpcs'],
173
+ resources: ['*'],
174
+ }));
175
+ role.addToPolicy(new iam.PolicyStatement({ sid: 'EniCreateScoped', actions: ['ec2:CreateNetworkInterface'], resources: [eniArn, agentSgArn, ...subnetArns] }));
176
+ role.addToPolicy(new iam.PolicyStatement({ sid: 'EniManageScoped', actions: ['ec2:ModifyNetworkInterfaceAttribute', 'ec2:DeleteNetworkInterface'], resources: [eniArn, agentSgArn], conditions: { StringEquals: { 'ec2:Vpc': vpc.vpcArn } } }));
177
+ role.addToPolicy(new iam.PolicyStatement({ sid: 'EniPermission', actions: ['ec2:CreateNetworkInterfacePermission'], resources: [eniArn], conditions: { ArnEquals: { 'ec2:Subnet': subnetArns } } }));
178
+ // Logs: create/describe the configured group (spike: PREFLIGHT fails CLIENT_ERROR without CreateLogGroup on the group ARN).
179
+ role.addToPolicy(new iam.PolicyStatement({ sid: 'LogsGroup', actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents', 'logs:DescribeLogStreams'], resources: [
180
+ // logGroupArn already ends in ':*', which does NOT match the bare group ARN
181
+ // CreateLogGroup is authorized against (the spike's second PREFLIGHT failure).
182
+ this.formatArn({ service: 'logs', resource: 'log-group', resourceName: logGroupName, arnFormat: cdk.ArnFormat.COLON_RESOURCE_NAME }),
183
+ agentLogs.logGroupArn,
184
+ ] }));
185
+ role.addToPolicy(new iam.PolicyStatement({ sid: 'LogsDescribe', actions: ['logs:DescribeLogGroups'], resources: ['*'] }));
186
+ source.grantRead(role);
187
+ // Test actors (.prism/pentest.json "actors"): the agent reads their credentials with this role.
188
+ actorSecrets.forEach((arn, i) => (isCompleteSecretArn(arn)
189
+ ? secretsmanager.Secret.fromSecretCompleteArn(this, `ActorSecret${i}`, arn)
190
+ : secretsmanager.Secret.fromSecretPartialArn(this, `ActorSecret${i}`, arn)).grantRead(role));
191
+ if (actorLambdas.length) role.addToPolicy(new iam.PolicyStatement({ sid: 'ActorCredentials', actions: ['lambda:InvokeFunction'], resources: actorLambdas }));
192
+
193
+ const out = (oid: string, value: string) => new cdk.CfnOutput(this, oid, { value });
194
+ out('VpcArn', vpc.vpcArn);
195
+ out('SubnetArns', cdk.Fn.join(',', subnetArns));
196
+ out('AgentSecurityGroupArn', agentSgArn);
197
+ out('AlbDnsName', alb.loadBalancerDnsName);
198
+ out('PrivateHost', `app.${zoneName}`);
199
+ out('ServiceRoleArn', role.roleArn);
200
+ out('SourceBucket', source.bucketName);
201
+ out('AgentLogGroup', agentLogs.logGroupName);
202
+ out('TargetGroupArn', targetGroup.targetGroupArn);
203
+ out('AppLogGroup', appLogs.logGroupName);
204
+ out('ExpiresAt', new Date(Date.now() + ttlHours * 3_600_000).toISOString());
205
+ }
206
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "prism-pentest-target-cdk",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "cdk": "cdk"
7
+ },
8
+ "dependencies": {
9
+ "aws-cdk-lib": "^2.265.0",
10
+ "constructs": "^10.4.2"
11
+ },
12
+ "devDependencies": {
13
+ "@types/node": "^20.17.0",
14
+ "aws-cdk": "^2.1000.0",
15
+ "ts-node": "^10.9.2",
16
+ "typescript": "~5.6.0"
17
+ }
18
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "commonjs",
5
+ "moduleResolution": "node",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "noEmit": true
10
+ },
11
+ "include": ["bin/**/*.ts", "lib/**/*.ts"]
12
+ }
@@ -0,0 +1,17 @@
1
+ export declare const CONFIG_PATH = ".prism/config.json";
2
+ export declare const RULES_PATH = ".prism/review/rules.md";
3
+ export declare const GITLEAKS_BASELINE_PATH = ".prism/gitleaks-baseline.json";
4
+ export declare const GITLEAKS_CONFIG_PATH = ".gitleaks.toml";
5
+ /** Per-clone override of the team's reviewer choice: `git config prism.reviewer claude`. */
6
+ export declare const REVIEWER_GIT_CONFIG_KEY = "prism.reviewer";
7
+ export declare const DEFAULT_REVIEW_TIMEOUT_SECONDS = 300;
8
+ export interface EvalConfig {
9
+ reviewer?: string;
10
+ model?: string;
11
+ timeout_seconds?: number;
12
+ }
13
+ export declare function readPrismConfig(repoRoot: string): Record<string, any>;
14
+ export declare function readEvalConfig(repoRoot: string): EvalConfig;
15
+ /** Merges `patch` into the `eval` block, keeping every other key in the file. */
16
+ export declare function writeEvalConfig(repoRoot: string, patch: EvalConfig): void;
17
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/codewatch/config.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,WAAW,uBAAuB,CAAC;AAChD,eAAO,MAAM,UAAU,2BAA2B,CAAC;AACnD,eAAO,MAAM,sBAAsB,kCAAkC,CAAC;AACtE,eAAO,MAAM,oBAAoB,mBAAmB,CAAC;AACrD,4FAA4F;AAC5F,eAAO,MAAM,uBAAuB,mBAAmB,CAAC;AACxD,eAAO,MAAM,8BAA8B,MAAM,CAAC;AAElD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CASrE;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAG3D;AAED,iFAAiF;AACjF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAMzE"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * `.prism/config.json` access for codewatch (the local git-hook gate).
3
+ *
4
+ * The file is shared with other PRISM installers (team_id etc.) and is usually
5
+ * committed, so reads tolerate any shape and writes merge rather than replace.
6
+ */
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
8
+ import { dirname, resolve } from 'node:path';
9
+ export const CONFIG_PATH = '.prism/config.json';
10
+ export const RULES_PATH = '.prism/review/rules.md';
11
+ export const GITLEAKS_BASELINE_PATH = '.prism/gitleaks-baseline.json';
12
+ export const GITLEAKS_CONFIG_PATH = '.gitleaks.toml';
13
+ /** Per-clone override of the team's reviewer choice: `git config prism.reviewer claude`. */
14
+ export const REVIEWER_GIT_CONFIG_KEY = 'prism.reviewer';
15
+ export const DEFAULT_REVIEW_TIMEOUT_SECONDS = 300;
16
+ export function readPrismConfig(repoRoot) {
17
+ const p = resolve(repoRoot, CONFIG_PATH);
18
+ if (!existsSync(p))
19
+ return {};
20
+ try {
21
+ const v = JSON.parse(readFileSync(p, 'utf8'));
22
+ return v && typeof v === 'object' && !Array.isArray(v) ? v : {};
23
+ }
24
+ catch {
25
+ return {};
26
+ }
27
+ }
28
+ export function readEvalConfig(repoRoot) {
29
+ const e = readPrismConfig(repoRoot).eval;
30
+ return e && typeof e === 'object' ? e : {};
31
+ }
32
+ /** Merges `patch` into the `eval` block, keeping every other key in the file. */
33
+ export function writeEvalConfig(repoRoot, patch) {
34
+ const p = resolve(repoRoot, CONFIG_PATH);
35
+ const cfg = readPrismConfig(repoRoot);
36
+ cfg.eval = { ...(cfg.eval && typeof cfg.eval === 'object' ? cfg.eval : {}), ...patch };
37
+ mkdirSync(dirname(p), { recursive: true });
38
+ writeFileSync(p, JSON.stringify(cfg, null, 2) + '\n');
39
+ }
40
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/codewatch/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE7C,MAAM,CAAC,MAAM,WAAW,GAAG,oBAAoB,CAAC;AAChD,MAAM,CAAC,MAAM,UAAU,GAAG,wBAAwB,CAAC;AACnD,MAAM,CAAC,MAAM,sBAAsB,GAAG,+BAA+B,CAAC;AACtE,MAAM,CAAC,MAAM,oBAAoB,GAAG,gBAAgB,CAAC;AACrD,4FAA4F;AAC5F,MAAM,CAAC,MAAM,uBAAuB,GAAG,gBAAgB,CAAC;AACxD,MAAM,CAAC,MAAM,8BAA8B,GAAG,GAAG,CAAC;AAQlD,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,MAAM,CAAC,GAAG,OAAO,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IACzC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAC9C,OAAO,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,QAAgB;IAC7C,MAAM,CAAC,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;IACzC,OAAO,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC7C,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,eAAe,CAAC,QAAgB,EAAE,KAAiB;IACjE,MAAM,CAAC,GAAG,OAAO,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IACzC,MAAM,GAAG,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IACtC,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC;IACvF,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AACxD,CAAC"}
@@ -0,0 +1,49 @@
1
+ import { type Finding, type PrismResult } from './results.js';
2
+ /**
3
+ * Starter gitleaks config, written only when the repo has none.
4
+ *
5
+ * The secret-scan step in the workflow runs with or without this file --
6
+ * gitleaks ships usable defaults. What the defaults do not know is which
7
+ * credential-shaped strings in THIS repo are deliberate, so the first PR that
8
+ * touches a test fixture fails the gate on a fake key. A team that hits that
9
+ * with no obvious place to record the exception tends to delete the gate rather
10
+ * than tune it, so the tuning surface ships up front and is documented inline.
11
+ *
12
+ * `useDefault = true` is what keeps this additive: without it, declaring a
13
+ * config REPLACES the entire default ruleset and the gate silently stops
14
+ * detecting anything it does not name explicitly.
15
+ */
16
+ export declare const GITLEAKS_STARTER_CONFIG = "# gitleaks configuration for the PRISM eval gate.\n#\n# The gate scans only the commits a PR introduces, so anything flagged here is\n# something this PR added. Two ways to handle a false positive:\n#\n# 1. Allowlist the path or pattern below (preferred -- reviewable, and it\n# keeps working as the file changes).\n# 2. Add an inline \"gitleaks:allow\" comment on the offending line.\n#\n# A real exposure is NOT a false positive: rotate the credential first, because\n# deleting it from the branch does not un-expose what was already pushed.\n\n[extend]\n# Keep gitleaks' built-in ruleset. Removing this line replaces the defaults\n# entirely rather than adding to them, which turns the gate into a no-op for\n# every credential type not restated in this file.\nuseDefault = true\n\n[[allowlists]]\ndescription = \"Test fixtures and documented examples\"\npaths = [\n '''(^|/)tests?/''',\n '''(^|/)__fixtures__/''',\n '''(^|/)__tests__/''',\n '''\\.example($|\\.)''',\n]\n";
17
+ /**
18
+ * One-line, platform-specific gitleaks install hint. Never a curl-piped-to-shell.
19
+ * Single source for the installer, the pre-commit warning and the dashboard.
20
+ */
21
+ export declare function gitleaksInstallHint(platform?: NodeJS.Platform): string;
22
+ /** Writes the starter config unless the repo already has one. Returns true when written. */
23
+ export declare function installGitleaksConfig(gitRoot: string): boolean;
24
+ /**
25
+ * Converts a gitleaks JSON report to schema findings.
26
+ *
27
+ * Only location and rule fields are projected. `Secret` and `Match` are never
28
+ * read, so a result file cannot carry the credential even if --redact was lost.
29
+ */
30
+ export declare function parseGitleaksReport(json: string): Finding[];
31
+ export interface PreCommitOutcome {
32
+ /** Process exit code for the hook: 1 blocks the commit. */
33
+ exitCode: number;
34
+ result: PrismResult;
35
+ resultPath: string | null;
36
+ }
37
+ /**
38
+ * `gitleaks protect --staged` against the repo config and baseline.
39
+ *
40
+ * Blocks (exit 1) on a finding and when gitleaks runs but cannot produce a
41
+ * verdict (bad config, unreadable report): a broken secret scan should be
42
+ * fixed, not silently passed. A MISSING gitleaks does not block -- it warns and
43
+ * records a `skipped` result with reason `gitleaks-missing`; CI's gitleaks
44
+ * step remains the backstop.
45
+ */
46
+ export declare function runPreCommit(repoRoot: string, gitleaksBin?: string): PreCommitOutcome;
47
+ /** A result that cannot be written must never change the hook's verdict. */
48
+ export declare function safeWrite(repoRoot: string, r: PrismResult): string | null;
49
+ //# sourceMappingURL=gitleaks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitleaks.d.ts","sourceRoot":"","sources":["../../../src/codewatch/gitleaks.ts"],"names":[],"mappings":"AAQA,OAAO,EAAoD,KAAK,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAIhH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,uBAAuB,g+BA0BnC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,MAAM,CAKxF;AAED,4FAA4F;AAC5F,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAK9D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAW3D;AAED,MAAM,WAAW,gBAAgB;IAC/B,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAOD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,SAAa,GAAG,gBAAgB,CAyCzF;AAED,4EAA4E;AAC5E,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAEzE"}