Skip to content

fix(cli): protect HTML bundle inputs - #1690

Merged
Lorenzobattistela merged 6 commits into
bendlang:mainfrom
toprameen:protect-html-bundle-input
Oct 20, 2026
Merged

Lorenzobattistela merged 6 commits into
bendlang:mainfrom
toprameen:protect-html-bundle-input

Conversation

@toprameen

@toprameen toprameen commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

bend page.html -o <dir> can overwrite the page it bundles: directly (-o is the page's own directory), through a symlink, or through a hard link at <dir>/page.html.

In a Bun bundle only the page keeps its own name; chunks, CSS and assets are content-hashed. So the page is the one input an -o can overwrite by accident. cli_bundle now:

  • builds from the page's real path (path_real), so the output name is known and a symlinked page builds to <dir>/<target>.html instead of <dir>/_.._/<target>.html;
  • refuses the build when <dir>/<page> is already the page by device and inode (same_file), which covers the direct, symlink, hard-link and case-alias cases;
  • keeps Bun's outdir writing, so a non-directory -o and a missing page still get Bun's own errors.

Not covered on purpose: a link planted at an output path that points at another input (main behaves the same).

tests/io/bundle_page_input covers the refused cases, the symlinked build (its chunk runs) and the missing-page error; it fails on main. bend2/main.ts +172 ttok (11,871). Gates on ed186a4: repo 54/54, test 1683/1683, ping 63/63, perf 123/124, safe 0 false rejects.

🤖 Generated with Claude Code

@toprameen toprameen changed the title protect HTML bundle inputs fix(cli): protect HTML bundle inputs Oct 17, 2026
@toprameen
toprameen force-pushed the protect-html-bundle-input branch from cd2ba80 to 2a00e58 Compare October 17, 2026 23:28
@toprameen
toprameen marked this pull request as ready for review October 17, 2026 23:36
@Lorenzobattistela Lorenzobattistela self-assigned this Oct 19, 2026

@Lorenzobattistela Lorenzobattistela left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this. The direct and symlink collisions are refused now, and all gates pass merged onto main d194597 (repo 54/54, test 1663/1663, perf 124/124, ping 65/65 with your checks, safe 0 false rejects). A few things before merge:

  1. A hard link to the page still gets overwritten. The guard compares realpath strings, and a hard link has a path of its own:

    ln a/page.html h/page.html
    bend a/page.html -o h
    

    This exits 0, and a/page.html now holds the bundle, so the source is lost. Comparing dev+ino from fs.statSync (when the output exists) catches symlinks, hard links and case-aliased paths in one check.

  2. Only the entry page is checked. 1364d73 refuses an -o that resolves to any file the program reads. Here, an entry in the -o dir that is named like an output and points at a source .ts/.css/.bend (a stale symlink, say) is followed by Bun.write and overwritten. Could the check cover every file the build read, not just the page?

  3. Dropping outdir loses Bun's directory check. touch gf; bend g.html -o gf used to print Failed to create output directory "gf" is a file. Now it prints a raw ENOTDIR: not a directory, open '.../gf/chunk-....js', and only after the full build. Please keep a clear error for a non-directory -o. Also, writes that fail partway (EACCES, a read-only page.html) leave a half-written bundle.

  4. The cause looks like Bun naming a symlinked entry after its target, which is where dir/_.._/safe.html comes from. Passing path_real(page) as the entrypoint (or setting root) should give plain output names. The direct check then covers the symlink cases without the in-memory build and the second write loop. Could you try that?

  5. The "page symlink can bundle beside its link" check asserts the _.._/safe.html layout. That layout predates this PR, but the check would fail a fix that puts the page at dir/.html, so please don't pin it.

  6. gates/ping.ts is maintained by Taelin, and gate changes need his sign-off, so we can't merge the new checks as they are. Please move them out of gates/ (into tests/, if a test there can drive the CLI), or leave them out and we'll raise it with him.

@toprameen

Copy link
Copy Markdown
Contributor Author
  1. Switched to device/inode checks so hard links are caught too.
  2. Expanded protection to imported files and assets, plus tsconfig.json, jsconfig.json, extended configs and package package.json files used for resolution.
  3. Added a clear error when -o names a file, and staged publication with rollback so failed writes restore the previous outputs.
  4. Canonicalized the entry path and root as suggested. I kept the actual output checks because a generated JS chunk can still point to an input through a symlink.
  5. Removed the _.._/safe.html assumption. The test now finds the generated HTML and runs its referenced chunk.
  6. Moved the regressions to tests/io, so there are no gate changes left in the PR.

@toprameen
toprameen force-pushed the protect-html-bundle-input branch from 74b2324 to c3bd746 Compare October 20, 2026 01:34
Lorenzobattistela and others added 2 commits October 20, 2026 07:17
Only the page keeps its name in a Bun bundle; chunks and assets are
content-hashed, so the page is the one input an -o can overwrite by
accident. Build from the page's real path (so the output name is known
and a symlinked page no longer lands in _.._/), and refuse the build when
dir/<page> is already the page by device and inode, which covers the
direct, symlink and hard-link cases. Bun's outdir keeps writing and
validating the directory, so the staging, rollback, metafile and config
scans go, with their test; tests/io/bundle_page_input covers the cases.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
realpathSync ran before Bun.build, so `bend nope.html -o out` failed with
a raw ENOENT lstat instead of Bun's ModuleNotFound entry error. Resolve the
page with path_real and let same_file answer false for a missing file;
the test covers it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Lorenzobattistela
Lorenzobattistela merged commit 1a7fea9 into bendlang:main Oct 20, 2026
1 check passed
Lorenzobattistela added a commit to oxura/bend that referenced this pull request Oct 20, 2026
The page keeps its real path's name and is guarded against -o overwrite;
a missing page is left for Bun.build to report.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Lorenzobattistela Lorenzobattistela mentioned this pull request Oct 20, 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.

2 participants