@prism-d1/cli 1.0.82 → 1.0.84
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/dist/assets/github-workflows/prism-agent-eval.yml +6 -2
- package/dist/assets/github-workflows/prism-ai-metrics.yml +21 -5
- package/dist/assets/github-workflows/prism-eval-gate-kiro.yml +55 -25
- package/dist/assets/github-workflows/prism-eval-gate.yml +36 -20
- package/dist/assets/gitlab-workflows/prism-agent-eval.yml +3 -2
- package/dist/assets/gitlab-workflows/prism-ai-metrics.yml +11 -3
- package/dist/assets/gitlab-workflows/prism-eval-gate.yml +3 -2
- package/dist/assets/infra/lib/lambda/security-agent-processor.ts +76 -43
- package/dist/assets/infra/lib/metrics-pipeline-stack.ts +8 -0
- package/dist/src/commands/bootstrapper/install-eval-harness.d.ts.map +1 -1
- package/dist/src/commands/bootstrapper/install-eval-harness.js +25 -6
- package/dist/src/commands/bootstrapper/install-eval-harness.js.map +1 -1
- package/dist/src/commands/bootstrapper/install-github-workflows.d.ts +1 -0
- package/dist/src/commands/bootstrapper/install-github-workflows.d.ts.map +1 -1
- package/dist/src/commands/bootstrapper/install-github-workflows.js +81 -14
- package/dist/src/commands/bootstrapper/install-github-workflows.js.map +1 -1
- package/dist/src/commands/bootstrapper/install-gitlab-workflows.d.ts.map +1 -1
- package/dist/src/commands/bootstrapper/install-gitlab-workflows.js +9 -6
- package/dist/src/commands/bootstrapper/install-gitlab-workflows.js.map +1 -1
- package/dist/src/commands/bootstrapper/setup-github-oidc.d.ts +8 -2
- package/dist/src/commands/bootstrapper/setup-github-oidc.d.ts.map +1 -1
- package/dist/src/commands/bootstrapper/setup-github-oidc.js +17 -2
- package/dist/src/commands/bootstrapper/setup-github-oidc.js.map +1 -1
- package/dist/src/commands/bootstrapper/setup-gitlab-oidc.d.ts +8 -2
- package/dist/src/commands/bootstrapper/setup-gitlab-oidc.d.ts.map +1 -1
- package/dist/src/commands/bootstrapper/setup-gitlab-oidc.js +17 -2
- package/dist/src/commands/bootstrapper/setup-gitlab-oidc.js.map +1 -1
- package/dist/src/commands/workshop/verify-setup.d.ts.map +1 -1
- package/dist/src/commands/workshop/verify-setup.js +0 -2
- package/dist/src/commands/workshop/verify-setup.js.map +1 -1
- package/dist/src/utils/root.d.ts +8 -4
- package/dist/src/utils/root.d.ts.map +1 -1
- package/dist/src/utils/root.js +42 -20
- package/dist/src/utils/root.js.map +1 -1
- package/dist/src/utils/validate.d.ts +1 -0
- package/dist/src/utils/validate.d.ts.map +1 -1
- package/dist/src/utils/validate.js +42 -0
- package/dist/src/utils/validate.js.map +1 -1
- package/package.json +1 -1
|
@@ -23,6 +23,10 @@ on:
|
|
|
23
23
|
- '**/agent*.py'
|
|
24
24
|
- '**/agent*.ts'
|
|
25
25
|
|
|
26
|
+
|
|
27
|
+
env:
|
|
28
|
+
PRISM_AWS_REGION: us-west-2
|
|
29
|
+
|
|
26
30
|
permissions:
|
|
27
31
|
id-token: write
|
|
28
32
|
contents: read
|
|
@@ -41,7 +45,7 @@ jobs:
|
|
|
41
45
|
uses: aws-actions/configure-aws-credentials@b47578312673ae6fa5b5096b330d9fbac3d116df # v4.2.1
|
|
42
46
|
with:
|
|
43
47
|
role-to-assume: ${{ secrets.PRISM_METRICS_ROLE_ARN }}
|
|
44
|
-
aws-region:
|
|
48
|
+
aws-region: ${{ env.PRISM_AWS_REGION }}
|
|
45
49
|
|
|
46
50
|
- name: Read team ID from .prism/config.json
|
|
47
51
|
id: prism-config
|
|
@@ -298,7 +302,7 @@ jobs:
|
|
|
298
302
|
}')
|
|
299
303
|
|
|
300
304
|
aws events put-events \
|
|
301
|
-
--region
|
|
305
|
+
--region $PRISM_AWS_REGION \
|
|
302
306
|
--entries "[{
|
|
303
307
|
\"Source\": \"prism.d1.velocity\",
|
|
304
308
|
\"DetailType\": \"prism.d1.agent.eval\",
|
|
@@ -33,6 +33,10 @@ on:
|
|
|
33
33
|
types: [closed]
|
|
34
34
|
branches: [main, master]
|
|
35
35
|
|
|
36
|
+
|
|
37
|
+
env:
|
|
38
|
+
PRISM_AWS_REGION: us-west-2
|
|
39
|
+
|
|
36
40
|
permissions:
|
|
37
41
|
id-token: write
|
|
38
42
|
contents: read
|
|
@@ -54,7 +58,7 @@ jobs:
|
|
|
54
58
|
uses: aws-actions/configure-aws-credentials@b47578312673ae6fa5b5096b330d9fbac3d116df # v4.2.1
|
|
55
59
|
with:
|
|
56
60
|
role-to-assume: ${{ secrets.PRISM_METRICS_ROLE_ARN }}
|
|
57
|
-
aws-region:
|
|
61
|
+
aws-region: ${{ env.PRISM_AWS_REGION }}
|
|
58
62
|
|
|
59
63
|
- name: Read team ID from .prism/config.json
|
|
60
64
|
id: prism-config
|
|
@@ -86,8 +90,20 @@ jobs:
|
|
|
86
90
|
|
|
87
91
|
# --- Commit SHAs: immutable facts for deferred origin resolution.
|
|
88
92
|
# Capped at 50 to bound the event size and the render-time join.
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
#
|
|
94
|
+
# --no-merges matters here. HEAD_SHA is the PR's merge_commit_sha, so the
|
|
95
|
+
# range BASE..HEAD includes GitHub's own merge commit, authored by
|
|
96
|
+
# web-flow on GitHub's servers. codeburn attributes commits made on a
|
|
97
|
+
# developer's machine, so that commit can never appear in the attribution
|
|
98
|
+
# store. Counting it puts a structural ceiling on Attribution Coverage:
|
|
99
|
+
# a fully-onboarded team with 3 PRs and 9 authored commits reported
|
|
100
|
+
# 9 attributed / 12 seen = 75%, when the true coverage was 100%.
|
|
101
|
+
#
|
|
102
|
+
# Excluding every merge commit, not just GitHub's, is the right call for
|
|
103
|
+
# this pair: a merge commit introduces no authored lines, so it is not
|
|
104
|
+
# attributable content on either side of the ratio.
|
|
105
|
+
COMMIT_SHAS=$(git rev-list --no-merges "${BASE_SHA}..${HEAD_SHA}" 2>/dev/null | head -50 | jq -R . | jq -sc . || echo '[]')
|
|
106
|
+
TOTAL_COMMITS=$(git rev-list --no-merges --count "${BASE_SHA}..${HEAD_SHA}" 2>/dev/null || echo 0)
|
|
91
107
|
|
|
92
108
|
# --- Lead time: PR open → merge. From the event payload, not git, so
|
|
93
109
|
# it is unaffected by rebases and squashes.
|
|
@@ -173,7 +189,7 @@ jobs:
|
|
|
173
189
|
}')
|
|
174
190
|
|
|
175
191
|
aws events put-events \
|
|
176
|
-
--region
|
|
192
|
+
--region $PRISM_AWS_REGION \
|
|
177
193
|
--entries "[{
|
|
178
194
|
\"Source\": \"prism.d1.velocity\",
|
|
179
195
|
\"DetailType\": \"prism.d1.pr\",
|
|
@@ -213,7 +229,7 @@ jobs:
|
|
|
213
229
|
}')
|
|
214
230
|
|
|
215
231
|
aws events put-events \
|
|
216
|
-
--region
|
|
232
|
+
--region $PRISM_AWS_REGION \
|
|
217
233
|
--entries "[{
|
|
218
234
|
\"Source\": \"prism.d1.velocity\",
|
|
219
235
|
\"DetailType\": \"prism.d1.deploy\",
|
|
@@ -15,6 +15,10 @@ on:
|
|
|
15
15
|
types: [opened, synchronize, reopened]
|
|
16
16
|
branches: [main, master]
|
|
17
17
|
|
|
18
|
+
|
|
19
|
+
env:
|
|
20
|
+
PRISM_AWS_REGION: us-west-2
|
|
21
|
+
|
|
18
22
|
permissions:
|
|
19
23
|
id-token: write
|
|
20
24
|
contents: read
|
|
@@ -213,13 +217,13 @@ jobs:
|
|
|
213
217
|
uses: aws-actions/configure-aws-credentials@b47578312673ae6fa5b5096b330d9fbac3d116df # v4.2.1
|
|
214
218
|
with:
|
|
215
219
|
role-to-assume: ${{ env.METRICS_ROLE_ARN }}
|
|
216
|
-
aws-region:
|
|
220
|
+
aws-region: ${{ env.PRISM_AWS_REGION }}
|
|
217
221
|
continue-on-error: true
|
|
218
222
|
|
|
219
223
|
- name: Emit prism.d1.eval event
|
|
220
224
|
if: steps.aws-creds.outcome == 'success'
|
|
221
225
|
env:
|
|
222
|
-
AWS_REGION:
|
|
226
|
+
AWS_REGION: ${{ env.PRISM_AWS_REGION }}
|
|
223
227
|
TEAM_ID: ${{ steps.prism-config.outputs.team_id }}
|
|
224
228
|
REPO: ${{ github.repository }}
|
|
225
229
|
REVIEW_SCORE: ${{ steps.review.outputs.score }}
|
|
@@ -249,27 +253,25 @@ jobs:
|
|
|
249
253
|
eval:{score:($score|tonumber),result:$result,rubric:"kiro-headless",
|
|
250
254
|
findings:($findings|tonumber),high_findings:($high|tonumber),
|
|
251
255
|
pr_number:($pr_number|tonumber),method:"kiro-headless"}}')
|
|
252
|
-
aws events put-events --region
|
|
256
|
+
aws events put-events --region $PRISM_AWS_REGION --entries \
|
|
253
257
|
"[{\"Source\":\"prism.d1.velocity\",\"DetailType\":\"prism.d1.eval\",\"EventBusName\":\"prism-d1-metrics\",\"Detail\":$(echo "${DETAIL}" | jq -Rs '.')}]" || true
|
|
254
258
|
|
|
255
259
|
- name: AWS Continuum security scan (Tier 2 -- deterministic)
|
|
256
260
|
id: security-gate
|
|
257
261
|
if: steps.aws-creds.outcome == 'success'
|
|
258
262
|
env:
|
|
259
|
-
AWS_REGION:
|
|
263
|
+
AWS_REGION: ${{ env.PRISM_AWS_REGION }}
|
|
260
264
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
261
265
|
HEAD_SHA: ${{ github.sha }}
|
|
262
266
|
REPO: ${{ github.repository }}
|
|
263
267
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
264
268
|
run: |
|
|
265
|
-
#
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
fi
|
|
272
|
-
|
|
269
|
+
# Config is read FIRST, on purpose. "Continuum was never deployed" is a
|
|
270
|
+
# deployment choice and may legitimately skip the scan; "Continuum is
|
|
271
|
+
# deployed but the scan could not run" is a failure and must block the
|
|
272
|
+
# merge. Probing the CLI before knowing which case we are in cannot tell
|
|
273
|
+
# them apart, so a missing subcommand used to silently pass the gate on a
|
|
274
|
+
# repo that had opted into scanning.
|
|
273
275
|
AGENT_SPACE_ID=$(aws ssm get-parameter --name /prism/continuum/agent-space-id --query Parameter.Value --output text 2>/dev/null || echo "")
|
|
274
276
|
SCAN_BUCKET=$(aws ssm get-parameter --name /prism/continuum/scan-bucket --query Parameter.Value --output text 2>/dev/null || echo "")
|
|
275
277
|
|
|
@@ -280,6 +282,16 @@ jobs:
|
|
|
280
282
|
exit 0
|
|
281
283
|
fi
|
|
282
284
|
|
|
285
|
+
# Continuum IS configured from here on, so every failure below fails
|
|
286
|
+
# closed: scan_error=true and findings=-1, never findings=0.
|
|
287
|
+
if ! aws securityagent start-code-review-job help > /dev/null 2>&1; then
|
|
288
|
+
echo "::error::Continuum is configured but this AWS CLI lacks the securityagent code review commands (need v2.36+)."
|
|
289
|
+
echo " The security scan cannot run, so this PR is not verified. Upgrade the CLI in the workflow."
|
|
290
|
+
echo "scan_error=true" >> "$GITHUB_OUTPUT"
|
|
291
|
+
echo "findings=-1" >> "$GITHUB_OUTPUT"
|
|
292
|
+
exit 0
|
|
293
|
+
fi
|
|
294
|
+
|
|
283
295
|
REPO_SLUG=$(echo "${REPO}" | tr '/' '-')
|
|
284
296
|
CODE_REVIEW_ID=$(aws ssm get-parameter --name "/prism/continuum/code-review-id/${REPO_SLUG}" --query Parameter.Value --output text 2>/dev/null || echo "")
|
|
285
297
|
if [ -z "$CODE_REVIEW_ID" ]; then
|
|
@@ -307,10 +319,10 @@ jobs:
|
|
|
307
319
|
CODE_REVIEW_ID=$(echo "$CR_OUTPUT" | jq -r '.codeReviewId // empty' 2>/dev/null)
|
|
308
320
|
|
|
309
321
|
if [ -z "$CODE_REVIEW_ID" ]; then
|
|
310
|
-
echo "::
|
|
311
|
-
echo " Run '
|
|
312
|
-
echo "
|
|
313
|
-
echo "
|
|
322
|
+
echo "::error::Could not create the Continuum Code Review resource: ${CR_OUTPUT}"
|
|
323
|
+
echo " Run 'prism-cli securityagent setup' to create it manually."
|
|
324
|
+
echo "scan_error=true" >> "$GITHUB_OUTPUT"
|
|
325
|
+
echo "findings=-1" >> "$GITHUB_OUTPUT"
|
|
314
326
|
exit 0
|
|
315
327
|
fi
|
|
316
328
|
|
|
@@ -325,7 +337,13 @@ jobs:
|
|
|
325
337
|
# Diff must be uploaded with .patch extension (API requirement)
|
|
326
338
|
git diff "${BASE_SHA}..${HEAD_SHA}" > /tmp/changes.patch
|
|
327
339
|
if [ ! -s /tmp/changes.patch ]; then
|
|
340
|
+
echo "No changes to scan."
|
|
341
|
+
# critical/high must be set explicitly. The gate tests
|
|
342
|
+
# `critical != '0'`, and an unset output is '', which is != '0' in a
|
|
343
|
+
# GitHub expression -- leaving them unset failed the gate on a no-op PR.
|
|
328
344
|
echo "findings=0" >> "$GITHUB_OUTPUT"
|
|
345
|
+
echo "critical=0" >> "$GITHUB_OUTPUT"
|
|
346
|
+
echo "high=0" >> "$GITHUB_OUTPUT"
|
|
329
347
|
exit 0
|
|
330
348
|
fi
|
|
331
349
|
DIFF_KEY="diffs/${REPO_SLUG}/${PR_NUMBER}/${HEAD_SHA}.patch"
|
|
@@ -350,18 +368,26 @@ jobs:
|
|
|
350
368
|
done
|
|
351
369
|
|
|
352
370
|
if [ "$STATUS" != "COMPLETED" ]; then
|
|
353
|
-
echo "::
|
|
354
|
-
echo "
|
|
355
|
-
echo "
|
|
371
|
+
echo "::error::Continuum scan did not complete (status: ${STATUS})."
|
|
372
|
+
echo " An incomplete scan is not a clean scan -- failing closed. Re-run, or check the Continuum job logs."
|
|
373
|
+
echo "scan_error=true" >> "$GITHUB_OUTPUT"
|
|
374
|
+
echo "findings=-1" >> "$GITHUB_OUTPUT"
|
|
356
375
|
exit 0
|
|
357
376
|
fi
|
|
358
377
|
|
|
359
378
|
# NOTE: list-findings takes agent-space-id + code-review-job-id only.
|
|
360
379
|
# Response field is 'findingsSummaries', risk field is 'riskLevel'.
|
|
361
|
-
|
|
380
|
+
# No `|| echo '{"findingsSummaries":[]}'` fallback: an API error there is
|
|
381
|
+
# indistinguishable from a clean scan, so it would pass the gate.
|
|
382
|
+
if ! FINDINGS_JSON=$(aws securityagent list-findings \
|
|
362
383
|
--agent-space-id "${AGENT_SPACE_ID}" \
|
|
363
384
|
--code-review-job-id "${JOB_ID}" \
|
|
364
|
-
--output json 2
|
|
385
|
+
--output json 2>&1); then
|
|
386
|
+
echo "::error::Continuum scan completed but its findings could not be retrieved: ${FINDINGS_JSON}"
|
|
387
|
+
echo "scan_error=true" >> "$GITHUB_OUTPUT"
|
|
388
|
+
echo "findings=-1" >> "$GITHUB_OUTPUT"
|
|
389
|
+
exit 0
|
|
390
|
+
fi
|
|
365
391
|
|
|
366
392
|
TOTAL=$(echo "$FINDINGS_JSON" | jq '.findingsSummaries | length')
|
|
367
393
|
CRITICAL=$(echo "$FINDINGS_JSON" | jq '[.findingsSummaries[] | select(.riskLevel == "CRITICAL")] | length')
|
|
@@ -385,7 +411,7 @@ jobs:
|
|
|
385
411
|
if: steps.security-gate.outputs.skipped != 'true' && steps.security-gate.outputs.findings != '0' && steps.security-gate.outputs.findings != ''
|
|
386
412
|
continue-on-error: true
|
|
387
413
|
env:
|
|
388
|
-
AWS_REGION:
|
|
414
|
+
AWS_REGION: ${{ env.PRISM_AWS_REGION }}
|
|
389
415
|
TEAM_ID: ${{ steps.prism-config.outputs.team_id }}
|
|
390
416
|
REPO: ${{ github.repository }}
|
|
391
417
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
@@ -436,14 +462,14 @@ jobs:
|
|
|
436
462
|
if [ "$TOTAL" -gt 0 ]; then
|
|
437
463
|
# EventBridge accepts max 10 entries per call
|
|
438
464
|
for i in $(seq 0 10 $((TOTAL - 1))); do
|
|
439
|
-
aws events put-events --region
|
|
465
|
+
aws events put-events --region $PRISM_AWS_REGION \
|
|
440
466
|
--entries "$(echo "${ENTRIES}" | jq -c ".[$i:$((i+10))]")" || true
|
|
441
467
|
done
|
|
442
468
|
echo "Emitted ${TOTAL} Continuum finding events to EventBridge"
|
|
443
469
|
fi
|
|
444
470
|
|
|
445
471
|
- name: Fail on eval gate failure
|
|
446
|
-
if: ${{ steps.review.outputs.result == 'FAIL' || steps.review.outputs.result == 'ERROR' || (steps.security-gate.outputs.skipped != 'true' && (steps.security-gate.outputs.critical != '0' || steps.security-gate.outputs.high != '0')) }}
|
|
472
|
+
if: ${{ steps.review.outputs.result == 'FAIL' || steps.review.outputs.result == 'ERROR' || steps.security-gate.outputs.scan_error == 'true' || (steps.security-gate.outputs.skipped != 'true' && (steps.security-gate.outputs.critical != '0' || steps.security-gate.outputs.high != '0')) }}
|
|
447
473
|
env:
|
|
448
474
|
REVIEW_RESULT: ${{ steps.review.outputs.result }}
|
|
449
475
|
REVIEW_HIGH: ${{ steps.review.outputs.high }}
|
|
@@ -451,6 +477,7 @@ jobs:
|
|
|
451
477
|
REVIEW_SUMMARY: ${{ steps.review.outputs.summary }}
|
|
452
478
|
SEC_CRITICAL: ${{ steps.security-gate.outputs.critical }}
|
|
453
479
|
SEC_HIGH: ${{ steps.security-gate.outputs.high }}
|
|
480
|
+
SEC_SCAN_ERROR: ${{ steps.security-gate.outputs.scan_error }}
|
|
454
481
|
run: |
|
|
455
482
|
if [ "${REVIEW_RESULT}" = "ERROR" ]; then
|
|
456
483
|
echo "❌ Eval gate failed closed: review output could not be parsed"
|
|
@@ -458,7 +485,10 @@ jobs:
|
|
|
458
485
|
echo "❌ Kiro found ${REVIEW_HIGH:-?} high-severity issues (score: ${REVIEW_SCORE:-?})"
|
|
459
486
|
echo "Summary: ${REVIEW_SUMMARY}"
|
|
460
487
|
fi
|
|
461
|
-
if [ "${
|
|
488
|
+
if [ "${SEC_SCAN_ERROR}" = "true" ]; then
|
|
489
|
+
echo "❌ Security gate failed closed: the Continuum scan produced no verdict."
|
|
490
|
+
echo " This PR has NOT been security scanned. Re-run the workflow, or check the Continuum job logs."
|
|
491
|
+
elif [ "${SEC_CRITICAL:-0}" -gt 0 ] || [ "${SEC_HIGH:-0}" -gt 0 ]; then
|
|
462
492
|
echo "❌ Continuum found ${SEC_CRITICAL:-0} critical, ${SEC_HIGH:-0} high security findings"
|
|
463
493
|
fi
|
|
464
494
|
exit 1
|
|
@@ -16,6 +16,10 @@ on:
|
|
|
16
16
|
types: [opened, synchronize, reopened]
|
|
17
17
|
branches: [main, master]
|
|
18
18
|
|
|
19
|
+
|
|
20
|
+
env:
|
|
21
|
+
PRISM_AWS_REGION: us-west-2
|
|
22
|
+
|
|
19
23
|
permissions:
|
|
20
24
|
id-token: write
|
|
21
25
|
contents: read
|
|
@@ -34,7 +38,7 @@ jobs:
|
|
|
34
38
|
uses: aws-actions/configure-aws-credentials@b47578312673ae6fa5b5096b330d9fbac3d116df # v4.2.1
|
|
35
39
|
with:
|
|
36
40
|
role-to-assume: ${{ secrets.PRISM_METRICS_ROLE_ARN }}
|
|
37
|
-
aws-region:
|
|
41
|
+
aws-region: ${{ env.PRISM_AWS_REGION }}
|
|
38
42
|
|
|
39
43
|
- name: Read team ID
|
|
40
44
|
id: prism-config
|
|
@@ -217,12 +221,12 @@ jobs:
|
|
|
217
221
|
eval: {total_files: $total_files, pass_count: $pass_count, fail_count: $fail_count,
|
|
218
222
|
result: $result, pr_number: ($pr_number | tonumber), hallucinations_caught: $hallucinations}
|
|
219
223
|
}')
|
|
220
|
-
aws events put-events --region
|
|
224
|
+
aws events put-events --region $PRISM_AWS_REGION --entries "[{
|
|
221
225
|
\"Source\":\"prism.d1.velocity\",\"DetailType\":\"prism.d1.eval\",
|
|
222
226
|
\"EventBusName\":\"prism-d1-metrics\",
|
|
223
227
|
\"Detail\":$(echo "${EVAL_EVENT}" | jq -c '.' | jq -Rs '.')}]"
|
|
224
228
|
|
|
225
|
-
- name: Upgrade AWS CLI v2 (securityagent code review requires v2.
|
|
229
|
+
- name: Upgrade AWS CLI v2 (securityagent code review requires v2.36+)
|
|
226
230
|
run: |
|
|
227
231
|
curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip
|
|
228
232
|
curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip.sha256" -o /tmp/awscliv2.zip.sha256
|
|
@@ -238,16 +242,8 @@ jobs:
|
|
|
238
242
|
- name: AWS Continuum diff scan
|
|
239
243
|
id: security-gate
|
|
240
244
|
env:
|
|
241
|
-
AWS_REGION:
|
|
245
|
+
AWS_REGION: ${{ env.PRISM_AWS_REGION }}
|
|
242
246
|
run: |
|
|
243
|
-
# Pre-flight: verify AWS CLI has securityagent subcommand
|
|
244
|
-
if ! aws securityagent start-code-review-job help > /dev/null 2>&1; then
|
|
245
|
-
echo "::warning::AWS CLI does not support 'securityagent code review' commands after upgrade. Skipping scan."
|
|
246
|
-
echo "findings=0" >> "$GITHUB_OUTPUT"
|
|
247
|
-
echo "skipped=true" >> "$GITHUB_OUTPUT"
|
|
248
|
-
exit 0
|
|
249
|
-
fi
|
|
250
|
-
|
|
251
247
|
echo "::group::Retrieve Continuum config from SSM"
|
|
252
248
|
AGENT_SPACE_ID=$(aws ssm get-parameter --name /prism/continuum/agent-space-id --query Parameter.Value --output text 2>/dev/null || echo "")
|
|
253
249
|
SCAN_BUCKET=$(aws ssm get-parameter --name /prism/continuum/scan-bucket --query Parameter.Value --output text 2>/dev/null || echo "")
|
|
@@ -262,6 +258,19 @@ jobs:
|
|
|
262
258
|
exit 0
|
|
263
259
|
fi
|
|
264
260
|
|
|
261
|
+
# Continuum IS configured from here on, so every failure below fails
|
|
262
|
+
# closed. The CLI probe runs after the config lookup on purpose: before
|
|
263
|
+
# it, "never deployed" and "deployed but cannot scan" are
|
|
264
|
+
# indistinguishable, and the old order silently passed the gate on a repo
|
|
265
|
+
# that had opted into scanning.
|
|
266
|
+
if ! aws securityagent start-code-review-job help > /dev/null 2>&1; then
|
|
267
|
+
echo "::error::Continuum is configured but this AWS CLI lacks the securityagent code review commands (need v2.36+)."
|
|
268
|
+
echo " The security scan cannot run, so this PR is not verified."
|
|
269
|
+
echo "scan_error=true" >> "$GITHUB_OUTPUT"
|
|
270
|
+
echo "findings=-1" >> "$GITHUB_OUTPUT"
|
|
271
|
+
exit 0
|
|
272
|
+
fi
|
|
273
|
+
|
|
265
274
|
# Fallback for service role if SSM param not yet deployed
|
|
266
275
|
if [[ -z "$SERVICE_ROLE" ]]; then
|
|
267
276
|
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
|
|
@@ -296,10 +305,9 @@ jobs:
|
|
|
296
305
|
if [[ -z "$CODE_REVIEW_ID" ]]; then
|
|
297
306
|
echo " ⚠️ Bootstrap failed:"
|
|
298
307
|
echo " $CR_OUTPUT"
|
|
299
|
-
echo " Run '
|
|
300
|
-
echo "
|
|
301
|
-
echo "findings
|
|
302
|
-
echo "skipped=true" >> "$GITHUB_OUTPUT"
|
|
308
|
+
echo " Run 'prism-cli securityagent setup' to create it manually."
|
|
309
|
+
echo "scan_error=true" >> "$GITHUB_OUTPUT"
|
|
310
|
+
echo "findings=-1" >> "$GITHUB_OUTPUT"
|
|
303
311
|
exit 0
|
|
304
312
|
fi
|
|
305
313
|
|
|
@@ -377,10 +385,18 @@ jobs:
|
|
|
377
385
|
fi
|
|
378
386
|
|
|
379
387
|
echo "::group::Retrieve findings"
|
|
380
|
-
|
|
388
|
+
# No empty-array fallback: an API error there is indistinguishable from a
|
|
389
|
+
# clean scan, so it would pass the gate.
|
|
390
|
+
if ! FINDINGS_JSON=$(aws securityagent list-findings \
|
|
381
391
|
--agent-space-id "${AGENT_SPACE_ID}" \
|
|
382
392
|
--code-review-job-id "${JOB_ID}" \
|
|
383
|
-
--output json 2
|
|
393
|
+
--output json 2>&1); then
|
|
394
|
+
echo "::error::Continuum scan completed but its findings could not be retrieved: ${FINDINGS_JSON}"
|
|
395
|
+
echo "scan_error=true" >> "$GITHUB_OUTPUT"
|
|
396
|
+
echo "findings=-1" >> "$GITHUB_OUTPUT"
|
|
397
|
+
echo "::endgroup::"
|
|
398
|
+
exit 0
|
|
399
|
+
fi
|
|
384
400
|
|
|
385
401
|
TOTAL=$(echo "$FINDINGS_JSON" | jq '.findingsSummaries | length')
|
|
386
402
|
CRITICAL=$(echo "$FINDINGS_JSON" | jq '[.findingsSummaries[] | select(.riskLevel == "CRITICAL")] | length')
|
|
@@ -402,7 +418,7 @@ jobs:
|
|
|
402
418
|
- name: Emit Continuum findings to EventBridge
|
|
403
419
|
if: steps.security-gate.outputs.findings != '0' && steps.security-gate.outputs.skipped != 'true'
|
|
404
420
|
env:
|
|
405
|
-
AWS_REGION:
|
|
421
|
+
AWS_REGION: ${{ env.PRISM_AWS_REGION }}
|
|
406
422
|
run: |
|
|
407
423
|
PR_NUMBER="${{ github.event.pull_request.number }}"
|
|
408
424
|
REPO="${{ github.repository }}"
|
|
@@ -441,7 +457,7 @@ jobs:
|
|
|
441
457
|
fi
|
|
442
458
|
|
|
443
459
|
- name: Fail on eval or security issues
|
|
444
|
-
if: steps.eval.outputs.overall_result == 'FAIL' || (steps.security-gate.outputs.findings != '0' && steps.security-gate.outputs.skipped != 'true')
|
|
460
|
+
if: steps.eval.outputs.overall_result == 'FAIL' || steps.security-gate.outputs.scan_error == 'true' || (steps.security-gate.outputs.findings != '0' && steps.security-gate.outputs.skipped != 'true')
|
|
445
461
|
run: |
|
|
446
462
|
[[ "${{ steps.eval.outputs.overall_result }}" == "FAIL" ]] && echo "❌ Eval gate failed"
|
|
447
463
|
if [[ "${{ steps.security-gate.outputs.scan_error }}" == "true" ]]; then
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
#
|
|
7
7
|
# Required setup:
|
|
8
8
|
# - CI/CD variable PRISM_METRICS_ROLE_ARN (IAM role with bedrock:InvokeModel + events:PutEvents)
|
|
9
|
-
# -
|
|
9
|
+
# - PRISM_AWS_REGION variable in the variables: block below controls the region
|
|
10
10
|
# - .prism/config.json committed to the repo
|
|
11
11
|
# - .prism/eval-harness/ installed (run: prism-cli bootstrapper install-eval-harness)
|
|
12
12
|
# - (Optional) Agent entry point supports --mock flag for dry-run execution
|
|
@@ -26,7 +26,8 @@ prism-agent-eval:
|
|
|
26
26
|
- "**/*agent*.py"
|
|
27
27
|
- "**/*agent*.ts"
|
|
28
28
|
variables:
|
|
29
|
-
|
|
29
|
+
PRISM_AWS_REGION: us-west-2
|
|
30
|
+
AWS_DEFAULT_REGION: $PRISM_AWS_REGION
|
|
30
31
|
before_script:
|
|
31
32
|
- apt-get update -qq && apt-get install -y -qq jq bc git curl unzip
|
|
32
33
|
- curl -s "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip
|
|
@@ -32,7 +32,8 @@ prism-ai-metrics:
|
|
|
32
32
|
rules:
|
|
33
33
|
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "push"'
|
|
34
34
|
variables:
|
|
35
|
-
|
|
35
|
+
PRISM_AWS_REGION: us-west-2
|
|
36
|
+
AWS_DEFAULT_REGION: $PRISM_AWS_REGION
|
|
36
37
|
before_script:
|
|
37
38
|
- yum install -y -q jq git
|
|
38
39
|
- git fetch --unshallow || true
|
|
@@ -74,8 +75,15 @@ prism-ai-metrics:
|
|
|
74
75
|
HEAD_SHA="${CI_COMMIT_SHA}"
|
|
75
76
|
|
|
76
77
|
# --- Commit SHAs: immutable facts for deferred origin resolution.
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
#
|
|
79
|
+
# --no-merges for the same reason as the GitHub workflow: under a
|
|
80
|
+
# merge-commit strategy CI_COMMIT_SHA is the merge commit GitLab created,
|
|
81
|
+
# which no developer machine authored and codeburn can therefore never
|
|
82
|
+
# attribute. Counting it caps Attribution Coverage below 100% for a fully
|
|
83
|
+
# onboarded team. Harmless under squash or fast-forward, where the range
|
|
84
|
+
# holds no merge commits to exclude.
|
|
85
|
+
COMMIT_SHAS=$(git rev-list --no-merges "${BASE_SHA}..${HEAD_SHA}" 2>/dev/null | head -50 | jq -R . | jq -sc . || echo '[]')
|
|
86
|
+
TOTAL_COMMITS=$(git rev-list --no-merges --count "${BASE_SHA}..${HEAD_SHA}" 2>/dev/null || echo "1")
|
|
79
87
|
|
|
80
88
|
# --- Lead time: MR open → now (this job runs on the post-merge push).
|
|
81
89
|
LEAD_TIME_SECONDS=0
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
#
|
|
7
7
|
# Required setup:
|
|
8
8
|
# - CI/CD variable PRISM_METRICS_ROLE_ARN (IAM role with bedrock:InvokeModel + events:PutEvents)
|
|
9
|
-
# -
|
|
9
|
+
# - PRISM_AWS_REGION variable in the variables: block below controls the region
|
|
10
10
|
# - .prism/eval-harness/ directory in your repo (run: prism-cli bootstrapper install-eval-harness)
|
|
11
11
|
# - At least one rubric JSON in .prism/eval-harness/rubrics/
|
|
12
12
|
|
|
@@ -21,7 +21,8 @@ prism-eval-gate:
|
|
|
21
21
|
rules:
|
|
22
22
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
23
23
|
variables:
|
|
24
|
-
|
|
24
|
+
PRISM_AWS_REGION: us-west-2
|
|
25
|
+
AWS_DEFAULT_REGION: $PRISM_AWS_REGION
|
|
25
26
|
before_script:
|
|
26
27
|
- yum install -y -q jq bc git
|
|
27
28
|
- git fetch --unshallow || true
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DynamoDBClient,
|
|
3
|
-
|
|
3
|
+
GetItemCommand,
|
|
4
4
|
ScanCommand,
|
|
5
5
|
} from '@aws-sdk/client-dynamodb';
|
|
6
6
|
import {
|
|
@@ -14,6 +14,9 @@ const eventBridgeClient = new EventBridgeClient({});
|
|
|
14
14
|
const EVENTS_TABLE = process.env.EVENTS_TABLE!;
|
|
15
15
|
const METADATA_TABLE = process.env.METADATA_TABLE!;
|
|
16
16
|
const EVENT_BUS_NAME = process.env.EVENT_BUS_NAME!;
|
|
17
|
+
// Optional: unset when the OTEL collector is skipped, in which case no
|
|
18
|
+
// attribution exists to join against and AI origin stays 'unknown'.
|
|
19
|
+
const AI_USAGE_TABLE = process.env.AI_USAGE_TABLE;
|
|
17
20
|
|
|
18
21
|
interface SecurityAgentPayload {
|
|
19
22
|
findings: Array<{
|
|
@@ -177,55 +180,85 @@ async function lookupTeamId(repo: string): Promise<string> {
|
|
|
177
180
|
}
|
|
178
181
|
}
|
|
179
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Candidate `REPO#` partition keys for one repository name.
|
|
185
|
+
*
|
|
186
|
+
* The two sides of this join spell the repo differently and nothing normalizes
|
|
187
|
+
* either. codeburn sets the `git.repo` span attribute from the git remote, so
|
|
188
|
+
* the attribution store holds a host-qualified path (`github.com/owner/name`).
|
|
189
|
+
* CI sends `GITHUB_REPOSITORY`, which is bare (`owner/name`). A single GetItem
|
|
190
|
+
* on whichever form the caller happened to pass would miss roughly half the
|
|
191
|
+
* time — and miss silently, since a missing item is indistinguishable from an
|
|
192
|
+
* unattributed commit.
|
|
193
|
+
*
|
|
194
|
+
* Rather than guess, try the value as given and then the plausible
|
|
195
|
+
* translations: add a host if it looks bare, strip one if it looks qualified.
|
|
196
|
+
*/
|
|
197
|
+
function repoKeyCandidates(repo: string): string[] {
|
|
198
|
+
const bare = repo.replace(/^https?:\/\//, '').replace(/\.git$/, '').replace(/\/+$/, '');
|
|
199
|
+
const candidates = [bare];
|
|
200
|
+
const segments = bare.split('/');
|
|
201
|
+
if (segments.length === 2) {
|
|
202
|
+
// owner/name → try the hosts codeburn would have recorded.
|
|
203
|
+
candidates.push(`github.com/${bare}`, `gitlab.com/${bare}`);
|
|
204
|
+
} else if (segments.length > 2) {
|
|
205
|
+
// host/owner/name → try dropping the host.
|
|
206
|
+
candidates.push(segments.slice(1).join('/'));
|
|
207
|
+
}
|
|
208
|
+
return [...new Set(candidates)];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Resolves whether the code a finding lands on was AI-written.
|
|
213
|
+
*
|
|
214
|
+
* Reads the attribution store, which is the only place that answer now lives.
|
|
215
|
+
* This previously queried the events table for `prism.d1.commit` and counted
|
|
216
|
+
* ai vs human across the last 7 days — but nothing emits `prism.d1.commit`.
|
|
217
|
+
* The git hooks that were once expected to only ever wrote commit-message
|
|
218
|
+
* trailers, so the query never matched and every finding was tagged 'unknown',
|
|
219
|
+
* silently, with the CISO dashboard's AI-code-risk panel downstream of it.
|
|
220
|
+
*
|
|
221
|
+
* The per-commit verdict is frozen onto the COMMIT# item at ingest by
|
|
222
|
+
* otel-receiver (`ai_origin`), so a point lookup is enough — no span join, and
|
|
223
|
+
* no dependence on a span TTL that is shorter than the commit TTL.
|
|
224
|
+
*/
|
|
180
225
|
async function lookupAiOrigin(
|
|
181
226
|
teamId: string,
|
|
182
227
|
repo: string,
|
|
183
228
|
commitSha: string | null,
|
|
184
229
|
prNumber: number | null,
|
|
185
230
|
): Promise<'ai-generated' | 'ai-assisted' | 'human' | 'unknown'> {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const now = new Date();
|
|
192
|
-
const lookback = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); // 7 days
|
|
193
|
-
|
|
194
|
-
const result = await dynamoClient.send(
|
|
195
|
-
new QueryCommand({
|
|
196
|
-
TableName: EVENTS_TABLE,
|
|
197
|
-
KeyConditionExpression: 'pk = :pk AND sk >= :start',
|
|
198
|
-
FilterExpression: 'detail_type = :dt',
|
|
199
|
-
ExpressionAttributeValues: {
|
|
200
|
-
':pk': { S: pk },
|
|
201
|
-
':start': { S: lookback.toISOString() },
|
|
202
|
-
':dt': { S: 'prism.d1.commit' },
|
|
203
|
-
},
|
|
204
|
-
ScanIndexForward: false, // Most recent first
|
|
205
|
-
Limit: 20,
|
|
206
|
-
}),
|
|
207
|
-
);
|
|
231
|
+
// A PR number alone cannot identify a commit in the attribution store, whose
|
|
232
|
+
// key is REPO#<repo>/COMMIT#<sha>. Returning 'unknown' is correct here rather
|
|
233
|
+
// than guessing from the repo's recent history.
|
|
234
|
+
if (!commitSha) return 'unknown';
|
|
235
|
+
if (!AI_USAGE_TABLE) return 'unknown';
|
|
208
236
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (
|
|
223
|
-
|
|
237
|
+
for (const candidate of repoKeyCandidates(repo)) {
|
|
238
|
+
try {
|
|
239
|
+
const result = await dynamoClient.send(
|
|
240
|
+
new GetItemCommand({
|
|
241
|
+
TableName: AI_USAGE_TABLE,
|
|
242
|
+
Key: {
|
|
243
|
+
pk: { S: `REPO#${candidate}` },
|
|
244
|
+
sk: { S: `COMMIT#${commitSha}` },
|
|
245
|
+
},
|
|
246
|
+
ProjectionExpression: 'ai_origin',
|
|
247
|
+
}),
|
|
248
|
+
);
|
|
249
|
+
const origin = result.Item?.ai_origin?.S;
|
|
250
|
+
if (origin === 'ai-generated' || origin === 'human') return origin;
|
|
251
|
+
// Item exists but carries no frozen verdict (written before write-time
|
|
252
|
+
// resolution). Treat as unresolved rather than trying another key.
|
|
253
|
+
if (result.Item) return 'unknown';
|
|
254
|
+
} catch (err) {
|
|
255
|
+
console.error(`AI origin lookup failed for REPO#${candidate}:`, err);
|
|
256
|
+
return 'unknown';
|
|
224
257
|
}
|
|
225
|
-
|
|
226
|
-
return 'unknown';
|
|
227
|
-
} catch (err) {
|
|
228
|
-
console.error('Failed to look up AI origin:', err);
|
|
229
|
-
return 'unknown';
|
|
230
258
|
}
|
|
259
|
+
|
|
260
|
+
// No attribution for this commit — the author's machine is not onboarded to
|
|
261
|
+
// codeburn sync. Distinct from 'human', which is a positive verdict.
|
|
262
|
+
console.log(`No attribution found for ${repo}@${commitSha} (team ${teamId}, PR ${prNumber ?? 'n/a'})`);
|
|
263
|
+
return 'unknown';
|
|
231
264
|
}
|
|
@@ -839,6 +839,10 @@ export class MetricsPipelineStack extends cdk.Stack {
|
|
|
839
839
|
EVENTS_TABLE: this.eventsTable.tableName,
|
|
840
840
|
METADATA_TABLE: this.metadataTable.tableName,
|
|
841
841
|
EVENT_BUS_NAME: this.eventBus.eventBusName,
|
|
842
|
+
// Findings are tagged AI vs human by looking the commit up in the
|
|
843
|
+
// attribution store. Left unset when the OTEL collector is skipped,
|
|
844
|
+
// in which case there is no attribution and origin stays 'unknown'.
|
|
845
|
+
AI_USAGE_TABLE: this.aiUsageTable.tableName,
|
|
842
846
|
},
|
|
843
847
|
logRetention: logs.RetentionDays.ONE_MONTH,
|
|
844
848
|
description: 'Normalizes AWS Security Agent findings and emits to PRISM pipeline',
|
|
@@ -846,6 +850,10 @@ export class MetricsPipelineStack extends cdk.Stack {
|
|
|
846
850
|
|
|
847
851
|
this.eventsTable.grantReadData(securityAgentProcessor);
|
|
848
852
|
this.metadataTable.grantReadData(securityAgentProcessor);
|
|
853
|
+
// Read-only: the processor resolves a finding's AI origin from the frozen
|
|
854
|
+
// ai_origin on the COMMIT# item. grantReadData also grants kms:Decrypt on
|
|
855
|
+
// the table's customer-managed key, which a bare IAM action would not.
|
|
856
|
+
this.aiUsageTable.grantReadData(securityAgentProcessor);
|
|
849
857
|
this.eventBus.grantPutEventsTo(securityAgentProcessor);
|
|
850
858
|
|
|
851
859
|
// Security Remediation Tracker
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install-eval-harness.d.ts","sourceRoot":"","sources":["../../../../src/commands/bootstrapper/install-eval-harness.ts"],"names":[],"mappings":";;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"install-eval-harness.d.ts","sourceRoot":"","sources":["../../../../src/commands/bootstrapper/install-eval-harness.ts"],"names":[],"mappings":";;;;;;;;;;;iBAmEqB;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE;;AAVvI,wBAwDE"}
|