Skip to content

fix(flue-review): cap tar entry size and honor pax linkpath overrides - #2388

Merged
ascorbic merged 3 commits into
mainfrom
fix/flue-review-untar-size-cap-linkpath
Aug 9, 2026
Merged

fix(flue-review): cap tar entry size and honor pax linkpath overrides#2388
ascorbic merged 3 commits into
mainfrom
fix/flue-review-untar-size-cap-linkpath

Conversation

@ascorbic

@ascorbic ascorbic commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Closes the two remaining findings from the review of #2386 on the flue-review untar parser:

  • Entry size cap. The parser buffers one entry's content at a time in Durable Object memory and previously trusted the declared size field unconditionally, so a malformed or hostile header could trigger an unbounded allocation. Entries larger than 64 MiB (or with a non-numeric/negative size field) are now rejected.
  • pax linkpath support. Only the pax path keyword was parsed, so a symlink whose target exceeds the 100-byte ustar linkname field would have been silently truncated. The parser now honors pax linkpath and the GNU K long-linkname record, and applies the same escape validation to overridden targets.

Also fixes a one-line noUncheckedIndexedAccess violation in the R2 instrumentation that surfaced when running tsc --noEmit against freshly generated worker types.

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs — a workflow extracts catalogs on merge to main.
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: https://github.com/emdash-cms/emdash/discussions/...

Changeset and i18n are n/a: this touches only the private infra/flue-review worker, no published package or admin UI. Typecheck was run for infra/flue-review (tsc --noEmit); tests are the flue-review vitest suite (49 passing, including new cases for the size cap, pax linkpath override, escaping linkpath targets, and GNU K records).

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Fable 5 (Claude Code)

Screenshots / test output

 Test Files  5 passed (5)
      Tests  49 passed (49)

Try this PR

Open a fresh playground →

A full working EmDash site, deployed from this branch. Each visit gets its own session-scoped sandbox: no login needed and no shared state. Try the admin, edit content, hit the public site.

Tracks fix/flue-review-untar-size-cap-linkpath. Updated automatically when the playground redeploys.

Copilot AI lite review requested due to automatic review settings August 8, 2026 23:32
@changeset-bot

changeset-bot Bot commented Aug 8, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 5347166

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions github-actions Bot added size/M review/needs-review No maintainer or bot review yet labels Aug 8, 2026
@pkg-pr-new

pkg-pr-new Bot commented Aug 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@2388

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@2388

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@2388

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@2388

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@2388

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@2388

emdash

npm i https://pkg.pr.new/emdash@2388

create-emdash

npm i https://pkg.pr.new/create-emdash@2388

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@2388

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@2388

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@2388

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@2388

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@2388

@emdash-cms/registry-verification

npm i https://pkg.pr.new/@emdash-cms/registry-verification@2388

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@2388

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@2388

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@2388

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@2388

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@2388

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@2388

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@2388

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@2388

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@2388

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@2388

commit: 5347166

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is the right follow-up: it closes the two security/correctness gaps in the flue-review untar parser (entry size cap and pax linkpath / GNU K long-link support) without touching published packages or the admin UI. The approach is sound for GitHub source tarballs, and the override precedence (pax > GNU longlink/longname > header) matches tar conventions.

I traced the changed paths, the escape validation applied to overridden symlink targets, and the new tests. The logic looks correct, but the PR claims to reject non-numeric and negative size fields in addition to oversized ones and only adds a test for the oversized case. AGENTS.md is explicit that bug fixes need reproducing tests. I also flagged the one-line noUncheckedIndexedAccess fix in the R2 instrumentation as a minor scope-discipline note; it is unrelated to the untar parser changes.

No query-count, i18n, RTL, SQL, API-envelope, authorization, or changeset issues apply here — the worker is private and has no user-facing UI.

),
).rejects.toThrow(/size out of range/);
expect(r.files.size).toBe(0);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[needs fixing] The PR description says entries with a non-numeric or negative size field are now rejected, but the only new test covers an oversized (128 MiB) field. AGENTS.md asks for a failing test before the fix and verification after it; adding coverage for malformed size fields protects the new guard from future regressions.

Suggested change
});
});
it("rejects entries with a non-numeric or negative size field", async () => {
async function expectRejected(sizeBytes: string) {
const block = header({ name: "repo-abc/bad.bin", size: 0 });
const bytes = [...sizeBytes].map((c) => c.charCodeAt(0));
block.set(bytes, 124);
block[124 + bytes.length] = 0;
const r = recorder();
await expect(
untarInto(r.target, tarball([block]), "/repo"),
).rejects.toThrow(/size out of range/);
expect(r.files.size).toBe(0);
}
await expectRejected("bogus");
await expectRejected("-1");
});

try {
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
const result = await (bucket as unknown as Record<string, Function>)[method](...args);
const result = await (bucket as unknown as Record<string, Function>)[method]!(...args);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[suggestion] This one-line ! addition in the R2 instrumentation fixes a type error but is unrelated to the untar parser change. AGENTS.md discourages drive-by cleanups in unrelated files; if this was needed to keep pnpm typecheck green for the worker, note that in the test evidence, otherwise it belongs in its own PR.

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-review No maintainer or bot review yet labels Aug 8, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 8, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-cache 5347166 Aug 09 2026, 06:37 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 8, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-playground 5347166 Aug 09 2026, 06:37 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 8, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-do 5347166 Aug 09 2026, 06:39 AM

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the infra/flue-review tarball untar parser by adding defensive limits around entry sizes and improving correctness for symlink target decoding when tar headers exceed legacy ustar field sizes.

Changes:

  • Add a 64 MiB per-entry declared-size cap (and reject negative / non-finite sizes) to avoid unbounded buffering in Durable Object memory.
  • Honor pax linkpath and GNU K long-link records for symlink targets, applying the same escape validation as for normal link targets.
  • Fix a noUncheckedIndexedAccess type error in R2 operation instrumentation by asserting the wrapped method exists.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
infra/flue-review/.flue/lib/untar.ts Adds entry size cap and support for pax/GNU symlink target overrides in the untar trust-boundary parser.
infra/flue-review/test/untar.test.ts Adds regression tests for symlink target override handling and the entry-size cap.
infra/flue-review/.flue/sandboxes/cloudflare-shell.ts Adjusts R2 instrumentation to satisfy noUncheckedIndexedAccess.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 96 to 100
const rawName = readCString(header.subarray(0, 100));
const prefix = readCString(header.subarray(345, 500));
const size = parseInt(readCString(header.subarray(124, 136)).trim() || "0", 8);
// Mode bytes (100-108) are ignored: the workspace has no chmod and the
// reviewer never executes files.
@ascorbic

ascorbic commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator Author

Both review rounds addressed in 5347166. The size field is now validated as strictly octal before parsing — Copilot's point was right that parseInt("10x", 8) silently parses the prefix and desyncs the stream — and there's a test iterating malformed fields (10x, size!, -0000001), which also covers the non-numeric/negative claim from the PR description that emdashbot noted was untested. The unreachable Number.isFinite/negative branch is gone since the strict check subsumes it.

On the R2 instrumentation !: it's the fix that keeps pnpm typecheck green for this worker against freshly generated types, as noted in the description — without it the checklist claim would be false. Kept here rather than a separate PR since it's one character in the same package.

~ 🤖 Claude Fable 5

@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Aug 9, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is the right follow-up to the flue-review untar parser review. The PR closes the two stated gaps — an unbounded entry-size allocation risk and missing pax linkpath / GNU long-link (K) support — without touching published packages or the admin UI.

The previous re-review noted that the PR claimed to reject non-numeric and negative size fields but only tested the oversized case. That has now been addressed in infra/flue-review/test/untar.test.ts with the "rejects entries whose size field is not strictly octal" case, which includes "-0000001".

I re-read the untar parser, the new tests, and the one-line noUncheckedIndexedAccess fix in sandboxes/cloudflare-shell.ts. The size-cap placement (before need(padded)) prevents trying to buffer an oversized entry, the octal validation is strict and rejects non-numeric/negative fields, and the override precedence (pax > GNU longlink/longname > header) plus escape validation for overridden symlink targets is correct. No AGENTS.md issues apply: no i18n, RTL, SQL, API-envelope, auth, locale-filtering, or changeset concerns touch this private infrastructure worker; nothing here is on a logged-out hot path.

The R2 instrumentation change remains a minor scope note — it is a one-character type-only fix in a different file than the untar changes, though still within the same private worker package and needed to keep tsc --noEmit green. Given it is transparent in the description and does not change runtime behavior, it does not change my sign-off.

Approving with no blocking findings.

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Aug 9, 2026
@ascorbic
ascorbic enabled auto-merge (squash) August 9, 2026 06:37
@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/awaiting-author Reviewed; waiting on the author to respond labels Aug 9, 2026
@ascorbic
ascorbic merged commit c8b0eb9 into main Aug 9, 2026
49 checks passed
@ascorbic
ascorbic deleted the fix/flue-review-untar-size-cap-linkpath branch August 9, 2026 07:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review/approved Approved; no new commits since size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants