Skip to content

fix: validate file contents in dynamic mode#1924

Open
spokodev wants to merge 1 commit into
elysiajs:mainfrom
spokodev:fix/dynamic-file-type-validation
Open

fix: validate file contents in dynamic mode#1924
spokodev wants to merge 1 commit into
elysiajs:mainfrom
spokodev:fix/dynamic-file-type-validation

Conversation

@spokodev

@spokodev spokodev commented Jun 16, 2026

Copy link
Copy Markdown

Closes #1922.

t.File({ type }) / t.Files({ type }) validates the actual file bytes (magic-byte check via fileTypefile-type) in AOT mode, but in dynamic mode (aot: false) the same schema only ran the synchronous TypeBox check, which trusts the client-supplied multipart MIME type. A file whose bytes do not match the declared type was accepted in dynamic mode and rejected in AOT mode — the two pipelines disagreed on the same schema.

Repro from the issue (SVG bytes uploaded as image/png): AOT → 422, dynamic → 200.

Fix

compose.ts (AOT) injects fileType(c.body.k, extension, 'body.k') for every File/Files field that declares an extension. createDynamicHandler did not. After body validation, iterate the body schema's properties (via the existing getSchemaProperties, which also flattens unions) and run the same fileType(...) for File/Files fields with an extension — so both modes enforce the identical guarantee using the identical code path and error (InvalidFileType422).

Tests

Added validate actual file in dynamic mode to test/validator/body.test.ts, mirroring the existing AOT validate actual file test but with new Elysia({ aot: false }): a real millenium.jpg passes (200), a spoofed fake.jpg is rejected (422). Red before the change (dynamic returned 200), green after. test/validator/body.test.ts (57), test/type-system/files.test.ts + formdata.test.ts (31), and test/core/dynamic.test.ts (32) all pass; tsc --project tsconfig.test.json is clean.

Summary by CodeRabbit

Bug Fixes

  • File uploads now validate against their declared file type consistently across all application modes.
  • File type verification is enforced in dynamic mode, preventing mismatched file uploads.

`t.File`/`t.Files` with a declared type validates the actual file bytes
(magic-byte check via `fileType`) in AOT mode, but the dynamic handler
(`aot: false`) only ran the synchronous schema check, accepting a file
whose contents do not match the declared type.

Mirror the AOT path in `createDynamicHandler`: after body validation, run
`fileType(...)` for File/Files fields that declare an extension, so both
modes enforce the same guarantee.

Closes elysiajs#1922
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9b5d9591-2d35-4cfb-84ec-20dde4e9bf96

📥 Commits

Reviewing files that changed from the base of the PR and between 56310be and 22e233a.

📒 Files selected for processing (2)
  • src/dynamic-handle.ts
  • test/validator/body.test.ts

Walkthrough

src/dynamic-handle.ts adds imports for Kind and unwrapImportSchema, then introduces a post-body-decode block that iterates body schema properties, detects File/Files fields with an extension, and calls fileType() to verify uploaded bytes match the declared type. A regression test in test/validator/body.test.ts covers the same scenario in aot: false mode.

Changes

Dynamic mode file-type validation

Layer / File(s) Summary
Imports and post-decode file validation
src/dynamic-handle.ts
Adds Kind and unwrapImportSchema/ElysiaTypeCheck imports, then introduces a loop after body decoding that inspects File/Files schema properties for declared extension and calls fileType() against each decoded value, returning 422 on mismatch — mirroring AOT behavior.
Regression test for dynamic mode
test/validator/body.test.ts
New validate actual file in dynamic mode test creates an Elysia({ aot: false }) route with t.File({ type: 'image' }), asserts a real JPEG returns 200 and a spoofed JPEG returns 422.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant createDynamicHandler
  participant unwrapImportSchema
  participant fileType

  Client->>createDynamicHandler: multipart POST (File field)
  createDynamicHandler->>createDynamicHandler: parse & decode body
  createDynamicHandler->>unwrapImportSchema: resolve body validator schema
  unwrapImportSchema-->>createDynamicHandler: schema with File/Files properties
  loop each property with extension
    createDynamicHandler->>fileType: fileType(decoded file value)
    fileType-->>createDynamicHandler: { ext } or null
    alt extension mismatch
      createDynamicHandler-->>Client: 422 Unprocessable Entity
    end
  end
  createDynamicHandler-->>Client: 200 OK
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Poem

Heeh~, you thought a fake .jpg could fool me?? ヾ(≧▽≦*)o
I peeked at your bytes and went "nope, not an image♡"
fileType() tattled on your liiiittle lie, how embarrassing

Dynamic mode's not dumb anymore, baka!! ( ˘ ³˘)♥
Now go cry in 422 where you belong~~~

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: validate file contents in dynamic mode' accurately and specifically describes the main change—implementing file content validation in dynamic mode to match AOT behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/dynamic-handle.ts

Oops! Something went wrong! :(

ESLint: 9.39.4

Error: ESLint configuration in --config » plugin:sonarjs/recommended is invalid:

  • Unexpected top-level property "name".

Referenced from: /.eslintrc.json
at ConfigValidator.validateConfigSchema (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2277:19)
at ConfigArrayFactory._normalizeConfigData (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3123:19)
at ConfigArrayFactory._loadExtendedPluginConfig (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3346:25)
at ConfigArrayFactory._loadExtends (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3259:29)
at ConfigArrayFactory._normalizeObjectConfigDataBody (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3199:25)
at _normalizeObjectConfigDataBody.next (<anonym

... [truncated 291 characters] ...

/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2952:16)
at createCLIConfigArray (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3768:35)
(node:2) ESLintRCWarning: You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.
(Use node --trace-warnings ... to show where the warning was created)

test/validator/body.test.ts

Oops! Something went wrong! :(

ESLint: 9.39.4

Error: ESLint configuration in --config » plugin:sonarjs/recommended is invalid:

  • Unexpected top-level property "name".

Referenced from: /.eslintrc.json
at ConfigValidator.validateConfigSchema (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2277:19)
at ConfigArrayFactory._normalizeConfigData (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3123:19)
at ConfigArrayFactory._loadExtendedPluginConfig (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3346:25)
at ConfigArrayFactory._loadExtends (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3259:29)
at ConfigArrayFactory._normalizeObjectConfigDataBody (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3199:25)
at _normalizeObjectConfigDataBody.next (<anonym

... [truncated 291 characters] ...

/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2952:16)
at createCLIConfigArray (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3768:35)
(node:2) ESLintRCWarning: You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.
(Use node --trace-warnings ... to show where the warning was created)


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@robertokbr

robertokbr commented Jun 17, 2026

Copy link
Copy Markdown

You're just trying to take credit for someone else's discovery. Do better, man.
#1922

@bmtec

bmtec commented Jun 17, 2026

Copy link
Copy Markdown

Hey @spokodev, this looks really close to an earlier contribution: #1922
How does yours differ from it? Did you use it as a starting point, by any chance? Just curious =)

@spokodev

Copy link
Copy Markdown
Author

@bmtec good question — yes, this PR is the implementation of #1922, which is why it opens with "Closes #1922" and reuses @rafaelcoelhox's repro. The discovery and the diagnosis are theirs; I have not claimed otherwise.

The issue was open with no PR attached, so I took a shot at the actual fix — mirroring the AOT fileType(...) call into the dynamic handler so both modes enforce the same guarantee, plus a regression test. Glad to have the maintainers, or @rafaelcoelhox, take it forward however they prefer.

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.

t.File/t.Files content validation is skipped in dynamic mode (aot: false)

3 participants