Skip to content

Block tilde expansion to prevent user info leak#2

Merged
AlexandreYang merged 2 commits intomainfrom
dd/fix/block-tilde-expansion-user-leak
Mar 8, 2026
Merged

Block tilde expansion to prevent user info leak#2
AlexandreYang merged 2 commits intomainfrom
dd/fix/block-tilde-expansion-user-leak

Conversation

@AlexandreYang
Copy link
Copy Markdown
Member

Summary

The upstream mvdan.cc/sh/v3 expand package calls os/user.Lookup() when it encounters ~username tilde expansion (e.g., echo ~root). This leaks host system user home directories, bypassing the shell's sandbox. Since tilde is parsed as a plain *syntax.Lit (no dedicated AST node), the existing validator had no way to catch it.

Changes

  • interp/validate.go: Added *syntax.Word check in validateNode that rejects any unquoted word starting with ~ at the first Lit part. Heredoc bodies are excluded via a hdocWords tracking set (heredocs don't undergo tilde expansion).
  • SHELL_FEATURES.md: Updated tilde expansion entry to include ~user form and note that it is rejected at validation (exit code 2).
  • Updated test scenarios: tilde_not_expanded.yaml and tilde_path_not_expanded.yaml now expect exit code 2 (validation rejection) instead of runtime passthrough.
  • New test scenarios:
    • tilde_username_blocked.yaml — verifies ~root is rejected (the critical security case)
    • tilde_in_heredoc_allowed.yaml — verifies ~ in heredoc bodies still works
    • tilde_in_variable_assignment_blocked.yaml — verifies DIR=~/path is rejected
    • tilde_quoted_allowed.yaml — verifies "~root" and '~/path' are allowed (no expansion in quotes)
    • tilde_mid_word_allowed.yaml — verifies foo~bar is allowed (not a tilde prefix)

Testing

  • go build ./... — compiles cleanly
  • go test ./... — all tests pass (interp, cmd/rshell, scenario tests)
  • go vet ./interp/... — no issues
  • gofmt — formatted

PR by Bits
View session in Datadog

Comment @DataDog to request changes

Co-authored-by: AlexandreYang <49917914+AlexandreYang@users.noreply.github.com>
@datadog-datadog-prod-us1
Copy link
Copy Markdown

View session in Datadog

Bits Dev status: ✅ Done

CI Auto-fix: Disabled | Enable

Comment @DataDog to request changes

Comment thread SHELL_FEATURES.md Outdated
@AlexandreYang AlexandreYang merged commit 5af2a78 into main Mar 8, 2026
6 of 7 checks passed
@AlexandreYang AlexandreYang deleted the dd/fix/block-tilde-expansion-user-leak branch March 8, 2026 23:16
@datadog-official datadog-official Bot mentioned this pull request Mar 10, 2026
4 tasks
julesmcrt added a commit that referenced this pull request Apr 30, 2026
Two Codex round-6 items, both real:

1. P1 — saturating block multiply. unix.Statfs_t.Blocks * bsize could
   wrap a uint64 if a buggy/malicious FUSE filesystem reports counts
   above MaxUint64/bsize, producing tiny/zero Total/Used/Free for that
   mount and corrupting the --total accumulation. Added a mulSat(a, b)
   helper to diskstats_unix.go that clamps to MaxUint64 on overflow.
   Linux and Darwin backends now use mulSat for Total, Free, and Used.
   Locked in by TestMulSat covering boundary, just-over-boundary, and
   the realistic FUSE-rogue (maxU * 4096) case.

2. P2 — last-flag-wins for -h / -H. The previous code unconditionally
   preferred -h, so `df -hH` (intended SI override) and shell aliases
   that append -H to a default got the wrong size column. pflag.Visit
   walks set flags in lexicographical order, not argv order, so it
   cannot be used to honor input order. Refactored to a custom
   unitFlag (a pflag.Value) where -h and -H share a single *unitMode
   target and each Set call overwrites it — the LAST one wins by
   construction. registerUnitFlag wraps fs.VarPF + NoOptDefVal="true"
   so the flags accept no argument, matching pflag's bool convention.
   Confirmed locally: `df -hH` prints SI (995G), `df -Hh` prints IEC
   (927G). New TestUnitFlag_LastFlagWins covers 10 argv interleavings
   including combined short flags (-hH, -Hh).

Side effect of #2: dropped the unused `human`/`si` *bool fields from
the flags struct and the resolveUnitMode helper; mode is now read
directly from a *unitMode field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants