-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaction.yml
More file actions
351 lines (335 loc) · 15.2 KB
/
Copy pathaction.yml
File metadata and controls
351 lines (335 loc) · 15.2 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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
name: "HOL AI Plugin Scanner"
description: "Scan AI plugin repositories for security, publishability, and best-practice quality signals."
author: "HOL"
inputs:
mode:
description: "Execution mode: scan, lint, verify, or submit"
required: false
default: "scan"
plugin_dir:
description: "Path to a single plugin directory or a repo marketplace root (default: repository root)"
required: false
default: "."
format:
description: "Output format: text, json, markdown, or sarif"
required: false
default: "text"
output:
description: "Write report to this file path"
required: false
default: ""
profile:
description: "Policy profile to apply: default, public-marketplace, or strict-security"
required: false
default: "default"
config:
description: "Optional path to a scanner config file such as .plugin-scanner.toml"
required: false
default: ""
baseline:
description: "Optional path to a baseline suppression file"
required: false
default: ""
trust_repository_policy:
description: "Allow repository-owned scanner config and baselines to affect Action verdicts"
required: false
default: "false"
online:
description: "Enable live network probing for verify mode"
required: false
default: "false"
upload_sarif:
description: "Upload the generated SARIF report to GitHub code scanning. Requires security-events: write in the calling workflow."
required: false
default: "false"
sarif_category:
description: "SARIF category used when upload_sarif is enabled"
required: false
default: "ai-plugin-scanner"
write_step_summary:
description: "Write the full report inline for markdown scans, otherwise write a concise GitHub Actions job summary"
required: false
default: "true"
registry_payload_output:
description: "Write a machine-readable plugin ecosystem payload JSON file for registry or awesome-list automation"
required: false
default: ""
min_score:
description: "Fail the job if the score is below this threshold (0-100)"
required: false
default: "0"
fail_on_severity:
description: "Fail if any finding meets or exceeds this severity (none, critical, high, medium, low, info)"
required: false
default: "none"
cisco_skill_scan:
description: "Cisco skill-scanner mode: auto, on, or off"
required: false
default: "auto"
cisco_policy:
description: "Cisco skill-scanner policy preset: permissive, balanced, or strict"
required: false
default: "balanced"
install_source:
description: "Package install source: pypi for the reviewed release path, or local for source-repo dogfooding"
required: false
default: "pypi"
install_cisco:
description: "Install the opt-in Cisco skill-scanner dependency used by this repo"
required: false
default: "false"
submission_enabled:
description: "Open submission issues for awesome-list and registry automation when the plugin clears the submission threshold"
required: false
default: "false"
submission_score_threshold:
description: "Minimum score required before a submission issue is created"
required: false
default: "80"
submission_repos:
description: "Comma-separated GitHub repositories that should receive the submission issue"
required: false
default: "hashgraph-online/awesome-codex-plugins"
submission_token:
description: "Required when submission_enabled is true; use a token with issues:write access to the submission repositories"
required: false
default: ""
submission_labels:
description: "Comma-separated labels to apply when creating submission issues"
required: false
default: "plugin-submission"
submission_category:
description: "Listing category to include in the submission issue body"
required: false
default: "Community Plugins"
submission_plugin_name:
description: "Override the plugin name used in the submission issue"
required: false
default: ""
submission_plugin_url:
description: "Override the plugin repository URL used in the submission issue"
required: false
default: ""
submission_plugin_description:
description: "Override the plugin description used in the submission issue"
required: false
default: ""
submission_author:
description: "Override the plugin author used in the submission issue"
required: false
default: ""
pr_comment:
description: "PR comment mode: auto, always, or off"
required: false
default: "auto"
pr_comment_style:
description: "PR comment style: concise or detailed"
required: false
default: "concise"
pr_comment_max_findings:
description: "Maximum findings to include in PR comment summaries"
required: false
default: "5"
outputs:
score:
description: "The numeric score (0-100)"
value: ${{ steps.scan.outputs.score }}
grade:
description: "The letter grade (A-F)"
value: ${{ steps.scan.outputs.grade }}
grade_label:
description: "The human-readable grade label"
value: ${{ steps.scan.outputs.grade_label }}
policy_pass:
description: "Whether the selected policy profile passed"
value: ${{ steps.scan.outputs.policy_pass }}
verify_pass:
description: "Whether runtime verification passed"
value: ${{ steps.scan.outputs.verify_pass }}
max_severity:
description: "The most severe finding in the scan result, or none"
value: ${{ steps.scan.outputs.max_severity }}
findings_total:
description: "The total number of findings across all severities"
value: ${{ steps.scan.outputs.findings_total }}
report_path:
description: "The path to the rendered report file, if output was requested"
value: ${{ steps.scan.outputs.report_path }}
registry_payload_path:
description: "The path to the machine-readable plugin ecosystem payload file, if requested"
value: ${{ steps.scan.outputs.registry_payload_path }}
submission_eligible:
description: "Whether the plugin met the submission threshold and passed the configured severity gate"
value: ${{ steps.scan.outputs.submission_eligible }}
submission_performed:
description: "Whether a submission issue was created or reused"
value: ${{ steps.scan.outputs.submission_performed }}
submission_issue_urls:
description: "Comma-separated URLs for submission issues created or reused"
value: ${{ steps.scan.outputs.submission_issue_urls }}
submission_issue_numbers:
description: "Comma-separated issue numbers for submission issues created or reused"
value: ${{ steps.scan.outputs.submission_issue_numbers }}
action_exit_code:
description: "Action execution exit code"
value: ${{ steps.scan.outputs.action_exit_code }}
pr_comment_status:
description: "PR comment status: created, updated, unchanged, skipped, or disabled"
value: ${{ steps.scan.outputs.pr_comment_status }}
pr_comment_id:
description: "Created or updated PR comment id when available"
value: ${{ steps.scan.outputs.pr_comment_id }}
pr_comment_url:
description: "Created or updated PR comment URL when available"
value: ${{ steps.scan.outputs.pr_comment_url }}
branding:
icon: "check-circle"
color: "blue"
runs:
using: "composite"
steps:
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: "3.12"
- name: Install scanner
shell: bash
env:
INSTALL_SOURCE: ${{ inputs.install_source }}
INSTALL_CISCO: ${{ inputs.install_cisco }}
PYTHONNOUSERSITE: "1"
PYTHONSAFEPATH: "1"
run: |
ACTION_RUNTIME_ROOT="${RUNNER_TEMP:-${TMPDIR:-/tmp}}"
ACTION_RUNTIME_DIR="$(mktemp -d "$ACTION_RUNTIME_ROOT/hol-guard-action-XXXXXX")"
trap 'cd "$ACTION_RUNTIME_ROOT" 2>/dev/null || cd /; rm -rf "$ACTION_RUNTIME_DIR"' EXIT
cd "$ACTION_RUNTIME_DIR"
LOCAL_SOURCE=""
for candidate in "$GITHUB_ACTION_PATH" "$GITHUB_ACTION_PATH/.."; do
if [ -f "$candidate/pyproject.toml" ] && [ -d "$candidate/src/codex_plugin_scanner" ]; then
LOCAL_SOURCE="$candidate"
break
fi
done
if [ "$INSTALL_SOURCE" = "local" ]; then
if [ -z "$LOCAL_SOURCE" ]; then
echo "install_source=local requires the hol-guard source checkout (for example: uses: ./action inside hol-guard)." >&2
exit 1
fi
if [ "$INSTALL_CISCO" = "true" ]; then
python3 -P -m pip install "$LOCAL_SOURCE[cisco]"
else
python3 -P -m pip install "$LOCAL_SOURCE"
fi
elif [ "$INSTALL_SOURCE" = "pypi" ]; then
SCANNER_VERSION_FILE="$GITHUB_ACTION_PATH/scanner-version.txt"
SCANNER_SHA256_FILE="$GITHUB_ACTION_PATH/scanner-sha256.txt"
CISCO_VERSION_FILE="$GITHUB_ACTION_PATH/cisco-version.txt"
PYPI_ATTESTATIONS_VERSION_FILE="$GITHUB_ACTION_PATH/pypi-attestations-version.txt"
PYPI_ATTESTATIONS_REQUIREMENTS_FILE="$GITHUB_ACTION_PATH/pypi-attestations-requirements.txt"
SCANNER_RUNTIME_REQUIREMENTS_FILE="$GITHUB_ACTION_PATH/scanner-runtime-requirements.txt"
SCANNER_CISCO_RUNTIME_REQUIREMENTS_FILE="$GITHUB_ACTION_PATH/scanner-cisco-runtime-requirements.txt"
if [ ! -f "$SCANNER_VERSION_FILE" ] || [ ! -f "$SCANNER_SHA256_FILE" ] || [ ! -f "$CISCO_VERSION_FILE" ] || [ ! -f "$PYPI_ATTESTATIONS_VERSION_FILE" ] || [ ! -f "$PYPI_ATTESTATIONS_REQUIREMENTS_FILE" ] || [ ! -f "$SCANNER_RUNTIME_REQUIREMENTS_FILE" ] || [ ! -f "$SCANNER_CISCO_RUNTIME_REQUIREMENTS_FILE" ]; then
echo "Action install metadata files are missing from the Marketplace bundle." >&2
exit 1
fi
SCANNER_VERSION="$(tr -d '[:space:]' < "$SCANNER_VERSION_FILE")"
EXPECTED_SHA256="$(tr -d '[:space:]' < "$SCANNER_SHA256_FILE")"
CISCO_VERSION="$(tr -d '[:space:]' < "$CISCO_VERSION_FILE")"
PYPI_ATTESTATIONS_VERSION="$(tr -d '[:space:]' < "$PYPI_ATTESTATIONS_VERSION_FILE")"
SCANNER_REPOSITORY="https://github.com/hashgraph-online/hol-guard"
DIST_DIR="$ACTION_RUNTIME_DIR/ai-plugin-scanner-dist"
mkdir -p "$DIST_DIR"
if ! grep -q "^pypi-attestations==${PYPI_ATTESTATIONS_VERSION} " "$PYPI_ATTESTATIONS_REQUIREMENTS_FILE"; then
echo "pypi-attestations requirements lock does not match pypi-attestations-version.txt." >&2
exit 1
fi
python3 -P -m pip install --require-hashes --only-binary=:all: -r "$PYPI_ATTESTATIONS_REQUIREMENTS_FILE"
python3 -P -m pip download --only-binary=:all: --no-deps --dest "$DIST_DIR" "plugin-scanner==${SCANNER_VERSION}"
DIST_BASENAME="$(
DIST_DIR="$DIST_DIR" python3 -P -c "import os, sys; from pathlib import Path; candidates = sorted(Path(os.environ['DIST_DIR']).glob('plugin_scanner-*.whl')); (len(candidates) == 1) or sys.exit(f'Expected exactly one downloaded scanner wheel, found {len(candidates)}'); print(candidates[0].name)"
)"
ACTUAL_SHA256="$(
DIST_DIR="$DIST_DIR" DIST_BASENAME="$DIST_BASENAME" python3 -P -c "import hashlib, os; from pathlib import Path; wheel = Path(os.environ['DIST_DIR']) / os.environ['DIST_BASENAME']; print(hashlib.sha256(wheel.read_bytes()).hexdigest())"
)"
if [ "$ACTUAL_SHA256" != "$EXPECTED_SHA256" ]; then
echo "Downloaded scanner wheel SHA256 does not match scanner-sha256.txt." >&2
exit 1
fi
WHEEL_PATH="$DIST_DIR/$DIST_BASENAME"
if ! python3 -P -m pypi_attestations verify pypi \
--repository "$SCANNER_REPOSITORY" \
"$WHEEL_PATH"; then
echo "Failed to verify PyPI provenance for plugin-scanner==${SCANNER_VERSION}." >&2
exit 1
fi
if [ "$INSTALL_CISCO" = "true" ]; then
if ! grep -q "^cisco-ai-skill-scanner==${CISCO_VERSION} " "$SCANNER_CISCO_RUNTIME_REQUIREMENTS_FILE"; then
echo "Cisco runtime requirements lock does not match cisco-version.txt." >&2
exit 1
fi
python3 -P -m pip install --no-deps --require-hashes --only-binary=:all: -r "$SCANNER_CISCO_RUNTIME_REQUIREMENTS_FILE"
else
python3 -P -m pip install --no-deps --require-hashes --only-binary=:all: -r "$SCANNER_RUNTIME_REQUIREMENTS_FILE"
fi
python3 -P -m pip install --no-deps "$WHEEL_PATH"
else
echo "Unsupported install_source: $INSTALL_SOURCE" >&2
exit 1
fi
- name: Run scanner
id: scan
shell: bash
env:
MODE: ${{ inputs.mode }}
PLUGIN_DIR: ${{ inputs.plugin_dir }}
FORMAT: ${{ inputs.format }}
OUTPUT: ${{ inputs.output }}
PROFILE: ${{ inputs.profile }}
CONFIG: ${{ inputs.config }}
BASELINE: ${{ inputs.baseline }}
TRUST_REPOSITORY_POLICY: ${{ inputs.trust_repository_policy }}
ONLINE: ${{ inputs.online }}
UPLOAD_SARIF: ${{ inputs.upload_sarif }}
WRITE_STEP_SUMMARY: ${{ inputs.write_step_summary == 'true' && inputs.format != 'markdown' }}
REGISTRY_PAYLOAD_OUTPUT: ${{ inputs.registry_payload_output }}
MIN_SCORE: ${{ inputs.min_score }}
FAIL_ON: ${{ inputs.fail_on_severity }}
CISCO_SCAN: ${{ inputs.cisco_skill_scan }}
CISCO_POLICY: ${{ inputs.cisco_policy }}
SUBMISSION_ENABLED: ${{ inputs.submission_enabled }}
SUBMISSION_SCORE_THRESHOLD: ${{ inputs.submission_score_threshold }}
SUBMISSION_REPOS: ${{ inputs.submission_repos }}
SUBMISSION_TOKEN: ${{ inputs.submission_token }}
SUBMISSION_LABELS: ${{ inputs.submission_labels }}
SUBMISSION_CATEGORY: ${{ inputs.submission_category }}
SUBMISSION_PLUGIN_NAME: ${{ inputs.submission_plugin_name }}
SUBMISSION_PLUGIN_URL: ${{ inputs.submission_plugin_url }}
SUBMISSION_PLUGIN_DESCRIPTION: ${{ inputs.submission_plugin_description }}
SUBMISSION_AUTHOR: ${{ inputs.submission_author }}
PR_COMMENT: ${{ inputs.pr_comment }}
PR_COMMENT_STYLE: ${{ inputs.pr_comment_style }}
PR_COMMENT_MAX_FINDINGS: ${{ inputs.pr_comment_max_findings }}
GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }}
PYTHONNOUSERSITE: "1"
PYTHONSAFEPATH: "1"
run: python3 -P -m codex_plugin_scanner.action_runner
- name: Publish Markdown report to job summary
if: ${{ inputs.write_step_summary == 'true' && inputs.mode == 'scan' && inputs.format == 'markdown' && steps.scan.outputs.report_path != '' }}
shell: bash
env:
REPORT_PATH: ${{ steps.scan.outputs.report_path }}
run: |
set -euo pipefail
if [ ! -s "$REPORT_PATH" ]; then
echo "Markdown report was requested for the job summary but was not generated: $REPORT_PATH" >&2
exit 1
fi
cat "$REPORT_PATH" >> "$GITHUB_STEP_SUMMARY"
printf '\n' >> "$GITHUB_STEP_SUMMARY"
- name: Upload SARIF
if: ${{ inputs.upload_sarif == 'true' && inputs.mode == 'scan' && steps.scan.outputs.report_path != '' }}
uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7
with:
sarif_file: ${{ steps.scan.outputs.report_path }}
category: ${{ inputs.sarif_category }}