Skip to content

fix(api): reject newline/CR/NUL in secret key to prevent secrets.env line injection#6300

Merged
houko merged 1 commit into
mainfrom
fix/secrets-env-key-newline-validation
Jun 24, 2026
Merged

fix(api): reject newline/CR/NUL in secret key to prevent secrets.env line injection#6300
houko merged 1 commit into
mainfrom
fix/secrets-env-key-newline-validation

Conversation

@houko

@houko houko commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

routes::secrets_env::upsert_secret validated the secret value for \n / \r / \0 (lines 24-33) but the key check (line 44) only rejected =, leading/trailing whitespace, and empty.

A key containing an interior newline (e.g. FOO\nBAR) passes every check — key.trim() != key is false because the newline is not at an edge, and key.contains('=') is false — so the out.push_str(&format!("{key}={value}\n")) write emits:

FOO
BAR=value

injecting an extra BAR=value line into secrets.env. That file is loaded into the process environment at daemon boot (librefang_extensions::dotenv::load_dotenv) and inherited by sidecar child processes, so the injected line becomes a live environment variable.

This is a direct inconsistency with the sibling helper routes::skills::write_secret_env, which already rejects key.contains('\n') || key.contains('\r').

Fix

Reject \n / \r / \0 in the key before the existing checks, mirroring the hardened write_secret_env path, so a key can never break the KEY=VALUE line framing.

Tests

Adds an in-crate #[cfg(test)] module:

  • key_with_newline_is_rejected_and_does_not_inject_a_line
  • key_with_carriage_return_or_nul_is_rejected
  • well_formed_key_still_writes_a_single_line (positive case)

Verification

This host has no native Rust toolchain and no working container runtime, so local cargo verification was not possible — CI is the gate. The change is a few lines of input validation plus self-contained unit tests. Reviewers / CI: cargo test -p librefang-api.

Scope

One of several independent findings from a repo audit; filed as its own PR per the one-PR-one-domain policy.

@github-actions github-actions Bot added area/docs Documentation and guides size/M 50-249 lines changed size/S 10-49 lines changed and removed size/M 50-249 lines changed labels Jun 24, 2026

@houko houko left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

One mechanical fix pushed: the 5-line comment block before the new key check was collapsed to a single line per the project comment rule ("never write multi-line comment blocks — one short line max"; CLAUDE.md).

Missing integration test (CLAUDE.md §Integration Testing)

CLAUDE.md requires a #[tokio::test] against TestServer for any route / wiring change:

Add a #[tokio::test] against TestServer in the matching tests/*.rs file. Pattern: spawn router via start_test_server(), hit the endpoint with reqwest, assert status and response shape; for write endpoints, follow up with a read and assert the side effect.

The three tests added here are unit tests on upsert_secret directly (inside the source file's #[cfg(test)] block), not integration tests through the HTTP layer.
A test in crates/librefang-api/tests/ that calls PUT /api/secrets/env/FOO%0ABAR and expects a 400 / 422 would cover the route-level wiring (parameter extraction, handler dispatch, error serialisation) that the unit tests do not reach.
The unit tests are a good complement but cannot substitute for it per the project policy.


Generated by Claude Code

@houko
houko enabled auto-merge (squash) June 24, 2026 01:22
@github-actions github-actions Bot added the has-conflicts PR has merge conflicts that need resolution label Jun 24, 2026
@houko
houko force-pushed the fix/secrets-env-key-newline-validation branch from 409d7ed to 0f14401 Compare June 24, 2026 01:25
@github-actions github-actions Bot added size/M 50-249 lines changed ready-for-review PR is ready for maintainer review has-conflicts PR has merge conflicts that need resolution and removed size/S 10-49 lines changed has-conflicts PR has merge conflicts that need resolution ready-for-review PR is ready for maintainer review labels Jun 24, 2026
…line injection

routes::secrets_env::upsert_secret validated the secret value for newline/CR/NUL but the key check only rejected `=`, edge whitespace, and empty.
A key with an interior newline (e.g. `FOO\nBAR`) passed all checks, and the `format!("{key}={value}\n")` write emitted `FOO\nBAR=value`, injecting an extra `BAR=value` line into secrets.env — which is loaded into the process environment at boot and inherited by sidecar children.

The key is now rejected for newline/CR/NUL, mirroring the already-hardened routes::skills::write_secret_env path. Adds unit tests for the rejection and the well-formed happy path.
@houko
houko force-pushed the fix/secrets-env-key-newline-validation branch from 0f14401 to a8739a0 Compare June 24, 2026 03:15
@github-actions github-actions Bot added ready-for-review PR is ready for maintainer review and removed has-conflicts PR has merge conflicts that need resolution labels Jun 24, 2026
@houko
houko merged commit 28e7ef9 into main Jun 24, 2026
31 checks passed
@houko
houko deleted the fix/secrets-env-key-newline-validation branch June 24, 2026 03:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docs Documentation and guides ready-for-review PR is ready for maintainer review size/M 50-249 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant