Summary
Deno 2.9.7 successfully generates a lockfile for a remote JavaScript module served with Content-Type: application/javascript; charset=iso-8859-1, but subsequently rejects that same module with Integrity check failed when running deno cache --frozen.
The URL, response body, response headers, and lockfile are unchanged between runs.
In this reproduction, the checksum written to the lockfile matches the SHA-256 of the decoded source encoded as UTF-8. However, the “Actual” checksum reported during verification matches the SHA-256 of the original Latin-1 response bytes.
This indicates an inconsistency in the byte representation used for lockfile generation and verification.
Environment
- Deno: 2.9.7
- OS: macOS
- Lockfile format: v5
Reproduction
Run the following command with Deno 2.9.7.
deno eval --no-config --no-lock '
const source = "export default \"café\";\n";
const bytes = Uint8Array.from(source, c => c.charCodeAt(0));
const dir = await Deno.makeTempDir({ prefix: "deno-lock-repro-" });
const server = Deno.serve(
{ hostname: "127.0.0.1", port: 0, onListen() {} },
() => new Response(bytes, {
headers: {
"Content-Type": "application/javascript; charset=iso-8859-1",
},
}),
);
const url = `http://127.0.0.1:${server.addr.port}/mod.js`;
const lockPath = `${dir}/deno.lock`;
const sha256 = async data => Array.from(
new Uint8Array(await crypto.subtle.digest("SHA-256", data)),
b => b.toString(16).padStart(2, "0"),
).join("");
try {
console.log(`Deno ${Deno.version.deno}`);
for (const frozen of [false, true]) {
const result = await new Deno.Command(Deno.execPath(), {
args: [
"cache",
"--no-config",
`--lock=${lockPath}`,
"--allow-import=127.0.0.1",
...(frozen ? ["--frozen"] : []),
url,
],
cwd: dir,
env: {
DENO_DIR: `${dir}/cache`,
NO_COLOR: "1",
},
}).output();
console.log(
`${frozen ? "Second run (--frozen)" : "First run"}: exit ${result.code}`,
);
console.log(new TextDecoder().decode(result.stderr));
}
const lock = JSON.parse(await Deno.readTextFile(lockPath));
console.log("Lock hash: ", lock.remote[url]);
console.log("Raw Latin-1 hash: ", await sha256(bytes));
console.log(
"Decoded UTF-8 hash:",
await sha256(new TextEncoder().encode(source)),
);
} finally {
await server.shutdown();
await Deno.remove(dir, { recursive: true });
}
'
Expected behavior
Both runs should succeed. The second run should validate the unchanged module against the lockfile generated by the first run, without modifying the lockfile.
Lockfile generation and verification should hash the same byte representation.
Actual behavior
- The first
deno cache run exits with code 0 and generates the lockfile.
- The second
deno cache --frozen run exits with code 10 and reports Integrity check failed.
- The checksum in
lock.remote[url] equals the printed “Decoded UTF-8 hash”.
- The error’s “Actual” checksum equals the printed “Raw Latin-1 hash”.
- Those two hashes differ.
Analysis
The observed hashes indicate that these paths use different inputs:
- Lockfile generation: original response bytes → charset decoding → UTF-8 encoding of the decoded source → SHA-256.
- Subsequent verification: original response bytes → SHA-256 → comparison with the stored checksum.
So i think either deno should just use raw module bytes to calculate hash or always use the decoded text.
Summary
Deno 2.9.7 successfully generates a lockfile for a remote JavaScript module served with
Content-Type: application/javascript; charset=iso-8859-1, but subsequently rejects that same module withIntegrity check failedwhen runningdeno cache --frozen.The URL, response body, response headers, and lockfile are unchanged between runs.
In this reproduction, the checksum written to the lockfile matches the SHA-256 of the decoded source encoded as UTF-8. However, the “Actual” checksum reported during verification matches the SHA-256 of the original Latin-1 response bytes.
This indicates an inconsistency in the byte representation used for lockfile generation and verification.
Environment
Reproduction
Run the following command with Deno 2.9.7.
Expected behavior
Both runs should succeed. The second run should validate the unchanged module against the lockfile generated by the first run, without modifying the lockfile.
Lockfile generation and verification should hash the same byte representation.
Actual behavior
deno cacherun exits with code0and generates the lockfile.deno cache --frozenrun exits with code10and reportsIntegrity check failed.lock.remote[url]equals the printed “Decoded UTF-8 hash”.Analysis
The observed hashes indicate that these paths use different inputs:
So i think either deno should just use raw module bytes to calculate hash or always use the decoded text.