Skip to content

Accept any openable path for reads and add native environment support - #2

Merged
kjanat merged 2 commits into
masterfrom
native-env-support
Jul 31, 2026
Merged

Accept any openable path for reads and add native environment support#2
kjanat merged 2 commits into
masterfrom
native-env-support

Conversation

@kjanat

@kjanat kjanat commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Problem

envctl redact <(env) failed with envctl: no such file: /proc/self/fd/14. Process-substitution paths are FIFOs, and the file gate required S_ISREG before ever opening anything — then blamed the rejection on a missing file. The reader itself is plain fopen and handles FIFOs fine; only the mutating commands genuinely need a regular file (atomic temp + rename).

There was also no way to read the process environment natively — an env dump had to be piped in through the env binary.

Fix

Split the file predicate:

  • Read paths (get, list, redact's env file, bare-form get) accept any existing openable path: FIFOs from <(...), /dev/fd/N, character devices.
  • Mutating commands (set, disable, enable, delete) keep requiring a regular file and now fail with not a regular file: PATH; no such file is reserved for paths that actually don't exist.
  • The implicit ./.env default still requires a regular file, per the documented contract.

Native environment support

  • envctl env — prints the whole environment as KEY=VALUE with redaction always on (rejects --raw, like filter mode). One-word replacement for env | envctl redact --no-env.
  • envctl get --env KEY — exit 1 if unset; follows the existing get redact rules, so it stays composable in scripts.
  • envctl list --env [--values] — same rules as file list.
  • envctl redact --env — filter stdin using the environment's values (plus base64/hex/URL/JSON-escaped encodings) as the literal mask set.

Environ entries are treated as atomic KEY=VALUE pairs in a dedicated envsrc module: env-file span logic never applies (a value with an unclosed quote can't swallow the next variable), and values with embedded newlines take the same segment-masking path as multi-line file values.

Breaking: env is now a command word. A key literally named env needs the explicit form envctl get env (consistent with the existing "command name wins" rule).

Tests

19 new cases, all verified to fail against the unfixed binary. Harness additions:

  • fifo-file section: serves a fixture through a FIFO with {FIFO} argv expansion, standing in for process substitution; skipped where mkfifo is unusable (incl. MSYS).
  • posix-env mode: gates environ-order expectations off Windows, where the runtime reorders the environment block.

filter-epipe-reports-write-failure fails in my sandbox both before and after this change (container pipe-timing); unrelated.

Docs

README (usage, default-file rules, flags table, filter mode, new Process environment section), -h/--help text and regenerated fixtures, CONTRIBUTING (envsrc module, fifo-file section, mode table).

envctl redact <(env) failed with "no such file: /proc/self/fd/N": the
regular-file gate rejected FIFOs even though every read path handles
them fine. Split the predicate - read commands (get, list, redact's
env file) now accept any existing path, while the mutating commands
keep requiring a regular file and report "not a regular file" when
handed anything else, keeping "no such file" truthful.

Add the process environment as a native source, so no env binary is
needed:

- get --env KEY and list --env [--values] read the environment under
  the existing redact rules, so scripts stay composable
- redact --env uses the environment's values as the literal mask set
  when filtering stdin
- envctl env prints the whole environment with redaction always on,
  the safe one-word replacement for env | envctl redact --no-env

Environ entries are atomic KEY=VALUE pairs: env-file span logic never
applies, and a value containing newlines takes the segment-masking
path that multi-line file values already use.

Test harness: a fifo-file section serves a fixture through a FIFO with
{FIFO} argv expansion, standing in for process substitution; posix-env
mode gates environ-order expectations off Windows, where the runtime
reorders the environment block.

Breaking: env is now a command word; a key literally named env needs
the explicit form envctl get env.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 48ff8904-5547-4e7d-8dd0-24e5a97bc235

📥 Commits

Reviewing files that changed from the base of the PR and between fab8194 and 744f204.

⛔ Files ignored due to path filters (2)
  • tests/fixtures/expected/help-agent.out is excluded by !**/*.out
  • tests/fixtures/expected/help-long.out is excluded by !**/*.out
📒 Files selected for processing (1)
  • src/help.c
📜 Recent review details
⏰ Context from checks skipped due to timeout. (3)
  • GitHub Check: windows-2025-vs2026
  • GitHub Check: Analyze (c-cpp)
  • GitHub Check: Analyze (actions)
🧰 Additional context used
🔍 Remote MCP GitHub Grep

Relevant review context

  • POSIX environment handling commonly iterates environ until a null pointer and declares it as extern char **environ; this pattern appears in H2O and Redis.
  • Established implementations identify entries by the first = using strchr, skip malformed entries without =, and treat everything after the first separator as the value. OpenJDK, jq, mruby, and related projects use this approach.
  • FIFO creation and use is platform-dependent: NuttX documents that opening a FIFO read-only can block until a writer opens it, while other codebases explicitly account for differing FIFO open modes. This supports careful writer lifecycle cleanup and platform gating in the test harness.
  • Conventional read paths use open(path, O_RDONLY) directly for special files such as proc/sysfs entries, rather than requiring regular-file metadata first.
  • No public repository matched the PR-specific symbols (env_entries, maskset_load_env, or act_env_list), so direct implementation-level comparison remains unavailable.
🔇 Additional comments (1)
src/help.c (1)

38-38: LGTM!

Also applies to: 41-45, 55-58, 83-85


📝 Walkthrough

Add support for non-regular read paths and process-environment sources

Read commands now accept FIFOs, /dev/fd paths and character devices, while mutating commands require regular files and distinguish missing paths from invalid file types.

Add env, get --env, list --env and redact --env, including atomic KEY=VALUE handling, multiline values, redaction and encoding support. Update command parsing, help text, documentation and environment masking.

Expand the test harness with FIFO fixtures, POSIX environment-order handling and 19 new test cases.

Walkthrough

Added process-environment retrieval, listing, dumping, and redaction through new env and --env paths. Read commands now accept existing non-regular sources, while mutations still require regular files. Environment values can populate masking sets. The test runner now supports FIFO fixtures, platform capability checks, and environment-order checks, with coverage for successful commands, invalid combinations, redaction, missing variables, and FIFO inputs.

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

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant envctl
  participant envsrc
  participant maskset
  User->>envctl: run env or get --env
  envctl->>envsrc: enumerate or retrieve environment entries
  envsrc-->>envctl: entries or requested value
  envctl-->>User: print environment output
  User->>envctl: run redact --env
  envctl->>envsrc: obtain environment entries
  envsrc-->>envctl: KEY=VALUE entries
  envctl->>maskset: load environment values
  maskset-->>envctl: configured masks
  envctl-->>User: redact input and print output
Loading

Poem

Environment treasures, neatly in line,
FIFOs now ferry secrets just fine.
Masks catch the tokens, plain or encoded,
Bad flags walk the plank, errors well stated.
A tidy new source sails into the build! 🏴‍☠️

🚥 Pre-merge checks | ✅ 5 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title accurately describes the changes but is 69 characters long and does not use the required lowercase conventional-commit prefix. Rewrite it with a lowercase verb prefix and keep it concise, for example: "add: support openable reads and native env".
Changelog Update ⚠️ Warning Arrr, source files were modified (src/envsrc.c, main.c, filter.c, mask.c, headers), but no CHANGELOG.md exists in the PR or base, so the required Unreleased entry is missing. Add CHANGELOG.md with a '## [Unreleased]' section describing the FIFO/openable-path and native environment support changes.
Semver Version Bump Validation ⚠️ Warning Source files changed, but neither base nor PR contains any permitted version file; no SemVer bump is detectable, despite breaking API/CLI changes requiring a MAJOR bump. Add a permitted version file with a documented base SemVer and increment MAJOR for the changed act_redact signature and incompatible CLI semantics.
✅ Passed checks (5 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly and comprehensively explains the path-handling changes, native environment support, tests, and documentation updates.
Docstring Coverage ✅ Passed Docstring coverage is 30.00% which is sufficient. The required threshold is 30.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.
Agents.Md Documentation Updated ✅ Passed No AGENTS.md exists anywhere in the repository; therefore the conditional update requirement is not applicable to these CLI and automation changes.

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

@kjanat kjanat self-assigned this Jul 31, 2026
@kjanat kjanat added the cr:review Allow CodeRabbit review label Jul 31, 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: 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 `@src/help.c`:
- Line 38: Update the redact command synopsis in the help text to include the
supported --no-env option alongside the existing file and --env choices, while
preserving the current masking description.

In `@tests/cases/get-env-missing.case`:
- Around line 1-4: Update the get-env-missing fixture’s --env input to use the
harness’s unset facility or a guaranteed-unique variable name, ensuring the
variable is deterministically absent regardless of the developer or CI
environment.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2db8f715-07ee-4621-9d2b-2e3ca2b16897

📥 Commits

Reviewing files that changed from the base of the PR and between ddfc786 and fab8194.

⛔ Files ignored due to path filters (2)
  • tests/fixtures/expected/help-agent.out is excluded by !**/*.out
  • tests/fixtures/expected/help-long.out is excluded by !**/*.out
📒 Files selected for processing (32)
  • CONTRIBUTING.md
  • Makefile
  • README.md
  • src/envsrc.c
  • src/envsrc.h
  • src/filter.c
  • src/filter.h
  • src/help.c
  • src/main.c
  • src/mask.c
  • src/mask.h
  • tests/cases/bare-env-get.case
  • tests/cases/bare-env-set-rejected.case
  • tests/cases/env-cmd-basic.case
  • tests/cases/env-cmd-rejects-positional.case
  • tests/cases/env-cmd-rejects-raw.case
  • tests/cases/error-set-fifo-not-regular.case
  • tests/cases/filter-fifo-envfile.case
  • tests/cases/get-env-basic.case
  • tests/cases/get-env-missing.case
  • tests/cases/get-env-redacted.case
  • tests/cases/get-fifo-bare.case
  • tests/cases/help-short.case
  • tests/cases/list-env-keys.case
  • tests/cases/list-env-values-redacted.case
  • tests/cases/list-fifo-values.case
  • tests/cases/redact-env-literals.case
  • tests/cases/redact-env-noenv-conflict.case
  • tests/cases/redact-env-with-file.case
  • tests/cases/set-env-rejected.case
  • tests/cases/tty-agent-env-get.case
  • tests/run.sh
📜 Review details
🧰 Additional context used
🪛 Cppcheck (2.21.0)
src/mask.c

[style] 320-320: The function 'maskset_load_env' is never used.

(unusedFunction)

src/filter.c

[style] 13-13: The function 'act_redact' is never used.

(unusedFunction)

src/envsrc.c

[style] 27-27: The function 'act_env_get' is never used.

(unusedFunction)


[style] 60-60: The function 'act_env_dump' is never used.

(unusedFunction)


[style] 24-24: The function 'env_entries' should have static linkage since it is not used outside of its translation unit.

(staticFunction)


[style] 35-35: The function 'act_env_list' should have static linkage since it is not used outside of its translation unit.

(staticFunction)

🪛 Shellcheck (0.11.0)
tests/run.sh

[info] 48-50: Consider adding a default *) case, even if it just exits with error.

(SC2249)


[info] 56-58: Consider adding a default *) case, even if it just exits with error.

(SC2249)

🔍 Remote MCP GitHub Grep

Relevant external context

  • GNU Coreutils’ printenv iterates environ, skips arguments containing =, and matches variable names against entries—useful comparison for env_entries() lookup and malformed-entry handling.
  • H2O counts and scans environ via extern char **environ, confirming the conventional POSIX integration pattern used by the new environment source.
  • Ruby and mruby enumerate environment entries by locating the first = with strchr, consistent with treating the remainder—including additional = characters—as the value.
  • util-linux FIFO tests create a FIFO, launch writers asynchronously, read synchronization data from the FIFO, and explicitly terminate/reap writers during cleanup.
  • FreeBSD tests exercise reader/writer lifecycle behavior and verify that closing a FIFO can terminate the writer, providing a useful comparison for the PR’s epipe-open and FIFO cleanup handling.
  • No public repository containing the exact envctl, maskset_load_env, or act_env_list symbols was found, so target-specific implementation validation was not possible from GitHub Grep.
🔇 Additional comments (31)
src/envsrc.h (1)

1-9: LGTM!

src/envsrc.c (1)

1-60: LGTM!

tests/cases/get-env-redacted.case (1)

1-11: LGTM!

tests/cases/help-short.case (1)

7-12: LGTM!

tests/cases/list-env-keys.case (1)

1-13: LGTM!

tests/cases/list-env-values-redacted.case (1)

1-17: LGTM!

tests/cases/redact-env-literals.case (1)

1-16: LGTM!

tests/cases/redact-env-noenv-conflict.case (1)

1-12: LGTM!

tests/cases/redact-env-with-file.case (1)

1-15: LGTM!

tests/cases/set-env-rejected.case (1)

1-13: LGTM!

src/mask.h (1)

32-32: LGTM!

src/mask.c (1)

314-338: LGTM!

src/filter.h (1)

4-4: LGTM!

src/filter.c (1)

4-4: LGTM!

Also applies to: 13-13, 25-27

tests/cases/tty-agent-env-get.case (1)

1-15: LGTM!

CONTRIBUTING.md (1)

23-23: LGTM!

Also applies to: 50-53, 63-74

tests/run.sh (1)

42-59: LGTM!

Also applies to: 81-81, 221-228, 248-278, 378-384

tests/cases/error-set-fifo-not-regular.case (1)

1-17: LGTM!

tests/cases/filter-fifo-envfile.case (1)

1-19: LGTM!

tests/cases/get-fifo-bare.case (1)

1-10: LGTM!

tests/cases/list-fifo-values.case (1)

1-13: LGTM!

Makefile (1)

11-12: LGTM!

src/main.c (1)

8-8: LGTM!

Also applies to: 30-30, 44-52, 64-64, 100-101, 120-121, 156-169, 179-236, 245-260, 276-280

src/help.c (1)

11-16: LGTM!

Also applies to: 41-45, 55-58, 83-85

README.md (1)

73-97: LGTM!

Also applies to: 108-109, 118-124, 155-197, 258-259

tests/cases/bare-env-get.case (1)

1-10: LGTM!

tests/cases/bare-env-set-rejected.case (1)

1-13: LGTM!

tests/cases/env-cmd-basic.case (1)

1-15: LGTM!

tests/cases/env-cmd-rejects-positional.case (1)

1-12: LGTM!

tests/cases/env-cmd-rejects-raw.case (1)

1-12: LGTM!

tests/cases/get-env-basic.case (1)

1-11: LGTM!

Comment thread src/help.c Outdated
Comment thread tests/cases/get-env-missing.case
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cr:review Allow CodeRabbit review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant