mirror of
https://github.com/anthropics/claude-code.git
synced 2026-01-30 04:02:03 +00:00
fix: prevent API key exfiltration in dedupe workflow
Security fix to address potential prompt injection attack vector where malicious issue content could exploit gh api/comment permissions to exfiltrate the ANTHROPIC_API_KEY. Changes: - Remove gh api:* and gh issue comment:* from dedupe command allowed-tools - Command now outputs structured JSON to /tmp/dedupe-result.json - Comment posting moved to isolated workflow step without API key access - Added URL validation to prevent injection in comment content The Claude Code step can now only read issues (gh issue view/search/list), while comment posting happens in a separate step that only has GITHUB_TOKEN.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
---
|
||||
allowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(gh api:*), Bash(gh issue comment:*)
|
||||
allowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*)
|
||||
description: Find duplicate GitHub issues
|
||||
---
|
||||
|
||||
@@ -7,32 +7,33 @@ Find up to 3 likely duplicate issues for a given GitHub issue.
|
||||
|
||||
To do this, follow these steps precisely:
|
||||
|
||||
1. Use an agent to check if the Github issue (a) is closed, (b) does not need to be deduped (eg. because it is broad product feedback without a specific solution, or positive feedback), or (c) already has a duplicates comment that you made earlier. If so, do not proceed.
|
||||
1. Use an agent to check if the Github issue (a) is closed, (b) does not need to be deduped (eg. because it is broad product feedback without a specific solution, or positive feedback), or (c) already has a duplicates comment that you made earlier. If so, do not proceed and write `{"skip": true, "reason": "..."}` to `/tmp/dedupe-result.json`.
|
||||
2. Use an agent to view a Github issue, and ask the agent to return a summary of the issue
|
||||
3. Then, launch 5 parallel agents to search Github for duplicates of this issue, using diverse keywords and search approaches, using the summary from #1
|
||||
4. Next, feed the results from #1 and #2 into another agent, so that it can filter out false positives, that are likely not actually duplicates of the original issue. If there are no duplicates remaining, do not proceed.
|
||||
5. Finally, comment back on the issue with a list of up to three duplicate issues (or zero, if there are no likely duplicates)
|
||||
4. Next, feed the results from #1 and #2 into another agent, so that it can filter out false positives, that are likely not actually duplicates of the original issue. If there are no duplicates remaining, write `{"skip": true, "reason": "no duplicates found"}` to `/tmp/dedupe-result.json` and do not proceed.
|
||||
5. Finally, write a JSON file to `/tmp/dedupe-result.json` with the list of duplicate issue URLs. Do NOT post comments directly.
|
||||
|
||||
IMPORTANT: You must write output to `/tmp/dedupe-result.json` - never post comments directly. The workflow will handle posting.
|
||||
|
||||
The JSON output format must be exactly:
|
||||
```json
|
||||
{
|
||||
"duplicates": ["https://github.com/owner/repo/issues/123", "https://github.com/owner/repo/issues/456"]
|
||||
}
|
||||
```
|
||||
|
||||
Or if skipping:
|
||||
```json
|
||||
{
|
||||
"skip": true,
|
||||
"reason": "reason for skipping"
|
||||
}
|
||||
```
|
||||
|
||||
Notes (be sure to tell this to your agents, too):
|
||||
|
||||
- Use `gh` to interact with Github, rather than web fetch
|
||||
- Do not use other tools, beyond `gh` (eg. don't use other MCP servers, file edit, etc.)
|
||||
- Use `gh` to interact with Github for reading issues only, rather than web fetch
|
||||
- Do not use other tools beyond `gh` for reading (eg. don't use other MCP servers, file edit beyond the output JSON, etc.)
|
||||
- Do NOT use `gh issue comment` or `gh api` - these are not permitted for security reasons
|
||||
- Make a todo list first
|
||||
- For your comment, follow the following format precisely (assuming for this example that you found 3 suspected duplicates):
|
||||
|
||||
---
|
||||
|
||||
Found 3 possible duplicate issues:
|
||||
|
||||
1. <link to issue>
|
||||
2. <link to issue>
|
||||
3. <link to issue>
|
||||
|
||||
This issue will be automatically closed as a duplicate in 3 days.
|
||||
|
||||
- If your issue is a duplicate, please close it and 👍 the existing issue instead
|
||||
- To prevent auto-closure, add a comment or 👎 this comment
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
---
|
||||
- Output must be written to `/tmp/dedupe-result.json` - the workflow handles comment posting
|
||||
|
||||
67
.github/workflows/claude-dedupe-issues.yml
vendored
67
.github/workflows/claude-dedupe-issues.yml
vendored
@@ -23,14 +23,81 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run Claude Code slash command
|
||||
id: claude
|
||||
uses: anthropics/claude-code-base-action@beta
|
||||
with:
|
||||
prompt: "/dedupe ${{ github.repository }}/issues/${{ github.event.issue.number || inputs.issue_number }}"
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
claude_args: "--model claude-sonnet-4-5-20250929"
|
||||
# Note: GH_TOKEN only provides read access for issue viewing/searching
|
||||
# Comment posting is handled in a separate isolated step below
|
||||
claude_env: |
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# SECURITY: This step runs in isolation without access to ANTHROPIC_API_KEY
|
||||
# It only has GITHUB_TOKEN for posting comments, preventing secret exfiltration
|
||||
- name: Post duplicate comment (isolated from API key)
|
||||
if: success()
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
ISSUE_NUMBER=${{ github.event.issue.number || inputs.issue_number }}
|
||||
RESULT_FILE="/tmp/dedupe-result.json"
|
||||
|
||||
if [ ! -f "$RESULT_FILE" ]; then
|
||||
echo "No dedupe result file found, skipping comment"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if we should skip
|
||||
if jq -e '.skip' "$RESULT_FILE" > /dev/null 2>&1; then
|
||||
REASON=$(jq -r '.reason // "unknown"' "$RESULT_FILE")
|
||||
echo "Skipping comment: $REASON"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get duplicates array
|
||||
DUPLICATES=$(jq -r '.duplicates // []' "$RESULT_FILE")
|
||||
COUNT=$(echo "$DUPLICATES" | jq 'length')
|
||||
|
||||
if [ "$COUNT" -eq 0 ]; then
|
||||
echo "No duplicates found, skipping comment"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Build comment body (limit to 3 duplicates for safety)
|
||||
SAFE_COUNT=$((COUNT > 3 ? 3 : COUNT))
|
||||
|
||||
COMMENT="Found $SAFE_COUNT possible duplicate issue"
|
||||
if [ "$SAFE_COUNT" -ne 1 ]; then
|
||||
COMMENT="${COMMENT}s"
|
||||
fi
|
||||
COMMENT="${COMMENT}:"
|
||||
COMMENT="${COMMENT}
|
||||
"
|
||||
|
||||
for i in $(seq 0 $((SAFE_COUNT - 1))); do
|
||||
URL=$(echo "$DUPLICATES" | jq -r ".[$i]")
|
||||
# Validate URL format to prevent injection
|
||||
if [[ "$URL" =~ ^https://github\.com/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/issues/[0-9]+$ ]]; then
|
||||
COMMENT="${COMMENT}
|
||||
$((i + 1)). $URL"
|
||||
fi
|
||||
done
|
||||
|
||||
COMMENT="${COMMENT}
|
||||
|
||||
This issue will be automatically closed as a duplicate in 3 days.
|
||||
|
||||
- If your issue is a duplicate, please close it and 👍 the existing issue instead
|
||||
- To prevent auto-closure, add a comment or 👎 this comment
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)"
|
||||
|
||||
# Post the comment
|
||||
gh issue comment "$ISSUE_NUMBER" --repo "${{ github.repository }}" --body "$COMMENT"
|
||||
echo "Posted duplicate comment on issue #$ISSUE_NUMBER"
|
||||
|
||||
- name: Log duplicate comment event to Statsig
|
||||
if: always()
|
||||
env:
|
||||
|
||||
Reference in New Issue
Block a user