Repository navigation
Expand file tree
/
Copy pathaction.yml
More file actions
156 lines (148 loc) · 7.04 KB
/
Copy pathaction.yml
File metadata and controls
156 lines (148 loc) · 7.04 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
name: 'node9 Agent Security'
description: 'Scan a repo for agent-security risks (injectable CI workflows, unsafe agent configs, MCP) and comment on the PR.'
author: 'node9'
branding:
icon: 'shield'
color: 'purple'
inputs:
fail-on:
description: 'Fail the check when the worst finding is at least this severity: never | medium | high | critical'
required: false
default: 'never'
fail-on-scope:
description: >-
Which findings the gate judges: "all" (the repo as it stands, the default and the
pre-CI-5 behaviour) or "introduced" (only what this PR added or made worse). Use
"introduced" to turn the gate on for a repository that is already dirty. When the
base commit cannot be read the scan falls back to "all" rather than passing.
required: false
default: 'all'
annotate:
description: 'Annotate the findings inline on the PR diff'
required: false
default: 'true'
comment:
description: 'Post a sticky comment on the PR with the findings'
required: false
default: 'true'
node9-version:
description: 'node9-ai version to run (pin for reproducibility; "latest" for newest)'
required: false
default: 'latest'
github-token:
description: 'Token for posting the comment + check-run (defaults to the job token)'
required: false
default: ${{ github.token }}
node9-upload-key:
description: >-
Repository upload key from the node9 dashboard (Repositories, Scan on every push).
When set, each push scan is also sent to that node9 workspace. Add `id-token: write`
to the job's existing permissions (keep `contents: read` and the others). Empty
(the default) sends nothing. Uploads never change the check result.
required: false
default: ''
node9-api-url:
description: "node9 API for uploads. Only https://api.node9.ai (the default) or node9's test deployment are accepted."
required: false
default: 'https://api.node9.ai'
runs:
using: 'composite'
steps:
# Decide first whether a scan here would mean anything. Under `pull_request_target`,
# checkout gets the BASE branch, so a scan would compare base with base and never see the
# PR — and checking out the PR head in that job (it holds secrets and a write token) is
# the exact pattern CI-2 flags. So: do not scan, and say so (neutral, never blocks).
- name: Check the event
id: guard
shell: bash
env:
EVENT: ${{ github.event_name }}
run: |
if [ "$EVENT" = "pull_request_target" ]; then
echo "skip=pull_request_target" >> "$GITHUB_OUTPUT"
fi
# Self-contained: check out the repo so we have the config to scan. Idempotent
# if the caller already checked out.
- name: Check out repository
if: steps.guard.outputs.skip == ''
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# Always: comment.js needs Node even when the scan is skipped.
- name: Set up Node
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '22'
# CI-5: the base side. `actions/checkout` fetches depth 1, so the PR's base commit is
# NOT in the object store — one extra shallow fetch of that single SHA is all the diff
# needs. A failure here is deliberately non-fatal: the scan then reports that it could
# not read the base, which degrades the gate to the strict absolute answer.
- name: Fetch the base commit
if: steps.guard.outputs.skip == '' && github.event.pull_request.base.sha != ''
shell: bash
env:
NODE9_BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=1 origin "$NODE9_BASE_SHA" 2>/dev/null \
|| echo "node9: could not fetch the base commit; the scan will report every finding."
- name: Run node9 agent-security scan
id: scan
if: steps.guard.outputs.skip == ''
shell: bash
env:
NODE9_BASE_SHA: ${{ github.event.pull_request.base.sha }}
NODE9_VERSION_INPUT: ${{ inputs.node9-version }}
NODE9_WILL_UPLOAD: ${{ github.event_name == 'push' && inputs.node9-upload-key != '' }}
run: |
set -euo pipefail
# Uploads only: resolve the exact scanner version first and run that one,
# so the upload reports the version that really scanned. A range or a
# dist-tag resolves to its highest matching version (resolve-version.js,
# bounded at 30 s on every runner OS). Other runs keep the input as
# given, with no extra registry call. If npm cannot answer, the scan
# still runs with the input and the version stays unknown (the upload
# is then skipped; the check itself is unaffected).
VERSION=""
if [ "$NODE9_WILL_UPLOAD" = "true" ]; then
VERSION="$(node "${GITHUB_ACTION_PATH}/resolve-version.js" "$NODE9_VERSION_INPUT" || true)"
fi
SPEC="${VERSION:-$NODE9_VERSION_INPUT}"
# Static, config-only scan of the checked-out workspace. Never fails this
# step on a finding (the gate is applied by comment.js per fail-on).
# `--base` is passed whenever the event has one, INCLUDING when the fetch above
# failed: an unreadable base must be reported as such, not silently skipped.
BASE_ARGS=()
if [ -n "${NODE9_BASE_SHA:-}" ]; then BASE_ARGS=(--base "$NODE9_BASE_SHA"); fi
STARTED="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
npx -y "node9-ai@${SPEC}" scan-repo "$GITHUB_WORKSPACE" \
"${BASE_ARGS[@]}" --json > "${RUNNER_TEMP}/node9-result.json" || true
COMPLETED="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
{
echo "result=${RUNNER_TEMP}/node9-result.json"
echo "version=${VERSION}"
echo "started=${STARTED}"
echo "completed=${COMPLETED}"
} >> "$GITHUB_OUTPUT"
# Repository dashboard upload: push runs with a key only. Before the gate, so a
# failing gate cannot skip it; upload.js always exits 0, so it cannot fail the
# job either. The key is passed to this step alone.
- name: Send the scan to node9
if: steps.guard.outputs.skip == '' && github.event_name == 'push' && inputs.node9-upload-key != ''
shell: bash
env:
NODE9_UPLOAD_KEY: ${{ inputs.node9-upload-key }}
NODE9_API_URL: ${{ inputs.node9-api-url }}
NODE9_RESULT: ${{ steps.scan.outputs.result }}
NODE9_SCANNER_VERSION: ${{ steps.scan.outputs.version }}
NODE9_SCAN_STARTED_AT: ${{ steps.scan.outputs.started }}
NODE9_SCAN_COMPLETED_AT: ${{ steps.scan.outputs.completed }}
run: node "${GITHUB_ACTION_PATH}/upload.js" || true
- name: Comment + gate
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.github-token }}
NODE9_RESULT: ${{ steps.scan.outputs.result }}
NODE9_FAIL_ON: ${{ inputs.fail-on }}
NODE9_FAIL_ON_SCOPE: ${{ inputs.fail-on-scope }}
NODE9_COMMENT: ${{ inputs.comment }}
NODE9_ANNOTATE: ${{ inputs.annotate }}
NODE9_SKIPPED: ${{ steps.guard.outputs.skip }}
run: node "${GITHUB_ACTION_PATH}/comment.js"