Skip to content

feat: add realpath command for resolving canonical virtual filesystem paths - #447

Open
felixarntz wants to merge 13 commits into
mainfrom
fa/add-realpath
Open

felixarntz wants to merge 13 commits into
mainfrom
fa/add-realpath

Conversation

@felixarntz

@felixarntz felixarntz commented Sep 21, 2026

Copy link
Copy Markdown

Adds a native realpath FILE... command to just-bash. This removes the need for consumers to seed a readlink-based workaround, as described in vercel/ai#20989.

just-bash already provided the foundation for realpath, but not an actual command for it.

The command delegates canonicalization to the filesystem abstraction, supports multiple operands, --help, and --, and preserves exact canonical path strings, including whitespace and newlines. In-memory path resolution now follows chained intermediate symlinks so command output and filesystem operations agree.

Given security considerations and edge-cases, achieving parity with the real Bash realpath command uncovered flaws in the underlying IFileSystem implementations, which this PR addresses (see intermediate agent reviews). To avoid increasing complexity in duplicate implementations, several chunks of common logic shared across the implementations were broken out into shared utility functions.

@vercel

vercel Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
just-bash-website Ready Ready Preview, v0 Sep 22, 2026 3:10pm UTC
1 Skipped Deployment
Project Deployment Actions Updated
just-bash Ignored Ignored v0 Sep 22, 2026 3:10pm UTC

@auto-maintain

auto-maintain Bot commented Sep 21, 2026

Copy link
Copy Markdown

🤖 auto-maintain review

Automated, advisory triage for @felixarntz's PR. Facts below are read from the GitHub API.

Check Result
Author's merged PRs (this repo) 0 — ⚠️ first-time contributor
Account established ✅ (age 4971d · 582 followers · 108 public repos)
Commits signed/verified ✅ 13/13
Changeset included ✅ (.changeset/realpath-command.md)

Review panel: 🟡 medium highest severity

just-bash maintainer code review: 🟡 medium

The shared resolver introduces strictness, compatibility, and mount-confinement regressions that should be fixed before merge.

  • packages/just-bash/src/commands/js-exec/run-runtime.ts:934 — `fs.realpathSync()` now uses `realpathFromCwd`, whose all-but-last mode accepts a missing leaf. This breaks Node compatibility: `fs.realpathSync('missing')` returns an absolute path instead of throwing ENOENT.
  • packages/just-bash/src/fs/interface.ts:288 — Making `realpathFromCwd` mandatory breaks exported `IFileSystem` implementations and existing custom filesystems at runtime, including previously working js-exec realpath calls. Keep it optional with a fallback or resolve cwd internally.
  • packages/just-bash/src/fs/physical-path.ts:136 — The mount adapter preserves an underlying symlink's `reject: false`, allowing relative targets such as `../../secret.txt` to canonicalize outside the mount. Thus `realpath('/mnt/data/escape')` can return `/secret.txt` even though normal mounted reads deliberately confine that link and reject it.
  • packages/just-bash/src/fs/in-memory-fs/in-memory-fs.ts:94 — After physical resolution fails, `resolveExisting` retries the lexically normalized path. For `/link/../file` where `link -> /target/dir`, a missing `/target/file` can therefore fall back to and read `/file`, contradicting realpath semantics and the intended agreement between filesystem operations and canonicalization.

General code review: 🟡 medium

The shared resolver is promising, but it introduces a custom-filesystem compatibility break and retains incorrect lexical fallback behavior for in-memory operations.

  • packages/just-bash/src/interpreter/defense-aware-command-context.ts:171 — `realpathFromCwd` is bound unconditionally, so existing custom filesystems without this newly added method throw before any command executes under the default defense-in-depth mode. Keep the method optional and provide a fallback using the existing `realpath` API.
  • packages/just-bash/src/fs/in-memory-fs/in-memory-fs.ts:101 — Retrying ENOENT paths after lexical normalization defeats physical symlink resolution: if `/link -> /target/dir`, `/link/../secret` incorrectly falls back to `/secret` when `/target/secret` is absent. This makes read/stat/exists/utimes disagree with `realpath` and access the wrong entry.

