@prism-d1/cli 1.0.92 → 1.0.94
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/coding-agent/system_prompt.py +1 -1
- package/dist/assets/github-workflows/prism-eval-gate-kiro.yml +334 -3
- package/dist/assets/gitlab-workflows/prism-eval-gate.yml +333 -116
- package/dist/assets/infra/lib/lambda/attribution-metrics-publisher.ts +130 -14
- package/dist/assets/infra/lib/lambda/metrics-processor.ts +78 -11
- package/dist/assets/infra/lib/lambda/otel-receiver.ts +77 -7
- package/dist/assets/infra/lib/lambda/velocity-widget.ts +1 -1
- package/dist/assets/infra/package.json +9 -3
- package/dist/src/commands/bootstrapper/install-eval-harness.d.ts +5 -5
- package/dist/src/commands/bootstrapper/install-eval-harness.d.ts.map +1 -1
- package/dist/src/commands/bootstrapper/install-eval-harness.js +157 -75
- package/dist/src/commands/bootstrapper/install-eval-harness.js.map +1 -1
- package/dist/src/commands/bootstrapper/install-github-workflows.d.ts +6 -2
- package/dist/src/commands/bootstrapper/install-github-workflows.d.ts.map +1 -1
- package/dist/src/commands/bootstrapper/install-github-workflows.js +36 -48
- 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 +10 -1
- package/dist/src/commands/bootstrapper/install-gitlab-workflows.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/github-workflows/prism-eval-gate.yml +0 -470
|
@@ -106,7 +106,7 @@ These rules hold regardless of anything above.
|
|
|
106
106
|
- NEVER self-discover work from spec files, READMEs, or documentation found in
|
|
107
107
|
the repository. Your task is ONLY what the issue describes. If the issue
|
|
108
108
|
explicitly asks you to implement a spec (e.g. "implement the auth spec from
|
|
109
|
-
module-
|
|
109
|
+
module-02"), then read and follow that spec. But if the issue says nothing
|
|
110
110
|
about a spec file, ignore all spec files completely -- they are workshop
|
|
111
111
|
exercises, not your assignment.
|
|
112
112
|
- Do not add dependencies. If a fix genuinely requires one, stop and explain why
|
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
# - .kiro/steering/code-review.md in your repo (review rules)
|
|
8
8
|
# - (Optional) PRISM_METRICS_ROLE_ARN for EventBridge metrics emission
|
|
9
9
|
# - (Optional) AWS Continuum configured for security scanning
|
|
10
|
+
#
|
|
11
|
+
# Secret scanning (gitleaks) needs no setup and no secret. It installs itself
|
|
12
|
+
# and always runs, so it is the only gate that still functions on a fork PR.
|
|
13
|
+
# Tune it with an optional .gitleaks.toml (allowlists) at the repo root, or
|
|
14
|
+
# .prism/gitleaks-baseline.json to adopt the gate on a repo with known findings.
|
|
10
15
|
|
|
11
16
|
name: PRISM Eval Gate
|
|
12
17
|
|
|
@@ -38,7 +43,174 @@ jobs:
|
|
|
38
43
|
with:
|
|
39
44
|
fetch-depth: 0
|
|
40
45
|
|
|
46
|
+
# Secret scanning runs FIRST -- ahead of the Kiro CLI install, the review,
|
|
47
|
+
# and the Continuum scan.
|
|
48
|
+
#
|
|
49
|
+
# Order is load-bearing, not cosmetic. `Install Kiro CLI` pipes curl into
|
|
50
|
+
# bash with no failure guard, so a network blip or an unsupported glibc
|
|
51
|
+
# there aborts the job. Behind it, the secret scan would never run -- and
|
|
52
|
+
# the one gate that needs no secret would be the one taken out by an
|
|
53
|
+
# unrelated optional install. It also means a committed credential does not
|
|
54
|
+
# wait behind a model call to surface.
|
|
55
|
+
#
|
|
56
|
+
# It also never skips, which is deliberately unlike the two gates below.
|
|
57
|
+
# Those skip for structural reasons: fork PRs cannot receive secrets, so
|
|
58
|
+
# KIRO_API_KEY is genuinely absent, and Continuum may simply not be
|
|
59
|
+
# deployed. Neither applies here -- gitleaks needs no secret and no AWS
|
|
60
|
+
# role, and this workflow installs the binary itself, so "unavailable"
|
|
61
|
+
# is an install failure rather than a deployment choice. Every failure
|
|
62
|
+
# path below therefore fails closed. A secret scanner that reports green
|
|
63
|
+
# without having scanned is worse than no scanner, because it launders a
|
|
64
|
+
# leak into a passing check.
|
|
65
|
+
#
|
|
66
|
+
# Needing no secret is also why this is the one gate with teeth on a fork
|
|
67
|
+
# PR -- exactly where an untrusted contributor might push a credential.
|
|
68
|
+
- name: Install gitleaks
|
|
69
|
+
id: gitleaks-install
|
|
70
|
+
env:
|
|
71
|
+
# Pinned deliberately. Floating to latest would let an upstream
|
|
72
|
+
# release change what this gate blocks without a repo change.
|
|
73
|
+
GITLEAKS_VERSION: 8.28.0
|
|
74
|
+
# SHA256 of gitleaks_8.28.0_linux_x64.tar.gz, verified out of band.
|
|
75
|
+
#
|
|
76
|
+
# Hard-pinned rather than read from the release's own checksums.txt:
|
|
77
|
+
# that file ships from the same release, so it confirms nothing if the
|
|
78
|
+
# release itself is replaced. Pinning the digest here means a swapped
|
|
79
|
+
# artifact fails the gate loudly instead of installing quietly.
|
|
80
|
+
# Bumping GITLEAKS_VERSION requires updating this value too.
|
|
81
|
+
GITLEAKS_SHA256: a65b5253807a68ac0cafa4414031fd740aeb55f54fb7e55f386acb52e6a840eb
|
|
82
|
+
run: |
|
|
83
|
+
set -o pipefail
|
|
84
|
+
cd "${RUNNER_TEMP}"
|
|
85
|
+
TARBALL="gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
|
|
86
|
+
URL="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/${TARBALL}"
|
|
87
|
+
|
|
88
|
+
if ! curl -fsSL --retry 3 --retry-delay 2 -o "${TARBALL}" "${URL}"; then
|
|
89
|
+
echo "::error::Could not download gitleaks ${GITLEAKS_VERSION} -- this PR has NOT been secret-scanned."
|
|
90
|
+
echo "error=true" >> "$GITHUB_OUTPUT"
|
|
91
|
+
exit 0
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
if ! echo "${GITLEAKS_SHA256} ${TARBALL}" | sha256sum -c -; then
|
|
95
|
+
echo "::error::gitleaks checksum mismatch -- refusing to install an unverified binary."
|
|
96
|
+
echo " Expected ${GITLEAKS_SHA256}"
|
|
97
|
+
echo " If you just bumped GITLEAKS_VERSION, update GITLEAKS_SHA256 to match."
|
|
98
|
+
echo "error=true" >> "$GITHUB_OUTPUT"
|
|
99
|
+
exit 0
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
tar -xzf "${TARBALL}" gitleaks
|
|
103
|
+
install -m 0755 gitleaks /usr/local/bin/gitleaks
|
|
104
|
+
gitleaks version
|
|
105
|
+
|
|
106
|
+
- name: Scan for committed secrets (gitleaks)
|
|
107
|
+
id: gitleaks
|
|
108
|
+
if: steps.gitleaks-install.outputs.error != 'true'
|
|
109
|
+
env:
|
|
110
|
+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
111
|
+
HEAD_SHA: ${{ github.sha }}
|
|
112
|
+
run: |
|
|
113
|
+
REPORT="${RUNNER_TEMP}/gitleaks-report.json"
|
|
114
|
+
|
|
115
|
+
if [ -z "${BASE_SHA}" ] || [ -z "${HEAD_SHA}" ]; then
|
|
116
|
+
echo "::error::Missing base/head SHA -- cannot determine what to scan, failing closed."
|
|
117
|
+
echo "error=true" >> "$GITHUB_OUTPUT"
|
|
118
|
+
echo "leaks=-1" >> "$GITHUB_OUTPUT"
|
|
119
|
+
exit 0
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# Scope is the commits this PR introduces, NOT the whole history.
|
|
123
|
+
#
|
|
124
|
+
# A full-history scan on every PR re-reports every secret that predates
|
|
125
|
+
# the gate, so the gate can never go green and gets switched off within
|
|
126
|
+
# a week. Pre-existing leaks need a one-time `gitleaks git` sweep plus
|
|
127
|
+
# a credential rotation -- work an unrelated PR cannot do and should
|
|
128
|
+
# not be blocked on.
|
|
129
|
+
#
|
|
130
|
+
# --redact is load-bearing: without it gitleaks prints the matched
|
|
131
|
+
# secret into the build log, which is public on a public repo. That
|
|
132
|
+
# would turn the scanner into a second disclosure channel for the
|
|
133
|
+
# thing it just found.
|
|
134
|
+
#
|
|
135
|
+
# Smoke-testing this gate: verify your fixture with gitleaks itself
|
|
136
|
+
# before trusting a green result. A hand-written fake credential can
|
|
137
|
+
# look completely plausible to a human and still match no rule, in
|
|
138
|
+
# which case "no leaks found" is correct and the gate only appears
|
|
139
|
+
# broken. Never infer that from the rule internals -- they are version
|
|
140
|
+
# specific and change without notice. Ask the tool:
|
|
141
|
+
#
|
|
142
|
+
# gitleaks dir /path/to/fixture --redact --no-banner -v
|
|
143
|
+
#
|
|
144
|
+
# A non-zero finding count there is the only evidence the fixture is
|
|
145
|
+
# usable. Two known traps it will surface for you: gitleaks' defaults
|
|
146
|
+
# allowlist AWS's own documentation keys (AKIAIOSFODNN7EXAMPLE), and
|
|
147
|
+
# tooling that redacts credential-shaped text in transit can rewrite a
|
|
148
|
+
# key NAME out of your fixture, which disarms the rules that need a
|
|
149
|
+
# nearby keyword.
|
|
150
|
+
#
|
|
151
|
+
# A PEM block is the most robust fixture -- no charset constraints and
|
|
152
|
+
# no keyword needed -- and fires `private-key`:
|
|
153
|
+
# printf -- '-----BEGIN RSA PRIVATE KEY-----\n%s\n-----END RSA PRIVATE KEY-----\n' \
|
|
154
|
+
# "$(tr -dc 'A-Za-z0-9+/' </dev/urandom | head -c 64)" > id_rsa_test
|
|
155
|
+
ARGS=(git "${GITHUB_WORKSPACE}"
|
|
156
|
+
--log-opts="${BASE_SHA}..${HEAD_SHA}"
|
|
157
|
+
--report-format=json
|
|
158
|
+
--report-path="${REPORT}"
|
|
159
|
+
--redact
|
|
160
|
+
--exit-code=1
|
|
161
|
+
--no-banner)
|
|
162
|
+
|
|
163
|
+
# .gitleaks.toml at the repo root is picked up automatically and is
|
|
164
|
+
# where per-repo allowlists belong (test fixtures, example keys).
|
|
165
|
+
# A baseline lets a repo with known accepted findings adopt the gate
|
|
166
|
+
# without rewriting history first.
|
|
167
|
+
if [ -f .prism/gitleaks-baseline.json ]; then
|
|
168
|
+
ARGS+=(--baseline-path=.prism/gitleaks-baseline.json)
|
|
169
|
+
echo "Using baseline: .prism/gitleaks-baseline.json"
|
|
170
|
+
fi
|
|
171
|
+
if [ -f .gitleaks.toml ]; then
|
|
172
|
+
echo "Using repo config: .gitleaks.toml"
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
set +e
|
|
176
|
+
gitleaks "${ARGS[@]}"
|
|
177
|
+
RC=$?
|
|
178
|
+
set -e
|
|
179
|
+
|
|
180
|
+
case "${RC}" in
|
|
181
|
+
0)
|
|
182
|
+
echo "✅ No secrets detected in ${BASE_SHA}..${HEAD_SHA}"
|
|
183
|
+
echo "leaks=0" >> "$GITHUB_OUTPUT"
|
|
184
|
+
;;
|
|
185
|
+
1)
|
|
186
|
+
# Exit 1 means leaks were found. If the count cannot be read the
|
|
187
|
+
# scan still found something, so this must not degrade to 0.
|
|
188
|
+
if ! COUNT=$(jq 'length' "${REPORT}" 2>/dev/null) || [ -z "${COUNT}" ]; then
|
|
189
|
+
echo "::error::gitleaks reported findings but the report could not be parsed -- failing closed."
|
|
190
|
+
echo "error=true" >> "$GITHUB_OUTPUT"
|
|
191
|
+
echo "leaks=-1" >> "$GITHUB_OUTPUT"
|
|
192
|
+
exit 0
|
|
193
|
+
fi
|
|
194
|
+
echo "::error::gitleaks found ${COUNT} secret(s) in this PR's commits"
|
|
195
|
+
# Only safe fields are projected. The secret itself is never
|
|
196
|
+
# echoed, even though --redact already replaced it.
|
|
197
|
+
jq -r '.[] | "::error file=\(.File),line=\(.StartLine)::[\(.RuleID)] secret detected in commit \(.Commit[0:8])"' "${REPORT}" || true
|
|
198
|
+
echo "leaks=${COUNT}" >> "$GITHUB_OUTPUT"
|
|
199
|
+
;;
|
|
200
|
+
*)
|
|
201
|
+
echo "::error::gitleaks exited ${RC} -- the scan produced no verdict, so this PR is not verified."
|
|
202
|
+
echo "error=true" >> "$GITHUB_OUTPUT"
|
|
203
|
+
echo "leaks=-1" >> "$GITHUB_OUTPUT"
|
|
204
|
+
;;
|
|
205
|
+
esac
|
|
206
|
+
|
|
207
|
+
# Only installed when the key exists. Without it the review below reports
|
|
208
|
+
# SKIPPED without ever invoking the binary, so installing it would pipe
|
|
209
|
+
# curl into bash on every fork PR for a CLI that is never called. The
|
|
210
|
+
# review step keeps its own HAS_KIRO_KEY check -- this gate is an
|
|
211
|
+
# optimisation, that one is the correctness guarantee.
|
|
41
212
|
- name: Install Kiro CLI
|
|
213
|
+
if: env.HAS_KIRO_KEY == 'true'
|
|
42
214
|
run: curl -fsSL https://cli.kiro.dev/install | bash
|
|
43
215
|
|
|
44
216
|
- name: Read team ID
|
|
@@ -156,6 +328,9 @@ jobs:
|
|
|
156
328
|
REVIEW_MEDIUM: ${{ steps.review.outputs.medium }}
|
|
157
329
|
REVIEW_RESULT: ${{ steps.review.outputs.result }}
|
|
158
330
|
REVIEW_SUMMARY: ${{ steps.review.outputs.summary }}
|
|
331
|
+
GITLEAKS_LEAKS: ${{ steps.gitleaks.outputs.leaks }}
|
|
332
|
+
GITLEAKS_ERROR: ${{ steps.gitleaks.outputs.error }}
|
|
333
|
+
GITLEAKS_INSTALL_ERROR: ${{ steps.gitleaks-install.outputs.error }}
|
|
159
334
|
with:
|
|
160
335
|
script: |
|
|
161
336
|
const fs = require('fs');
|
|
@@ -199,6 +374,32 @@ jobs:
|
|
|
199
374
|
body += `> Powered by kiro-cli headless - Model: claude-haiku-4.5 - Threshold: 0.82`;
|
|
200
375
|
}
|
|
201
376
|
|
|
377
|
+
// Secret scanning is reported for every result path, including the
|
|
378
|
+
// ones where the Kiro review skipped. On a fork PR the review has no
|
|
379
|
+
// API key and reports SKIPPED, so without this the comment would
|
|
380
|
+
// read as "nothing was checked" when in fact the repo WAS scanned
|
|
381
|
+
// for credentials -- the one check that still ran.
|
|
382
|
+
const gl = process.env.GITLEAKS_LEAKS;
|
|
383
|
+
const glErr = process.env.GITLEAKS_ERROR === 'true';
|
|
384
|
+
const glInstallErr = process.env.GITLEAKS_INSTALL_ERROR === 'true';
|
|
385
|
+
|
|
386
|
+
let secretLine;
|
|
387
|
+
if (glInstallErr) {
|
|
388
|
+
secretLine = '❌ **Secret scan** -- gitleaks could not be installed or verified. This PR has **not** been secret-scanned; failing closed.';
|
|
389
|
+
} else if (glErr) {
|
|
390
|
+
secretLine = '❌ **Secret scan** -- gitleaks did not produce a verdict. This PR has **not** been secret-scanned; failing closed.';
|
|
391
|
+
} else if (gl === '0') {
|
|
392
|
+
secretLine = '✅ **Secret scan** -- no credentials found in this PR\'s commits.';
|
|
393
|
+
} else if (gl) {
|
|
394
|
+
secretLine = `❌ **Secret scan** -- gitleaks found **${gl}** secret(s). See the annotations on the Files tab.\n\n` +
|
|
395
|
+
'> Rotate the exposed credential first -- removing it from the branch does not un-expose it. ' +
|
|
396
|
+
'False positive? Add an allowlist entry to `.gitleaks.toml`.';
|
|
397
|
+
} else {
|
|
398
|
+
secretLine = '⚠️ **Secret scan** -- no result reported.';
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
body += `\n\n---\n\n${secretLine}`;
|
|
402
|
+
|
|
202
403
|
const { data: comments } = await github.rest.issues.listComments({
|
|
203
404
|
owner: context.repo.owner, repo: context.repo.repo,
|
|
204
405
|
issue_number: context.issue.number
|
|
@@ -257,6 +458,101 @@ jobs:
|
|
|
257
458
|
aws events put-events --region $PRISM_AWS_REGION --entries \
|
|
258
459
|
"[{\"Source\":\"prism.d1.velocity\",\"DetailType\":\"prism.d1.eval\",\"EventBusName\":\"prism-d1-metrics\",\"Detail\":$(echo "${DETAIL}" | jq -Rs '.')}]" || true
|
|
259
460
|
|
|
461
|
+
- name: Emit gitleaks findings to EventBridge
|
|
462
|
+
if: steps.aws-creds.outcome == 'success' && steps.gitleaks.outputs.leaks != '0' && steps.gitleaks.outputs.leaks != '' && steps.gitleaks.outputs.leaks != '-1'
|
|
463
|
+
continue-on-error: true
|
|
464
|
+
env:
|
|
465
|
+
AWS_REGION: ${{ env.PRISM_AWS_REGION }}
|
|
466
|
+
TEAM_ID: ${{ steps.prism-config.outputs.team_id }}
|
|
467
|
+
REPO: ${{ env.PRISM_REPO }}
|
|
468
|
+
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
469
|
+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
470
|
+
HEAD_SHA: ${{ github.sha }}
|
|
471
|
+
run: |
|
|
472
|
+
# Feeds the Team Velocity / CISO dashboards. Without this the gate
|
|
473
|
+
# blocks the PR but the exposure never reaches the metrics pipeline,
|
|
474
|
+
# so "secrets caught per 100 commits" is unanswerable.
|
|
475
|
+
#
|
|
476
|
+
# continue-on-error, and gated on findings != -1: the gate verdict is
|
|
477
|
+
# decided by the scan step, never by whether telemetry landed. A
|
|
478
|
+
# scan-error PR (findings=-1) has no findings to report.
|
|
479
|
+
REPORT="${RUNNER_TEMP}/gitleaks-report.json"
|
|
480
|
+
if [ ! -s "${REPORT}" ]; then
|
|
481
|
+
echo "No report file — nothing to emit"
|
|
482
|
+
exit 0
|
|
483
|
+
fi
|
|
484
|
+
|
|
485
|
+
# Deferred origin join, same contract as the Continuum step: emit the
|
|
486
|
+
# PR's commit SHAs as facts and let dashboards resolve AI-vs-human at
|
|
487
|
+
# render time. Attribution spans arrive on a ~12h codeburn schedule,
|
|
488
|
+
# so a verdict computed now would usually be wrong and would be baked
|
|
489
|
+
# into an immutable event.
|
|
490
|
+
COMMIT_SHAS=$(git rev-list "${BASE_SHA}..${HEAD_SHA}" 2>/dev/null | head -50 | jq -R . | jq -sc . || echo '[]')
|
|
491
|
+
|
|
492
|
+
# Severity drives real automation: prism.d1.security.code_review also
|
|
493
|
+
# routes to the security-response-automator, which on CRITICAL/HIGH
|
|
494
|
+
# writes a penalty record and fires the SecurityCriticalFinding alarm.
|
|
495
|
+
#
|
|
496
|
+
# So the mapping is deliberately fail-safe rather than uniform.
|
|
497
|
+
# gitleaks' provider-specific rules (aws-access-token, github-pat,
|
|
498
|
+
# private-key, ...) are high-confidence and page as HIGH. Its
|
|
499
|
+
# catch-all entropy rules are not: generic-api-key fired on an
|
|
500
|
+
# `aws_secret_access_key = ...` line in testing, and paging on every
|
|
501
|
+
# one of those trains people to ignore the alarm. Those report MEDIUM
|
|
502
|
+
# — still recorded, still blocking the PR via the gate, just not
|
|
503
|
+
# paging. An unrecognised NEW rule id defaults to HIGH, so the list
|
|
504
|
+
# below only ever downgrades known-noisy rules.
|
|
505
|
+
#
|
|
506
|
+
# CWE-798 = Use of Hard-coded Credentials, correct for every rule here.
|
|
507
|
+
#
|
|
508
|
+
# Fingerprint is gitleaks' own stable id (commit:file:rule:line), so
|
|
509
|
+
# re-running the workflow re-emits the same finding_id instead of
|
|
510
|
+
# inflating counts. Only safe fields are projected -- never .Secret or
|
|
511
|
+
# .Match (both already REDACTED), and never .Message, which is the
|
|
512
|
+
# commit message and could itself contain the credential.
|
|
513
|
+
ENTRIES=$(jq -c \
|
|
514
|
+
--arg team_id "${TEAM_ID}" \
|
|
515
|
+
--arg repo "${REPO}" \
|
|
516
|
+
--argjson commit_shas "${COMMIT_SHAS}" \
|
|
517
|
+
--argjson pr_number "${PR_NUMBER}" \
|
|
518
|
+
--argjson low_confidence '["generic-api-key"]' \
|
|
519
|
+
'[ .[] | . as $f | {
|
|
520
|
+
Source: "prism.d1.velocity",
|
|
521
|
+
DetailType: "prism.d1.security.code_review",
|
|
522
|
+
EventBusName: "prism-d1-metrics",
|
|
523
|
+
Detail: ({
|
|
524
|
+
team_id: $team_id,
|
|
525
|
+
repo: $repo,
|
|
526
|
+
timestamp: (now | todate),
|
|
527
|
+
prism_level: 3,
|
|
528
|
+
metric: { name: "security_finding", value: 1, unit: "count" },
|
|
529
|
+
ai_context: { tool: "gitleaks", model: "gitleaks-8.28.0" },
|
|
530
|
+
security_agent_finding: {
|
|
531
|
+
finding_id: ("gl-" + ($f.Fingerprint // "unknown")),
|
|
532
|
+
phase: "code_review",
|
|
533
|
+
severity: (if ($low_confidence | index($f.RuleID)) then "MEDIUM" else "HIGH" end),
|
|
534
|
+
category: ($f.RuleID // "unknown"),
|
|
535
|
+
title: ($f.Description // $f.RuleID // "Committed secret detected"),
|
|
536
|
+
cwe_id: "CWE-798",
|
|
537
|
+
cvss_score: null,
|
|
538
|
+
exploit_validated: false,
|
|
539
|
+
commit_shas: $commit_shas,
|
|
540
|
+
pr_number: $pr_number,
|
|
541
|
+
found_at: (now | todate)
|
|
542
|
+
}
|
|
543
|
+
} | tostring)
|
|
544
|
+
} ]' "${REPORT}" 2>/dev/null || echo '[]')
|
|
545
|
+
|
|
546
|
+
TOTAL=$(echo "${ENTRIES}" | jq 'length')
|
|
547
|
+
if [ "${TOTAL}" -gt 0 ]; then
|
|
548
|
+
# EventBridge accepts max 10 entries per call
|
|
549
|
+
for i in $(seq 0 10 $((TOTAL - 1))); do
|
|
550
|
+
aws events put-events --region "$PRISM_AWS_REGION" \
|
|
551
|
+
--entries "$(echo "${ENTRIES}" | jq -c ".[$i:$((i+10))]")" || true
|
|
552
|
+
done
|
|
553
|
+
echo "Emitted ${TOTAL} gitleaks finding events to EventBridge"
|
|
554
|
+
fi
|
|
555
|
+
|
|
260
556
|
- name: AWS Continuum security scan (Tier 2 -- deterministic)
|
|
261
557
|
id: security-gate
|
|
262
558
|
if: steps.aws-creds.outcome == 'success'
|
|
@@ -298,6 +594,21 @@ jobs:
|
|
|
298
594
|
if [ -z "$CODE_REVIEW_ID" ]; then
|
|
299
595
|
echo "No Code Review found for ${REPO_SLUG} -- bootstrapping..."
|
|
300
596
|
|
|
597
|
+
# Continuum rejects a title containing anything outside
|
|
598
|
+
# [A-Za-z0-9_-]. REPO_SLUG keeps the dot from the host component
|
|
599
|
+
# (github.com-owner-repo) because SSM parameter names and S3 keys
|
|
600
|
+
# both accept dots and those paths must stay stable. The title is
|
|
601
|
+
# the only consumer that cannot, so it gets its own sanitised form
|
|
602
|
+
# rather than reshaping the slug everywhere.
|
|
603
|
+
#
|
|
604
|
+
# This broke when CI switched to emitting fully-qualified repo names
|
|
605
|
+
# (github.com/owner/repo) so the attribution keys would match
|
|
606
|
+
# codeburn's. A bare owner/repo slug had no dot and validated fine.
|
|
607
|
+
#
|
|
608
|
+
# printf, not echo: tr -c would otherwise rewrite the trailing
|
|
609
|
+
# newline into a hyphen and leave every title ending in one.
|
|
610
|
+
CR_TITLE=$(printf '%s-ci-diff-scan' "${REPO_SLUG}" | tr -c 'A-Za-z0-9_-' '-' | cut -c1-100)
|
|
611
|
+
|
|
301
612
|
# Get service role (SSM or convention fallback)
|
|
302
613
|
SERVICE_ROLE=$(aws ssm get-parameter --name /prism/continuum/service-role-arn --query Parameter.Value --output text 2>/dev/null || echo "")
|
|
303
614
|
if [ -z "$SERVICE_ROLE" ]; then
|
|
@@ -313,11 +624,17 @@ jobs:
|
|
|
313
624
|
# Create the Code Review resource
|
|
314
625
|
CR_OUTPUT=$(aws securityagent create-code-review \
|
|
315
626
|
--agent-space-id "$AGENT_SPACE_ID" \
|
|
316
|
-
--title "$
|
|
627
|
+
--title "$CR_TITLE" \
|
|
317
628
|
--service-role "$SERVICE_ROLE" \
|
|
318
629
|
--assets "{\"sourceCode\":[{\"s3Location\":\"s3://${SCAN_BUCKET}/repo/${REPO_SLUG}/latest.zip\"}]}" \
|
|
319
630
|
--output json 2>&1) || true
|
|
320
|
-
|
|
631
|
+
# `|| echo ""` is load-bearing. CR_OUTPUT captures stderr via 2>&1,
|
|
632
|
+
# so a failed create leaves AWS's error TEXT here, not JSON. jq exits
|
|
633
|
+
# 5 on a parse error, and under bash -eo pipefail that aborts the
|
|
634
|
+
# whole step -- which made the diagnostic below unreachable and
|
|
635
|
+
# surfaced a bare "Process completed with exit code 5" instead of the
|
|
636
|
+
# actual ValidationException.
|
|
637
|
+
CODE_REVIEW_ID=$(echo "$CR_OUTPUT" | jq -r '.codeReviewId // empty' 2>/dev/null || echo "")
|
|
321
638
|
|
|
322
639
|
if [ -z "$CODE_REVIEW_ID" ]; then
|
|
323
640
|
echo "::error::Could not create the Continuum Code Review resource: ${CR_OUTPUT}"
|
|
@@ -470,7 +787,7 @@ jobs:
|
|
|
470
787
|
fi
|
|
471
788
|
|
|
472
789
|
- name: Fail on eval gate failure
|
|
473
|
-
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')) }}
|
|
790
|
+
if: ${{ steps.gitleaks-install.outputs.error == 'true' || steps.gitleaks.outputs.error == 'true' || (steps.gitleaks.outputs.leaks != '0' && steps.gitleaks.outputs.leaks != '') || 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')) }}
|
|
474
791
|
env:
|
|
475
792
|
REVIEW_RESULT: ${{ steps.review.outputs.result }}
|
|
476
793
|
REVIEW_HIGH: ${{ steps.review.outputs.high }}
|
|
@@ -479,7 +796,21 @@ jobs:
|
|
|
479
796
|
SEC_CRITICAL: ${{ steps.security-gate.outputs.critical }}
|
|
480
797
|
SEC_HIGH: ${{ steps.security-gate.outputs.high }}
|
|
481
798
|
SEC_SCAN_ERROR: ${{ steps.security-gate.outputs.scan_error }}
|
|
799
|
+
GITLEAKS_LEAKS: ${{ steps.gitleaks.outputs.leaks }}
|
|
800
|
+
GITLEAKS_ERROR: ${{ steps.gitleaks.outputs.error }}
|
|
801
|
+
GITLEAKS_INSTALL_ERROR: ${{ steps.gitleaks-install.outputs.error }}
|
|
482
802
|
run: |
|
|
803
|
+
if [ "${GITLEAKS_INSTALL_ERROR}" = "true" ]; then
|
|
804
|
+
echo "❌ Secret scan failed closed: gitleaks could not be installed or verified."
|
|
805
|
+
echo " This PR has NOT been scanned for credentials. Re-run the workflow."
|
|
806
|
+
elif [ "${GITLEAKS_ERROR}" = "true" ]; then
|
|
807
|
+
echo "❌ Secret scan failed closed: gitleaks produced no verdict."
|
|
808
|
+
echo " This PR has NOT been scanned for credentials. Re-run the workflow."
|
|
809
|
+
elif [ -n "${GITLEAKS_LEAKS}" ] && [ "${GITLEAKS_LEAKS}" != "0" ]; then
|
|
810
|
+
echo "❌ gitleaks found ${GITLEAKS_LEAKS} secret(s) in this PR's commits"
|
|
811
|
+
echo " Rotate the credential first -- deleting it from the branch does not un-expose it."
|
|
812
|
+
echo " False positive? Add an allowlist entry to .gitleaks.toml."
|
|
813
|
+
fi
|
|
483
814
|
if [ "${REVIEW_RESULT}" = "ERROR" ]; then
|
|
484
815
|
echo "❌ Eval gate failed closed: review output could not be parsed"
|
|
485
816
|
elif [ "${REVIEW_RESULT}" = "FAIL" ]; then
|