Skip to content

feat(shell): allowlisted $VAR expansion, increment 6 — Track A complete (#34)#44

Merged
hartsock merged 1 commit into
mainfrom
issue-34/varexpand
Jun 25, 2026
Merged

feat(shell): allowlisted $VAR expansion, increment 6 — Track A complete (#34)#44
hartsock merged 1 commit into
mainfrom
issue-34/varexpand

Conversation

@hartsock

Copy link
Copy Markdown
Member

Increment 6 of #34 — the finale (ADR 0005 D3). The safe-subset engine now expands whole-word $VAR / ${VAR} for a small secret-free allowlist (your ratified policy: allowlist-only).

What

  • parse.rsArg::Var(name) (read_variable: bare or braced, [A-Za-z_][A-Za-z0-9_]*; a variable must be a standalone word).
  • shell_tool.rsVAR_ALLOWLIST (HOME, PWD, USER, LOGNAME, TMPDIR, LANG, LC_ALL, SHELL, HOSTNAME, TERM, OLDPWD). The executor expands an allowlisted var from the env as a single literal (no re-split / no re-glob → no re-injection).

Security (the policy call)

The allowlist is enforced in invoke before any spawn: a $VAR outside the set (e.g. $AWS_SECRET_KEY) is denied with nothing spawned, even under full exec — a confined run can't splice a secret into an argument. Refused (documented follow-ups): a $VAR mixed into a word ($HOME/x), inside double quotes, in a redirect target, or as the program name; $(…) stays Dynamic-refused by design.

Testing (fully mocked + deep)

  • Parser (pure): standalone var marked; mixed/quoted/invalid/bare-$ refused; escaped \$ literal; var-in-redirect refused.
  • Mocked: allowlisted var reaches the spawner; non-allowlisted denied (spawner never called); var-as-program denied.
  • Integration (real env): echo $HOME == the process's HOME.

Test plan

just check green (fmt + clippy all-features & no-default-features + workspace tests).

This completes Track A (#34): argv → pipes → redirects → &&/||/; → globbing → $VAR.

🤖 Generated with Claude Code

…te (#34)

WHAT: The safe-subset engine now expands whole-word variable references $VAR / ${VAR}. parse.rs gains Arg::Var(name) (read_variable: bare or braced name, [A-Za-z_][A-Za-z0-9_]*; a variable must be a STANDALONE word). shell_tool.rs holds a small secret-free VAR_ALLOWLIST (HOME, PWD, USER, TMPDIR, LANG, SHELL, ...); the executor expands an allowlisted var from the env as a single literal (no re-split / no re-glob of the value).

WHY (security, the user's policy call): a *confined* engine must not splice secrets into arguments. So the allowlist is enforced in invoke BEFORE any spawn — a $VAR outside the set (e.g. $AWS_SECRET_KEY) is denied and nothing runs, even with full exec. Refused (documented follow-ups): a $VAR mixed into a larger word ($HOME/x), inside double quotes, in a redirect target, or in the program position; $(...) stays Dynamic-refused by design. No re-split/re-glob avoids the re-injection vector.

TEST: parser (standalone var marked; mixed/quoted/invalid/bare refused; escaped \$ literal; var-in-redirect refused) + mocked (allowlisted var reaches spawner; non-allowlisted denied with nothing spawned; var-as-program denied) + real integration (echo $HOME == the env value). just check green. This completes Track A (#34): argv, pipes, redirects, &&/||/;, globbing, $VAR.

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

Claude-Session: https://claude.ai/code/session_01HMGPEApE4XfwgMhgFbRn6c
@hartsock hartsock added the risk:low Low-risk change label Jun 25, 2026
@hartsock hartsock merged commit 94cc54f into main Jun 25, 2026
1 check passed
@hartsock hartsock deleted the issue-34/varexpand branch June 25, 2026 06:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:low Low-risk change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant