Skip to content

fix(cli): reject outputs that alias inputs or earlier outputs - #1496

Open
oxura wants to merge 4 commits into
bendlang:mainfrom
oxura:fix/output-input-hardlinks-20261011
Open

oxura wants to merge 4 commits into
bendlang:mainfrom
oxura:fix/output-input-hardlinks-20261011

Conversation

@oxura

@oxura oxura commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1495 and #1694.

Kept Nicolas's f0327f11 file-identity guard and merged current main. It now also remembers each successfully emitted output. A later -o alias fails instead of silently replacing the first program with another format. Recording the identity after emission also catches a dangling symlink to an output that did not exist yet.

Added an installed-CLI regression that creates hard-linked JS/C targets, rejects the second write, and runs the surviving JS. The existing input-hardlink regression remains.

Checked both the source CLI and a freshly compiled CLI on Linux: hard links, symlinks, dangling symlinks, repeated paths, protected inputs, and independent JS/C/native outputs. All 12 scenarios passed; the emitted programs printed 7n. Repository gate: 54/54. The full installer/hub gate was not run locally.

AI-assisted.

oxura and others added 2 commits October 11, 2026 20:13
…n input is that input

The output check compared real paths, then, for an existing output, a
lazily built set of device:inode ids. Ids alone cover both: a symlink
stats to its target, a hard link shares the inode, and a missing path
falls back to its resolved name. path_real had no other use and goes.
main.ts is +37 tokens over main instead of +131.
@nicolas-abril

Copy link
Copy Markdown
Collaborator

Unsupervised AI review (Claude, run by @nicolas-abril). Nobody has read this by hand yet, so please double-check it.

I pushed f0327f1 on top of this PR. It keeps the same behavior with less code:

  • The PR checks the real path first, then builds a set of dev:ino ids for an output that already exists. The ids alone cover every case: a symlink stats to its target, a hard link has the same inode, and a path that doesn't exist falls back to its resolved name.
  • So path_real (only used here) becomes path_id(p), which returns dev:ino, or "" for any directory, or the resolved path for a missing file. The guard is now ins.has(path_id(out)).
  • main.ts is now +37 tokens over main instead of +131.

Checks on macOS (M-series):

  • gates/repo.ts 54/54, gates/ping.ts 48/48 (this includes your hard-link case), gates/test.ts 1602/1602.
  • Ran by hand: a hard link, a symlink, a directory and the input itself as -o are all rejected; a new output path is written.

I didn't touch the README note or the ping test.

@oxura

oxura commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

Thanks. I verified f0327f11 on Linux and kept the file-id simplification unchanged.

Both the source CLI and a freshly compiled CLI reject -o to: the root's real hard link, its symlink, its own pathname, a directory, a hard link to an imported Bend file, and a hard link to an imported JS foreign. All protected inputs have the same SHA-256 before and after each rejected command; the imported program still prints 42n.

Both CLIs can write a fresh output and overwrite an independent existing output; each generated JS consumer prints 5n. gates/repo.ts passes 54/54. This is real Linux filesystem proof, not a new macOS/Windows run.

@oxura oxura changed the title fix(cli): reject hard-linked aliases of program inputs as outputs fix(cli): reject outputs that alias inputs or earlier outputs Oct 18, 2026
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.

CLI: -o through a hard-linked alias silently overwrites a program input

2 participants