Repository navigation
Expand file tree
/
Copy pathaction.yml
More file actions
218 lines (199 loc) · 7.12 KB
/
Copy pathaction.yml
File metadata and controls
218 lines (199 loc) · 7.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
name: 'ActionScope'
description: 'Map the AWS blast radius of GitHub Actions workflows and AI agent configs'
author: 'Rishabh Singh'
branding:
icon: 'shield'
color: 'orange'
inputs:
path:
description: 'Path to the repository root to scan'
required: false
default: '.'
fail-on:
description: 'Fail the action if overall risk is at or above this level (critical, high, medium, low)'
required: false
default: 'high'
output-format:
description: 'Output format: terminal, json, markdown, or sarif'
required: false
default: 'terminal'
upload-sarif:
description: 'Upload SARIF results to GitHub Code Scanning'
required: false
default: 'false'
resolve-pins:
description: 'Resolve unpinned action tags to current SHA via GitHub API'
required: false
default: 'false'
save-state:
description: 'Save scan state for delta comparison in future scans'
required: false
default: 'false'
load-state:
description: 'Compare against previous scan state for delta reporting'
required: false
default: 'false'
state-artifact-name:
description: 'GitHub Actions artifact name for persisting scan state'
required: false
default: 'actionscope-state'
comment-pr:
description: 'Post findings as a PR comment (requires pull-requests: write permission)'
required: false
default: 'false'
github-token:
description: 'GitHub token for posting PR comments'
required: false
default: '${{ github.token }}'
version:
description: 'ActionScope version to install (default: latest)'
required: false
default: ''
outputs:
overall-risk:
description: 'The overall risk level found (critical, high, medium, low, info)'
value: ${{ steps.scan.outputs.overall-risk }}
findings-json:
description: 'JSON string of all findings'
value: ${{ steps.scan.outputs.findings-json }}
credential-sources-count:
description: 'Number of AWS credential sources found'
value: ${{ steps.scan.outputs.credential-sources-count }}
runs:
using: 'composite'
steps:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install ActionScope
shell: bash
run: |
if [ -n "${{ inputs.version }}" ]; then
pip install "actionscope==${{ inputs.version }}"
else
pip install actionscope
fi
- name: Restore previous state
if: inputs.load-state == 'true'
uses: actions/download-artifact@v8
continue-on-error: true
with:
name: ${{ inputs.state-artifact-name }}
path: .actionscope/
- name: Verify restored state
if: inputs.load-state == 'true'
shell: bash
run: |
if [ ! -f .actionscope/last_scan.json ]; then
echo "::warning::ActionScope: load-state is true but .actionscope/last_scan.json was not restored. Delta reporting will be skipped."
fi
- name: Run ActionScope
id: scan
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.github-token }}
run: |
SCAN_FLAGS=""
if [ "${{ inputs.load-state }}" = "true" ]; then
SCAN_FLAGS="$SCAN_FLAGS --load-state"
fi
if [ "${{ inputs.save-state }}" = "true" ]; then
SCAN_FLAGS="$SCAN_FLAGS --save-state"
fi
if [ "${{ inputs.resolve-pins }}" = "true" ]; then
SCAN_FLAGS="$SCAN_FLAGS --resolve-pins"
fi
actionscope scan ${{ inputs.path }} \
--output-format json \
--output-file /tmp/actionscope-results.json \
$SCAN_FLAGS \
--no-color 2>/tmp/actionscope-stderr.txt || {
echo "ActionScope scan failed. See stderr:"
cat /tmp/actionscope-stderr.txt || true
exit 1
}
if [ -f /tmp/actionscope-results.json ]; then
RISK=$(python3 -c "
import json
d = json.load(open('/tmp/actionscope-results.json'))
print(d.get('overall_risk', 'info'))
")
COUNT=$(python3 -c "
import json
d = json.load(open('/tmp/actionscope-results.json'))
print(d.get('summary', {}).get('credential_sources', 0))
")
echo "overall-risk=$RISK" >> "$GITHUB_OUTPUT"
{
echo 'findings-json<<JSON_EOF'
cat /tmp/actionscope-results.json
# The JSON reporter does not emit a trailing newline; without one
# here `JSON_EOF` ends up appended to the closing brace, so the
# runner can't find the delimiter and rejects the output write.
echo ''
echo JSON_EOF
} >> "$GITHUB_OUTPUT"
echo "credential-sources-count=$COUNT" >> "$GITHUB_OUTPUT"
if [ "${{ inputs.output-format }}" != "json" ]; then
actionscope report \
--from-json /tmp/actionscope-results.json \
--format "${{ inputs.output-format }}" || true
fi
if [ "${{ inputs.upload-sarif }}" = "true" ]; then
actionscope report \
--from-json /tmp/actionscope-results.json \
--format sarif > /tmp/actionscope-results.sarif || true
fi
fi
- name: Save state artifact
if: inputs.save-state == 'true'
uses: actions/upload-artifact@v7
with:
name: ${{ inputs.state-artifact-name }}
path: .actionscope/last_scan.json
retention-days: 30
- name: Upload SARIF to GitHub Code Scanning
if: inputs.upload-sarif == 'true'
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: /tmp/actionscope-results.sarif
category: actionscope
- name: Post PR comment
if: inputs.comment-pr == 'true' && github.event_name == 'pull_request'
shell: bash
env:
GH_TOKEN: ${{ inputs.github-token }}
run: |
MARKDOWN=$(actionscope report \
--from-json /tmp/actionscope-results.json \
--format markdown 2>/dev/null || \
echo "ActionScope scan complete. See workflow run for details.")
gh pr comment ${{ github.event.pull_request.number }} \
--body "$MARKDOWN" \
--edit-last 2>/dev/null || \
gh pr comment ${{ github.event.pull_request.number }} \
--body "$MARKDOWN"
- name: Evaluate risk threshold
if: inputs.fail-on != ''
shell: bash
run: |
python3 - <<'EOF'
import json, sys
RISK_ORDER = {"info": 0, "low": 1, "medium": 2, "high": 3, "critical": 4}
fail_on = "${{ inputs.fail-on }}".strip().lower()
try:
with open("/tmp/actionscope-results.json") as f:
data = json.load(f)
overall = data.get("overall_risk", "info").lower()
except Exception:
sys.exit(0)
if RISK_ORDER.get(overall, 0) >= RISK_ORDER.get(fail_on, 99):
print(f"ActionScope: overall risk '{overall}' meets or exceeds "
f"fail-on threshold '{fail_on}'. Failing.")
sys.exit(1)
else:
print(f"ActionScope: overall risk '{overall}' is below "
f"fail-on threshold '{fail_on}'. Passing.")
sys.exit(0)
EOF