@prism-d1/cli 1.0.27 → 1.0.29
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/eval-harness/README.md +114 -0
- package/dist/assets/eval-harness/eval-config.json +10 -0
- package/dist/assets/eval-harness/rubrics/agent-quality.json +79 -0
- package/dist/assets/eval-harness/rubrics/api-response-quality.json +45 -0
- package/dist/assets/eval-harness/rubrics/code-quality.json +98 -0
- package/dist/assets/eval-harness/rubrics/security-compliance.json +145 -0
- package/dist/assets/eval-harness/rubrics/spec-compliance.json +67 -0
- package/dist/assets/eval-harness/run-eval.sh +122 -0
- package/dist/assets/github-workflows/README.md +110 -0
- package/dist/assets/github-workflows/prism-agent-eval.yml +313 -0
- package/dist/assets/github-workflows/prism-ai-metrics.yml +261 -0
- package/dist/assets/github-workflows/prism-dora-weekly.yml +334 -0
- package/dist/assets/github-workflows/prism-eval-gate.yml +310 -0
- package/dist/assets/infra/bin/app.ts +56 -0
- package/dist/assets/infra/cdk.json +12 -0
- package/dist/assets/infra/lib/api-stack.ts +347 -0
- package/dist/assets/infra/lib/constructs/bedrock-guardrail-construct.ts +201 -0
- package/dist/assets/infra/lib/constructs/guardrail-enforcer-construct.ts +59 -0
- package/dist/assets/infra/lib/constructs/prism-vpc-construct.ts +75 -0
- package/dist/assets/infra/lib/constructs/security-agent-construct.ts +266 -0
- package/dist/assets/infra/lib/dashboard-stack.ts +1392 -0
- package/dist/assets/infra/lib/lambda/api-handler.ts +477 -0
- package/dist/assets/infra/lib/lambda/defect-correlator.ts +142 -0
- package/dist/assets/infra/lib/lambda/exfiltration-detector.ts +100 -0
- package/dist/assets/infra/lib/lambda/layers/guardrail-enforcer/nodejs/guardrail-enforcer.js +53 -0
- package/dist/assets/infra/lib/lambda/metrics-processor.ts +748 -0
- package/dist/assets/infra/lib/lambda/security-agent-processor.ts +231 -0
- package/dist/assets/infra/lib/lambda/security-remediation-tracker.ts +120 -0
- package/dist/assets/infra/lib/lambda/security-response-automator.ts +130 -0
- package/dist/assets/infra/lib/lambda/spec-to-code-calculator.ts +123 -0
- package/dist/assets/infra/lib/metrics-pipeline-stack.ts +701 -0
- package/dist/assets/infra/package.json +23 -0
- package/dist/assets/infra/tsconfig.json +24 -0
- package/dist/src/commands/bootstrapper/install-eval-harness.d.ts.map +1 -1
- package/dist/src/commands/bootstrapper/install-eval-harness.js +3 -4
- package/dist/src/commands/bootstrapper/install-eval-harness.js.map +1 -1
- package/dist/src/commands/bootstrapper/install-git-hooks.d.ts.map +1 -1
- package/dist/src/commands/bootstrapper/install-git-hooks.js +2 -5
- package/dist/src/commands/bootstrapper/install-git-hooks.js.map +1 -1
- package/dist/src/commands/securityagent/setup.d.ts.map +1 -1
- package/dist/src/commands/securityagent/setup.js +2 -3
- package/dist/src/commands/securityagent/setup.js.map +1 -1
- package/dist/src/commands/workshop/deploy-infra.d.ts.map +1 -1
- package/dist/src/commands/workshop/deploy-infra.js +2 -3
- package/dist/src/commands/workshop/deploy-infra.js.map +1 -1
- package/dist/src/commands/workshop/generate-demo-data.d.ts.map +1 -1
- package/dist/src/commands/workshop/generate-demo-data.js +3 -8
- package/dist/src/commands/workshop/generate-demo-data.js.map +1 -1
- package/dist/src/commands/workshop/perform-pen-test.d.ts.map +1 -1
- package/dist/src/commands/workshop/perform-pen-test.js +5 -14
- package/dist/src/commands/workshop/perform-pen-test.js.map +1 -1
- package/dist/src/utils/root.d.ts +6 -0
- package/dist/src/utils/root.d.ts.map +1 -1
- package/dist/src/utils/root.js +29 -0
- package/dist/src/utils/root.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# prism-eval-gate.yml — Evaluate AI-generated code quality on PRs.
|
|
2
|
+
#
|
|
3
|
+
# Copy this file to .github/workflows/prism-eval-gate.yml in your repo.
|
|
4
|
+
#
|
|
5
|
+
# Required setup:
|
|
6
|
+
# - OIDC provider configured for GitHub Actions in your AWS account
|
|
7
|
+
# - IAM role with bedrock:InvokeModel and events:PutEvents permissions
|
|
8
|
+
# - Repository secret PRISM_METRICS_ROLE_ARN set to the OIDC role ARN
|
|
9
|
+
# - .prism/eval-harness/ directory in your repo (run: bash prism-cli bootstrapper install-eval-harness)
|
|
10
|
+
# - At least one rubric JSON in .prism/eval-harness/rubrics/
|
|
11
|
+
|
|
12
|
+
name: PRISM Eval Gate
|
|
13
|
+
|
|
14
|
+
on:
|
|
15
|
+
pull_request:
|
|
16
|
+
types: [opened, synchronize, reopened]
|
|
17
|
+
branches: [main, master]
|
|
18
|
+
|
|
19
|
+
permissions:
|
|
20
|
+
id-token: write
|
|
21
|
+
contents: read
|
|
22
|
+
pull-requests: write
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
eval-gate:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
steps:
|
|
28
|
+
- name: Checkout
|
|
29
|
+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
30
|
+
with:
|
|
31
|
+
fetch-depth: 0
|
|
32
|
+
|
|
33
|
+
- name: Configure AWS credentials (OIDC)
|
|
34
|
+
uses: aws-actions/configure-aws-credentials@b47578312673ae6fa5b5096b330d9fbac3d116df # v4.2.1
|
|
35
|
+
with:
|
|
36
|
+
role-to-assume: ${{ secrets.PRISM_METRICS_ROLE_ARN }}
|
|
37
|
+
aws-region: us-west-2
|
|
38
|
+
|
|
39
|
+
- name: Read team ID
|
|
40
|
+
id: prism-config
|
|
41
|
+
run: |
|
|
42
|
+
TEAM_ID=$(jq -r '.team_id // "no_team"' .prism/config.json 2>/dev/null || echo "no_team")
|
|
43
|
+
echo "team_id=${TEAM_ID}" >> "$GITHUB_OUTPUT"
|
|
44
|
+
|
|
45
|
+
- name: Install dependencies
|
|
46
|
+
run: sudo apt-get update -qq && sudo apt-get install -y -qq jq bc
|
|
47
|
+
|
|
48
|
+
- name: Identify AI-generated changes
|
|
49
|
+
id: detect-ai
|
|
50
|
+
run: |
|
|
51
|
+
BASE_SHA="${{ github.event.pull_request.base.sha }}"
|
|
52
|
+
HEAD_SHA="${{ github.sha }}"
|
|
53
|
+
|
|
54
|
+
AI_SHAS=""
|
|
55
|
+
for SHA in $(git rev-list "${BASE_SHA}..${HEAD_SHA}"); do
|
|
56
|
+
if git log -1 --format='%B' "${SHA}" | grep -q 'AI-Origin: ai-generated\|AI-Origin: ai-assisted'; then
|
|
57
|
+
AI_SHAS="${AI_SHAS} ${SHA}"
|
|
58
|
+
fi
|
|
59
|
+
done
|
|
60
|
+
AI_SHAS=$(echo "${AI_SHAS}" | xargs)
|
|
61
|
+
|
|
62
|
+
if [[ -z "${AI_SHAS}" ]]; then
|
|
63
|
+
echo "has_ai_changes=false" >> "$GITHUB_OUTPUT"
|
|
64
|
+
exit 0
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
echo "has_ai_changes=true" >> "$GITHUB_OUTPUT"
|
|
68
|
+
echo "ai_shas=${AI_SHAS}" >> "$GITHUB_OUTPUT"
|
|
69
|
+
|
|
70
|
+
# Collect changed source files from AI commits
|
|
71
|
+
CHANGED_FILES=""
|
|
72
|
+
for SHA in ${AI_SHAS}; do
|
|
73
|
+
FILES=$(git diff-tree --no-commit-id --name-only -r "${SHA}" \
|
|
74
|
+
| grep -E '\.(ts|js|py|go|java|rs|tsx|jsx)$' | grep -v '\.test\.\|\.spec\.' || true)
|
|
75
|
+
CHANGED_FILES="${CHANGED_FILES}\n${FILES}"
|
|
76
|
+
done
|
|
77
|
+
|
|
78
|
+
echo -e "${CHANGED_FILES}" | sort -u | grep -v '^$' > /tmp/ai-changed-files.txt || true
|
|
79
|
+
FILE_COUNT=$(wc -l < /tmp/ai-changed-files.txt | tr -d ' ')
|
|
80
|
+
echo "file_count=${FILE_COUNT}" >> "$GITHUB_OUTPUT"
|
|
81
|
+
|
|
82
|
+
- name: Run evaluations
|
|
83
|
+
id: eval
|
|
84
|
+
if: steps.detect-ai.outputs.has_ai_changes == 'true'
|
|
85
|
+
run: |
|
|
86
|
+
set -o pipefail
|
|
87
|
+
chmod +x .prism/eval-harness/run-eval.sh
|
|
88
|
+
|
|
89
|
+
# Find spec from commit trailers
|
|
90
|
+
BASE_SHA="${{ github.event.pull_request.base.sha }}"
|
|
91
|
+
HEAD_SHA="${{ github.sha }}"
|
|
92
|
+
SPEC_REF=$(git log "${BASE_SHA}..${HEAD_SHA}" --format='%(trailers:key=Spec,valueonly)' | head -1 | tr -d '[:space:]' || true)
|
|
93
|
+
SPEC_FLAG=""
|
|
94
|
+
[[ -n "${SPEC_REF}" && -f "${SPEC_REF}" ]] && SPEC_FLAG="--spec ${SPEC_REF}"
|
|
95
|
+
|
|
96
|
+
PASS_COUNT=0
|
|
97
|
+
FAIL_COUNT=0
|
|
98
|
+
TOTAL_SCORE=0
|
|
99
|
+
TOTAL_HALLUCINATIONS=0
|
|
100
|
+
RESULTS=""
|
|
101
|
+
|
|
102
|
+
while IFS= read -r FILE; do
|
|
103
|
+
[[ -z "${FILE}" || ! -f "${FILE}" ]] && continue
|
|
104
|
+
|
|
105
|
+
# Auto-select rubric based on file path
|
|
106
|
+
if echo "${FILE}" | grep -qiE '(agent|assistant|orchestrat|workflow|chain)'; then
|
|
107
|
+
RUBRIC=".prism/eval-harness/rubrics/agent-quality.json"
|
|
108
|
+
elif echo "${FILE}" | grep -qiE '(auth|security|guard|policy|iam|permission|crypto)'; then
|
|
109
|
+
RUBRIC=".prism/eval-harness/rubrics/security-compliance.json"
|
|
110
|
+
elif echo "${FILE}" | grep -qiE '(api|handler|route|controller)'; then
|
|
111
|
+
RUBRIC=".prism/eval-harness/rubrics/api-response-quality.json"
|
|
112
|
+
else
|
|
113
|
+
RUBRIC=".prism/eval-harness/rubrics/code-quality.json"
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# Fall back to first available rubric if selected doesn't exist
|
|
117
|
+
[[ -f "${RUBRIC}" ]] || RUBRIC=$(ls .prism/eval-harness/rubrics/*.json 2>/dev/null | head -1)
|
|
118
|
+
[[ -f "${RUBRIC}" ]] || { echo "No rubrics found"; exit 1; }
|
|
119
|
+
|
|
120
|
+
echo "Evaluating: ${FILE} (rubric: $(basename "${RUBRIC}" .json))"
|
|
121
|
+
|
|
122
|
+
set +e
|
|
123
|
+
OUTPUT=$(./.prism/eval-harness/run-eval.sh "${RUBRIC}" "${FILE}" ${SPEC_FLAG} 2>&1)
|
|
124
|
+
EXIT_CODE=$?
|
|
125
|
+
set -e
|
|
126
|
+
|
|
127
|
+
SCORE=$(echo "${OUTPUT}" | grep '^Score:' | awk '{print $2}' || echo "0")
|
|
128
|
+
RESULT=$(echo "${OUTPUT}" | grep '^Result:' | awk '{print $2}' || echo "UNKNOWN")
|
|
129
|
+
HALLUC=$(echo "${OUTPUT}" | grep '^Hallucinations:' | awk '{print $2}' || echo "0")
|
|
130
|
+
|
|
131
|
+
TOTAL_SCORE=$(echo "${TOTAL_SCORE} + ${SCORE}" | bc -l)
|
|
132
|
+
TOTAL_HALLUCINATIONS=$((TOTAL_HALLUCINATIONS + HALLUC))
|
|
133
|
+
|
|
134
|
+
if [[ "${RESULT}" == "SKIP" ]]; then
|
|
135
|
+
continue
|
|
136
|
+
elif [[ "${RESULT}" == "PASS" ]]; then
|
|
137
|
+
PASS_COUNT=$((PASS_COUNT + 1))
|
|
138
|
+
else
|
|
139
|
+
FAIL_COUNT=$((FAIL_COUNT + 1))
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
RESULTS="${RESULTS}\n| \`${FILE}\` | $(basename "${RUBRIC}" .json) | ${SCORE} | ${RESULT} |"
|
|
143
|
+
done < /tmp/ai-changed-files.txt
|
|
144
|
+
|
|
145
|
+
TOTAL_FILES=$((PASS_COUNT + FAIL_COUNT))
|
|
146
|
+
AVG_SCORE="0"
|
|
147
|
+
[[ "${TOTAL_FILES}" -gt 0 ]] && AVG_SCORE=$(echo "scale=4; ${TOTAL_SCORE} / ${TOTAL_FILES}" | bc -l)
|
|
148
|
+
|
|
149
|
+
echo "pass_count=${PASS_COUNT}" >> "$GITHUB_OUTPUT"
|
|
150
|
+
echo "fail_count=${FAIL_COUNT}" >> "$GITHUB_OUTPUT"
|
|
151
|
+
echo "avg_score=${AVG_SCORE}" >> "$GITHUB_OUTPUT"
|
|
152
|
+
echo "total_files=${TOTAL_FILES}" >> "$GITHUB_OUTPUT"
|
|
153
|
+
echo "hallucinations=${TOTAL_HALLUCINATIONS}" >> "$GITHUB_OUTPUT"
|
|
154
|
+
echo -e "${RESULTS}" > /tmp/eval-results-table.txt
|
|
155
|
+
|
|
156
|
+
[[ "${FAIL_COUNT}" -gt 0 ]] && echo "overall_result=FAIL" >> "$GITHUB_OUTPUT" || echo "overall_result=PASS" >> "$GITHUB_OUTPUT"
|
|
157
|
+
|
|
158
|
+
- name: Post PR comment
|
|
159
|
+
if: always()
|
|
160
|
+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
|
161
|
+
with:
|
|
162
|
+
script: |
|
|
163
|
+
const fs = require('fs');
|
|
164
|
+
const hasAI = '${{ steps.detect-ai.outputs.has_ai_changes }}' === 'true';
|
|
165
|
+
|
|
166
|
+
let body;
|
|
167
|
+
if (!hasAI) {
|
|
168
|
+
body = ':white_check_mark: **PRISM Eval Gate** — No AI-generated code detected. Skipped.';
|
|
169
|
+
} else {
|
|
170
|
+
const avg = '${{ steps.eval.outputs.avg_score }}';
|
|
171
|
+
const pass = '${{ steps.eval.outputs.pass_count }}';
|
|
172
|
+
const fail = '${{ steps.eval.outputs.fail_count }}';
|
|
173
|
+
const total = '${{ steps.eval.outputs.total_files }}';
|
|
174
|
+
const result = '${{ steps.eval.outputs.overall_result }}';
|
|
175
|
+
const halluc = '${{ steps.eval.outputs.hallucinations }}';
|
|
176
|
+
const emoji = result === 'PASS' ? ':white_check_mark:' : ':x:';
|
|
177
|
+
const table = fs.existsSync('/tmp/eval-results-table.txt')
|
|
178
|
+
? fs.readFileSync('/tmp/eval-results-table.txt', 'utf8').trim() : '';
|
|
179
|
+
|
|
180
|
+
body = `## ${emoji} PRISM Eval Gate — ${result}\n\n` +
|
|
181
|
+
`**Score**: ${avg} | **Files**: ${total} | **Pass**: ${pass} | **Fail**: ${fail} | **Hallucinations**: ${halluc}\n\n` +
|
|
182
|
+
`| File | Rubric | Score | Result |\n|---|---|---|---|\n${table}\n\n` +
|
|
183
|
+
`> Threshold: 0.82 | Model: claude-sonnet-4-20250514`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const { data: comments } = await github.rest.issues.listComments({
|
|
187
|
+
owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number
|
|
188
|
+
});
|
|
189
|
+
const existing = comments.find(c => c.body.includes('PRISM Eval Gate'));
|
|
190
|
+
const params = { owner: context.repo.owner, repo: context.repo.repo, body };
|
|
191
|
+
|
|
192
|
+
if (existing) {
|
|
193
|
+
await github.rest.issues.updateComment({ ...params, comment_id: existing.id });
|
|
194
|
+
} else {
|
|
195
|
+
await github.rest.issues.createComment({ ...params, issue_number: context.issue.number });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
- name: Emit prism.d1.eval event
|
|
199
|
+
if: steps.detect-ai.outputs.has_ai_changes == 'true'
|
|
200
|
+
run: |
|
|
201
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
202
|
+
EVAL_EVENT=$(jq -n \
|
|
203
|
+
--arg team_id "${{ steps.prism-config.outputs.team_id }}" \
|
|
204
|
+
--arg repo "${{ github.repository }}" \
|
|
205
|
+
--arg timestamp "${TIMESTAMP}" \
|
|
206
|
+
--argjson score "${{ steps.eval.outputs.avg_score }}" \
|
|
207
|
+
--arg result "${{ steps.eval.outputs.overall_result }}" \
|
|
208
|
+
--argjson total_files "${{ steps.eval.outputs.total_files }}" \
|
|
209
|
+
--argjson pass_count "${{ steps.eval.outputs.pass_count }}" \
|
|
210
|
+
--argjson fail_count "${{ steps.eval.outputs.fail_count }}" \
|
|
211
|
+
--argjson hallucinations "${{ steps.eval.outputs.hallucinations }}" \
|
|
212
|
+
--arg pr_number "${{ github.event.pull_request.number }}" \
|
|
213
|
+
'{
|
|
214
|
+
team_id: $team_id, repo: $repo, timestamp: $timestamp, prism_level: 2,
|
|
215
|
+
metric: {name: "eval_score", value: $score, unit: "score"},
|
|
216
|
+
ai_context: {tool: "bedrock-eval", model: "anthropic.claude-sonnet-4-20250514", origin: "ai-generated"},
|
|
217
|
+
eval: {total_files: $total_files, pass_count: $pass_count, fail_count: $fail_count,
|
|
218
|
+
result: $result, pr_number: ($pr_number | tonumber), hallucinations_caught: $hallucinations}
|
|
219
|
+
}')
|
|
220
|
+
aws events put-events --region us-west-2 --entries "[{
|
|
221
|
+
\"Source\":\"prism.d1.velocity\",\"DetailType\":\"prism.d1.eval\",
|
|
222
|
+
\"EventBusName\":\"prism-d1-metrics\",
|
|
223
|
+
\"Detail\":$(echo "${EVAL_EVENT}" | jq -c '.' | jq -Rs '.')}]"
|
|
224
|
+
|
|
225
|
+
- name: Wait for Security Agent
|
|
226
|
+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
|
227
|
+
id: security-gate
|
|
228
|
+
with:
|
|
229
|
+
script: |
|
|
230
|
+
const pr = context.issue.number;
|
|
231
|
+
const opts = { owner: context.repo.owner, repo: context.repo.repo };
|
|
232
|
+
|
|
233
|
+
// Phase 1: Wait for Security Agent to start (2 min)
|
|
234
|
+
let reviewingComment = null;
|
|
235
|
+
for (let i = 0; i < 12; i++) {
|
|
236
|
+
const { data: comments } = await github.rest.issues.listComments({ ...opts, issue_number: pr });
|
|
237
|
+
reviewingComment = comments.find(c => c.user.login === 'aws-security-agent[bot]' && c.body.includes('reviewing your pull request'));
|
|
238
|
+
if (reviewingComment) break;
|
|
239
|
+
await new Promise(r => setTimeout(r, 10000));
|
|
240
|
+
}
|
|
241
|
+
if (!reviewingComment) { core.setOutput('findings', '0'); return; }
|
|
242
|
+
|
|
243
|
+
// Phase 2: Wait for completion (10 min) — look after the "reviewing" comment
|
|
244
|
+
const since = reviewingComment.created_at;
|
|
245
|
+
for (let i = 0; i < 40; i++) {
|
|
246
|
+
// Check for completed review submission (summary appears as review body, not issue comment)
|
|
247
|
+
const { data: reviews } = await github.rest.pulls.listReviews({ ...opts, pull_number: pr });
|
|
248
|
+
const botReview = reviews.find(r => r.user.login === 'aws-security-agent[bot]' && new Date(r.submitted_at) >= new Date(since));
|
|
249
|
+
// Check for inline review comments
|
|
250
|
+
const { data: reviewComments } = await github.rest.pulls.listReviewComments({ ...opts, pull_number: pr });
|
|
251
|
+
const findings = reviewComments.filter(c => c.user.login === 'aws-security-agent[bot]' && new Date(c.created_at) >= new Date(since));
|
|
252
|
+
if (botReview || findings.length > 0) {
|
|
253
|
+
core.setOutput('findings', String(findings.length));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
await new Promise(r => setTimeout(r, 15000));
|
|
257
|
+
}
|
|
258
|
+
core.setOutput('findings', '0');
|
|
259
|
+
|
|
260
|
+
- name: Forward Security Agent findings
|
|
261
|
+
if: steps.security-gate.outputs.findings != '0'
|
|
262
|
+
env:
|
|
263
|
+
GH_TOKEN: ${{ github.token }}
|
|
264
|
+
run: |
|
|
265
|
+
PR_NUMBER="${{ github.event.pull_request.number }}"
|
|
266
|
+
REPO="${{ github.repository }}"
|
|
267
|
+
TEAM_ID="${{ steps.prism-config.outputs.team_id }}"
|
|
268
|
+
BASE_SHA="${{ github.event.pull_request.base.sha }}"
|
|
269
|
+
HEAD_SHA="${{ github.sha }}"
|
|
270
|
+
|
|
271
|
+
AI_ORIGIN="human"
|
|
272
|
+
for SHA in $(git rev-list "${BASE_SHA}..${HEAD_SHA}"); do
|
|
273
|
+
if git log -1 --format='%B' "${SHA}" | grep -q 'AI-Origin: ai-generated\|AI-Origin: ai-assisted'; then
|
|
274
|
+
AI_ORIGIN="ai-assisted"; break
|
|
275
|
+
fi
|
|
276
|
+
done
|
|
277
|
+
|
|
278
|
+
REVIEW_COMMENTS=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}/comments" --paginate 2>/dev/null || echo "[]")
|
|
279
|
+
|
|
280
|
+
ENTRIES=$(echo "${REVIEW_COMMENTS}" | jq --arg team_id "${TEAM_ID}" --arg repo "${REPO}" \
|
|
281
|
+
--arg ai_origin "${AI_ORIGIN}" --argjson pr_number "${PR_NUMBER}" '
|
|
282
|
+
def cwe_severity:
|
|
283
|
+
{"89":"CRITICAL","78":"CRITICAL","94":"CRITICAL","502":"CRITICAL","798":"CRITICAL",
|
|
284
|
+
"79":"HIGH","22":"HIGH","918":"HIGH","352":"HIGH","287":"HIGH","862":"HIGH",
|
|
285
|
+
"200":"MEDIUM","532":"MEDIUM","601":"MEDIUM"};
|
|
286
|
+
[.[] | select(.user.login == "aws-security-agent[bot]")] |
|
|
287
|
+
to_entries | map(.value as $c |
|
|
288
|
+
($c.body | capture("CWE-(?<id>[0-9]+)") // {id: null}).id as $cwe_id |
|
|
289
|
+
(if $cwe_id then (cwe_severity[$cwe_id] // "MEDIUM") else "MEDIUM" end) as $severity |
|
|
290
|
+
{Source:"prism.d1.velocity", DetailType:"prism.d1.security.code_review",
|
|
291
|
+
EventBusName:"prism-d1-metrics",
|
|
292
|
+
Detail:({team_id:$team_id, repo:$repo, timestamp:$c.created_at, prism_level:3,
|
|
293
|
+
metric:{name:"security_finding",value:1,unit:"count"},
|
|
294
|
+
ai_context:{tool:"security-agent",model:"aws-security-agent",origin:$ai_origin},
|
|
295
|
+
security_agent_finding:{finding_id:"sa-pr\($pr_number)-\($c.id)", phase:"code_review",
|
|
296
|
+
severity:$severity, cwe_id:(if $cwe_id then "CWE-\($cwe_id)" else null end),
|
|
297
|
+
ai_origin:$ai_origin, found_at:$c.created_at}
|
|
298
|
+
} | tostring)})' 2>/dev/null || echo "[]")
|
|
299
|
+
|
|
300
|
+
TOTAL=$(echo "${ENTRIES}" | jq 'length')
|
|
301
|
+
for i in $(seq 0 10 $((TOTAL - 1))); do
|
|
302
|
+
aws events put-events --region us-west-2 --entries "$(echo "${ENTRIES}" | jq ".[$i:$((i+10))]")"
|
|
303
|
+
done
|
|
304
|
+
|
|
305
|
+
- name: Fail on eval or security issues
|
|
306
|
+
if: steps.eval.outputs.overall_result == 'FAIL' || steps.security-gate.outputs.findings != '0'
|
|
307
|
+
run: |
|
|
308
|
+
[[ "${{ steps.eval.outputs.overall_result }}" == "FAIL" ]] && echo "❌ Eval gate failed"
|
|
309
|
+
[[ "${{ steps.security-gate.outputs.findings }}" != "0" ]] && echo "❌ Security Agent found ${{ steps.security-gate.outputs.findings }} issues"
|
|
310
|
+
exit 1
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import 'source-map-support/register';
|
|
3
|
+
import * as cdk from 'aws-cdk-lib';
|
|
4
|
+
import { Aspects } from 'aws-cdk-lib';
|
|
5
|
+
import { AwsSolutionsChecks } from 'cdk-nag';
|
|
6
|
+
import { MetricsPipelineStack } from '../lib/metrics-pipeline-stack';
|
|
7
|
+
import { ApiStack } from '../lib/api-stack';
|
|
8
|
+
import { DashboardStack } from '../lib/dashboard-stack';
|
|
9
|
+
|
|
10
|
+
const app = new cdk.App();
|
|
11
|
+
|
|
12
|
+
const env: cdk.Environment = {
|
|
13
|
+
account: process.env.CDK_DEFAULT_ACCOUNT,
|
|
14
|
+
region: process.env.CDK_DEFAULT_REGION ?? 'us-west-2',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const pipelineStack = new MetricsPipelineStack(app, 'PrismD1MetricsPipeline', {
|
|
18
|
+
env,
|
|
19
|
+
description: 'PRISM D1 Velocity - Core metrics event pipeline (EventBridge, DynamoDB)',
|
|
20
|
+
tags: {
|
|
21
|
+
'prism:project': 'PRISM',
|
|
22
|
+
'prism:domain': 'D1-Velocity',
|
|
23
|
+
'prism:component': 'MetricsPipeline',
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const apiStack = new ApiStack(app, 'PrismD1Api', {
|
|
28
|
+
env,
|
|
29
|
+
description: 'PRISM D1 Velocity - Metric ingestion and query API',
|
|
30
|
+
eventBus: pipelineStack.eventBus,
|
|
31
|
+
eventsTable: pipelineStack.eventsTable,
|
|
32
|
+
metadataTable: pipelineStack.metadataTable,
|
|
33
|
+
kmsKey: pipelineStack.kmsKey,
|
|
34
|
+
tags: {
|
|
35
|
+
'prism:project': 'PRISM',
|
|
36
|
+
'prism:domain': 'D1-Velocity',
|
|
37
|
+
'prism:component': 'Api',
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const dashboardStack = new DashboardStack(app, 'PrismD1Dashboard', {
|
|
42
|
+
env,
|
|
43
|
+
description: 'PRISM D1 Velocity - CloudWatch dashboards and alarms',
|
|
44
|
+
tags: {
|
|
45
|
+
'prism:project': 'PRISM',
|
|
46
|
+
'prism:domain': 'D1-Velocity',
|
|
47
|
+
'prism:component': 'Dashboard',
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
apiStack.addDependency(pipelineStack);
|
|
52
|
+
|
|
53
|
+
// Enable cdk-nag AWS Solutions checks on all stacks
|
|
54
|
+
Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true }));
|
|
55
|
+
|
|
56
|
+
app.synth();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"app": "npx ts-node bin/app.ts",
|
|
3
|
+
"watch": {
|
|
4
|
+
"include": ["**"],
|
|
5
|
+
"exclude": ["README.md", "dist/**", "node_modules/**", "cdk.out/**"]
|
|
6
|
+
},
|
|
7
|
+
"context": {
|
|
8
|
+
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
|
|
9
|
+
"@aws-cdk/core:checkSecretUsage": true,
|
|
10
|
+
"@aws-cdk/core:target-partitions": ["aws"]
|
|
11
|
+
}
|
|
12
|
+
}
|