Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/commands/label-issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ TASK OVERVIEW:
- If you find similar issues using ./scripts/gh.sh search, consider using a "duplicate" label if appropriate. Only do so if the issue is a duplicate of another OPEN issue.

5. Apply the selected labels:
- Use `./scripts/edit-issue-labels.sh --issue NUMBER --add-label LABEL1 --add-label LABEL2` to apply your selected labels
- Use `./scripts/edit-issue-labels.sh --add-label LABEL1 --add-label LABEL2` to apply your selected labels (issue number is read from the workflow event)
- DO NOT post any comments explaining your decision
- DO NOT communicate directly with users
- If no labels are clearly applicable, do not apply any labels
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/issue-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ jobs:

- name: Run Claude Code for Issue Triage
uses: anthropics/claude-code-action@main
env:
CLAUDE_CODE_SCRIPT_CAPS: '{"edit-issue-labels.sh":2}'
with:
prompt: "/label-issue REPO: ${{ github.repository }} ISSUE_NUMBER: ${{ github.event.issue.number }}"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
Expand Down
21 changes: 21 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,26 @@ runs:
cd ${GITHUB_ACTION_PATH}
bun install --production

- name: Install subprocess isolation dependencies
# Install subprocess isolation dependencies when processing content from non-write users.
# Best-effort: skips on non-Linux or when sudo/apt unavailable (self-hosted runners).
if: ${{ inputs.allowed_non_write_users != '' && env.CLAUDE_CODE_SUBPROCESS_ENV_SCRUB != '0' && runner.os == 'Linux' }}
continue-on-error: true
shell: bash
run: |
if command -v apt-get >/dev/null && command -v sudo >/dev/null; then
for i in 1 2 3; do
sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends bubblewrap socat && break
echo "apt-get attempt $i failed, retrying..."
sleep 5
done
fi
# Ubuntu 24.04+ restricts unprivileged user namespaces via AppArmor.
# The sysctl doesn't exist on older kernels — that's fine.
if [ -f /proc/sys/kernel/apparmor_restrict_unprivileged_userns ] && command -v sudo >/dev/null; then
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
fi

- name: Run Claude Code Action
id: run
shell: bash
Expand All @@ -214,6 +234,7 @@ runs:
ALLOWED_BOTS: ${{ inputs.allowed_bots }}
ALLOWED_NON_WRITE_USERS: ${{ inputs.allowed_non_write_users }}
CLAUDE_CODE_SUBPROCESS_ENV_SCRUB: ${{ env.CLAUDE_CODE_SUBPROCESS_ENV_SCRUB || (inputs.allowed_non_write_users != '' && '1') || '' }}
CLAUDE_CODE_SCRIPT_CAPS: ${{ env.CLAUDE_CODE_SCRIPT_CAPS || '' }}
INCLUDE_COMMENTS_BY_ACTOR: ${{ inputs.include_comments_by_actor }}
EXCLUDE_COMMENTS_BY_ACTOR: ${{ inputs.exclude_comments_by_actor }}
GITHUB_RUN_ID: ${{ github.run_id }}
Expand Down
4 changes: 3 additions & 1 deletion docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
- Accepts either a comma-separated list of specific usernames or `*` to allow all users
- **Should be used with extreme caution** as it bypasses the primary security mechanism of this action
- Is designed for automation workflows where user permissions are already restricted by the workflow's permission scope
- When set, Claude does a best-effort scrub of Anthropic, cloud, and GitHub Actions secrets from subprocess environments. This reduces but does not eliminate prompt injection risk — keep workflow permissions minimal and validate all outputs. Set `CLAUDE_CODE_SUBPROCESS_ENV_SCRUB: 0` in your workflow or job `env:` block to opt out.
- When set, Claude does a best-effort scrub of Anthropic, cloud, and GitHub Actions secrets from subprocess environments. On Linux runners with bubblewrap available, subprocesses additionally run with PID-namespace isolation. This reduces but does not eliminate prompt injection risk — keep workflow permissions minimal and validate all outputs. Set `CLAUDE_CODE_SUBPROCESS_ENV_SCRUB: 0` in your workflow or job `env:` block to opt out.
- Optionally set `CLAUDE_CODE_SCRIPT_CAPS` in your workflow `env:` block to limit how many times Claude can call specific scripts per run. Value is JSON: `{"script-name.sh": maxCalls}`. Example: `CLAUDE_CODE_SCRIPT_CAPS: '{"edit-issue-labels.sh":2}'` allows at most 2 calls to `edit-issue-labels.sh`. Useful for write-capable helper scripts.
- When using `allowed_non_write_users`, always pass `github_token: ${{ secrets.GITHUB_TOKEN }}`. The auto-generated workflow token is scoped to the job's declared permissions and expires automatically, which limits blast radius. Personal access tokens are not recommended for untrusted-input workflows.
- **Token Permissions**: The GitHub app receives only a short-lived token scoped specifically to the repository it's operating in
- **No Cross-Repository Access**: Each action invocation is limited to the repository where it was triggered
- **Limited Scope**: The token cannot access other repositories or perform actions beyond the configured permissions
Expand Down
3 changes: 2 additions & 1 deletion docs/solutions.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,8 @@ jobs:
- `./scripts/gh.sh label list` to see available labels

Based on your analysis, add the appropriate labels using:
`./scripts/edit-issue-labels.sh --issue [number] --add-label "label1" --add-label "label2"`
`./scripts/edit-issue-labels.sh --add-label "label1" --add-label "label2"`
(the issue number is read automatically from the workflow event)

If it appears to be a duplicate, post a comment mentioning the original issue.

Expand Down
26 changes: 11 additions & 15 deletions scripts/edit-issue-labels.sh
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
#!/usr/bin/env bash
#
# Edits labels on a GitHub issue.
# Usage: ./scripts/edit-issue-labels.sh --issue 123 --add-label bug --add-label needs-triage --remove-label untriaged
# Usage: ./scripts/edit-issue-labels.sh --add-label bug --add-label needs-triage --remove-label untriaged
#
# The issue number is read from the workflow event payload.
#

set -euo pipefail

ISSUE=""
# Read from event payload so the issue number is bound to the triggering event
ISSUE=$(jq -r '.issue.number // empty' "${GITHUB_EVENT_PATH:?GITHUB_EVENT_PATH not set}")
if ! [[ "$ISSUE" =~ ^[0-9]+$ ]]; then
echo "Error: no issue number in event payload" >&2
exit 1
fi

ADD_LABELS=()
REMOVE_LABELS=()

# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--issue)
ISSUE="$2"
shift 2
;;
--add-label)
ADD_LABELS+=("$2")
shift 2
Expand All @@ -26,20 +30,12 @@ while [[ $# -gt 0 ]]; do
shift 2
;;
*)
echo "Error: unknown argument (only --add-label and --remove-label are accepted)" >&2
exit 1
;;
esac
done

# Validate issue number
if [[ -z "$ISSUE" ]]; then
exit 1
fi

if ! [[ "$ISSUE" =~ ^[0-9]+$ ]]; then
exit 1
fi

if [[ ${#ADD_LABELS[@]} -eq 0 && ${#REMOVE_LABELS[@]} -eq 0 ]]; then
exit 1
fi
Expand Down
33 changes: 28 additions & 5 deletions src/github/operations/git-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,34 @@ export async function configureGitAuth(
console.log("No existing authentication headers to remove");
}

// Update the remote URL to include the token for authentication
console.log("Updating remote URL with authentication...");
const remoteUrl = `https://x-access-token:${githubToken}@${serverUrl.host}/${context.repository.owner}/${context.repository.repo}.git`;
await $`git remote set-url origin ${remoteUrl}`;
console.log("✓ Updated remote URL with authentication token");
if (process.env.ALLOWED_NON_WRITE_USERS) {
// When processing content from non-write users, use a credential helper
// instead of embedding the token in the remote URL. The helper script reads
// from GH_TOKEN at auth time, so .git/config stays token-free. Written as a
// file to avoid shell-escaping the helper body; placed under
// GITHUB_ACTION_PATH so it sits alongside the action source.
console.log("Configuring git credential helper...");
process.env.GH_TOKEN = githubToken;
const helperPath = join(
process.env.GITHUB_ACTION_PATH || homedir(),
".git-credential-gh-token",
);
await writeFile(
helperPath,
'#!/bin/sh\necho username=x-access-token\necho password="$GH_TOKEN"\n',
{ mode: 0o700 },
);
const cleanUrl = `https://${serverUrl.host}/${context.repository.owner}/${context.repository.repo}.git`;
await $`git remote set-url origin ${cleanUrl}`;
await $`git config credential.helper ${helperPath}`;
console.log("✓ Configured credential helper");
} else {
// Update the remote URL to include the token for authentication
console.log("Updating remote URL with authentication...");
const remoteUrl = `https://x-access-token:${githubToken}@${serverUrl.host}/${context.repository.owner}/${context.repository.repo}.git`;
await $`git remote set-url origin ${remoteUrl}`;
console.log("✓ Updated remote URL with authentication token");
}

console.log("Git authentication configured successfully");
}
Expand Down
Loading