Skip to content

test(uploads): pin the browser's copies of the document list to ALLOWED_DOCUMENT_EXTS - #653

Merged
Ark0N merged 1 commit into
Ark0N:masterfrom
JDProfresh:test/upload-document-list-parity
Oct 18, 2026
Merged

Ark0N merged 1 commit into
Ark0N:masterfrom
JDProfresh:test/upload-document-list-parity

Conversation

@JDProfresh

Copy link
Copy Markdown
Contributor

The browser can't import the server's document allowlist from #621, so it keeps four hand copies of it, and the two file-path link patterns have to cover it too. Nothing checked any of them against ALLOWED_DOCUMENT_EXTS. This is the parity test you asked for in the #621 merge comment.

Problem

ALLOWED_DOCUMENT_EXTS (session-routes.ts) decides which documents the paste-image route accepts. The browser keeps its own copies:

  • _promptAttachKind() in image-input.js, the classifier every pick, drop and paste goes through
  • the accept= on the toolbar Attach input (#attachFileInput, index.html)
  • the accept= on the two .paste-file-input pickers (Compose and the paste dialog, keyboard-accessory.js)
  • FILE_PATH_LINK_PATTERN and RELATIVE_FILE_PATH_LINK_PATTERN in constants.js, which make the uploaded path clickable when the agent prints it back

If one copy is missed, nothing errors. Depending on which one, the file is greyed out in the picker, dropped client-side as "Unsupported file type", or sent and then refused with a 400, or its path comes back as plain text. In #621 I had to add tsv and zip to the link patterns by hand. #644 (still open) rewrites both patterns from a base older than #621, without tsv and zip.

Cause

The lists are copies with nothing tying them together, and ALLOWED_DOCUMENT_EXTS was a local inside registerSessionRoutes(), so a test could not read it.

Fix

  • ALLOWED_DOCUMENT_EXTS moves to module scope beside TEXT_UPLOAD_EXTS and is exported. Same set and same comment, plus one line pointing at the test. The route body is unchanged.
  • test/upload-document-list-parity.test.ts imports the shipped set and checks every copy against it:
    • _promptAttachKind() is loaded from image-input.js in a vm context, the way toolbar-attach.test.ts loads it, and called with report.<ext> and no MIME for every server extension. For the reverse direction, the test reads the document list from the live method's own text (comments stripped, exactly one list required), checks that each entry really returns document, and requires the list to equal the server set.
    • The accept= values are read from the real files (index.html through JSDOM, the two picker templates from keyboard-accessory.js, exactly two required) and must list exactly the server's extensions.
    • The two link patterns are loaded from constants.js and run on a sample path per extension. Each must match the whole path. This direction is one-way, since the patterns link many non-document types too.
  • CLAUDE.md lists the new test under Prompt uploads.

Tests

For each copy I dropped one extension and ran the test. Every time it failed and named both the file and the extension:

  • image-input.js without tsv: does not classify as document: tsv, and the list check shows - "tsv"
  • image-input.js with an extra rtf: the list check shows + "rtf"
  • index.html without .zip: index.html #attachFileInput accept= with - "zip"; with an extra .rtf: + "rtf"
  • keyboard-accessory.js first picker without .md, and the second picker without .xml: .paste-file-input #1 / #2 with the missing extension
  • constants.js FILE_PATH_LINK_PATTERN without zip: does not link: zip; RELATIVE_FILE_PATH_LINK_PATTERN without tsv: does not link: tsv
  • server set without .zip: the classifier list and all three accept= checks fail on + "zip"
  • a stale commented copy of the list above the live one, plus an extra rtf in the live one: fails on rtf

Verified

Test-only change, so there is no runtime behaviour to show. Upstream master 1.42.0 (ce809eac), Node 26.2.0, vitest 4.1.8: typecheck, lint, format:check, check:frontend-syntax, check:browser-excludes and the full npm test all pass (583 files, 11589 tests).

Invariant touched: none changed. The test pins the document list described in CLAUDE.md "Prompt uploads" and architecture-invariants#buffers-uploads-and-terminal-history.

…ED_DOCUMENT_EXTS

The browser cannot import the server's document allowlist, so the classifier, the three pickers' accept lists and the two file-path link patterns each carry a hand-kept copy. A missed copy fails silently: a file greyed out in the picker, an upload dropped client-side, or a pasted path that is no longer a link. Hoist the set to module scope so the test reads the shipped list.
@Ark0N

Ark0N commented Oct 18, 2026

Copy link
Copy Markdown
Owner

Thank you @JDProfresh, this is exactly the parity test I asked for in #621: it imports ALLOWED_DOCUMENT_EXTS and checks the classifier, all three pickers and both link patterns against it, and running the real classifier and regexes instead of copying them is the right call. I checked it by breaking each copy in turn and it caught every one with a message naming the file and the extension.

Two gaps turned up while doing that, plus one typing nit. I will apply these on top at merge time, so there is nothing you need to push:

  1. test/upload-document-list-parity.test.ts:72: the reverse check reads only the one if ([...].includes(ext)) { return 'document'; list, so a second branch such as if (ext === 'rtf') return 'document'; passes all six tests. I will add an assertion that the method contains exactly one return 'document'.
  2. test/upload-document-list-parity.test.ts:38: acceptDocumentExts() keeps only .ext tokens, so adding text/* to an accept= passes. I will assert the non-extension tokens are exactly image/* and video/*.
  3. src/web/routes/session-routes.ts:341: now that the set is exported I will type it ReadonlySet<string>, like VIDEO_ATTACHMENT_EXTENSIONS, so an importer cannot add to the live allowlist.

Thanks also for the note on #644: once this lands, that PR will need tsv and zip back in both patterns, and this test will tell it so.

@Ark0N
Ark0N merged commit 7e8ed90 into Ark0N:master Oct 18, 2026
2 checks passed
Ark0N pushed a commit that referenced this pull request Oct 18, 2026
The reverse classifier check read only the one includes(ext) list, so a
second branch such as `if (ext === 'rtf') return 'document';` passed every
test; the method may now return 'document' from exactly one place. The
accept= checks kept only .ext tokens, so adding text/* to a picker passed;
the non-extension tokens must be exactly image/* and video/*. Both verified
by mutation (each fails the suite).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ark0N pushed a commit that referenced this pull request Oct 18, 2026
Now that the set is exported for the parity test, type it read-only like
VIDEO_ATTACHMENT_EXTENSIONS, so an importer cannot add to the live upload
allowlist.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ark0N pushed a commit that referenced this pull request Oct 18, 2026
…he document list to ALLOWED_DOCUMENT_EXTS

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

# Conflicts:
#	CLAUDE.md
@Ark0N

Ark0N commented Oct 18, 2026

Copy link
Copy Markdown
Owner

Merged and shipped in 1.43.0. Thanks @JDProfresh! The three edits from my review are on top: _promptAttachKind() may return 'document' from one place only, each accept= may carry no MIME pattern besides image/* and video/*, and the export is typed ReadonlySet<string>. Both new assertions were checked by breaking the code on purpose. Your note about #644 held up too: its tsv/zip merge went through with this test watching.

@JDProfresh
JDProfresh deleted the test/upload-document-list-parity branch October 18, 2026 16:42
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