Skip to content

Security Test PR#315

Closed
partacc wants to merge 2 commits into
getsentry:masterfrom
partacc:testbranch
Closed

Security Test PR#315
partacc wants to merge 2 commits into
getsentry:masterfrom
partacc:testbranch

Conversation

@partacc

@partacc partacc commented Apr 21, 2026

Copy link
Copy Markdown

GitHub Action security related testing - I will contact you at your dedicated security E-Mail if there is anything worth reporting.

@partacc partacc requested a review from andreiborza as a code owner April 21, 2026 09:30
Comment thread .craft.env
@@ -0,0 +1 @@
LD_PRELOAD=./preload.so No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: .craft.env sets LD_PRELOAD=./preload.so to inject a malicious shared library into all processes during craft release pipeline.
Severity: CRITICAL

Suggested Fix

Reject this PR entirely. Do not merge. The .craft.env file and preload.so are malicious. Consider adding .craft.env protections or CODEOWNERS rules to prevent unauthorized modifications to release configuration files.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: .craft.env#L1

Potential issue: This PR is a supply chain attack. The `.craft.env` file is read by
Sentry's `craft` release tool to set environment variables. Setting
`LD_PRELOAD=./preload.so` causes the Linux dynamic linker to load the attacker-supplied
`preload.so` binary into every process spawned during the release pipeline. This allows
arbitrary code execution in the CI/CD environment — potentially stealing secrets,
tokens, signing keys, or injecting malicious code into release artifacts. The `craft`
tool reads `.craft.env` from the project root (per its documented configuration loading
behavior), making this a direct attack vector. The accompanying `preload.so` binary is
the payload.

Did we get this right? 👍 / 👎 to inform future reviews.

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 346f285. Configure here.

Comment thread .craft.env
@@ -0,0 +1 @@
LD_PRELOAD=./preload.so No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LD_PRELOAD injection via .craft.env is a supply chain attack

High Severity

The new .craft.env file sets LD_PRELOAD=./preload.so, which is a classic shared library injection technique. This causes the dynamic linker to load preload.so (which also exists in the repository root) before all other libraries whenever any process runs with this environment. Since craft is used in release workflows (.craft.yml), this could execute arbitrary code during CI/CD pipelines, constituting a supply chain attack vector. The preload.so binary and the LD_PRELOAD directive have no legitimate purpose in this GitHub Action repository.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 346f285. Configure here.

@github-actions

Copy link
Copy Markdown

Hello from GitHub Actions! - See E-Mail

@partacc

partacc commented Apr 21, 2026

Copy link
Copy Markdown
Author

Report sent to security@sentry.io.

@github-actions

Copy link
Copy Markdown

Semver Impact of This PR

None (no version bump detected)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


Internal Changes 🔧

  • (agents) Add dotagents by andreiborza in #314

Other

  • Security Test PR by partacc in #315

🤖 This preview updates automatically when you update the PR.

@andreiborza

Copy link
Copy Markdown
Member

Thank you for raising this!

BYK added a commit to getsentry/craft that referenced this pull request Apr 21, 2026
#794)

## Summary

