-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaction.yml
More file actions
106 lines (102 loc) · 4.51 KB
/
action.yml
File metadata and controls
106 lines (102 loc) · 4.51 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
name: exploit-guards
description: >
Runs exploit-guards checks on GitHub Actions workflow files and produces
SARIF output. Uses ${{ github.action_path }} to reference the bundled
script, enabling cross-repo usage via reusable workflows.
inputs:
target-dir:
description: Directory to scan (relative to workspace root)
required: false
default: '.'
fail-on-findings:
description: Fail the action when scanner reports findings (exit_code 1)
required: false
default: 'false'
outputs:
exit_code:
description: Exit code from the exploit-guards script
value: ${{ steps.run.outputs.exit_code }}
runs:
using: composite
steps:
# ── Run exploit-guards script ──────────────────────────────────────
- id: run
shell: bash
env:
TARGET_DIR: ${{ inputs.target-dir }}
run: |
set -euo pipefail
# Validate target-dir stays within GITHUB_WORKSPACE to prevent path traversal.
workspace="${GITHUB_WORKSPACE:-$(pwd)}"
resolved="$(PYTHONPATH='' python3 -I -P -c "
import os, sys
workspace = sys.argv[1]
target = sys.argv[2]
# Resolve relative to workspace; then verify it is strictly inside workspace.
candidate = os.path.realpath(os.path.join(workspace, target))
norm_ws = os.path.realpath(workspace)
if not (candidate == norm_ws or candidate.startswith(norm_ws + os.sep)):
sys.exit(f'::error::target-dir resolves outside GITHUB_WORKSPACE: {candidate!r}')
print(candidate)
" "$workspace" "$TARGET_DIR")"
txt_tmp="$(mktemp "${RUNNER_TEMP:-${TMPDIR:-/tmp}}/.exploit-guards.txt.XXXXXX")"
set +e # Disable -e so non-zero scanner exit doesn't kill the shell before ec=$?
bash "${{ github.action_path }}/gha-exploit-guard.sh" \
"$resolved" --no-color --sarif exploit-guards.sarif \
> "$txt_tmp" 2>&1
ec=$?
set -e # Re-enable -e so GITHUB_OUTPUT write failures are caught
# Symlink hardening: use O_NOFOLLOW to atomically write the output file,
# eliminating the TOCTOU window that exists between -L checks and rm/mv.
# O_NOFOLLOW causes open(2) to fail with ELOOP if the path is a symlink,
# so there is no race window regardless of when a symlink is planted.
PYTHONPATH='' python3 -I -P - "$txt_tmp" exploit-guards.txt << 'PYEOF'
import os, sys
src, dst = sys.argv[1], sys.argv[2]
try:
fd = os.open(dst, os.O_WRONLY | os.O_CREAT | os.O_TRUNC | os.O_NOFOLLOW, 0o644)
except OSError as e:
sys.exit(f"::error::Refusing to write to {dst!r} (symlink or permission error): {e}")
with os.fdopen(fd, "wb") as out_f:
# Read src with O_NOFOLLOW to close the TOCTOU window on the read side as well.
src_flags = os.O_RDONLY | (os.O_NOFOLLOW if hasattr(os, 'O_NOFOLLOW') else 0)
with os.fdopen(os.open(src, src_flags), "rb") as in_f:
out_f.write(in_f.read())
os.unlink(src)
PYEOF
echo "exit_code=$ec" >> "$GITHUB_OUTPUT"
# ── Validate SARIF output ──────────────────────────────────────────
- id: validate
env:
EXPLOIT_GUARDS_EXIT_CODE: ${{ steps.run.outputs.exit_code }}
FAIL_ON_FINDINGS: ${{ inputs.fail-on-findings }}
shell: bash
run: |
set -euo pipefail
[ -s exploit-guards.sarif ] || { echo "::error::exploit-guards.sarif was not generated. Scanner did not produce output -- check install and run steps above."; exit 1; }
PYTHONPATH='' python3 -I -P -c "
import json, os, sys
_flags = os.O_RDONLY | (os.O_NOFOLLOW if hasattr(os, 'O_NOFOLLOW') else 0)
try:
_fd = os.open('exploit-guards.sarif', _flags)
except OSError as _e:
sys.exit(f'::error::Cannot open exploit-guards.sarif: {_e}')
with os.fdopen(_fd, 'r', encoding='utf-8') as _f:
data = json.load(_f)
assert isinstance(data.get('runs'), list)
n = sum(len(r.get('results', [])) for r in data['runs'])
print(f'::notice::exploit-guards produced {n} finding(s)' if n else '::notice::exploit-guards completed with no findings')
"
ec="$EXPLOIT_GUARDS_EXIT_CODE"
if [ "$ec" -gt 1 ]; then
echo "::error::exploit-guards failed before producing trustworthy results (exit code $ec)."
exit "$ec"
fi
if [ "$ec" -eq 1 ]; then
case "${FAIL_ON_FINDINGS,,}" in
true|1|yes|on)
echo "::error::exploit-guards reported findings and fail-on-findings is enabled."
exit 1
;;
esac
fi