Skip to content

ci: triage-as-bestaxbot, safe robobun-style repro drafting + read-only security scan - #361

Merged
allxsmith merged 15 commits into
mainfrom
claude/github-actions-triage-author-b3h75o
Aug 2, 2026
Merged

allxsmith merged 15 commits into
mainfrom
claude/github-actions-triage-author-b3h75o

Conversation

@allxsmith

@allxsmith allxsmith commented Jul 24, 2026 •

Copy link
Copy Markdown
Owner

Description

GitHub Actions / CI automation only (no product code). Four commits, building up from the triage-author fix that motivated the rest:

  1. ci: post AI triage comments as bestaxbot — triage dedupe comments now post under the bestaxbot machine identity (robobun-style) instead of the anonymous github-actions[bot]; auto-close-duplicates.mjs + the triage command marker-checks learn the new author.
  2. ci: add author-only repro and security-scan workflows — the two new automations below.
  3. ci: guard AI entry points against re-trigger and flagged content — repo-wide hardening.
  4. docs: document repro and security-scan automation.
  • bulma-ui (@allxsmith/bestax-bulma)
  • create-bestax (create-bestax)
  • docs (@allxsmith/bestax-docs)
  • Other (please specify): GitHub Actions workflows / CI + AI automation

The two new automations (robobun-style, hardened for GitHub-hosted runners):

  • claude-repro.yml (author-only, 4 jobs, PAT-free) — a triage+ maintainer labels an issue claude-repro; Claude drafts a minimal Jest reproduction test and github-actions[bot] posts it for a human to run. Nothing executes the draft.
  • ai-scan.yml (separate workflow) — read-only Claude scan of new issues/PRs for malicious code, prompt injection, and social engineering; applies needs-security-review fail-closed. Holds no PAT and no write tools; the flag is advisory (pauses claude-repro/claude-fix, does not block @claude/@bestaxbot).

Two security invariants drive the design. A red-team of the first-draft two-job repro found it would leak CLAUDE_CODE_OAUTH_TOKEN to a public comment (secret-masking scrubs logs, not comment bodies) and let an @mention re-trigger a write-capable session (contains() matches the raw substring):

  • I1 — the model-auth token never shares a job with code execution (repro is author-only; the drafting job has no Bash/gh/network/Task tool, so it can't read env, fetch, or run anything).
  • I2 — no attacker/Claude free text reaches a re-trigger-capable comment (drafts are deterministically sanitized and posted via GITHUB_TOKEN, whose comments never re-trigger workflows).

Repo-wide guards (commit 3): claude.yml bestaxbot sender-exclusion (also closes the re-trigger class for the shipped triage path) + needs-security-review refusal; bestaxbot-reply.yml / claude-implement.yml flag refusal; harden-runner + kill-switch header on ai-triage.yml.

Related Issue(s)

Refs #362

Type of Change

  • Bug fix
  • New feature
  • Refactor
  • Documentation
  • Build tooling
  • Other (please describe): CI / AI-automation security

Checklist

  • My code follows the project style guidelines
  • I have performed a self-review of my code
  • I have added tests that prove my fix is effective or that my feature works — scripts/auto-close-duplicates.test.mjs now covers the auto-close decision logic (15 tests, mutation-checked), and root pnpm test runs node --test "scripts/*.test.mjs" so root scripts are no longer invisible to CI. The two shell parsers (publish sanitizer, scan-verdict parser) remain untested — they are embedded in workflow YAML and need extracting first: [Refactor] Extract the shell security parsers from workflow YAML so they can be tested #454. Workflows validated below and need a canary run (see Additional Context)
  • I have added/updated documentation as needed
  • I have updated Storybook stories as needed (bulma-ui) — n/a, no UI change
  • My changes require a change to the documentation
  • All new and existing tests passed — no product tests affected; workflows verified by YAML lint + logic unit tests
  • Any relevant dependencies are updated — n/a
  • If this PR changes commands, conventions, or package structure, the affected CLAUDE.md files are updated

Screenshots / Demos

n/a — CI automation.

Additional Context

Verification done: YAML valid on all 18 workflows; the publish sanitizer unit-tested (machine markers broken, @mentions defanged, legitimate imports untouched); the scan verdict parser tested fail-closed against clean/flagged/crashed/missing/forged inputs; prettier clean; no tabs; commitlint passed all commits.

Two rollout notes:

  1. harden-runner on the existing ai-triage.yml job ships in audit, not block. Flipping a live workflow straight to egress-block risks breaking it if claude-code-action's runtime egress needs an un-allow-listed host (it isn't cleanly documented). Audit monitors without blocking. Follow-up: after one real triage run, confirm the endpoint list and flip to block. The new jobs already run in block.
  2. Canary before relying on these (event-triggered, security-critical): on a maintainer-authored issue, apply claude-repro (confirm no PAT in the jobs, the draft posts as github-actions[bot], the label auto-removes); open a benign fake-injection issue (confirm it flags with no comment posted) and a normal issue (confirm it scans clean).

New labels needed: claude-repro (green #0e8a16), needs-security-review (red #b60205). Both were created ahead of merge, so neither path depends on a label being created on demand.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added automated, read-only security scans that can flag items for security review.
    • Added an author-only reproduction workflow that drafts tests without executing them.
  • Bug Fixes
    • Improved duplicate detection and triage marker recognition across current and legacy automation accounts.
    • Prevented automated fix, reply, and investigation workflows from running during security review.
  • Documentation
    • Updated guidance for reproduction tests, security scans, automation states, and triage behavior.

State at merge (updated after review rounds)

The security scanner does not start on merge. AI_SCAN_MODE is now explicit opt-in — it
runs only when set to on or y, and every other value (off, unset, empty, a typo)
disables it. Enable it deliberately once you are ready; merging alone changes nothing. Same for
claude-repro, which needs its label applied by a triage+ user.

Labels exist: claude-repro and needs-security-review were created ahead of merge, so
neither feature is inert on arrival, and neither depends on a label being created on demand.
(An earlier revision claimed the REST add-labels endpoint auto-creates a missing label. GitHub
does not document that either way, so the claim was removed rather than inverted — deep review
finding 3.)

Changes made in response to review, beyond the original four commits:

  • Closed the Read-based token-exfil path in claude-repro (Collect draft refuses a draft
    carrying a live credential, literal or base64/hex encoded), and rewrote I1 to state plainly
    that the encoding check is a backstop rather than a proof.
  • Scoped the repro comment edit by marker. --edit-last could overwrite an unrelated
    github-actions[bot] comment — including a historical <!-- ai-triage:dedupe --> marker
    that auto-close-duplicates.mjs still reads — destroying an auto-close candidate.
  • Corrected several security comments that claimed more than the mechanism delivered: the
    needs-security-review blast radius (it gates every entry point this repo controls; third
    party reviewers are not gated, and there is an open-time window), and the scan session's
    token, which is write-scoped rather than read-only.
  • Documented every AI repository variable and its unset default, after finding
    AI_LOOP_ENABLED had been documented backwards.

Final review round added .github/CLAUDE.md — the security contract for anything in
.github/workflows/. Copilot's allowedTools advisory asked for a comment at one call site;
the durable form is a document covering every workflow, so it states the two invariants,
allowlists as a confinement boundary that never widens casually, SHA pinning, == 'on' opt-in
gating, fail-closed verdicts vs fail-open budgets, marker-scoped comment edits, and a reviewer
checklist. Both root CLAUDE.md and this PR's other lessons feed it. Same commit also:

  • Aligned actions/checkout in ai-scan.yml and claude-repro.yml to the repo-wide pin. Both
    sat on 9c091bb (v7.0.0, twelve commits behind) while the other nineteen usages were on
    3d3c42e5 (v7.0.1) — and both were commented # v7, so the drift was invisible to a
    reader. Now exactly one SHA repo-wide.
  • Fixed the auto-close-duplicates.mjs header, which still described the objection rule as "no
    non-bot user" after the logic moved to isAutomationAuthor().

SECURITY.md and README.md describe the scanner as active while AI_SCAN_MODE ships
off. That is deliberate: the variable is turned on shortly after merge, and the steady state is
on, so wording the docs around a temporary off-state would need reverting immediately.

Known gaps, all filed: #454 (extract and test the two shell parsers), #455 (split ai-scan
so the model session runs with contents: read), #457 (publish triage comments from a
structured payload — this PR moves triage to a PAT-authored identity, whose comments can
re-trigger workflows, so I2 is currently held by every consumer excluding bestaxbot rather than
by construction; raised by CodeRabbit and verified), and #456 (the two post-merge obligations —
flipping ai-triage harden-runner from audit to block after one real run, and the canary
checklist for both new automations). #456 exists because those two were previously recorded
only in this description, which a squash merge discards.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds read-only security scanning and author-only reproduction drafting. It tightens AI workflow gates, switches triage attribution to a PAT-backed bot, broadens deduplication marker recognition, and adds validation coverage.

