Skip to content

Commit eb460a8

Browse files
author
Arnold Cartagena
committed
security: harden GitHub Actions workflows against exploitation
Critical: - Remove `pip install -e ".[dev]"` from review.yml — untrusted PR code could exfiltrate secrets via build hooks. Install only ruff/mypy directly. High: - Fix script injection: use env var for base.ref instead of direct interpolation in shell commands (review.yml) - Add scoped app token to claude.yml (was using default GITHUB_TOKEN with write permissions) - Add --allowedTools to claude.yml (was unrestricted — could run any bash command including curl for data exfiltration) - Add branch filter to review.yml (was triggering on PRs to any branch) Medium: - Security sweep creates PR instead of pushing directly to main - Reduce sweep permissions to contents:read (uses app token for writes)
1 parent efca7e4 commit eb460a8

File tree

3 files changed

+27
-11
lines changed

3 files changed

+27
-11
lines changed

.github/workflows/claude.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,20 @@ jobs:
4040
with:
4141
fetch-depth: 1
4242

43+
- name: Generate app token
44+
id: app-token
45+
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
46+
with:
47+
app-id: ${{ secrets.APP_ID }}
48+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
49+
4350
- name: Run Claude Code
4451
id: claude
4552
uses: anthropics/claude-code-action@9469d113c6afd29550c402740f22d1a97dd1209b # v1
4653
with:
4754
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
55+
github_token: ${{ steps.app-token.outputs.token }}
4856
model: claude-sonnet-4-6
49-
claude_args: '--max-turns 30'
57+
claude_args: '--max-turns 30 --allowedTools "Bash(git:*),Bash(gh:*),Bash(pytest:*),Bash(python3:*),Bash(ruff:*),Bash(mypy:*),Bash(pnpm:*),Read,Glob,Grep,Write,Edit"'
5058
additional_permissions: |
5159
actions: read

.github/workflows/review.yml

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ name: Code Review
22

33
on:
44
pull_request:
5+
branches: [main]
56
types: [opened, synchronize, ready_for_review, reopened]
67

78
concurrency:
@@ -29,12 +30,14 @@ jobs:
2930
# not the PR branch. A malicious PR could modify CLAUDE.md to inject
3031
# instructions into the reviewer agent.
3132
- name: Extract governance files from base branch
33+
env:
34+
BASE_REF: ${{ github.event.pull_request.base.ref }}
3235
run: |
3336
mkdir -p /tmp/governance
34-
git show origin/${{ github.event.pull_request.base.ref }}:CLAUDE.md > /tmp/governance/CLAUDE.md 2>/dev/null || true
35-
git show origin/${{ github.event.pull_request.base.ref }}:.claude/agents/code-reviewer.md > /tmp/governance/code-reviewer.md 2>/dev/null || true
36-
git show origin/${{ github.event.pull_request.base.ref }}:.github/review-instructions.md > /tmp/governance/review-instructions.md 2>/dev/null || true
37-
git show origin/${{ github.event.pull_request.base.ref }}:.github/review-template.md > /tmp/governance/review-template.md 2>/dev/null || true
37+
git show "origin/${BASE_REF}:CLAUDE.md" > /tmp/governance/CLAUDE.md 2>/dev/null || true
38+
git show "origin/${BASE_REF}:.claude/agents/code-reviewer.md" > /tmp/governance/code-reviewer.md 2>/dev/null || true
39+
git show "origin/${BASE_REF}:.github/review-instructions.md" > /tmp/governance/review-instructions.md 2>/dev/null || true
40+
git show "origin/${BASE_REF}:.github/review-template.md" > /tmp/governance/review-template.md 2>/dev/null || true
3841
3942
- name: Generate app token
4043
id: app-token
@@ -43,13 +46,16 @@ jobs:
4346
app-id: ${{ secrets.APP_ID }}
4447
private-key: ${{ secrets.APP_PRIVATE_KEY }}
4548

49+
# SECURITY: Do NOT run `pip install -e ".[dev]"` here.
50+
# The PR branch's pyproject.toml is untrusted — build hooks could
51+
# exfiltrate secrets. Install only the tools Claude needs directly.
4652
- name: Set up Python
4753
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
4854
with:
4955
python-version: "3.12"
5056

51-
- name: Install dependencies
52-
run: pip install -e ".[dev]"
57+
- name: Install review tools (NOT the project)
58+
run: pip install ruff mypy
5359

5460
- name: Run Code Review
5561
id: review
@@ -58,7 +64,7 @@ jobs:
5864
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
5965
github_token: ${{ steps.app-token.outputs.token }}
6066
model: claude-sonnet-4-6
61-
claude_args: '--max-turns 30 --allowedTools "Bash(git diff:*),Bash(git log:*),Bash(gh pr comment:*),Bash(gh api:*),Bash(cat:*),Bash(pytest:*),Bash(python:*),Bash(ruff:*),Bash(mypy:*),Read,Glob,Grep,Write"'
67+
claude_args: '--max-turns 30 --allowedTools "Bash(git diff:*),Bash(git log:*),Bash(gh pr comment:*),Bash(gh api:*),Bash(cat:*),Bash(ruff:*),Bash(mypy:*),Read,Glob,Grep,Write"'
6268
prompt: |
6369
You are an adversarial code reviewer for Edictum Console — a **security product** (self-hostable agent operations console). A single vulnerability destroys the credibility of a startup that sells trust. Think like an attacker first, reviewer second.
6470

.github/workflows/security-sweep.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
runs-on: ubuntu-latest
2121
timeout-minutes: 30
2222
permissions:
23-
contents: write
23+
contents: read
2424
issues: write
2525
id-token: write
2626

@@ -216,11 +216,13 @@ jobs:
216216
python3 security/manage-baseline.py add <ID> --severity <level> --file <path> --description "<desc>"
217217
```
218218
219-
If baseline.json was modified, commit and push:
219+
If baseline.json was modified, create a PR (never push directly to main):
220220
```bash
221+
git checkout -b chore/security-sweep-$(date +%Y%m%d)
221222
git add security/baseline.json
222223
git commit -m "chore: update security baseline from weekly sweep"
223-
git push
224+
git push -u origin HEAD
225+
gh pr create --title "chore: update security baseline" --body "Automated update from weekly security sweep." --label "security"
224226
```
225227
226228
## Rules

0 commit comments

Comments
 (0)