Skip to content

Commit 35eeabb

Browse files
committed
split job to better scope permissions. fixed docs inconsistency
1 parent 601e9db commit 35eeabb

3 files changed

Lines changed: 61 additions & 19 deletions

File tree

.cursor/skills/triage-issues/SKILL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Every issue type has its own research approach.
5151
## Stage 3: Summary
5252

5353
When finishing the analysis, outputs the findings using this exact template:
54-
```markdown
54+
~~~
5555
## Triage Report
5656
**Size**: [Tiny/Small/Medium/Large]
5757
**Type**: [Question/Bug]
@@ -75,4 +75,4 @@ When finishing the analysis, outputs the findings using this exact template:
7575
```
7676
// code
7777
```
78-
```
78+
~~~

.cursor/skills/triage-issues/source-map.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,6 @@ Keep this map structural (modules, packages, entry points, label → location).
88
Do not add line numbers or exhaustive class lists — they go stale. When a
99
mapping below is wrong because the code moved, fix the boundary here.
1010

11-
Maintenance: a drift check in `.github/workflows/triage-source-map-drift.yml`
12-
verifies the directories and entry-point files referenced here still exist. If
13-
you move a boundary, update both this file and the DIRS/FILES lists in that
14-
workflow.
15-
16-
1711
## Modules at a glance
1812

1913
| Module label | Directory | Root package | What it is |

.github/workflows/claude-issue-triage.yml

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@ name: Triage issue with Claude
1010
# triage it using only local information — the skill plus the checked-out source.
1111
# It then posts the resulting report back onto the issue as a sticky comment.
1212
#
13-
# Security: the issue body/comments are untrusted input. Claude runs fully
14-
# offline for triage — no WebFetch/WebSearch and no Bash, so it cannot follow
15-
# links or exfiltrate anything. It only produces a local report file; a separate
16-
# deterministic step (which holds the issues:write permission) posts the comment,
17-
# so a successful prompt injection cannot post a tampered comment on its own.
13+
# Security: the issue body/comments are untrusted input. Triage is split across
14+
# two jobs so the writable token is never present while the model runs:
15+
# * `triage` runs Claude with `contents: read` only — the GITHUB_TOKEN in its
16+
# environment cannot write to issues. Claude also runs fully offline (no
17+
# WebFetch/WebSearch and no Bash), so it cannot follow links or exfiltrate
18+
# anything. It only produces a local report file.
19+
# * `comment` is a separate, deterministic job that holds `issues: write` and
20+
# does nothing but post the report. It never runs the model.
21+
# So even a successful prompt injection has no write-capable token to abuse and
22+
# cannot post a tampered comment on its own.
1823

1924
on:
2025
issues:
@@ -28,9 +33,9 @@ on:
2833
required: true
2934
type: string
3035

36+
# Least privilege by default; each job narrows or widens this as needed.
3137
permissions:
3238
contents: read
33-
issues: write # only the final step uses this, to upsert the triage comment
3439

3540
jobs:
3641
triage:
@@ -46,13 +51,18 @@ jobs:
4651
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association))
4752
runs-on: ubuntu-latest
4853
timeout-minutes: 15
54+
# Read-only: the token handed to Claude below cannot write to issues.
55+
permissions:
56+
contents: read
4957
concurrency:
5058
group: claude-issue-triage-${{ github.repository }}-${{ github.event.inputs.issue_number || github.event.issue.number }}
5159
cancel-in-progress: true
60+
outputs:
61+
issue: ${{ steps.prep.outputs.issue }}
5262
steps:
5363
# Check out the repo so the triage skill (.cursor/skills/triage-issues/)
5464
# and the module source are available locally for offline triage.
55-
- uses: actions/checkout@v6
65+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
5666
with:
5767
fetch-depth: 1
5868
persist-credentials: false
@@ -102,13 +112,15 @@ jobs:
102112
uses: anthropics/claude-code-action@fefa07e9c665b7320f08c3b525980457f22f58aa # v1.0.111
103113
with:
104114
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
105-
# Use the runner-injected GITHUB_TOKEN rather than minting one via OIDC.
115+
# Use the runner-injected GITHUB_TOKEN. Because this job's permissions
116+
# are `contents: read`, this token is read-only and cannot post
117+
# comments or edit issues even if the model is manipulated.
106118
github_token: ${{ github.token }}
107119
# Local-only triage. Claude may read the checked-out repo (skill +
108120
# source) and write only the local report file. No network tools
109121
# (WebFetch/WebSearch) and no Bash, so it cannot follow links, run
110122
# commands, or exfiltrate anything. It cannot edit repo files or post
111-
# comments — the next workflow step does that deterministically.
123+
# comments — the separate `comment` job does that deterministically.
112124
claude_args: |
113125
--allowedTools "Read,Glob,Grep,Write"
114126
--disallowedTools "Edit,MultiEdit,NotebookEdit,WebFetch,WebSearch,Bash"
@@ -152,17 +164,53 @@ jobs:
152164
put open items under "Missing Information / Questions for User". Write only the
153165
report to that file — no extra commentary.
154166
167+
- name: Ensure triage report was produced
168+
run: |
169+
set -euo pipefail
170+
REPORT_FILE="triage/triage-report.md"
171+
if [ ! -s "$REPORT_FILE" ]; then
172+
echo "::error::Claude did not produce $REPORT_FILE"
173+
exit 1
174+
fi
175+
176+
# Hand the report to the privileged job via an artifact. Nothing with a
177+
# writable token has run up to this point.
178+
- name: Upload triage report
179+
uses: actions/upload-artifact@v4
180+
with:
181+
name: triage-report
182+
path: triage/triage-report.md
183+
if-no-files-found: error
184+
retention-days: 1
185+
186+
comment:
187+
name: Post triage comment
188+
needs: triage
189+
runs-on: ubuntu-latest
190+
timeout-minutes: 5
191+
# The only job that can write to issues. It runs no model — it just posts
192+
# the report produced by the read-only `triage` job.
193+
permissions:
194+
contents: read
195+
issues: write
196+
steps:
197+
- name: Download triage report
198+
uses: actions/download-artifact@v4
199+
with:
200+
name: triage-report
201+
path: triage
202+
155203
- name: Post triage report as an issue comment
156204
env:
157205
GH_TOKEN: ${{ github.token }}
158206
REPO: ${{ github.repository }}
159-
ISSUE: ${{ steps.prep.outputs.issue }}
207+
ISSUE: ${{ needs.triage.outputs.issue }}
160208
run: |
161209
set -euo pipefail
162210
163211
REPORT_FILE="triage/triage-report.md"
164212
if [ ! -s "$REPORT_FILE" ]; then
165-
echo "::error::Claude did not produce $REPORT_FILE"
213+
echo "::error::missing $REPORT_FILE artifact from triage job"
166214
exit 1
167215
fi
168216

0 commit comments

Comments
 (0)