Changes

Security controls and reproduction workflow

Layer / File(s) Summary
Security scan workflow
.github/workflows/ai-scan.yml
Adds gated, budgeted, hardened read-only scans for new issues and pull requests. Invalid or flagged results receive needs-security-review.
Author-only reproduction drafting
.github/workflows/claude-repro.yml
Adds authorized reproduction-test drafting, sanitized publication, credential filtering, and label cleanup without executing generated tests.
Security-aware AI gates and documentation
.github/workflows/bestaxbot-reply.yml, .github/workflows/claude-implement.yml, .github/workflows/claude.yml, .github/CLAUDE.md, CLAUDE.md, README.md, SECURITY.md, docs/docs/guides/getting-started/ai-development.md
Blocks selected workflows for security-flagged items and documents workflow controls, scan behavior, reproduction drafting, and configuration.

Triage execution and attribution

Layer / File(s) Summary
Hardened triage execution
.github/workflows/ai-triage.yml
Documents expanded switches and restrictions, adds runner hardening, and passes the AI loop PAT to Claude.
Triage marker checks
.claude/commands/triage-*.md
Recognizes current and legacy automation authors when checking triage markers.

Automation-authored deduplication

Layer / File(s) Summary
Automation author classification and validation
scripts/auto-close-duplicates.mjs, scripts/auto-close-duplicates.test.mjs, package.json
Recognizes bestaxbot and bot users as automation authors for marker detection, veto handling, and scan exclusions. Adds Node test coverage and includes it in the root test command.
Duplicate close candidate criteria
.github/workflows/auto-close-duplicates.yml
Updates candidate criteria documentation for markers posted by multiple automation accounts.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitHubEvent
  participant ai-scan
  participant GitHubAPI
  participant Claude
  GitHubEvent->>ai-scan: open issue or pull request
  ai-scan->>GitHubAPI: check and charge daily scan budget
  ai-scan->>Claude: run restricted read-only scan
  Claude-->>ai-scan: return security verdict
  ai-scan->>GitHubAPI: apply needs-security-review when verdict is not clean
Loading
sequenceDiagram
  participant IssueAuthor
  participant claude-repro
  participant GitHubIssue
  participant Claude
  IssueAuthor->>claude-repro: apply claude-repro label
  claude-repro->>GitHubIssue: verify authorization and labels
  claude-repro->>Claude: request REPRO-DRAFT
  Claude-->>claude-repro: return draft or infeasible result
  claude-repro->>GitHubIssue: publish sanitized comment
  claude-repro->>GitHubIssue: remove claude-repro label
Loading

Possibly related issues

  • allxsmith/bestax#362: Covers the claude-repro, ai-scan, and security-gating workflows added here.

Possibly related PRs

Suggested labels: released, claude-assisted

Suggested reviewers: bestaxbot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main CI automation changes, including bestaxbot attribution, reproduction drafting, and read-only security scanning.
Description check ✅ Passed The description follows the template, explains the changes and safeguards, identifies affected areas, references issue #362, and documents verification and rollout notes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/github-actions-triage-author-b3h75o

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://0e0bd899.bestax.pages.dev

@allxsmith allxsmith changed the title ci: post AI triage comments as bestaxbot instead of github-actions[bot] ci: triage-as-bestaxbot, safe robobun-style repro drafting + read-only security scan Jul 24, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
scripts/auto-close-duplicates.mjs (1)

85-109: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add regression coverage for the expanded automation-author contract.

