@prism-d1/cli 1.0.93 → 1.0.95
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-eval-gate-kiro.yml +311 -1
- package/dist/assets/gitlab-workflows/prism-eval-gate.yml +333 -116
- package/dist/assets/infra/lib/lambda/velocity-widget.ts +1 -1
- package/dist/src/commands/bedrock-protection/scan-account.d.ts +45 -0
- package/dist/src/commands/bedrock-protection/scan-account.d.ts.map +1 -0
- package/dist/src/commands/bedrock-protection/scan-account.js +105 -0
- package/dist/src/commands/bedrock-protection/scan-account.js.map +1 -0
- package/dist/src/commands/bedrock-protection/scan-repo.d.ts +16 -0
- package/dist/src/commands/bedrock-protection/scan-repo.d.ts.map +1 -0
- package/dist/src/commands/bedrock-protection/scan-repo.js +397 -0
- package/dist/src/commands/bedrock-protection/scan-repo.js.map +1 -0
- 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/dist/src/utils/audit-bedrock.d.ts +24 -0
- package/dist/src/utils/audit-bedrock.d.ts.map +1 -0
- package/dist/src/utils/audit-bedrock.js +331 -0
- package/dist/src/utils/audit-bedrock.js.map +1 -0
- package/dist/src/utils/audit-iam.d.ts +6 -0
- package/dist/src/utils/audit-iam.d.ts.map +1 -0
- package/dist/src/utils/audit-iam.js +239 -0
- package/dist/src/utils/audit-iam.js.map +1 -0
- package/dist/src/utils/audit.d.ts +61 -0
- package/dist/src/utils/audit.d.ts.map +1 -0
- package/dist/src/utils/audit.js +90 -0
- package/dist/src/utils/audit.js.map +1 -0
- package/dist/src/utils/aws.d.ts +38 -0
- package/dist/src/utils/aws.d.ts.map +1 -0
- package/dist/src/utils/aws.js +61 -0
- package/dist/src/utils/aws.js.map +1 -0
- package/package.json +1 -1
- package/dist/assets/github-workflows/prism-eval-gate.yml +0 -490
|
@@ -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'
|
|
@@ -491,7 +787,7 @@ jobs:
|
|
|
491
787
|
fi
|
|
492
788
|
|
|
493
789
|
- name: Fail on eval gate failure
|
|
494
|
-
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')) }}
|
|
495
791
|
env:
|
|
496
792
|
REVIEW_RESULT: ${{ steps.review.outputs.result }}
|
|
497
793
|
REVIEW_HIGH: ${{ steps.review.outputs.high }}
|
|
@@ -500,7 +796,21 @@ jobs:
|
|
|
500
796
|
SEC_CRITICAL: ${{ steps.security-gate.outputs.critical }}
|
|
501
797
|
SEC_HIGH: ${{ steps.security-gate.outputs.high }}
|
|
502
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 }}
|
|
503
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
|
|
504
814
|
if [ "${REVIEW_RESULT}" = "ERROR" ]; then
|
|
505
815
|
echo "❌ Eval gate failed closed: review output could not be parsed"
|
|
506
816
|
elif [ "${REVIEW_RESULT}" = "FAIL" ]; then
|