Skip to content

Commit cb0735b

Browse files
committed
ci: Sanitize upstream workflow triggers during sync\n\n🤖 Generated with [Nori](https://nori.ai)\n\nCo-Authored-By: Nori <noreply@tilework.tech>
Adds workflow sanitization to upstream-sync.yml that replaces all on: trigger blocks with workflow_dispatch in pulled upstream workflows. This prevents upstream workflows from running automatically in fork branches. Changes: Add awk-based YAML processing, exclude fork-specific workflows, commit sanitization as part of sync branch, include sanitized file list in PR body, add test script.
1 parent 1e44916 commit cb0735b

10 files changed

Lines changed: 291 additions & 0 deletions

‎.github/workflows/upstream-sync.yml‎

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ jobs:
122122
123123
- name: Create sync branch
124124
if: steps.check.outputs.exists == 'false'
125+
id: sync
125126
run: |
126127
set -euo pipefail
127128
@@ -133,8 +134,60 @@ jobs:
133134
134135
if [[ "$dry_run" == "true" ]]; then
135136
echo "::notice::DRY RUN: Would create branch $sync_branch from $target_tag"
137+
echo "sanitized_files=" >> "$GITHUB_OUTPUT"
136138
else
137139
git checkout -b "$sync_branch" "$target_tag"
140+
141+
# Sanitize upstream workflow triggers to prevent unwanted runs
142+
sanitized_files=""
143+
for workflow in .github/workflows/*.yml .github/workflows/*.yaml; do
144+
[ -f "$workflow" ] || continue
145+
146+
# Skip our fork-specific workflows
147+
case "$(basename "$workflow")" in
148+
upstream-sync.yml|rust-ci.yml) continue ;;
149+
esac
150+
151+
echo "Sanitizing: $workflow"
152+
153+
# Replace on: block with workflow_dispatch only using awk
154+
awk '
155+
/^on:/ {
156+
in_on = 1
157+
print "on: workflow_dispatch"
158+
next
159+
}
160+
in_on && /^$/ {
161+
in_on = 0
162+
print
163+
next
164+
}
165+
in_on && /^[^ \t#]/ {
166+
in_on = 0
167+
}
168+
in_on && /^[ \t]/ {
169+
next
170+
}
171+
in_on && /^#/ {
172+
next
173+
}
174+
!in_on { print }
175+
' "$workflow" > "${workflow}.tmp" && mv "${workflow}.tmp" "$workflow"
176+
177+
sanitized_files="$sanitized_files- \`$(basename "$workflow")\`\n"
178+
done
179+
180+
if [[ -n "$sanitized_files" ]]; then
181+
git add .github/workflows/
182+
git commit -m "chore: sanitize upstream workflow triggers for fork safety"
183+
echo "sanitized_files<<EOF" >> "$GITHUB_OUTPUT"
184+
echo -e "$sanitized_files" >> "$GITHUB_OUTPUT"
185+
echo "EOF" >> "$GITHUB_OUTPUT"
186+
else
187+
echo "::notice::No upstream workflows to sanitize"
188+
echo "sanitized_files=" >> "$GITHUB_OUTPUT"
189+
fi
190+
138191
git push origin "$sync_branch"
139192
fi
140193
@@ -144,6 +197,7 @@ jobs:
144197
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
145198
TARGET_TAG: ${{ steps.tag.outputs.target_tag }}
146199
SYNC_BRANCH: ${{ steps.tag.outputs.sync_branch }}
200+
SANITIZED_FILES: ${{ steps.sync.outputs.sanitized_files }}
147201
DRY_RUN: ${{ inputs.dry_run }}
148202
run: |
149203
set -euo pipefail
@@ -153,6 +207,16 @@ jobs:
153207
154208
pr_title="Sync upstream $TARGET_TAG"
155209
210+
# Build sanitized workflows section
211+
if [[ -n "$SANITIZED_FILES" ]]; then
212+
sanitized_section="### Workflow Sanitization
213+
214+
The following upstream workflows had their triggers replaced with \\\`workflow_dispatch\\\`:
215+
$SANITIZED_FILES"
216+
else
217+
sanitized_section=""
218+
fi
219+
156220
# Build PR body using heredoc
157221
pr_body=$(cat <<EOF
158222
## Upstream Sync
@@ -165,6 +229,8 @@ jobs:
165229
- **Commits to merge:** ~$commit_count
166230
- **Release notes:** [GitHub Release](https://github.com/openai/codex/releases/tag/$TARGET_TAG)
167231
232+
$sanitized_section
233+
168234
### Merge Instructions
169235
170236
1. Review the changes for conflicts with our ACP fork work
@@ -201,10 +267,17 @@ jobs:
201267
fi
202268
203269
- name: Summary
270+
env:
271+
SANITIZED_FILES: ${{ steps.sync.outputs.sanitized_files }}
204272
run: |
205273
echo "## Upstream Sync Summary" >> "$GITHUB_STEP_SUMMARY"
206274
echo "" >> "$GITHUB_STEP_SUMMARY"
207275
echo "- **Target tag:** ${{ steps.tag.outputs.target_tag }}" >> "$GITHUB_STEP_SUMMARY"
208276
echo "- **Sync branch:** ${{ steps.tag.outputs.sync_branch }}" >> "$GITHUB_STEP_SUMMARY"
209277
echo "- **Branch existed:** ${{ steps.check.outputs.exists }}" >> "$GITHUB_STEP_SUMMARY"
210278
echo "- **Dry run:** ${{ inputs.dry_run || 'false' }}" >> "$GITHUB_STEP_SUMMARY"
279+
if [[ -n "$SANITIZED_FILES" ]]; then
280+
echo "" >> "$GITHUB_STEP_SUMMARY"
281+
echo "### Sanitized Workflows" >> "$GITHUB_STEP_SUMMARY"
282+
echo "$SANITIZED_FILES" >> "$GITHUB_STEP_SUMMARY"
283+
fi
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# This workflow runs on push
2+
name: With Comments
3+
4+
on: workflow_dispatch
5+
6+
jobs:
7+
test:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- run: echo "test"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
name: Complex Workflow
2+
3+
on: workflow_dispatch
4+
5+
env:
6+
NODE_VERSION: '20'
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name: CI
2+
3+
on: workflow_dispatch
4+
5+
jobs:
6+
build:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v4
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name: Simple
2+
3+
on: workflow_dispatch
4+
5+
jobs:
6+
test:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- run: echo "hello"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# This workflow runs on push
2+
name: With Comments
3+
4+
on:
5+
# Run on push to main
6+
push:
7+
branches: [main]
8+
# Also on PRs
9+
pull_request:
10+
11+
jobs:
12+
test:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- run: echo "test"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Complex Workflow
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- 'release/**'
8+
paths:
9+
- 'src/**'
10+
- '!src/**/*.md'
11+
pull_request:
12+
types: [opened, synchronize, reopened]
13+
schedule:
14+
- cron: '0 0 * * *'
15+
workflow_dispatch:
16+
inputs:
17+
debug:
18+
description: 'Enable debug mode'
19+
required: false
20+
type: boolean
21+
22+
env:
23+
NODE_VERSION: '20'
24+
25+
jobs:
26+
build:
27+
runs-on: ubuntu-latest
28+
steps:
29+
- uses: actions/checkout@v4
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name: Simple
2+
3+
on: push
4+
5+
jobs:
6+
test:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- run: echo "hello"
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#!/usr/bin/env bash
2+
# Test script for workflow sanitization logic
3+
# RED phase: This test will fail until we implement the sanitize function
4+
5+
set -euo pipefail
6+
7+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8+
TEST_DIR="$SCRIPT_DIR/fixtures"
9+
TEMP_DIR="$(mktemp -d)"
10+
11+
trap "rm -rf $TEMP_DIR" EXIT
12+
13+
# Colors for output
14+
RED='\033[0;31m'
15+
GREEN='\033[0;32m'
16+
NC='\033[0m' # No Color
17+
18+
failed=0
19+
passed=0
20+
21+
# The sanitize function
22+
sanitize_workflow() {
23+
local input_file="$1"
24+
local output_file="$2"
25+
26+
# Replace on: block with workflow_dispatch only using awk
27+
# Logic:
28+
# 1. When we see ^on: (at column 0), enter "in_on" mode, print replacement
29+
# 2. While in_on, skip lines starting with whitespace (indented on: content)
30+
# 3. When we see a non-whitespace line at column 0, exit in_on mode
31+
# 4. Print all other lines normally
32+
awk '
33+
/^on:/ {
34+
in_on = 1
35+
print "on: workflow_dispatch"
36+
next
37+
}
38+
in_on && /^$/ {
39+
# Empty line ends the on: block
40+
in_on = 0
41+
print
42+
next
43+
}
44+
in_on && /^[^ \t#]/ {
45+
# Non-indented line ends the on: block
46+
in_on = 0
47+
}
48+
in_on && /^[ \t]/ {
49+
# Indented content - skip
50+
next
51+
}
52+
in_on && /^#/ {
53+
# Comment inside on: block - skip
54+
next
55+
}
56+
!in_on { print }
57+
' "$input_file" > "$output_file"
58+
}
59+
60+
# Test helper
61+
assert_output() {
62+
local test_name="$1"
63+
local input_file="$2"
64+
local expected_file="$3"
65+
66+
local actual_file="$TEMP_DIR/actual.yml"
67+
sanitize_workflow "$input_file" "$actual_file"
68+
69+
if diff -q "$expected_file" "$actual_file" > /dev/null 2>&1; then
70+
echo -e "${GREEN}✓ PASS${NC}: $test_name"
71+
passed=$((passed + 1))
72+
else
73+
echo -e "${RED}✗ FAIL${NC}: $test_name"
74+
echo " Expected:"
75+
sed 's/^/ /' "$expected_file"
76+
echo " Actual:"
77+
sed 's/^/ /' "$actual_file"
78+
echo " Diff:"
79+
diff "$expected_file" "$actual_file" | sed 's/^/ /' || true
80+
failed=$((failed + 1))
81+
fi
82+
}
83+
84+
echo "Running workflow sanitization tests..."
85+
echo
86+
87+
# Test 1: Multi-line on: block
88+
assert_output "Multi-line on: block" \
89+
"$TEST_DIR/input-multiline.yml" \
90+
"$TEST_DIR/expected-multiline.yml"
91+
92+
# Test 2: Single-line on: block
93+
assert_output "Single-line on: block" \
94+
"$TEST_DIR/input-singleline.yml" \
95+
"$TEST_DIR/expected-singleline.yml"
96+
97+
# Test 3: Complex on: block with nested structures
98+
assert_output "Complex on: block" \
99+
"$TEST_DIR/input-complex.yml" \
100+
"$TEST_DIR/expected-complex.yml"
101+
102+
# Test 4: on: block with comments
103+
assert_output "on: block with comments" \
104+
"$TEST_DIR/input-comments.yml" \
105+
"$TEST_DIR/expected-comments.yml"
106+
107+
echo
108+
echo "Results: $passed passed, $failed failed"
109+
110+
if [[ $failed -gt 0 ]]; then
111+
exit 1
112+
fi

0 commit comments

Comments
 (0)