Cover bestaxbot as a regular User, legacy [bot] authors, human users, marker selection, veto filtering, and issue skipping to prevent future changes from silently reintroducing incorrect auto-closes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/auto-close-duplicates.mjs` around lines 85 - 109, Add regression
tests for isAutomationAuthor and findMarkerComment covering bestaxbot as a
regular User, legacy [bot] authors, and human users; verify the latest valid
marker comment is selected, veto comments prevent closure, and issues with no
eligible marker are skipped. Use representative comment and issue fixtures to
preserve the expanded automation-author contract and auto-close behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ai-triage.yml:
- Around line 332-350: Constrain the PAT-backed comment commands in the
workflow’s Claude tool allowlist by routing issue and pull-request comment
operations through a trusted wrapper. The wrapper must fix the repository and
target number, allow only body updates and the required edit-last behavior, and
reject delete-last, repo overrides, and alternate targets; update the existing
Bash permissions to use this wrapper instead of unrestricted gh issue/pr comment
commands.

In @.github/workflows/auto-close-duplicates.yml:
- Around line 8-11: Revert the comment-only change in the workflow file so no
modifications remain under .github; do not move or add documentation there as
part of this fix.

In @.github/workflows/claude-repro.yml:
- Around line 105-106: Add issues: read to the job-level permissions block for
the author job alongside contents: read, preserving the existing contents
permission so gh issue view can access issue metadata and comments.

In @.github/workflows/claude.yml:
- Around line 35-60: Keep the existing blocking behavior for flagged items and
update the documentation to match it: retain the needs-security-review checks in
.github/workflows/claude.yml lines 35-60 and
.github/workflows/bestaxbot-reply.yml lines 55-56, then revise
.github/workflows/ai-scan.yml lines 10-15, CLAUDE.md lines 107-113, and
docs/docs/guides/getting-started/ai-development.md lines 97-111 to state that
the label gates both `@claude` and `@bestaxbot` entry points and remove
contradictory advisory-only guidance.

---

Nitpick comments:
In `@scripts/auto-close-duplicates.mjs`:
- Around line 85-109: Add regression tests for isAutomationAuthor and
findMarkerComment covering bestaxbot as a regular User, legacy [bot] authors,
and human users; verify the latest valid marker comment is selected, veto
comments prevent closure, and issues with no eligible marker are skipped. Use
representative comment and issue fixtures to preserve the expanded
automation-author contract and auto-close behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3c258c1b-f56d-4775-8490-b4b0401bac2f

📥 Commits

Reviewing files that changed from the base of the PR and between 71fb96f and 065ea43.

📒 Files selected for processing (13)
  • .claude/commands/triage-dedupe.md
  • .claude/commands/triage-find-duplicate-prs.md
  • .claude/commands/triage-find-issues.md
  • .github/workflows/ai-scan.yml
  • .github/workflows/ai-triage.yml
  • .github/workflows/auto-close-duplicates.yml
  • .github/workflows/bestaxbot-reply.yml
  • .github/workflows/claude-implement.yml
  • .github/workflows/claude-repro.yml
  • .github/workflows/claude.yml
  • CLAUDE.md
  • docs/docs/guides/getting-started/ai-development.md
  • scripts/auto-close-duplicates.mjs

Comment on lines +332 to +350
# passed explicitly so the action skips its OIDC→app token
# exchange (#312). It is bestaxbot's PAT (AI_LOOP_PAT — the same
# machine account claude-implement.yml and the PR loop use), so
# triage comments are credited to bestaxbot instead of the
# anonymous github-actions[bot] that GITHUB_TOKEN produced
# before (and claude[bot] before #312). bestaxbot is a machine
# USER account, not a Bot-type app: anything probing for triage
# comments must match the marker + (bestaxbot OR a Bot-type
# author) — auto-close-duplicates.mjs and the marker checks in
# .claude/commands/triage-*.md do exactly that; never probe one
# specific login. Trade-off vs GITHUB_TOKEN: PAT-authored
# comments DO emit issue_comment events that can re-trigger
# workflows — every comment-triggered workflow already gates
# bestaxbot out (bestaxbot-reply.yml excludes it as sender;
# claude.yml requires a literal "@claude", which the HARD RULES
# below forbid writing), and the tool allowlist below confines
# the PAT to GET-only reads plus the two comment commands.
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }}
github_token: ${{ secrets.AI_LOOP_PAT }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

sed -n '326,370p' .github/workflows/ai-triage.yml
gh issue comment --help | rg -- '--(edit-last|delete-last)|--repo'
gh pr comment --help | rg -- '--(edit-last|delete-last)|--repo'

Repository: allxsmith/bestax

Length of output: 3975


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Inspect the command allowlist, prompt boundaries, and any wrapper usage around gh comment commands.
rg -n "allowedTools|disallowedTools|gh pr comment|gh issue comment|comment:|REPO|NUMBER|delete-last|edit-last|--repo|-R" .github .claude 2>/dev/null || true

# Look for files that may define/parse command arguments for gh comment commands.
git ls-files | rg '(^|/)(claude|ai-triage|triage|comment|wrap|bash)' | sed -n '1,200p'

Repository: allxsmith/bestax

Length of output: 29633


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Demonstrate that a directly allowed Bash command can invoke the same gh command
# with --delete-last and target another repo, without a wrapper or sentinel.
python3 - <<'PY'
from pathlib import Path

for path in [
    ".github/workflows/ai-triage.yml",
    ".claude/commands/triage-find-issues.md",
    ".claude/commands/triage-find-duplicate-prs.md",
    ".claude/commands/triage-dedupe.md",
]:
    text = Path(path).read_text()
    print(f'--- {path} ---')
    for needle in ('allowed-tools', '--edit-last', '--delete-last', 'gh issue comment', 'gh pr comment', '--repo', 'REPO', 'NUMBER'):
        if needle in text:
            print(f'{needle}: present')
print("--- command-wildcard parser behavior ---")
allowed = ["Bash(gh issue comment:*)", "Bash(gh pr comment:*)", "Bash(gh issue view:*)"]
samples = [
    "gh issue comment 123 --repo allxsmith/bad --delete-last --edit-last --yes",
    "gh issue comment 123 --repo allxsmith/bad --edit-last --body x",
    "gh issue comment 123",
    "gh pr comment 123 --repo allxsmith/bad --delete-last --edit-last --yes",
    "gh issue view 123 --raw-field name",
]
for cmd in samples:
    print(cmd, "=>", any(cmd.startswith(prefix.split("(",1)[1][:-1]) for prefix in allowed))
PY

# Read the small triage command prompt files that define allowed command usage.
for f in .claude/commands/triage-dedupe.md .claude/commands/triage-find-duplicate-prs.md .claude/commands/triage-find-issues.md; do
  echo "===== $f ====="
  sed -n '1,90p' "$f"
done

Repository: allxsmith/bestax

Length of output: 12990


Constrain comment commands before exposing the PAT.

The workflow runs with github_token: ${{ secrets.AI_LOOP_PAT }} and allows Bash(gh issue comment:*) and Bash(gh pr comment:*), so a direct gh ... --edit-last --delete-last --yes command can delete bestaxbot comments without a wrapper guard. Put comment updates through a trusted command that fixes [REPO]/[NUMBER], allows only --body and required --edit-last, and rejects --delete-last, --repo, and alternate targets.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ai-triage.yml around lines 332 - 350, Constrain the
PAT-backed comment commands in the workflow’s Claude tool allowlist by routing
issue and pull-request comment operations through a trusted wrapper. The wrapper
must fix the repository and target number, allow only body updates and the
required edit-last behavior, and reject delete-last, repo overrides, and
alternate targets; update the existing Bash permissions to use this wrapper
instead of unrestricted gh issue/pr comment commands.

Comment thread .github/workflows/auto-close-duplicates.yml
Comment on lines +105 to +106
permissions:
contents: read # checkout the base default branch so Claude can read the API

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate file overview =="
if [ -f .github/workflows/claude-repro.yml ]; then
  wc -l .github/workflows/claude-repro.yml
  sed -n '90,155p' .github/workflows/claude-repro.yml
else
  echo "missing .github/workflows/claude-repro.yml"
fi

echo "== github issue/pr gh calls in workflows =="
rg -n "gh (issue|pr) view|permissions:" .github/workflows -g '*.yml' -g '*.yaml' || true

Repository: allxsmith/bestax

Length of output: 10316


🌐 Web query:

GitHub Actions GITHUB_TOKEN default permissions jobs permissions none gh API issues read

💡 Result:

When configuring the GITHUB_TOKEN in GitHub Actions, you can manage permissions at either the workflow level or the job level using the permissions key [1][2]. Key behaviors include: 1. Default Permissions: By default, newly created repositories in personal accounts or organizations are configured to grant the GITHUB_TOKEN read-only access to contents and packages [3][4]. You can change this default setting in the repository or organization's Actions settings to be more permissive if necessary [3]. 2. Using "none": When you use the permissions key, all permissions that are not explicitly specified are set to none [2]. For example, if you define permissions for a specific scope (e.g., contents: read) and omit others, those omitted scopes will have no access [5][2]. The metadata scope is an exception and will always retain read access [6]. 3. Handling "none" with API Issues: If you set permissions to none (or do not include a specific scope), any attempt to interact with the corresponding GitHub API endpoint (such as issues, pull requests, etc.) using the GITHUB_TOKEN will result in a 403 Forbidden error because the token lacks the necessary authorization [5][2]. 4. Implementation: To configure this, you can set permissions as follows in your YAML: permissions: issues: read # All other permissions default to 'none' To resolve issues where the GITHUB_TOKEN is being denied access, you must explicitly grant the required permission (e.g., issues: write or issues: read) within the job or workflow definition [1][2]. If your workflow requires access to resources outside the scope of the GITHUB_TOKEN, you should use a GitHub App installation access token or a Personal Access Token (PAT) stored as a secret instead [1][7].

Citations:


🌐 Web query:

GitHub Actions repository settings GITHUB_TOKEN public repository contents read checkout contents permission contents read action-gh-issues read

💡 Result:

In GitHub Actions, the GITHUB_TOKEN is a unique secret created for each workflow job to authenticate with GitHub on behalf of the workflow [1]. Its permissions are critical for security and functionality [2][3]. Repository Settings and Defaults The default permissions for the GITHUB_TOKEN depend on your repository's configuration [4][5]. In many repositories, especially those in personal accounts, the default is set to restricted permissions, providing only read access to repository contents and packages [4][5]. You can manage these settings under the repository's Settings > Actions > General page, where you can choose between a permissive setting (read and write access for all scopes) or a restricted one [4][5]. Required Permissions for Actions Certain actions require explicit permissions to function correctly: 1. actions/checkout: This action requires at least read access to the contents scope to check out your repository code [6][7][8]. If you use the permissions key to modify token access, failing to include contents: read will cause the checkout action to fail [7]. 2. Issue and Pull Request operations: If your workflow uses GitHub API toolsets or tools that interact with issues or pull requests, you must explicitly grant read access to those scopes in your workflow configuration [9]. For example, tools or commands (like those in GitHub CLI) that list or search issues often require explicit issues: read and pull-requests: read permissions [9][10]. Best Practices To ensure security and reliability, follow these guidelines: - Apply the Principle of Least Privilege: Use the permissions key in your workflow file to grant the GITHUB_TOKEN only the specific access required for the job [2][3][11]. - Declare Permissions Explicitly: Even if defaults exist, explicitly defining permissions in your workflow (at the workflow or job level) prevents issues caused by changes in repository settings [4][7]. Example Configuration: permissions: contents: read issues: read pull-requests: read

Citations:


Grant issues: read for the issue fetch in the author job.

Declaring permissions: contents: read at the job level scopes down the token; omitted scopes default to none, so gh issue view ... --json title,body,comments lacks the required issues API access and can fail before Claude runs. Add issues: read to the job-level permissions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/claude-repro.yml around lines 105 - 106, Add issues: read
to the job-level permissions block for the author job alongside contents: read,
preserving the existing contents permission so gh issue view can access issue
metadata and comments.

Comment thread .github/workflows/claude.yml
claude_args: |
--max-turns 30
--model claude-sonnet-5
--allowedTools "Read,Glob,Grep,Write,Edit"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invariant I1 is weaker than claimed — the Read tool can reach secrets and publish posts them publicly — 🟠 Major · Security

What: I1 (lines 13–17) claims that granting Claude only Read,Glob,Grep,Write,Edit (no Bash/Task/network) means "an injected session cannot read process.env." But removing Bash only closes the shell path. The Read tool is not confined to the workspace, so a prompt-injected drafter can Read /proc/self/environ (the Claude CLI's own environment, which holds CLAUDE_CODE_OAUTH_TOKEN and the job GITHUB_TOKEN) or the on-disk OAuth credential store (~/.claude/.credentials.json), embed the value in .repro/draft.test.tsx, and the publish job posts that file to a public comment. The sanitizer defangs @mentions/markers but does not redact secret-shaped strings, so the token leaks in cleartext.

Why it matters: This is exactly the leak I1 is meant to prevent, within the PR's own threat model (I1/I2 exist because injection is assumed possible). A leaked CLAUDE_CODE_OAUTH_TOKEN lets an attacker spend the maintainer's Claude quota / impersonate the session; secret-masking scrubs logs, not comment bodies (the PR body acknowledges this for the token case).

Fix: Either prove Read is confined so it cannot reach /proc or ~/.claude (and document that), or add a value-based redaction step in the author job (which is the only job that holds the secrets) before the draft is exported to the job output — e.g.:

      - name: Redact any secret values from the draft
        if: steps.claude.outcome == 'success'
        env:
          OAUTH: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
          GHT: ${{ secrets.GITHUB_TOKEN }}
        run: |
          set -euo pipefail
          [ -s .repro/draft.test.tsx ] || exit 0
          python3 - "$OAUTH" "$GHT" <<'PY'
          import sys, pathlib
          p = pathlib.Path(".repro/draft.test.tsx")
          t = p.read_text()
          for s in sys.argv[1:]:
              if s:
                  t = t.replace(s, "[REDACTED]")
          p.write_text(t)
          PY
Why the sibling ai-scan job is not exposed the same way

ai-scan.yml grants only Bash(gh …) (a strict whitelist) and its session has no public output channel (show_full_output off, no write tools, egress blocked), so even if it read a secret it could not exfiltrate it. claude-repro's author job is different precisely because it pairs a broad Read/Write grant with a downstream job that publishes the written file.

Comment thread CLAUDE.md Outdated
Comment on lines +112 to +113
`AI_SCAN_DAILY_LIMIT`). That flag is advisory — it pauses `claude-repro`/`claude-fix` but does
**not** block `@claude`/`@bestaxbot`, so never `@claude` a flagged item to investigate it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs contradict the code: needs-security-review does block @claude/@bestaxbot — 🟡 Minor · Correctness

What: This line (and the identical claim in docs/docs/guides/getting-started/ai-development.md:108) states the flag "does not block @claude/@bestaxbot." But commit 3 added !contains(github.event.issue.labels.*.name, 'needs-security-review') (and the pull_request variant) to the job if: of both claude.yml and bestaxbot-reply.yml. So a flagged item makes both entry points a silent no-op — they are blocked.

Why it matters: CLAUDE.md is the canonical instruction file read by CodeRabbit and the @claude action, and this describes a security control's behavior. The follow-on guidance ("so never @claude a flagged item to investigate it") only makes sense if the mention still fired — a maintainer will expect a response and get silence, or trust a false model of the control.

Fix: Blocking is the intended, safer behavior (per commit 3's rationale), so correct the docs to match the code:

Suggested change
`AI_SCAN_DAILY_LIMIT`). That flag is advisory — it pauses `claude-repro`/`claude-fix` but does
**not** block `@claude`/`@bestaxbot`, so never `@claude` a flagged item to investigate it.
`AI_SCAN_DAILY_LIMIT`). That flag pauses `claude-repro`/`claude-fix` and, since commit 3,
also gates `@claude`/`@bestaxbot` — a flagged item makes those entry points a silent no-op
until a maintainer removes the label, so investigate a flagged item by hand rather than mentioning a bot.

Apply the same correction to ai-development.md:108.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep review — 2 blocking · 2 advisory

# Severity Area Finding Location
1 🟠 Major Security I1 is overstated: the Read tool (not confined to the workspace) can reach /proc/self/environ or ~/.claude/.credentials.json, so an injected drafter can write the OAuth token into the draft, which publish posts to a public comment un-redacted .github/workflows/claude-repro.yml:161
2 🟡 Minor Correctness Docs claim needs-security-review does not block @claude/@bestaxbot, but claude.yml and bestaxbot-reply.yml both gate their job if: on the label — it does block them CLAUDE.md:113, docs/.../ai-development.md:108
3 🔵 Advisory Robustness The publish sanitizer defangs @mentions/markers but not triple-backticks, so a drafted test containing a fence can break out of the tsx code block into raw markdown (safe: posted as github-actions[bot], cannot re-trigger; markers already globally defanged — cosmetic only) .github/workflows/claude-repro.yml:290
4 🔵 Advisory Coverage PR body says the sanitizer, the fail-closed verdict parser, and isAutomationAuthor are "unit-tested locally," but no test is committed for scripts/auto-close-duplicates.mjs or the shell parsers — load-bearing security logic has no in-repo regression guard scripts/auto-close-duplicates.mjs:95

Overall: The architecture is careful and the fail-closed labeler, budget fail-open, fork-exclusion, live-role re-checks, and PAT/token compartmentalization are all sound — I traced the ai-scan verdict parser by hand across clean / flagged / is_error+clean / missing-record / trailing-clean / empty / missing-file inputs and it fails closed in every case. The riskiest part is finding #1: I1's "no Bash means can't read env" reasoning does not hold because Read remains and the author job is uniquely paired with a public-publish downstream job, so the OAuth-token exfil path the invariant advertises as closed is plausibly open. The human should focus there first — either confirm Read is confined on the pinned claude-code-action, or add value-based secret redaction in the author job. Finding #2 is a doc/behavior mismatch in the canonical instruction file and is a quick fix.

Residual risk — for the failure class this PR targets (untrusted issue/PR text steering write-capable automation):

  • Token exfil via a non-Bash tool — NOT refuted; this is finding #1. Read reaching /proc/creds, then Write, then public publish is a concrete channel the "remove Bash" mitigation does not cover.
  • Re-trigger via smuggled @claude/markers — refuted: repro drafts post as github-actions[bot] (Bot sender, so claude.yml's sender.type == 'User' gate rejects it) and markers are deterministically defanged; the bestaxbot PAT path is fenced by the new sender.login != 'bestaxbot' exclusions plus the GET-only tool allowlist.
  • Cross-issue indirect injection in repro — refuted: the author job has no gh/Bash tool and the issue is fetched deterministically by pinned number, so other-issue payloads are inert text.

🏄 Solid swell overall, brah — the compartments are watertight and the fail-closed sets hold their line. But there is one rogue current: I1 says "no shell, no env," yet the Read tool paddles right around it into /proc and out through the public comment. Patch that leak and this one is ready to drop in.

claude and others added 5 commits August 1, 2026 20:55
The ai-triage Claude step now uses bestaxbot's PAT (AI_LOOP_PAT, the
same machine account claude-implement.yml uses) as its github_token, so
triage comments are credited to bestaxbot rather than the anonymous
github-actions[bot]. Explicit github_token still short-circuits the
action's OIDC exchange (#312).

bestaxbot is a machine USER account, not a Bot-type app, so every probe
for triage comments now matches marker + (bestaxbot OR a Bot-type
author) instead of Bot-type alone:

- auto-close-duplicates.mjs gains isAutomationAuthor (isBot OR the
  bestaxbot login) for the marker finder, the human-objection veto, and
  the automation-issue skip — the last of which had silently never
  matched bestaxbot despite its comment claiming it did.
- The three .claude/commands/triage-*.md marker checks and the docs
  guide are updated to name the new author and the historical ones.

Trade-off vs GITHUB_TOKEN: PAT-authored comments emit issue_comment
events. All comment-triggered workflows already gate bestaxbot out
(bestaxbot-reply excludes it as sender; claude.yml needs a literal
@claude, which the triage HARD RULES forbid), and the triage tool
allowlist confines the PAT to GET-only reads plus the two comment
commands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xQ1BdCcYmxB13CWnNV5Nx
Two robobun-style automations, hardened to run safely on GitHub runners.

claude-repro.yml (author-only, PAT-free): a triage+ maintainer labels an
issue `claude-repro`; Claude DRAFTS a minimal Jest reproduction test and
github-actions[bot] posts it for a human to run. Nothing here executes the
draft. Two invariants:
- The model-auth token never shares a job with code execution. The `author`
  job holds CLAUDE_CODE_OAUTH_TOKEN but grants Claude no Bash/gh/network/Task
  tool, so an injected session cannot read env, fetch, or run anything.
- No attacker/Claude free text reaches a re-trigger-capable comment. The
  `publish` job defangs machine markers and @mentions in the draft, then posts
  via GITHUB_TOKEN (whose comments never re-trigger workflows). No PAT anywhere.
The triggering issue is fetched deterministically by its number and Claude has
no gh tool, so it cannot be steered into a second issue where a payload is
staged. harden-runner egress-block, timeouts, a REPRO-DRAFT sentinel watchdog,
and an always() cleanup that de-wedges the label round it out.

ai-scan.yml (separate workflow, per-item concurrency so bursts cannot drop a
scan): a read-only Claude session assesses new issues/PRs for malicious code,
prompt injection, and social engineering. It holds no PAT and no write tools,
show_full_output is off (its reasoning is the detection logic), and a
deterministic step applies `needs-security-review` fail-CLOSED (a crashed or
inconclusive scan flags rather than passes). Own kill switches (AI_SCAN_MODE,
AI_SCAN_DAILY_LIMIT) and a fail-open daily budget marker on #290.

Co-Authored-By: Claude <noreply@anthropic.com>
Repo-wide hardening surfaced by the security red-team of the repro/scan design.

- claude.yml: fence the @claude trigger with a real-User + not-bestaxbot sender
  guard (contains() matches the RAW body, so a bot/PAT-authored comment carrying
  the literal "@claude" could otherwise re-trigger this write-capable session),
  and refuse to run on an issue/PR flagged needs-security-review. This also
  retroactively protects the shipped triage-as-bestaxbot path.
- bestaxbot-reply.yml and claude-implement.yml: refuse needs-security-review
  items, so the flag actually pauses the automation paths that consume it.
- ai-triage.yml: add harden-runner to the PAT-holding triage job (audit mode
  first — a live workflow whose action egress is undocumented; flip to block
  after one run confirms the allowlist), and rewrite the kill-switch header to
  enumerate per-surface behavior including the new AI_SCAN_MODE/AI_SCAN_DAILY_LIMIT.

Co-Authored-By: Claude <noreply@anthropic.com>
Add `claude-repro` and `needs-security-review` to the label table, and new
"Reproduction (author-only)" and "Security scan" sections covering how each runs,
the no-credentials-near-untrusted-code posture, and — importantly — that the
security flag is advisory: it pauses claude-repro/claude-fix but does NOT block
@claude/@bestaxbot, so a flagged item must be inspected by hand, not investigated
by mentioning a bot. Mirror the summary into the root CLAUDE.md AI loop section.

Co-Authored-By: Claude <noreply@anthropic.com>
Deep-review findings on #361 — the two blocking ones plus the sanitizer
advisory.

**I1 was overstated, and the gap was real.** It claimed the `author` job's lack
of a Bash tool meant an injected session "cannot read process.env". Removing
Bash does stop shelling out, but `Read` is not confined to the workspace:
/proc/self/environ and ~/.claude/.credentials.json stay readable, `Write` puts
whatever is found into the draft, and `publish` posts that draft to a public
comment. The one job holding CLAUDE_CODE_OAUTH_TOKEN is precisely the one
feeding a public-publish job, so the channel the invariant advertised as closed
was open.

`Collect draft` now refuses to emit a draft containing a live credential value
(compared against the tokens the job actually holds) or a credential-shaped
string (for a re-encoded token, or a secret this job cannot compare against).
Refuse rather than redact: a credential in the draft means the session went
somewhere it had no business going, and publishing a scrubbed copy would
destroy the only signal that it happened. The job fails, `cleanup` still removes
the label, and the reason stays in the private run log. I1 now describes what
removing Bash does and does not buy, and names this check as load-bearing rather
than belt-and-braces.

**The security flag blocks more than the docs said.** Both CLAUDE.md and the
AI-development guide stated `needs-security-review` "does not block
`@claude`/`@bestaxbot`" — but claude.yml and bestaxbot-reply.yml both gate their
job `if:` on that label, so it does. Wrong in the safer direction, but it would
send someone hunting a broken workflow when the label is the answer. Corrected in
both, and in the label table, keeping the accurate half: a *clean verdict* is
advisory (it only covers the text as scanned at open time) while the *flag* is a
gate.

**Fence delimiters are now defanged** in the publish sanitizer. A drafted line of
backticks closed the ```tsx block early and rendered the rest as markdown.
Cosmetic — it posts as github-actions[bot] and the machine markers were already
neutralized — but a broken-out draft is what a reviewer skims past.

