Skip to content

fix: empty options, GET with an empty body, and crypt on Cloudflare Pages - #40

Merged
sntran merged 5 commits into
mainfrom
fix/empty-options
Oct 21, 2026
Merged

sntran merged 5 commits into
mainfrom
fix/empty-options

Conversation

@sntran

@sntran sntran commented Oct 21, 2026

Copy link
Copy Markdown
Owner

Summary

Five fixes for rfetch as a Worker:

  • An empty option is no option. A header RFETCH_* or RCLONE_* with an empty value, and a field of the query with an empty value, are not options. Before, an empty value took the place of the default of rclone, or of a value in a different place.
  • A GET or a HEAD with an empty body reads the file. workerd gives a body stream to a GET with Content-Length: 0. The Worker gave that body to fetch(), and fetch() refuses a GET or a HEAD with a body, so the request failed with the error 1101. Now the Worker gives a body only to the other methods.
  • An error of the keys of crypt tells its cause. Before, each error of the keys gave "The password is not obscured". Now only a password or a salt that reveal cannot read gives that answer (400). A different error gives 500 with its cause, with no part of the password.
  • crypt works in a build of Cloudflare Pages. In that build, createCipheriv of node:crypto is a stub of unenv that throws: "[unenv] crypto.createCipheriv is not implemented yet!". Thus crypt could not make the cipher of the names, and each request failed. aes256 still uses AES-256-ECB of node:crypto. When createCipheriv throws, it uses aes256js: AES-256 as FIPS-197, in JavaScript, with no dependency. scrypt, createHash and createHmac of node:crypto work in that build, so s3 worked before too.

Tests

  • worker_test.js:
    • A header or a field with an empty value is not an option.
    • A GET and a HEAD with an empty body stream read the file.
  • backend/crypt/main_test.js:
    • A password that is not obscured gets 400 with that text.
    • A different error of the keys gets 500 with its cause.
  • backend/crypt/aes_test.js:
    • aes256js gives the vector of FIPS-197, appendix C.3.
    • It gives the blocks of node:crypto on random keys and blocks.
    • aes256 uses it when createCipheriv throws.
    • It refuses a key or data of a wrong size.
  • backend/crypt/eme_test.js: the vectors of EME pass with the two ciphers.
  • npm test: 1,187 tests, 1,173 pass, 14 skip because a tool is not installed. deno fmt --check passes.

Check on the preview of this branch

Done with an R2 bucket as the remote of crypt:

  • PUT of dir/a file.txt gives 201.
  • GET gives the text.
  • The list (Accept: application/json) shows a file.txt. R2 keeps only the encrypted names.
  • DELETE gives 204.

A proxy can copy a header that its client did not send, as an empty
header. Before, rfetch took such a header as an option with the value
"", and the default of the option was lost. For example, an empty
RFETCH_S3_REGION gave the region "".

Now a header RFETCH_* or RCLONE_* with an empty value is not an option
and not a variable, in the backends (optionsOf) and in the remotes of
the variables (RFETCH_CONFIG_NAME_*). The next place gives the value: a
header of the other prefix, a variable of the environment, or the
default. An empty RFETCH_CONFIG_NAME_TYPE gives no remote.
The Worker gives the fields of the query to the remote as the
parameters of a connection string, and a parameter takes the place of a
header. A proxy can copy a field that its client did not send, as an
empty field: then `?remote=` gave the option remote="", and the header
RFETCH_ALIAS_REMOTE of the client was lost.

Now the Worker gives no parameter for a field with an empty value, as
for a header with an empty value.
workerd gives a body stream to a GET with Content-Length: 0. The Worker
gave that body to fetch(), and fetch() refuses a GET or a HEAD with a
body, so the request failed with the error 1101. Now the Worker gives a
body only to the other methods.
Each error of the keys gave "The password is not obscured", also an
error of scrypt or of AES-256 in node:crypto. Now only a password or a
salt that reveal cannot read gives that answer (400). A different error
gives 500 with its cause, with no part of the password.
In a build of Cloudflare Pages, createCipheriv of node:crypto is a stub
of unenv that throws, so crypt could not make the cipher of the names.
aes256 still uses AES-256-ECB of node:crypto, and takes aes256js when
createCipheriv throws: AES-256 as FIPS-197, in JavaScript, with no
dependency. The tests compare it with the vector of FIPS-197, with
node:crypto on random keys and blocks, and with the vectors of EME.
@sntran
sntran merged commit 4381c6e into main Oct 21, 2026
11 checks passed
@sntran
sntran deleted the fix/empty-options branch October 21, 2026 00:13
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.

1 participant