Allowlist runner hook events and tighten runner egress
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/kubeconform Pipeline was successful

This commit is contained in:
2026-11-10 22:24:48 +11:00
parent 5cbbb9ea21
commit c75ed6497f
10 changed files with 86 additions and 39 deletions
+16 -13
View File
@@ -1,23 +1,26 @@
#!/bin/sh
set -eu
event="${GITHUB_EVENT_NAME:-}"
refuse() {
echo "job-started hook: refusing ${GITHUB_EVENT_NAME} job: $1" >&2
echo "job-started hook: refusing '${event}' job: $1" >&2
exit 1
}
case "${GITHUB_EVENT_NAME}" in
pull_request_target | workflow_run)
refuse "event not allowed on self-hosted runners"
;;
[ -r "${GITHUB_EVENT_PATH:-}" ] || refuse "event payload missing"
jq -e 'type == "object"' "${GITHUB_EVENT_PATH}" >/dev/null 2>&1 || refuse "event payload unparseable"
case "${event}" in
push | workflow_dispatch | schedule | merge_group | release) ;;
pull_request)
same_repo=$(jq -r '.pull_request.head.repo.fork == false' "${GITHUB_EVENT_PATH}")
assoc=$(jq -r '.pull_request.author_association // "NONE"' "${GITHUB_EVENT_PATH}")
if [ "${same_repo}" != true ]; then
case "${assoc}" in
OWNER | MEMBER | COLLABORATOR) ;;
*) refuse "fork pull request from untrusted author (${assoc})" ;;
esac
fi
trusted=$(jq -r '
.pull_request.head.repo.full_name as $head
| if $head == null then false
elif $head == .repository.full_name then true
else (.pull_request.author_association | IN("OWNER", "MEMBER", "COLLABORATOR"))
end' "${GITHUB_EVENT_PATH}") || refuse "event payload unreadable"
[ "${trusted}" = true ] || refuse "pull request from untrusted fork"
;;
*) refuse "event not allowed on self-hosted runners" ;;
esac
+7 -18
View File
@@ -30,27 +30,16 @@ spec:
- 172.16.0.0/12
- 192.168.0.0/16
- 198.18.0.0/15
- 0.0.0.0/8
- 100.64.0.0/10
- 127.0.0.0/8
- 169.254.0.0/16
- 224.0.0.0/4
- 240.0.0.0/4
ports:
- protocol: TCP
port: 443
# artifactapi.k8s.syd1.au.unkin.net: traefik-internal LB, DNATed to its pods in-cluster
- to:
- ipBlock:
cidr: 198.18.200.4/32
ports:
- protocol: TCP
port: 443
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: traefik-system
podSelector:
matchLabels:
app.kubernetes.io/instance: traefik-internal-traefik-system
ports:
- protocol: TCP
port: websecure
# vault.service.consul
# vault.service.consul servers, from `dig vault.service.consul`
- to:
- ipBlock:
cidr: 198.18.25.78/32
+1
View File
@@ -0,0 +1 @@
not json {
@@ -0,0 +1,11 @@
{
"comment": {
"author_association": "NONE"
},
"issue": {
"number": 1
},
"repository": {
"full_name": "IUCLabs/app"
}
}
@@ -1,8 +1,11 @@
{
"pull_request": {
"author_association": "NONE",
"author_association": "MEMBER",
"head": {
"repo": null
}
},
"repository": {
"full_name": "IUCLabs/app"
}
}
+5 -1
View File
@@ -3,8 +3,12 @@
"author_association": "NONE",
"head": {
"repo": {
"fork": true
"fork": true,
"full_name": "evil/app"
}
}
},
"repository": {
"full_name": "IUCLabs/app"
}
}
@@ -0,0 +1,14 @@
{
"pull_request": {
"author_association": "NONE",
"head": {
"repo": {
"fork": false,
"full_name": "evil/app"
}
}
},
"repository": {
"full_name": "IUCLabs/app"
}
}
+5 -1
View File
@@ -3,8 +3,12 @@
"author_association": "MEMBER",
"head": {
"repo": {
"fork": true
"fork": true,
"full_name": "someone/app"
}
}
},
"repository": {
"full_name": "IUCLabs/app"
}
}
+5 -1
View File
@@ -3,8 +3,12 @@
"author_association": "CONTRIBUTOR",
"head": {
"repo": {
"fork": false
"fork": false,
"full_name": "IUCLabs/app"
}
}
},
"repository": {
"full_name": "IUCLabs/app"
}
}
+18 -4
View File
@@ -7,19 +7,33 @@ fail=0
check() {
local want=$1 event=$2 fixture=$3 got=0
GITHUB_EVENT_NAME=$event GITHUB_EVENT_PATH=$fixtures/$fixture "$hook" >/dev/null 2>&1 || got=1
local -a env=(-u GITHUB_EVENT_NAME -u GITHUB_EVENT_PATH)
[ "$event" = - ] || env+=("GITHUB_EVENT_NAME=$event")
[ "$fixture" = - ] || env+=("GITHUB_EVENT_PATH=$fixtures/$fixture")
env "${env[@]}" "$hook" >/dev/null 2>&1 || got=1
if [ "$got" -ne "$want" ]; then
echo "FAIL: $event $fixture exit=$got want=$want" >&2
fail=1
fi
}
check 0 push push.json
for e in push workflow_dispatch schedule merge_group release; do
check 0 "$e" push.json
done
check 0 pull_request pr-member.json
check 0 pull_request pr-same-repo.json
check 1 pull_request pr-fork-none.json
check 1 pull_request pr-fork-spoofed.json
check 1 pull_request pr-deleted-fork.json
check 1 pull_request_target pr-member.json
check 1 workflow_run push.json
check 1 pull_request push.json
for e in pull_request_target workflow_run pull_request_review pull_request_review_comment issue_comment issues discussion discussion_comment create delete unknown_event; do
check 1 "$e" pr-member.json
done
check 1 pull_request_review pr-fork-none.json
check 1 issue_comment issue-comment.json
check 1 push -
check 1 push missing.json
check 1 push invalid.txt
check 1 - push.json
exit "$fail"