Skip to content

Update Deno to 2.9.8 for the eMudhra certificate distrust - #15186

Open
cderv wants to merge 13 commits into
mainfrom
fix/issue-15152
Open

cderv wants to merge 13 commits into
mainfrom
fix/issue-15152

Conversation

@cderv

@cderv cderv commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

The bundled Deno 2.7.14 still trusts the eight eMudhra / emSign intermediates whose private keys are recoverable, so every HTTPS request Quarto makes from Deno (quarto publish, quarto add, quarto use template, quarto install) accepts certificates minted with them. Deno 2.9.8 is the first release that distrusts them (denoland/deno#37046), so the version bump is the fix. A 2.9.x below 2.9.8 is not enough, and DENO_TLS_CA_STORE can't work around it since our launchers always set it to system,mozilla.

With the dev build on 2.9.8, the probe from #15152 now rejects both eMudhra test endpoints with invalid peer certificate: CertDistrusted, with DENO_TLS_CA_STORE unset and with the launcher value, while github.com still fetches fine. Deno 2.7.14 accepts both.

Changes required by Deno 2.9

  • Vendor scripts use --allow-import instead of --allow-all. Deno 2.9 rejects permission flags on a non-global deno install (refactor: remove clap from the tree denoland/deno#36489), and because vendor.sh / vendor.cmd continue on error, configure finished normally but left the Deno cache empty.
  • TypeScript 6 type errors that failed deno test: JSON imports move from assert to with (TS2880), the Posit Connect Cloud client takes Uint8Array<ArrayBuffer> for fetch bodies, and test timer handles use ReturnType<typeof setTimeout>. All three also type-check on 2.7.14. The editor tooling bundles are regenerated after the with change; esbuild now wraps the JSON module as ESM, same data and same .default.
  • findOpenPort excluded 4160 as "sieve", but the Fetch standard's bad port is 4190. Browsers always refused 4190, and Deno 2.9 now applies the same list to its own fetch, so a server started there could not even be polled for readiness (headless Chrome falls back to findOpenPort when 9222 is busy). The new test asks Deno's fetch which ports it blocks, so it follows the runtime list.
  • logError prints the cause chain. Deno 2.9 reports every failed fetch as TypeError: fetch failed with the reason in cause, so quarto add against a server with an expired certificate said only "fetch failed" where 2.7 said "invalid peer certificate: certificate expired". No Quarto code creates errors with a cause, so only runtime errors gain the extra text.

The cross-version Deno KV cache problem (caches written by 2.9.8 breaking older Quarto) is already handled by #15180, which this PR builds on.

The website test fixtures also get the **/*.quarto_ipynb .gitignore entry that rendering appends, so a local smoke test run leaves the tree clean.

Fixes #15157

Refs #15152

Checklist

I have (if applicable):

  • referenced the GitHub issue this PR closes
  • updated the appropriate changelog in the PR
  • ensured the present test suite passes
  • added new tests
  • created a separate documentation PR in Quarto's website repo and linked it to this PR
AI-assisted PR
  • AI tool used: Claude Code, OpenAI Codex (review)
  • Codebase grounding: local clone, Deno v2.9.8 source and release notes
  • Human review: I have reviewed, tested, and verified the AI-generated content before submitting.

Note: autonomous AI agents submitting PRs without human oversight are not permitted — see the Code of Conduct.

@posit-snyk-bot

posit-snyk-bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
✅ Open Source Security 0 0 0 0 0 issues
✅ Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

cderv added 13 commits October 23, 2026 17:11
Deno's CLI parser rewrite (denoland/deno#36489, shipped in the 2.9
line) rejects permission flags on a non-global `deno install`:
"Permission flags can only be used in a global setting". Deno 2.7.14
accepted `--allow-all` there. Because vendor.sh and vendor.cmd continue
on error, configure on Deno 2.9.8 finished normally but left the Deno
cache empty.

The only permission the cache step needs is import access to remote
modules (cdn.skypack.dev, deno.land), and `--allow-import` is the one
permission flag the non-global form still accepts. Works on 2.7.14 too.
Deno 2.9.8 bundles TypeScript 6.0, and `deno test` type-checks every
test's import graph, so these errors fail the whole suite:

- import assertions (`assert`) are removed in favour of import
  attributes (`with`), TS2880;
- `fetch` bodies no longer accept `Uint8Array<ArrayBufferLike>`, so the
  Posit Connect Cloud client takes `Uint8Array<ArrayBuffer>`, which is
  what `Deno.readFile` returns;
- `setTimeout` is typed as returning Node's `Timeout`, so the two test
  timer handles use `ReturnType<typeof setTimeout>`.

All three forms also type-check on Deno 2.7.14.
Deno 2.9.8 is the first release whose certificate path verification
rejects the compromised eMudhra / emSign intermediates
(denoland/deno#37046: rustls-webpki rejects paths through the eight
compromised keys, cross-signed ones included, and webpki-roots drops
emSign ECC Root CA - G3). Deno 2.7.14 accepts certificates chaining to
them, and Quarto's launchers force DENO_TLS_CA_STORE=system,mozilla,
so no store setting protects `quarto publish`, `quarto add` or the
tool installers. 2.9.7 and earlier lack the fix, so 2.9.8 is the
minimum (#15152).

The runtime migration plans also target the 2.9 line for Deno's Node
compatibility work (node:http upgrade handling, Node 26 targets); those
fixes were not checked against 2.7.14.
esbuild bundles a JSON import with `with { type: "json" }` as an ES
module instead of a CommonJS wrapper; `.default` still resolves to the
same object. Produced by `quarto dev-call build-artifacts`.
The assert -> with edits landed with the Deno 2.9.8 bump, which needs
them for TypeScript 6, and they do change the IDE bundles.
…xtures

Rendering a project appends `**/*.quarto_ipynb` to its .gitignore when the
entry is missing. These eleven fixture projects did not have it yet, so
every local run of the website, search and site-navigation smoke tests left
them modified. Committing the entry keeps the tree clean after a test run.
The unsafe-port list marked 4160 as "sieve", but the Fetch standard's
bad port for sieve is 4190; 4160 is on no browser list. Browsers have
always refused 4190, and Deno 2.9 now applies the same list to its own
fetch, so a server Quarto starts there cannot even be polled for
readiness (headless Chrome falls back to findOpenPort when 9222 is busy).

The test asks Deno's fetch which candidate ports it blocks, so it
tracks the runtime's list instead of a copy of it.
Deno 2.9 reports every failed fetch as TypeError("fetch failed") and
moves the reason (TLS, DNS, connection) into `cause`. logError printed
only the message, so `quarto add` against a server with an expired
certificate said "fetch failed" where Quarto on Deno 2.7 said
"invalid peer certificate: certificate expired".

logError now appends each message in the cause chain. No Quarto code
creates errors with a cause, so only runtime errors gain the extra text.

No changelog entry: the missing reason arrived with the Deno 2.9.8
update in this same release.
An error whose cause chain loops back on itself (`e.cause = e`, or two
errors citing each other) made logError append messages until V8 threw
RangeError: Invalid string length. Quarto never builds such chains, but
extension code and engine hooks pass their errors through unchanged, so
logError must survive one. The loop now stops at the first error it has
already printed.
Tests take assertions from tests/asserts.ts so the library can be swapped
in one place; importing testing/asserts directly raised the Runtime
Boundary tests std-import count from 177 to 178.
The runtime-migration plans stay frozen, and findings that change another
plan's prerequisites go to README "Open decisions with an owner", which each
plan's step 0 reads. So the PR 01 status note moves out of its plan into a
row, and rows are added for PR 00b (deno install --allow-import), PR 15
(port.ts listing still has 4160 and a private isPortSafe) and the sanitizer
checks in PRs 15, 16, 23 and 31 (sanitizers are off by default on Deno 2.9
and the harness leaks when they are on). HANDOFF names 00a as in progress.
@cderv

cderv commented Oct 23, 2026

Copy link
Copy Markdown
Member Author

Merging this puts the fix in 1.11. Whether to backport the Deno bump to 1.10.x is not decided yet and I'm tracking it separately on #15152. A backport would need #15180 and the compatibility changes listed above, so it is a larger change than this PR alone.

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.

Update bundled Deno from 2.7.14 to 2.9.x

2 participants