Skip to content

chore(release): promote next to production (2026-06-01)#528

Closed
hello-happy-puppy wants to merge 12 commits into
mainfrom
release/next-to-main-20260601-185159
Closed

chore(release): promote next to production (2026-06-01)#528
hello-happy-puppy wants to merge 12 commits into
mainfrom
release/next-to-main-20260601-185159

Conversation

@hello-happy-puppy

@hello-happy-puppy hello-happy-puppy commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

Production Deployment

This PR promotes the next branch to main for production release.

Commits included (12):

27e7e2d fix(slack-oauth-backend): use fresh bot token, drop SLACK_BOT_TOKEN (#523)
8356485 fix(workflows): tighten bullfrog allowlists per workflow (#516)
776e81f fix(workflows): allowlist toolkit_ref to block fork-branch RCE (#510)
dab0e80 fix(workflows): set BUN_CONFIG_FILE=/dev/null in remaining workflows (#511)
09491fb fix(claude-task-worker): harden against prompt injection (#514)
c431f5b fix(workflows): replace sed-with-pipe-delimiter with python str.replace (#513)
0e09ace chore(sync): [skip ci] sync next with main
07fdbd9 chore(sync): [skip ci] sync next with main
80ac06e chore(slack-oauth-backend): log OAuth request and Slack-granted scopes (#520)
7d0d6a3 chore(sync): [skip ci] sync next with main
a48592e fix(slack-oauth-backend): drop incoming-webhook bot scope (#518)
e1f1cf6 feat(slack-oauth-backend): expand OAuth scope requests to match app manifest (#517)

Merge Strategy

Using merge commit to preserve full commit history for changelog generation.

Temporary Branch

This PR is created from a temporary branch release/next-to-main-20260601-185159 that will be deleted after merge.


This PR was automatically created by the Update Production workflow.


Changes Summary

✨ Features (1)

  • Expand OAuth scope requests in Slack OAuth backend to match app manifest

🐛 Bug Fixes (6)

  • Use fresh bot token and drop SLACK_BOT_TOKEN environment variable in Slack OAuth backend
  • Tighten bullfrog allowlists per workflow to reduce attack surface
  • Allowlist toolkit_ref to block fork-branch RCE attempts in workflows
  • Set BUN_CONFIG_FILE=/dev/null in remaining workflows for security hardening
  • Harden Claude task worker against prompt injection attacks
  • Drop incoming-webhook bot scope from Slack OAuth backend

🔧 Maintenance (4)

  • Replace sed-with-pipe-delimiter with Python str.replace in workflows for reliability
  • Log OAuth request and Slack-granted scopes in Slack OAuth backend for debugging
  • Sync next branch with main (multiple syncs)

Full Commit List

27e7e2d fix(slack-oauth-backend): use fresh bot token, drop SLACK_BOT_TOKEN (#523)
8356485 fix(workflows): tighten bullfrog allowlists per workflow (#516)
776e81f fix(workflows): allowlist toolkit_ref to block fork-branch RCE (#510)
dab0e80 fix(workflows): set BUN_CONFIG_FILE=/dev/null in remaining workflows (#511)
09491fb fix(claude-task-worker): harden against prompt injection (#514)
c431f5b fix(workflows): replace sed-with-pipe-delimiter with python str.replace (#513)
0e09ace chore(sync): [skip ci] sync next with main
07fdbd9 chore(sync): [skip ci] sync next with main
80ac06e chore(slack-oauth-backend): log OAuth request and Slack-granted scopes (#520)
7d0d6a3 chore(sync): [skip ci] sync next with main
a48592e fix(slack-oauth-backend): drop incoming-webhook bot scope (#518)
e1f1cf6 feat(slack-oauth-backend): expand OAuth scope requests to match app manifest (#517)

wkoutre and others added 12 commits May 28, 2026 18:23
…anifest (#517)

Sync the bot and user scope arrays in createOAuthHandler() with the
current Slack app manifest so granted tokens carry all configured
permissions.

Bot scopes: 14 to 15 (adds incoming-webhook).
User scopes: 14 to 39 (adds search:read family, canvases, bookmarks,
links, reminders, pins, files read/write, emoji:read, channels:write,
groups:write, mpim:write, users:read.email).

Existing tokens retain their previous narrower scopes; users must
re-authorize to receive the new permissions.
The scope was added in PR #517 to mirror the Slack app manifest, but
the OAuth callback handler discards tokenResponse.incoming_webhook
(only forwards team, enterprise, scopes, bot_user_id). The app does
not consume webhook URLs, so requesting the scope adds a channel-
picker step to install for no functional benefit.

Manifest should be updated separately to remove incoming-webhook from
oauth_config.scopes.bot so the two stay aligned.
#520)

* chore(slack-oauth-backend): log OAuth request scopes and Slack-granted scopes

Adds two diagnostic logger.info calls to surface what we ask Slack for
and what Slack returns on each install, so future scope-grant mismatches
can be debugged against Vercel logs instead of trial-and-error reinstalls.

handler.ts (after exchangeCodeForToken): logs botScopesGranted,
userScopesGranted, presence flags for bot/user access and refresh
tokens, token types, team id, enterprise id. Token values themselves
are not logged.

routes/oauth.ts (in /authorize handler): logs the full redirect URL
including scope and user_scope query params, so we can verify the
request side independently of Slack's response.

Temporary instrumentation for the ongoing org-install debugging; safe
to leave in but a follow-up can prune if these become noisy.

* chore(slack-oauth-backend): scope-only log + drop "Debug" framing

Addresses two review comments on #520:

routes/oauth.ts: parse the authorize URL and log only the scope,
user_scope, and redirect_uri params instead of the full URL. The full
URL contains the `state` CSRF nonce, which would otherwise leak into
Vercel logs and weaken the CSRF protection validated on callback.

handler.ts + routes/oauth.ts: drop the "Debug:" framing from both log
comments. The logs fire at info level in production and the data they
capture (scope strings, presence flags) is permanent OAuth observability,
not temporary instrumentation, so the comments now reflect that intent.
…ce (#513)

_claude-docs-check.yml's Build Docs Check Prompt step substitutes 8
placeholders into the prompt template via `sed -i "s|\${VAR}|$VALUE|g"`
with `|` as the sed delimiter. The escape applied to $CHANGED_FILES
on line 520 is `sed 's/[&/\]/\\&/g'` — it escapes `&`, `/`, `\` but
NOT `|` (the actual delimiter).

A filename in $CHANGED_FILES containing `|` (legal on Linux; not
quoted by git's default core.quotepath since `|` is in the printable
ASCII range) breaks the substitution into garbage flags and causes
the docs-check workflow to fail or write a partial value into the
prompt. PR authors can introduce such filenames via fork PRs.

Replace the 8 sed -i calls with a single python str.replace pass.
Python's str.replace is plain-text and immune to this entire class
of delimiter-injection bugs.

Discovered during the bug bounty review that produced
Uniswap/default-token-list#2484 and #509.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(workflows): harden Claude session in task worker (drop --dangerously-skip-permissions, bullfrog block)

The autonomous task worker had two structural gaps that any prompt-
injection bug would weaponize:

1. Claude ran with `--dangerously-skip-permissions`, meaning every tool
   call was approved automatically. Combined with the absence of a
   file-allowlist post-flight check (see the separate Validate-changed-
   files PR), a prompt-injected session could call any tool unrestricted.

2. bullfrog egress-policy was `audit` (log-only). Network calls to
   attacker-controlled endpoints would be logged but not blocked.

This patch:

a) Replaces `--dangerously-skip-permissions` with an explicit
   `--allowedTools` list. Without an allowlist Claude prompts on every
   tool call (no human to approve in CI, so the job stalls); with the
   explicit allowlist the worker can still operate but a hijacked
   session cannot call arbitrary tools (curl, wget for exfil; dangerous
   mcp__linear__ operations like delete_*; etc.). Refine the list as
   legitimate tasks reveal additional needed tools.

b) Switches bullfrog from `egress-policy: audit` to `egress-policy:
   block` with an allowlist matching the other ai-toolkit workflows
   that already run in block mode (_claude-main.yml,
   _claude-code-review.yml, _claude-docs-check.yml,
   _generate-pr-metadata.yml), plus api.linear.app for the Linear MCP
   server this worker uses.

A follow-up that this patch does NOT address: the GitHub App's
installation permissions are configured server-side and are out of
scope for a code PR. Audit the App's scopes in the GitHub UI and
reduce to the minimum needed (contents:write, pull_requests:write,
issues:read).

Discovered during the bug bounty review that produced
Uniswap/default-token-list#2484 and #509.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(workflows): tighten bullfrog allowlist to aitk-worker-specific empirically-derived hosts

The previous commit shipped a 10-entry allowlist matching the dtl
worker's set, but the aitk Task Worker has different needs (uses
registry.npmjs.org as its npm registry, not registry.yarnpkg.com)
and the shared list included entries unused in practice.

Per-workflow analysis based on 15 audit-mode runs of
_generate-pr-metadata.yml (the only ai-toolkit workflow with recent
runs and audit logs showing the same setup-bun + bun install pattern)
plus reading this worker's code:

  Setup-phase empirical:
    - registry.npmjs.org      (341 audit lines across 15 sister runs)
    - release-assets.githubusercontent.com  (covered by *.githubusercontent.com)

  Runtime (invisible to audit per agent/queue_audit.nft — DNS reply
  queue means cached resolutions emit no further events):
    - api.anthropic.com       (claude-code-action)
    - api.linear.app          (mcp__linear__ tools at runtime)

  Default-allowed by bullfrog (agent/agent.go:18 defaultDomains):
    - github.com, api.github.com — silently allowed

Dropped vs the previous shared allowlist:

  - github.com / *.github.com   — covered by defaultDomains
  - bun.sh                       — 0 audit lines in 15 setup-bun runs;
                                   the binary comes from github releases
                                   via *.githubusercontent.com
  - claude.ai / *.claude.ai      — OAuth path only; this worker uses
                                   ANTHROPIC_API_KEY

Final allowlist: 5 entries (down from 10), each empirically justified
or required by code that audit logs can't observe.

Sister PR Uniswap/default-token-list#2485 makes the analogous
tightening for the dtl Task Worker.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(claude-task-worker): clarify allowlist limits + add Bash(rg:*)

Rewords the comment above allowedTools to stop overstating what the
list contains. node/python3/bunx/npx are all arbitrary code execution;
the real containment is the bullfrog egress block above. Documents
that explicitly so a future maintainer doesn't loosen the bullfrog
allowlist assuming the tool allowlist constrains exfiltration — it
does not.

Also adds Bash(rg:*) preemptively per the PR's own 'add the tool
here' guidance: Claude's default search flows prefer ripgrep and would
otherwise stall on a permission prompt mid-task.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…511)

* fix(workflows): set BUN_CONFIG_FILE=/dev/null to neutralize bunfig.toml.preload RCE

`bun run`, `bun install`, and `bun x` auto-load bunfig.toml from CWD.
The bunfig.toml.preload array executes arbitrary code before the
intended script. When a workflow checks out PR head content (any of
our reusable workflows that take a pr_number input do this), `bun run`
loads bunfig.toml from the checkout root, and a malicious preload
entry executes with the caller's secrets.

Disable bunfig.toml loading by setting BUN_CONFIG_FILE=/dev/null at
the workflow level. This propagates to all jobs and steps and covers
every `bun *` invocation in the workflow.

Affected files (all use `bun run`/`bun install` after checkout):
- _claude-docs-check.yml
- _claude-code-review.yml
- _claude-task-worker.yml
- ci-pr-checks.yml
- publish-packages.yml

Discovered during the bug bounty review that produced
Uniswap/default-token-list#2484 and #509 (semgrep
flagged 8+ sites; this fix is uniform across all of them).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(workflows): also prefix BUN_CONFIG_FILE=/dev/null inline on bun run

Belt-and-suspenders: job env already sets BUN_CONFIG_FILE=/dev/null, but
pinning it inline on the bun run invocation matches the Semgrep CI
comment's suggested fix and survives anyone later moving this into a
step that overrides the job env.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(workflows): validate toolkit_ref against allowlist before downloading scripts

All six reusable workflow_call workers (_claude-task-worker.yml,
_claude-code-review.yml, _claude-docs-check.yml, _claude-main.yml,
_generate-pr-metadata.yml, _update-action-versions-worker.yml) accept
a `toolkit_ref` input and use it directly in curl URLs to fetch
action.yml / post-*.ts script content from this repository:

  curl -L "https://api.github.com/repos/Uniswap/ai-toolkit/contents/.github/.../action.yml?ref=${TOOLKIT_REF}"

The downloaded content is then executed inside the worker job with
access to its secrets (ANTHROPIC_API_KEY, LINEAR_API_KEY, GitHub App
installation tokens). If `toolkit_ref` is set to an attacker-controlled
ref — e.g., a fork branch — they get RCE-with-secrets the moment any
of these workflows is invoked with the attacker's branch.

Add a "Validate toolkit_ref" step as the FIRST step in each worker job.
Allowlist: main, next, or a 40-char SHA. Anything else fails the job
with a clear error.

The validation runs before the bullfrog egress scan (which is the next
step) so even if the egress allowlist is misconfigured, attacker-
controlled refs are rejected before any network call is made.

Discovered during the bug bounty review that produced
Uniswap/default-token-list#2484 and #509.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(workflows): pin claude-docs-check toolkit_ref to main

This caller previously passed `toolkit_ref: ${{ github.head_ref || github.ref_name }}`,
which resolves to the PR branch name on every pull_request: run. The
downstream worker (_claude-docs-check.yml) curls action.yml / post-*.ts
script content from `Uniswap/ai-toolkit@$TOOLKIT_REF` and executes it
inside the worker job with access to the caller's secrets
(ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, WORKFLOW_PAT) and the
GitHub App installation token.

A PR author could therefore land malicious script content on their own
branch and have the docs-check workflow execute it with full secrets.
For internal-author PRs (anyone with push access to this repo) the
attack is complete RCE-with-secrets; for fork PRs the blast is bounded
by what `pull_request:` flows expose, but the pattern is brittle either
way.

Sequencing: this PR should land BEFORE the toolkit_ref allowlist
validation in #511. After this lands, #511's validation step has no
in-repo caller that would fail the check. Landing #511 first without
this PR would break docs-check on every non-main/next branch.

Trade-off accepted: PR authors who modify worker scripts
(post-docs-check.ts, validate-claude-auth/action.yml, etc.) can no
longer self-test their changes via docs-check on the same PR. Test
flow is now:
1. Land the script change on `next` first
2. Verify docs-check works on next
3. Promote next → main via the existing release-notes flow

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(workflows): move toolkit_ref validation after bullfrog per CLAUDE.md

The 'bullfrog must be first step' rule in .github/workflows/CLAUDE.md is
documented as having 'no exceptions'. Validate toolkit_ref does no
network egress (case/printf/grep), so the threat model is unchanged
either way — but conventionally bullfrog comes first.

Validate still runs before any step that consumes toolkit_ref to
download action.yml or post-*.ts content, so its security role is
preserved.

Also extends the claude-docs-check.yml comment block to note that
external repos copying the consumer workflow as a template should keep
toolkit_ref pinned to 'main' — the reusable worker's allowlist blocks
fork branches but still permits 'next'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(workflows): tighten bullfrog allowlists per workflow from empirical egress data

Three workflows tightened based on bullfrog log analysis. Each workflow
now has an allowlist that matches its actual observed egress instead
of inheriting the shared 9-entry kitchen-sink list.

Method: pulled all available successful run logs per workflow over the
last 30 days. Extracted bullfrog audit-mode and block-mode events.
Cross-referenced with each workflow's runtime code (Linear MCP yes/no,
OAuth yes/no, package manager). Settled on the smallest allowlist that
covers observed setup-phase egress plus required runtime hosts that
audit mode can't see (DNS-reply coalescing per agent/queue_audit.nft).

Cross-cutting drops vs the previous shared 9-entry allowlist:

  github.com / *.github.com   — in bullfrog defaultDomains
                                (agent/agent.go:18); 0 audit lines
                                across all 17 audit-mode runs sampled.
  claude.ai / *.claude.ai      — OAuth path; all three workflows use
                                ANTHROPIC_API_KEY path; 0 audit lines.
  bun.sh                       — setup-bun fetches from github releases
                                via *.githubusercontent.com; verified
                                by 15 _generate-pr-metadata setup-bun
                                runs showing 0 bun.sh lines.
  *.githubusercontent.com      — only release-assets.githubusercontent.com
                                is empirically used; pinned to the
                                specific subdomain.
  *.npmjs.org                  — only registry.npmjs.org is used;
                                pinned to the specific subdomain.

Per-workflow tuning beyond cross-cutting drops:

  _claude-code-review.yml      — block mode (unchanged). Empirical: 2
                                runs showed only Datadog logs agent
                                blocked. No Linear MCP, dropped
                                api.linear.app. Final allowlist: 3
                                entries.
  _claude-docs-check.yml       — block mode (unchanged). Empirical:
                                12 runs showed only Datadog blocked.
                                No Linear MCP. Final: 3 entries.
  _generate-pr-metadata.yml    — FLIPPED audit → block. Empirical: 15
                                audit runs showed registry.npmjs.org
                                + release-assets.githubusercontent.com.
                                No Linear MCP. Final: 3 entries.

NOT TOUCHED (insufficient data):
  _claude-main.yml             — 0 runs in 30d; can't validate
                                empirically. Leaving the existing
                                allowlist as-is until data exists.
  _update-action-versions-worker.yml — 0 runs in 30d (weekly cron
                                fired outside the window).

Wildcards: bullfrog uses Go's path.Match (agent/agent.go:181), so
*.foo.com covers any subdomain depth. We pin to exact subdomains
empirically observed instead, matching the "based only on logs"
review directive.

Sister PRs:
  Uniswap/default-token-list#2485 — dtl Task Worker (per-job tight)
  #514          — aitk Task Worker (per-job tight)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(workflows): allow raw.githubusercontent.com for plugin discovery

build-plugin-config/action.yml fetches marketplace.json from
raw.githubusercontent.com/Uniswap/ai-toolkit/next/.claude-plugin/marketplace.json
for plugin discovery. The audit-mode sampling that informed the tight
allowlists missed this domain because bullfrog v0.8.4 queues DNS replies
(agent/queue_audit.nft) and once a hostname is resolved+cached the kernel
resolver short-circuits — no further audit events fire. In block mode the
fetch now fails and breaks the "Build plugin configuration" composite
step in docs-check, code-review, and generate-pr-metadata.

Add raw.githubusercontent.com to all three tightened allowlists and update
the explanatory comments so future maintainers understand the audit-mode
caveat that led to the gap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(workflows): allow claude.ai for Claude SDK install + runtime

Second iteration of audit-mode-coalescing miss: the audit logs that
informed the original tightening showed no claude.ai traffic, so the
allowlist dropped it. Block-mode CI then failed at the Claude SDK
invocation with:

  ##[warning] Blocked DNS request to claude.ai from unknown process
  ReferenceError: Claude Code native binary not found at
    /home/runner/.local/bin/claude

The Claude SDK action installs the native CLI from claude.ai/install.sh
and also touches claude.ai at runtime for telemetry/session-token
lookups even on ANTHROPIC_API_KEY auth — none of which appeared in
audit logs because bullfrog v0.8.4 queues DNS *replies* and cached
resolutions emit no further events (agent/queue_audit.nft:13-14).

Add claude.ai to all three tightened allowlists (_claude-docs-check.yml,
_claude-code-review.yml, _generate-pr-metadata.yml). Update inline
comments to record the methodology lesson so future tightenings don't
repeat the trap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(workflows): allow downloads.claude.ai for Claude CLI binary install

Third iteration of audit-mode-coalescing miss. After adding claude.ai
the Claude SDK install script entry point resolved, but its actual
binary download fetches from a different host:

  curl: (6) Could not resolve host: downloads.claude.ai
  ReferenceError: Claude Code native binary not found at
    /home/runner/.local/bin/claude

Add downloads.claude.ai to all three tightened allowlists.

Lesson recorded in inline comments: bullfrog v0.8.4 audit-mode coalescing
hides multiple domains per workflow, so the correct iterative tightening
methodology is to flip block-mode in a single test run and harvest from
the *block* logs (which DO show every blocked destination), not the
audit logs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(workflows): drop 'Dropped:'/'Pinned:' enumerations from allowlist comments

git blame on allowed-domains: already reveals what was removed and why,
and runtime block-mode CI failure logs show what's currently blocked.
Keep the rationale for each *kept* domain inline; let history speak for
the rest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…523)

* fix(slack-oauth-backend): use fresh exchange bot token, drop static SLACK_BOT_TOKEN

The users.info enrichment and DM delivery both authenticated with the
static SLACK_BOT_TOKEN env var. With token rotation enabled, bot tokens
are short-lived (xoxe, ~12h) and a static env var goes stale; it also
dies on every reinstall. This surfaced as `account_inactive` from
users.info in the OAuth callback (observed in Vercel logs).

Both consumers run during the callback, when the fresh bot token from
the exchange (tokenResponse.access_token) is in hand. Use it directly:

- handler.ts: enrich users.info with the fresh token; surface it as
  OAuthResult.botAccessToken.
- routes/oauth.ts: pass botAccessToken to createSlackClient for DM auth.
- client.ts: SlackClient takes a per-request botToken instead of reading
  config; createSlackClient bypasses the singleton when a botToken is
  given so a per-request token is never cached across installs.
- config/index.ts: remove slackBotToken; .env examples + README +
  DEPLOYMENT docs updated to note there is no static bot token.

Health check (/health testAuth) gracefully reports token_rotation mode
when no bot token is present, unchanged.

Specs updated: bot token now passed explicitly to the constructor in
client tests; new tests cover the oauth-only constructor path and the
singleton-bypass factory behavior. 74/74 pass, typecheck + lint clean.

* test(slack-oauth-backend): distinct bot/user token fixtures prove sourcing

Addresses code-review feedback on #523: the handleCallback fixture used
the same value for the bot slot (access_token) and user slot
(authed_user.access_token), so the botAccessToken assertion passed
vacuously and couldn't catch a regression that sourced it from the wrong
slot. Give the bot slot a distinct xoxb- value.

This also surfaced (and the test now pins) the bot-token fallback: when
no user token is present, the selected accessToken correctly falls back
to tokenResponse.access_token.
@hello-happy-puppy hello-happy-puppy requested a review from a team as a code owner June 1, 2026 18:52
@vercel

vercel Bot commented Jun 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ai-toolkit-slack-oauth-backend Ready Ready Preview, Comment Jun 1, 2026 6:53pm

Request Review


# Disable Bun's automatic bunfig.toml loading from CWD. When a workflow
# checks out PR head content (this workflow checks out PR refs to read
# diffs and post comments), `bun run` would otherwise auto-load

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Semgrep identified an issue in your code:
Reusable workflow (on: workflow_call) uses bun run. Bun auto-loads bunfig.toml from CWD, and its preload array executes arbitrary code before the intended script. If the workflow checks out fork PR code, this enables RCE with access to the caller's secrets. Fix: set BUN_CONFIG_FILE=/dev/null in the job env.

To resolve this comment:

✨ Commit fix suggestion

Suggested change
# diffs and post comments), `bun run` would otherwise auto-load
BUN_CONFIG_FILE=/dev/null bun run "$POST_SCRIPT" $SCRIPT_ARGS
View step-by-step instructions
  1. Update the line that runs the Bun script to set the environment variable BUN_CONFIG_FILE to /dev/null to prevent Bun from auto-loading an attacker-controlled configuration file.
  2. Change the execution line to: BUN_CONFIG_FILE=/dev/null bun run "$POST_SCRIPT" $SCRIPT_ARGS.

This protects the workflow from remote code execution via malicious bunfig.toml files in forked pull requests.

💬 Ignore this finding

Reply with Semgrep commands to ignore this finding.

  • /fp <comment> for false positive
  • /ar <comment> for acceptable risk
  • /other <comment> for all other reasons

Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by bun-run-in-reusable-workflow.

You can view more details about this finding in the Semgrep AppSec Platform.

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

📚 Documentation Check ✅

Verdict: Passed

No plugin files were modified (no version bump required). README.md, DEPLOYMENT.md, and env example files were already updated in the PR. One documentation inaccuracy exists in .github/workflows/CLAUDE.md but fail_on_missing_docs is false.


PR #528 Documentation Check — PASS

Summary of Changes

This PR has two main areas of change:

  1. GitHub Actions security hardening across 7 workflow files: adding BUN_CONFIG_FILE=/dev/null at workflow level, tightening bullfrog allowlists from wildcard to specific domains, switching _claude-task-worker.yml from audit to block egress policy, adding toolkit_ref validation steps, replacing --dangerously-skip-permissions with an explicit --allowedTools list in the task worker, and pinning claude-docs-check.yml to toolkit_ref: 'main'.

  2. Slack OAuth token rotation fix in apps/slack-oauth-backend/: removes the static SLACK_BOT_TOKEN env var, adds botAccessToken to OAuthResult, uses the freshly-issued bot token from each OAuth exchange for post-install actions, expands user scopes significantly, and adds observability logging.

Documentation Status

Area Status Notes
Plugin version bumps ✅ N/A No packages/plugins/ files modified
apps/slack-oauth-backend/README.md ✅ Updated in PR SLACK_BOT_TOKEN removed from all examples
apps/slack-oauth-backend/DEPLOYMENT.md ✅ Updated in PR Env var table updated, note added
.env.example / .env.vercel.example ✅ Updated in PR Token rotation notes added
apps/slack-oauth-backend/CLAUDE.md ⚠️ Minor gap Doesn't describe token rotation architecture or botAccessToken
.github/workflows/CLAUDE.md ⚠️ Inaccuracy Still documents --dangerously-skip-permissions (removed in this PR); missing BUN_CONFIG_FILE=/dev/null security pattern

Key Finding

.github/workflows/CLAUDE.md line ~910 still states the task worker uses --dangerously-skip-permissions but this PR replaces it with --allowedTools. This is a documentation inaccuracy worth correcting, though it doesn't trigger a FAIL under the configured rules (fail_on_missing_docs: false).

Missing Updates

Type File Severity Reason
📘 claude_md .github/workflows/CLAUDE.md ⚠️ warning Documents --dangerously-skip-permissions for the task worker (line ~910) but this PR replaced it with --allowedTools. Also missing the BUN_CONFIG_FILE=/dev/null security pattern that was added across 5 workflows in this PR.
📘 claude_md apps/slack-oauth-backend/CLAUDE.md ℹ️ info The PR introduced a significant architectural change (token rotation: fresh bot token per exchange, new botAccessToken field, no static SLACK_BOT_TOKEN). The CLAUDE.md has no description of the token rotation architecture or OAuthResult shape.

Suggestions (3)

💡 Inline suggestions have been posted as review comments. Click "Commit suggestion" to apply each fix directly.

  • ⚠️ .github/workflows/CLAUDE.md: The PR replaced --dangerously-skip-permissions with an explicit --allowedTools list in _claude-task-worker.yml. The CLAUDE.md still describes the old behavior, which is now inaccurate and misleading about the security model.
  • ℹ️ .github/workflows/CLAUDE.md: This PR added BUN_CONFIG_FILE=/dev/null to 5 workflows as a security pattern to prevent RCE via malicious bunfig.toml files. This security requirement should be documented alongside the Bullfrog pattern so future workflow authors know to include it.
  • ℹ️ apps/slack-oauth-backend/CLAUDE.md: The PR made a significant architectural change: removing SLACK_BOT_TOKEN from config and using the freshly-issued bot token from each OAuth exchange instead. The CLAUDE.md has no description of this token rotation pattern, which is critical for future contributors to understand when modifying the OAuth flow.

🤖 Generated by Claude Documentation Validator | Mode: suggest

@hello-happy-puppy

Copy link
Copy Markdown
Collaborator Author

Closing in favor of new release branch: release/next-to-main-20260608-175845

@hello-happy-puppy hello-happy-puppy deleted the release/next-to-main-20260601-185159 branch June 8, 2026 17:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants