diff --git a/README.md b/README.md index 02737ec3e..d9c1b7776 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,9 @@ curl -fsSL https://bend-lang.com/install.sh | sh This `bend` ignores a project's `bunfig.toml` and `.env`; `bun bend2/main.ts` from a checkout reads them, so check untrusted code with `bend`. +Compilation rejects `-o` paths that match a program input by path or file +identity, including symlinks and hard links. + ### 2. Tell your agent to use Bend: Add this to your `AGENTS.md`: diff --git a/bend2/main.ts b/bend2/main.ts index f26292508..6409efec1 100755 --- a/bend2/main.ts +++ b/bend2/main.ts @@ -311,10 +311,9 @@ async function cli_file(args: string[]): Promise { return; } const ins = new Set([...seen.keys(), ...Object.values(book.tlds).flatMap((t) => - t.$ === "Def" && t.i !== undefined ? t.i.map(path_real) : [])]); + t.$ === "Def" && t.i !== undefined ? t.i : [])].map(path_id)).add(""); for (const out of outs) { - const at = path_real(out); - if (ins.has(at) || (fs.existsSync(at) && fs.statSync(at).isDirectory())) { + if (ins.has(path_id(out))) { cli_fail("-o " + out + " is a file the program reads, or a directory"); } cli_emit(book, out); @@ -356,8 +355,12 @@ async function cli_checkup(file: string): Promise { } } -function path_real(p: string): string { - return fs.existsSync(p) ? fs.realpathSync(p) : path.resolve(p); +// A path names a file by device and inode, so links to one file agree, and +// every directory is "". +function path_id(p: string): string { + const s = fs.statSync(p, { bigint: true, throwIfNoEntry: false }); + return s === undefined ? path.resolve(p) + : s.isDirectory() ? "" : s.dev + ":" + s.ino; } function cli_emit(book: Bend.Book, out: string): void { diff --git a/gates/ping.ts b/gates/ping.ts index a72189e0a..9b164fbaf 100644 --- a/gates/ping.ts +++ b/gates/ping.ts @@ -300,6 +300,12 @@ try { && page.err.includes("page.ts:1:") && !page.err.includes("Bun v")); const bad_file = path.join(TMP, "bad.bend"); const sum_file = path.join(TMP, "sum.bend"); + const sum_alias = path.join(TMP, "sum-alias.js"); + fs.linkSync(sum_file, sum_alias); + const linked_out = await bend([sum_file, "-o", sum_alias]); + const linked_run = await bend([sum_file]); + check("an output hard link cannot destroy its input", + linked_out.code === 1 && linked_run.code === 0 && linked_run.out === "5n\n"); const unsafe_file = path.join(TMP, "unsafe.bend"); const checkup = path.join(TMP, "checkup.bend"); const good_checkup = path.join(TMP, "good_checkup.bend");