Verified: all three workflows parse; the sanitizer neutralizes ``` and ~~~ as
well as mentions; and the refusal logic allows a clean test while rejecting an
exact OAuth token, an exact job token, a foreign sk-ant key, a github_pat_, and
a PRIVATE KEY block.

Claude-Session: https://claude.ai/code/session_01TGA6sFTUGsJ6oXhfpjKEnh
@allxsmith
allxsmith force-pushed the claude/github-actions-triage-author-b3h75o branch from 065ea43 to 27fbb2d Compare August 2, 2026 00:58
@allxsmith

Copy link
Copy Markdown
Owner Author

Rebased and review findings addressed

Was 70 commits behind (branch point 2026-07-24). Rebased onto main — no conflicts.

🟠 Blocking #1 — I1's token-exfil path. Fixed, and the finding was right.

I1 claimed the author job's lack of a Bash tool meant an injected session "cannot read
process.env". Removing Bash stops it shelling out, but Read is not confined to the
workspace
— /proc/self/environ and ~/.claude/.credentials.json stay readable, Write puts
what it finds into the draft, and publish posts that draft to a public comment. The one job
holding CLAUDE_CODE_OAUTH_TOKEN is exactly the one feeding a public publish, so the channel the
invariant advertised as closed was open.

Collect draft now refuses to emit a draft containing a live credential value (compared
against the tokens the job actually holds) or a credential-shaped string (a re-encoded token, or
a secret this job cannot compare against).

Refuse rather than redact, deliberately: a credential in the draft means the session went
somewhere it had no business going, and quietly publishing a scrubbed copy would destroy the
only signal that it happened. The job fails, cleanup still removes the label so the button
does not wedge, and the reason stays in the private run log.

I1 is rewritten to say what removing Bash does and does not buy, and to name this check as
load-bearing — not belt-and-braces. Without it the invariant does not hold.

Verified against the actual logic:

draft contains result
a clean test allowed
the exact OAuth token refused
the exact job token refused
a different sk-ant- key refused
github_pat_… refused
a PRIVATE KEY block refused

🟡 Blocking #2 — the flag blocks more than the docs said. Fixed.

CLAUDE.md and the AI-development guide both said needs-security-review "does not block
@claude/@bestaxbot" — but claude.yml:49-50 and bestaxbot-reply.yml:55-56 gate their job
if: on that label, so it does. Wrong in the safer direction, but it would send someone hunting
a broken workflow when the label is the answer.

Corrected in both files and in the label table, keeping the accurate half of the original claim:
a clean verdict is advisory (it only covers the text as scanned at open time) while the
flag is a gate. Added the practical consequence — if @claude seems to ignore a mention,
check for the label first.

🔵 Advisory #3 — fence break-out. Fixed.

The publish sanitizer now defangs and ~~~ at line start, so a drafted fence can no longer close the wrappingtsx block and spill the rest as rendered markdown.

🔵 Advisory #4 — no committed tests. Not addressed.

Still true: the sanitizer, the fail-closed verdict parser and isAutomationAuthor have no
in-repo regression guard. That wants a test harness for shell/workflow logic that does not exist
here yet, so I have left it rather than bolt on something token. Worth its own issue — say the
word and I will file it.

All three workflows parse (claude-repro.yml, ai-scan.yml, claude.yml), and the sanitizer
was exercised against fences and mentions directly.

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://14edb508.bestax.pages.dev

@bestaxbot

Copy link
Copy Markdown
Collaborator

Went through the current branch (27fbb2d) against the open threads. The three deep-review items you called out are in: the credential-refusal in Collect draft, the flag-blast-radius corrections across the docs/label table, and the fence defang in the publish sanitizer all check out.

Heads-up though — three CodeRabbit threads are still open and didn't make it into your summary. My read on each:

1. claude-repro.yml — issues: read on the author job (likely a real latent failure). The author job (permissions: block at ~L112) grants only contents: read, but the Fetch the triggering issue step at ~L150 runs gh issue view "$NUMBER" --json title,body,comments. Since setting the permissions key defaults every unlisted scope to none, that call is liable to 403 before Claude ever runs. The prepare job already carries issues: read (L66); the author job needs the same. This is exactly the kind of thing your planned canary run will surface — cheap to pre-empt.

2. ai-triage.yml — PAT + unrestricted comment allowlist (the one I'd take most seriously). This PR is what puts AI_LOOP_PAT on that job (L350), and the allowlist is Bash(gh pr comment:*),Bash(gh issue comment:*) with no wrapper (L368). Triage runs on untrusted new-issue/PR text, so an injected session now has a write-capable gh ... comment it can drive with --delete-last or --repo to retarget as bestaxbot. It's bounded by the PAT's scope, but it's a genuine injection surface that the rest of this PR works hard to close elsewhere. Worth the wrapper CodeRabbit suggests, or dropping the write-comment tools if the triage comment is posted by a separate deterministic step.

3. auto-close-duplicates.yml — comment-only hunk. This one I'd push back on. The 'loop refuses PRs touching .github' guideline is about the autonomous fix loop — this is a maintainer-authored PR, and the hunk is a docstring-only update that keeps the file honest about the isAutomationAuthor/bestaxbot change shipping in the same PR. Reverting it would leave the comment describing stale claude[bot]-only behavior. I'd keep it.

All three live under .github/**, so I've pushed nothing — that's off-limits for me and these are yours to take or leave. #1 and #2 look load-bearing enough that I'd resolve them (or the threads) before merge rather than leave them for the canary.

On #4 — happy to file the issue for a shell/workflow test harness covering the sanitizer, the fail-closed verdict parser, and isAutomationAuthor whenever you want it.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
docs/docs/guides/getting-started/ai-development.md (2)

58-58: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Clarify author-only in the guide.

author-only does not limit applying claude-repro or requesting a draft; the docs already say triage+ users apply it. Add a brief note that author-only means the workflow drafts the reproduction but never runs it in CI.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/docs/guides/getting-started/ai-development.md` at line 58, Update the
claude-repro workflow description in the getting-started guide to clarify that
“author-only” means CI drafts the reproduction test but never runs it; preserve
the existing statement that triage+ users can apply it and request drafts.

102-103: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Tie the no-leak claim back to the documented controls.

No write tools and no PAT remove GitHub write access here. The workflow also keeps show_full_output off, allows only gh Bash tools against the triggering issue/PR, blocks non-approved tools such as WebFetch, and applies egress blocking with harden-runner. State those documented controls, or revise the absolute “no channel to post or leak anything” wording if log, model, network, or other runtime paths are not bounded by the same controls.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/docs/guides/getting-started/ai-development.md` around lines 102 - 103,
Update the scan security explanation near the “no write tools and no PAT”
statement to enumerate the documented controls: disabled show_full_output,
restricted gh Bash access to the triggering issue/PR, blocked non-approved tools
such as WebFetch, and harden-runner egress blocking. If those controls do not
bound all log, model, network, and runtime paths, replace the absolute “no
channel to post or leak anything” claim with appropriately qualified wording.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/docs/guides/getting-started/ai-development.md`:
- Line 59: Update the needs-security-review documentation entry and the related
workflow section at lines 111-113 to limit the blocking claim to
repository-controlled AI workflows, including claude-repro, claude-fix, `@claude`,
and `@bestaxbot` only; do not imply that CodeRabbit is blocked unless its behavior
is documented separately.

---

Nitpick comments:
In `@docs/docs/guides/getting-started/ai-development.md`:
- Line 58: Update the claude-repro workflow description in the getting-started
guide to clarify that “author-only” means CI drafts the reproduction test but
never runs it; preserve the existing statement that triage+ users can apply it
and request drafts.
- Around line 102-103: Update the scan security explanation near the “no write
tools and no PAT” statement to enumerate the documented controls: disabled
show_full_output, restricted gh Bash access to the triggering issue/PR, blocked
non-approved tools such as WebFetch, and harden-runner egress blocking. If those
controls do not bound all log, model, network, and runtime paths, replace the
absolute “no channel to post or leak anything” claim with appropriately
qualified wording.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f031d713-6dbe-443f-ad34-9d1e6fe6fd80

📥 Commits

Reviewing files that changed from the base of the PR and between 065ea43 and 27fbb2d.

📒 Files selected for processing (12)
  • .claude/commands/triage-dedupe.md
  • .claude/commands/triage-find-duplicate-prs.md
  • .claude/commands/triage-find-issues.md
  • .github/workflows/ai-scan.yml
  • .github/workflows/ai-triage.yml
  • .github/workflows/auto-close-duplicates.yml
  • .github/workflows/bestaxbot-reply.yml
  • .github/workflows/claude-implement.yml
  • .github/workflows/claude-repro.yml
  • .github/workflows/claude.yml
  • CLAUDE.md
  • docs/docs/guides/getting-started/ai-development.md
🚧 Files skipped from review as they are similar to previous changes (10)
  • .github/workflows/claude-implement.yml
  • .github/workflows/auto-close-duplicates.yml
  • .github/workflows/claude.yml
  • .claude/commands/triage-dedupe.md
  • .github/workflows/bestaxbot-reply.yml
  • .claude/commands/triage-find-duplicate-prs.md
  • .github/workflows/ai-scan.yml
  • .claude/commands/triage-find-issues.md
  • .github/workflows/ai-triage.yml
  • .github/workflows/claude-repro.yml

Comment thread docs/docs/guides/getting-started/ai-development.md Outdated
Addresses the coverage advisory on #361, for the half of it that does not need
new infrastructure.

`scripts/auto-close-duplicates.mjs` closes people's issues and had no test at
all — root `test` was `turbo run test`, which fans out to the four packages and
never reached root `scripts/`. It now also runs `node --test "scripts/*.test.mjs"`,
matching how docs/scripts is already covered.

The assertions are written around the consequence rather than the shape, because
both failure modes are invisible in review — the code reads fine either way:

- classify a human as automation and `humanCommentAfter` stops seeing their
  objection, so an issue closes over a live veto
- loosen the marker match and a quoted or forged `Duplicate of #N` closes the
  wrong issue

One test exists purely to document a cross-file coupling: claude-repro.yml
defangs `Duplicate of #` in drafted tests *because* github-actions[bot] is an
author this parser trusts. The sanitizer and this consumer have to stay in step,
and nothing else says so.

Checked the tests can actually fail, by mutating the source three ways and
confirming each is caught by exactly the intended assertion:

  isAutomationAuthor matches any login containing "bot"
    -> "a real contributor is never automation" fails
  findMarkerComment drops its automation-author check
    -> "a human cannot forge a close by writing the marker themselves" fails
  humanCommentAfter drops the same-second id tiebreak
    -> "same-second posts are ordered by id, not dropped" fails

Still uncovered, and deliberately left: the ai-scan verdict parser and the
claude-repro publish sanitizer. Both are shell embedded in workflow YAML, so
testing them means extracting them to files the workflows call — a refactor of
security-critical automation that wants its own PR and its own review.

Claude-Session: https://claude.ai/code/session_01TGA6sFTUGsJ6oXhfpjKEnh
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://dbc46c09.bestax.pages.dev

The switches were documented in prose across three sections, one was not
documented anywhere, and the thing most likely to surprise someone — which
variables are ON when unset — was never stated plainly.

`AI_LOOP_COPILOT` had no mention in the docs or CLAUDE.md despite being read by
claude-implement.yml and set on the repo. It requests a Copilot review on loop
PRs, which has to be asked for explicitly because Copilot's own automatic review
skips bot-authored PRs on personal repos.

The table leads with "unset means", not the value list, because two of these
(`AI_LOOP_ENABLED`, `AI_SCAN_MODE`) are enabled when absent and so start
spending Claude usage the moment their workflows land. Anyone staging a rollout
needs to set them before merging, not after.

Also notes that COPILOT_AGENT_FIREWALL_ENABLED and
COPILOT_AGENT_FIREWALL_ALLOW_LIST_ADDITIONS appear in the same settings list but
belong to GitHub's Copilot coding agent — nothing in .github/workflows/ reads
them — so nobody hunts for the code that does.

These are variables, not secrets, and the workflows that read them are public,
so the names and effects are already discoverable by reading .github/workflows/.
Documenting them discloses nothing and makes the kill switches findable, which
is the point of having them.

Claude-Session: https://claude.ai/code/session_01TGA6sFTUGsJ6oXhfpjKEnh
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://b44eeaf0.bestax.pages.dev

@allxsmith

Copy link
Copy Markdown
Owner Author

@coderabbitai Full Review again

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://ba9e8018.bestax.pages.dev

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (6)

.claude/commands/triage-find-issues.md:25

  • This still relies on --edit-last, which can overwrite the wrong bestaxbot triage comment when multiple triage commands run on the same PR (the exact failure .github/CLAUDE.md warns about). Instead, only use --edit-last after verifying the last bestaxbot comment contains this command’s marker; otherwise post a fresh comment.
   - `TRIGGER=labeled` and marker present → continue; at the end refresh
     that comment (`gh pr comment NUMBER --repo REPO --edit-last --body ...`
     if it is your most recent comment on the PR; otherwise post fresh).

.claude/commands/triage-find-duplicate-prs.md:25

  • This still relies on --edit-last, which can overwrite a different bestaxbot marker comment if multiple triage commands have posted on the same PR. Make the refresh step marker-scoped: only use --edit-last when your most recent comment contains the <!-- ai-triage:find-duplicate-prs --> marker; otherwise post a new comment.
   - `TRIGGER=labeled` and marker present → continue; at the end refresh
     that comment (`gh pr comment NUMBER --repo REPO --edit-last --body ...`
     if it is your most recent comment on the PR; otherwise post fresh).