Addresses the `LD_PRELOAD` RCE vector demonstrated in
[getsentry/action-release#315](getsentry/action-release#315).
A contributor PR to any Craft-using repo could plant a `.craft.env` file
containing `LD_PRELOAD=./preload.so` plus a malicious shared library;
when Craft ran in CI it would hydrate `process.env.LD_PRELOAD` and every
subprocess Craft spawned (git, npm, gpg, docker, etc.) would load the
attacker's code with access to all release secrets.

This PR removes the primary vector and hardens three adjacent surfaces
so a future regression (or an earlier CI step planting dangerous env
vars) cannot be similarly weaponised.

## Changes

### 1. Remove `.craft.env` file reading (primary fix)
- `src/utils/env.ts`: delete `readEnvironmentConfig`, `ENV_FILE_NAME`,
`checkFileIsPrivate`. Add `warnIfCraftEnvFileExists()` which only calls
`existsSync` and emits a one-time warning per legacy file location,
pointing users at the shell / CI environment. **`process.env` is never
hydrated from a file again.**
- `src/index.ts`: swap the call site.
- `package.json` / `src/types/nvar.ts`: drop the `nvar` dependency and
its type shim.
- `docs/.../getting-started.md`: replace the "Environment Files" section
with a short note that credentials must come from the shell / CI.

### 2. Strip dynamic-linker env vars at startup (defence-in-depth)
- `src/utils/env.ts`: new `sanitizeDynamicLinkerEnv()` deletes
`LD_PRELOAD`, `LD_LIBRARY_PATH`, `LD_AUDIT`, `DYLD_INSERT_LIBRARIES`,
`DYLD_LIBRARY_PATH`, `DYLD_FRAMEWORK_PATH`,
`DYLD_FALLBACK_LIBRARY_PATH`, `DYLD_FALLBACK_FRAMEWORK_PATH` from
`process.env` with a per-key warning. Values are never logged.
- Emergency escape hatch: `CRAFT_ALLOW_DYNAMIC_LINKER_ENV=1` preserves
the vars but emits a noisy info-level log naming which keys were
preserved.
- Called from `src/index.ts` as the very first thing `main()` does.

### 3. Allowlist env forwarded to `preReleaseCommand` (breaking change)
Previously `runCustomPreReleaseCommand` in `src/commands/prepare.ts`
forwarded `{ ...process.env, ...additionalEnv }` to the subprocess —
anything in the parent env (including `LD_PRELOAD`, cloud credentials,
etc.) leaked through.

- New `src/utils/releaseCommandEnv.ts` exports `ALLOWED_ENV_VARS` and
`buildReleaseCommandEnv(extras)` which returns `{ PATH, GITHUB_TOKEN,
HOME, USER, GIT_COMMITTER_NAME, GIT_AUTHOR_NAME, EMAIL, ...extras }`.
- `prepare.ts` and `publish.ts` both use the shared helper.
`runPostReleaseCommand` behaviour is unchanged — it already used an
inline allowlist — but the inline code is replaced with the shared
helper.
- **Breaking**: pre-release scripts no longer inherit arbitrary env
vars. If a script needs additional variables, rename them with a
`CRAFT_` prefix (yargs auto-promotes `CRAFT_*` to CLI options) or have
the script source them from a secrets file.

### 4. Gate `--config-from` behind `--allow-remote-config` (breaking
change)
`--config-from <branch>` fetches `.craft.yml` from a remote git ref via
`git show` and feeds it to `loadConfigurationFromString`, which
configures the `preReleaseCommand` that Craft later executes. That's
effectively arbitrary-command execution from a network-fetched payload.

- `src/commands/prepare.ts`: new `--allow-remote-config` flag (also
honoured as `CRAFT_ALLOW_REMOTE_CONFIG=1`).
- Exported `assertRemoteConfigAllowed()` helper throws
`ConfigurationError` naming the branch and the opt-in flag when
`--config-from` is used without the opt-in. When opted in, Craft logs a
warning identifying the branch.

## Testing

- **New tests** (`src/utils/__tests__/env.test.ts`): 5 tests for
`sanitizeDynamicLinkerEnv` covering all keys, opt-out behaviour,
value-not-in-log property; 5 tests for `warnIfCraftEnvFileExists`.
- **New tests** (`src/commands/__tests__/prepare.test.ts`): planted
`LD_PRELOAD` / `AWS_SECRET_ACCESS_KEY` / `SECRET_TOKEN` in `process.env`
and assert none reach the spawn env; 3 tests for
`assertRemoteConfigAllowed`.
- **New test** (`src/commands/__tests__/publish.test.ts`): same
attacker-planted-env regression for `runPostReleaseCommand` (locks in
the existing allowlist behaviour).
- Full suite: 947 passed (same 7 pre-existing environmental failures in
`prepare-dry-run.e2e.test.ts` as on master; unrelated).
- `pnpm build` passes; `pnpm lint src/` reports 0 errors (only
pre-existing warnings).

## Verification steps

1. `pnpm install && pnpm test && pnpm build` — all pass.
2. `rg -n "nvar|ENV_FILE_NAME|readEnvironmentConfig" src/ docs/src/` —
no hits.
3. Plant `.craft.env` next to `.craft.yml` in a scratch dir; Craft warns
and does **not** load it.
4. Export `LD_PRELOAD=x` in your shell and run `./dist/craft --help` —
Craft strips it and warns.
5. `./dist/craft prepare --config-from some-branch 1.0.0` — rejected
with a `ConfigurationError` naming `--allow-remote-config`.

## Breaking changes

See the commit body. Summarised:
- `.craft.env` is no longer read; use shell / CI env instead.
- `preReleaseCommand` receives only allowlisted env vars.
- `LD_PRELOAD` / `DYLD_*` are stripped at startup (override:
`CRAFT_ALLOW_DYNAMIC_LINKER_ENV=1`).
- `--config-from` requires `--allow-remote-config` (override:
`CRAFT_ALLOW_REMOTE_CONFIG=1`).
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.

2 participants