Adversarial security: 🟢 low

No actionable adversarial security issues found in the complete base-to-head diff.

Adversarial security (second opinion): 🟡 medium

No backdoors, exfiltration, dependency/CI tampering, or sandbox-escape regressions found; the path-resolution rewrite preserves default-deny symlink and root-containment behavior and is well covered by new tests. Two actionable issues: a required-interface addition that breaks custom `IFileSystem` embedders at runtime, and an inconsistent ENOENT retry in `InMemoryFs` that makes file operations disagree with `realpath`.

  • packages/just-bash/src/interpreter/defense-aware-command-context.ts:171 — `realpathFromCwd` is added as a required member of the publicly exported `IFileSystem` interface and is bound unconditionally here (`fs.realpathFromCwd.bind(fs)`). `createDefenseAwareCommandContext` runs on every command dispatch (builtin-dispatch.ts:949), so any embedder passing a pre-existing custom filesystem via the public `BashOptions.fs` option will now get `TypeError: Cannot read properties of undefined (reading 'bind')` on every command, not just `realpath`. The same file already handles this exact case for `readFileBytes` with a `typeof fs.readFileBytes === "function"` guard; apply the same guard (or make the interface member optional). The changeset also labels this `patch` despite the interface break.
  • packages/just-bash/src/fs/in-memory-fs/in-memory-fs.ts:98 — `resolveExisting` retries with lexically-normalized `..` whenever strict resolution returns ENOENT, but `InMemoryFs.realpath`/`realpathFromCwd` call `resolveFsPath` directly with no retry. The result is that `realpath('/missing/../target/file.txt')` throws ENOENT (asserted in the new in-memory-fs.realpath.test.ts) while `readFile`/`stat`/`access`/`utimes` on the identical path succeed via the fallback — the opposite of the PR's stated goal that command output and filesystem operations agree. It also silently accepts paths whose intermediate components do not exist, and lexically collapses `..` across symlinked components that the strict pass would have followed.

Standard Bash and host portability: 🟡 medium

The implementation has two portability regressions affecting custom filesystems and Windows host-backed symlinks.

  • packages/just-bash/src/interpreter/defense-aware-command-context.ts:171 — Unconditionally binding the new `realpathFromCwd` method breaks existing custom `IFileSystem` implementations at runtime, even for unrelated commands. Preserve compatibility with a fallback through the existing `realpath` API, as done for optional `readFileBytes`.
  • packages/just-bash/src/fs/host-path-access.ts:98 — Relative host symlink targets are returned without converting Windows `&Introduce our defense-in-depth inside of workers #96; separators, but the shared virtual resolver splits only on `/`. Consequently, ReadWriteFs/OverlayFs realpath resolution fails for ordinary relative symlinks on Windows; normalize separators before returning the target.

Posted by auto-maintain. This automated code review is advisory; a human maintainer makes the call.

Comment thread packages/just-bash/src/fs/in-memory-fs/in-memory-fs.ts Outdated
@vercel-security-reviewer

Copy link
Copy Markdown

Security review details

@auto-maintain

auto-maintain Bot commented Sep 21, 2026

Copy link
Copy Markdown

⚠️ auto-maintain: review severity raised to 🔴 high

A new push changed this PR and the review now contains a higher-severity finding. See the updated review comment above.

@felixarntz

Copy link
Copy Markdown
Author

@cramforce I addressed the auto-maintain feedback from #447 (comment) and the security feedback from #447 (comment) and #447 (comment)

one thing worth highlighting: there's now a new realpathFromCwd on the filesystem interface. All implementations part of just-bash support it, but wanted to flag in case it's common for third parties to use their own filesystem implementations. If so, this would be a breaking change, so needs a decision whether it's fine or we would want to make it optional (with its own drawbacks of course).

This branch was successfully deployed

1 active deployment
Preview – just-bash-website 0de69a46 Deployed Sep 22, 2026 by vercel[bot]
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.

1 participant