.claude/commands/triage-dedupe.md:29

  • The REFRESH guidance uses --edit-last, which can clobber the wrong bestaxbot comment when multiple automation markers exist on the same issue. Make the instruction marker-scoped so the agent only edits when it can confirm the last comment is actually the <!-- ai-triage:dedupe --> marker comment; otherwise it should post a fresh comment.
   - `TRIGGER=labeled` and marker present → continue; at the end REFRESH
     that comment instead of posting a new one: if it is your most recent
     comment on the issue, use
     `gh issue comment NUMBER --repo REPO --edit-last --body ...`;
     otherwise post a fresh comment (the old one stays as history).

.github/workflows/claude.yml:50

  • The label guards call contains() on github.event.issue.labels / github.event.pull_request.labels across multiple event shapes. For review_comment/review events, github.event.issue is absent; depending on expression coercion, contains(null, ...) can error and prevent the job from ever running. Make the label checks null-safe so they are inert when the object is missing.
      !contains(github.event.issue.labels.*.name, 'needs-security-review') &&
      !contains(github.event.pull_request.labels.*.name, 'needs-security-review') &&

.github/workflows/bestaxbot-reply.yml:56

  • Same as claude.yml: this workflow handles multiple event types, so github.event.issue/pull_request can be null. Guard the needs-security-review label checks to avoid contains(null, ...) potentially breaking the entire job condition.
      !contains(github.event.issue.labels.*.name, 'needs-security-review') &&
      !contains(github.event.pull_request.labels.*.name, 'needs-security-review') &&

.github/workflows/claude-repro.yml:299

  • Collecting the drafted test into a job output uses an unbounded cat, which can exceed GitHub’s step output size limits if the drafter emits an unexpectedly large file (prompt injection / runaway output). Truncate the payload before writing it to $GITHUB_OUTPUT (the publish step already truncates for the comment).
            {
              echo "test<<$DELIM"
              cat .repro/draft.test.tsx
              echo "$DELIM"
            } >> "$GITHUB_OUTPUT"

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/CLAUDE.md:
- Around line 55-71: Remove the PAT-backed comment permissions from the Claude
session configuration in ai-triage.yml by deleting Bash(gh pr comment:*) and
Bash(gh issue comment:*) from its allowlist. Route comment publication through a
deterministic publisher using GITHUB_TOKEN or a separate publisher that accepts
only a fixed, validated payload, while preserving the model’s read-only gh
access.

In `@SECURITY.md`:
- Around line 77-87: The public documentation overstates the inbound security
triage fail-closed guarantee. In SECURITY.md lines 77-87, state that non-clean
or invalid verdicts receive needs-security-review, while explicitly documenting
that budget or charging failures may skip scanning without applying that label;
in README.md line 194, replace the unconditional fail-closed statement with the
same distinction.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7bf43414-1846-4fb3-b14a-3b42a393524d

📥 Commits

Reviewing files that changed from the base of the PR and between 29ee909 and 04103cd.

📒 Files selected for processing (9)
  • .github/CLAUDE.md
  • .github/workflows/ai-scan.yml
  • .github/workflows/ai-triage.yml
  • .github/workflows/claude-repro.yml
  • CLAUDE.md
  • README.md
  • SECURITY.md
  • docs/docs/guides/getting-started/ai-development.md
  • scripts/auto-close-duplicates.mjs
🚧 Files skipped from review as they are similar to previous changes (5)
  • scripts/auto-close-duplicates.mjs
  • .github/workflows/ai-triage.yml
  • .github/workflows/claude-repro.yml
  • docs/docs/guides/getting-started/ai-development.md
  • .github/workflows/ai-scan.yml

Comment thread .github/CLAUDE.md
Comment on lines +55 to +71
Two sessions where the allowlist is the _only_ thing between untrusted text and repository
write:

| Workflow | Credential in the job | What the allowlist is holding back |
| --------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `ai-triage.yml` | `AI_LOOP_PAT` (bestaxbot) | Full repo write. Confined to GET-only `gh` reads plus the two comment commands. |
| `ai-scan.yml` | job `GITHUB_TOKEN`, **write**-scoped (`issues`, `pull-requests`) | The gate charges a budget marker and the labeler applies `needs-security-review`, so the token must be write-scoped. The session cannot use it _only_ because the Bash allowlist admits nothing that writes. |

Concrete rules:

- Adding **any** entry to those two allowlists is a security change. Say so in the PR
description and explain why the entry cannot write.
- Never add `Bash(gh api:*)` to a session that ingests untrusted issue/PR text — it is a
general-purpose write primitive wearing a read-shaped name.
- Never add `Edit`, `Write`, `MultiEdit`, or `Task` to `ai-scan.yml`.
- `--disallowedTools` is defense in depth, and its deny rules do take precedence over the
allows — but do not lean on it as the primary control. Narrow the allowlist.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C 12 -e 'AI_LOOP_PAT|allowedTools|Bash\(gh|gh (issue|pr) comment|gh api' .github/workflows/ai-triage.yml

Repository: allxsmith/bestax

Length of output: 13303


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'CLAUDE.md relevant lines:\n'
sed -n '50,75p' .github/CLAUDE.md | cat -n

printf '\nai-triage relevant lines:\n'
sed -n '340,372p' .github/workflows/ai-triage.yml | cat -n
printf '\nAllowlist strings in ai-triage.yml:\n'
rg -n -C 2 -- '--allowedTools|--disallowedTools' .github/workflows/ai-triage.yml

Repository: allxsmith/bestax

Length of output: 6043


Remove PAT-backed comment writes from the model session.

ai-triage.yml gives the Claude session AI_LOOP_PAT plus Bash(gh pr comment:*) and Bash(gh issue comment:*). A prompt-injected request can reach a re-trigger-capable issue/PR comment as bestaxbot. Remove those comment tools from the model allowance; use a deterministic publisher with GITHUB_TOKEN, or pass only a fixed, validated payload through a separate publisher.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/CLAUDE.md around lines 55 - 71, Remove the PAT-backed comment
permissions from the Claude session configuration in ai-triage.yml by deleting
Bash(gh pr comment:*) and Bash(gh issue comment:*) from its allowlist. Route
comment publication through a deterministic publisher using GITHUB_TOKEN or a
separate publisher that accepts only a fixed, validated payload, while
preserving the model’s read-only gh access.

Comment thread SECURITY.md
Comment on lines +77 to +87
- **Inbound security triage** — new issues and pull requests are assessed by a
read-only AI session for three things: code crafted to harm whoever runs it,
prompt injection aimed at this repository's own automation, and social
engineering. Anything not positively clean is labeled `needs-security-review`,
which every AI entry point we control refuses until a maintainer clears it.
Three properties make it worth trusting: it **fails closed** (a crashed or
unparsable scan flags rather than passes), the session holds **no write tools
and no PAT** so an injected scan cannot post or act, and its reasoning is never
published — only a coarse category — so a flag cannot be used as an oracle for
tuning an evasion. A clean verdict covers the text as it stood when the item
opened, not edits made afterwards.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Align the public descriptions with the scan contract.

Only verdict parsing fails closed. Budget and charging errors can fail open and skip scanning, so an item can remain unscanned without needs-security-review.

  • SECURITY.md#L77-L87: state that non-clean or invalid verdicts are labeled, and document that budget failures can skip scanning.
  • README.md#L194-L194: replace the unconditional fail-closed statement with the same verdict and budget distinction.
🧰 Tools
🪛 LanguageTool

[locale-violation] ~87-~87: In American English, ‘afterward’ is the preferred variant. ‘Afterwards’ is more commonly used in British English and other dialects.
Context: ... when the item opened, not edits made afterwards. Consumers can verify provenance thems...

(AFTERWARDS_US)

📍 Affects 2 files
  • SECURITY.md#L77-L87 (this comment)
  • README.md#L194-L194
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SECURITY.md` around lines 77 - 87, The public documentation overstates the
inbound security triage fail-closed guarantee. In SECURITY.md lines 77-87, state
that non-clean or invalid verdicts receive needs-security-review, while
explicitly documenting that budget or charging failures may skip scanning
without applying that label; in README.md line 194, replace the unconditional
fail-closed statement with the same distinction.

The three triage commands told the session to refresh its marker comment with
`--edit-last` "if it is your most recent comment" — an authorship test, not a
marker test. `--edit-last` selects the newest comment by that author, and after
this PR every triage path posts as bestaxbot, so the newest bestaxbot comment on
an item is often a DIFFERENT marker: find-issues and find-duplicate-prs both run
on PRs, and an issue can carry a dedupe marker plus a repro draft.

Overwriting `<!-- ai-triage:dedupe -->` is the one with teeth — auto-close-
duplicates.mjs reads that marker, so clobbering it destroys an auto-close
candidate silently. Same defect the workflow-side fix already closed; these
three prompt files were the remaining instances.

Each now permits `--edit-last` only when the session's most recent comment IS
its own marker comment, and points at .github/CLAUDE.md rule 6.

Found by Copilot against the rule the new .github/CLAUDE.md had just added.

Refs #362

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep review — 0 blocking · 3 advisory

# Severity Area Finding Location
1 🔵 Advisory Security The AI verdict is defeatable by a successful prompt-injection that makes the model's final line SECURITY-SCAN: clean; the design accepts this (advisory verdict + no write tools + triage+ live-role gate is the real barrier). .github/workflows/ai-scan.yml:452
2 🔵 Advisory Robustness The scan/repro gates fail open on any transient GitHub API error during the budget/label read — the item is silently unscanned and indistinguishable from clean downstream. Deliberate and documented. .github/workflows/ai-scan.yml:389
3 🔵 Advisory API The Apply label comment claims the REST add-labels endpoint "auto-creates the label if missing." That endpoint returns 422 for an unknown label — it does not create one. Moot today (both needs-security-review and claude-repro already exist in the repo), but a false recovery assumption if a label is ever deleted. .github/workflows/ai-scan.yml:538

Overall: The change is sound and unusually well-defended for CI/AI automation. The two stated invariants hold under scrutiny — I1 (the OAuth token never shares a job with attacker-influenced execution) is preserved because the author job grants Claude no Bash/Task/network tool and the credential-shape check in Collect draft backstops the Read-can-reach-/proc/environ gap; I2 (no untrusted free text reaches a re-trigger-capable identity) holds because the draft is deterministically defanged and posted via GITHUB_TOKEN, whose comments emit no workflow events. The riskiest surface is the switch of ai-triage.yml to bestaxbot's PAT (AI_LOOP_PAT), whose comments do re-trigger — but every comment-triggered workflow (claude.yml, bestaxbot-reply.yml) now excludes sender.login == 'bestaxbot', and the only other issue_comment-triggered file is ai-triage.yml itself, which triggers on issues/pull_request opened/labeled, not comments, so the loop is closed. A human should focus first on the PAT-confinement allowlist in ai-triage.yml (the .github/CLAUDE.md contract correctly flags it as the one load-bearing control) and on running the canary described in the PR body before flipping AI_SCAN_MODE on.

Residual risk: the failure class this PR addresses is "untrusted issue/PR content escalating through AI automation." Ways it could still occur, each checked:

  • Re-trigger via a bestaxbot PAT comment — refuted: the only issue_comment consumers (claude.yml, bestaxbot-reply.yml) both gate sender.login != 'bestaxbot'; ai-triage.yml/ai-scan.yml don't listen to comments; auto-close-duplicates trusts bestaxbot but requires marker + Duplicate of #N + a 14-day no-objection window + AI_TRIAGE_AUTOCLOSE=on (default off), and the repro publisher defangs both.
  • Token exfil via the drafting session — refuted for the naive path (no execution primitive, egress-block, exact+shape credential check, publish via a separate PAT-free job); the encoding check is correctly documented as a backstop, not a proof, so a chunked/rot13 exfil stays theoretically open — mitigated because the only published byte-stream is draft.test.tsx and it is scanned.
  • Open-time window on the flag — confirmed as an accepted limitation (scanner and consumers both fire on opened); bounded because every same-event consumer independently requires a trusted author, and external actors cannot satisfy @claude's OWNER/MEMBER/COLLABORATOR gate.

🏄 Dude, this PR is a triple-reef break with the channel clearly marked — split the token off from the gnarly code, defang the froth before it hits the lineup, and every entry point runs the buddy system. Nothing here's gonna pitch you over the falls. Paddle out, it's good to go — just watch that audit→block set wave and run the canary first. 🌊

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://a940c36c.bestax.pages.dev

The `Apply label` comment asserted that REST addLabels auto-creates a missing
label, unlike `gh --add-label`. Deep review flagged it as false (422 on an
unknown label); GitHub's REST docs state neither behavior, so the claim is
unverified in both directions rather than simply backwards.

Removed instead of inverted. The comment now says what is actually true and
load-bearing: needs-security-review is created ahead of merge, so this step
never depends on on-demand creation, and that behavior must not be treated as
recovery if the label is later deleted. Also records that a failure here leaves
the item unlabeled — the gate fails open by design.

Moot today (both labels exist), but the repo's own review checklist says a
security comment must claim exactly what the mechanism delivers.

Refs #362

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://125cbdb7.bestax.pages.dev

@allxsmith
allxsmith merged commit 040b726 into main Aug 2, 2026
19 checks passed
@allxsmith
allxsmith deleted the claude/github-actions-triage-author-b3h75o branch August 2, 2026 15:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (2)

.github/workflows/auto-close-duplicates.yml:13

  • The workflow header still says the first veto is a “non-bot comment”, but the underlying decision logic now treats bestaxbot (a machine User account) as automation. To avoid operators misunderstanding what counts as an objection, update this comment to say “non-automation” (human) rather than “non-bot”.
# the `<!-- ai-triage:dedupe -->` marker and a `Duplicate of #N` line, aged
# >= 14 days. Three vetoes, any one of which blocks the close forever or until it
# clears: a non-bot comment after the marker, a 👎 reaction on the marker
# comment, or a `reopened` event in the issue timeline (a reopened issue is

.github/workflows/claude-repro.yml:214

  • The prompt claims the job “FAILS without” the REPRO-DRAFT final-line sentinel, but the workflow intentionally skips the sentinel check when the Claude action produces no execution_file (“no session to judge”). Update the wording so it matches the actual enforcement, otherwise it overstates the guarantee to future maintainers.
            FINAL MESSAGE (machine-checked; the job FAILS without it): your last
            output message must be exactly one line and nothing else, of the form
              REPRO-DRAFT: drafted
            or
              REPRO-DRAFT: infeasible (<short reason>)

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep review — 0 blocking · 3 advisory

# Severity Area Finding Location
1 🔵 Advisory Robustness The documented pre-PR gate pnpm all does not run the new scripts/*.test.mjs — only ci.yml's separate pnpm run test step does. package.json:24
2 🔵 Advisory Security I2 is now held by convention across every issue_comment consumer excluding bestaxbot, not by construction (triage posts via bestaxbot's PAT). Verified both consumers exclude it. .github/workflows/ai-triage.yml:687
3 🔵 Advisory Robustness claude-repro's security-flag interlock fails open on a transient gh error (empty pipe input → no match → repro proceeds). .github/workflows/claude-repro.yml:849

Overall: The change is sound and unusually well-hardened — the two invariants (I1 token/execution separation, I2 no free text to a re-trigger identity) are coherently enforced, the verdict paths fail closed, the budget paths fail open, SHA pins are uniform (21/21 on the single repo-wide checkout SHA), and the new .github/CLAUDE.md codifies the rules that prior review rounds surfaced. The riskiest surface is the triage-as-bestaxbot switch, because it trades a construction-level I2 guarantee (GITHUB_TOKEN comments never re-trigger) for a convention (every issue_comment consumer must exclude bestaxbot) — I confirmed the only two such consumers, claude.yml and bestaxbot-reply.yml, both do. A human should focus first on the bestaxbot PAT scope confinement (the tool allowlist is the only thing between injected issue text and repo write) and confirm the post-merge canary before flipping AI_SCAN_MODE on.

Residual risk — ways the re-trigger / injection class could still occur:

  • PAT-authored triage comment re-triggering a write-capable session — refuted: the only two issue_comment:-triggered workflows (grep -rln "issue_comment:" → claude.yml, bestaxbot-reply.yml) both gate sender.login != 'bestaxbot'; claude.yml additionally requires a literal @claude that the HARD RULES forbid the session from writing. claude-pr-loop.yml fires on pull_request_review, not comments, so triage comments cannot reach it.
  • A flagged item slipping past a consumer — bounded, and documented: the opened-event open-time window is real but every same-event path independently requires a trusted author, and third-party reviewers (CodeRabbit/Copilot) are explicitly out of scope. On the record, not a new defect.
  • Token exfil via the repro drafter — refuted for the naive path: author job has no Bash/Task/network tool, harden-runner blocks egress, Collect draft refuses literal/base64/hex/shape-matched credentials, and publish never co-locates with the OAuth token. The encoding check is correctly documented as a backstop, not a proof (chunked/rot13/arithmetic encodings defeat it) — an accepted, written-down limitation.

🏄 Gnarly amount of defense-in-depth on this one, dude — the token never surfs the same wave as the code, the verdicts wipe out safe, and every re-trigger channel's got a bestaxbot buoy in it. No blockers in the lineup; just three little ripples on the record. Paddle it out. 🌊

@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 4.0.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 5.8.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 1.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 2.0.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants