Skip to content

M69: web fetch on both backends, with M44b's network safety - #52

Open
RandyNorthrup wants to merge 14 commits into
mainfrom
feature/m69-web-fetch
Open

RandyNorthrup wants to merge 14 commits into
mainfrom
feature/m69-web-fetch

Conversation

@RandyNorthrup

Copy link
Copy Markdown
Owner

What

M69 (PLAN D49, folds in M44b): the model can read a web page on both backends.

  • Model API: a native web_fetch tool.
  • Muse Code: mcp__ide__webFetch on the extension's ide server (Muse Code's own web_fetch is switched off).

Network safety

  • HTTPS only; public addresses only. Loopback, private, link-local, CGNAT, cloud-metadata and reserved ranges are refused, and IPv4 embedded in IPv6 (mapped, compatible, NAT64 including RFC 7050-discovered prefixes, 6to4) is judged as IPv4.
  • The name is resolved locally and the connection is pinned to the checked address, also through VS Code's proxy (CONNECT <address>:443, TLS SNI = host). Candidates race per RFC 8305. PAC/noProxy rules see the IP; that is documented as a deliberate trade-off (sending the name would let the proxy resolve it again).
  • Same-host redirects are re-checked and re-pinned (max 5); a redirect to another host goes back to the model for its own approval.
  • 5 MiB after decompression, 30 s, text types only, HTML converted to Markdown with a bounded converter. Page text, title and final URL reach the model inside random untrusted-content markers; server header values appear outside them only as short tokens.

Permissions

A new network tool class: Bypass runs it, Plan refuses it, Manual / Edit automatically / Auto ask per host. Restricted Mode refuses. On Muse Code the tool is listed only in a trusted workspace where sandboxNetwork is not restricted, declares readOnlyHint: false, openWorldHint: true, and the extension asks in its own dialog before every call; a Muse Code Stop cancels the call (captured live).

Review

Built, then reviewed in three parallel classes (concurrency/lifecycle; wire/security; failure paths/docs); every finding fixed in b1ac17f or answered with evidence in docs/certification/m69.md.

Gate

quality:gates exited 0 locally (format, lint, typecheck, l10n, knip, dpdm, jscpd 0 clones, 2,629 unit tests, build and budgets, audit). The full local a11y run did not complete (headless Chrome crashed under machine load); the affected and new scenarios passed separately, as did gitleaks and semgrep. CI's three-OS run is the full gate. 59 red drills recorded.

Live: Muse Code 1.4.0 on the contributor model, 8 model attempts in all (listing and calling, then the cancel captures).

Owner questions left open (in the cert record): an enterprise off switch; whether Muse Code's MCP "always allow" should silence the extension's dialog; allowing fetches in Plan mode.

🤖 Generated with Claude Code

RandyNorthrup and others added 3 commits September 28, 2026 01:16
PLAN.md D49 (folds in M44b), built to the plan review's six rules.

- web_fetch on the Model API backend (a new `network` tool class): HTTPS
  only; every DNS answer checked against the non-public ranges (loopback,
  private, link-local, CGNAT, metadata, reserved; IPv4 inside IPv6 judged
  as IPv4); the connection pinned to a checked address through Node's
  https, which VS Code patches for its proxy and certificates (the proxy is
  asked to tunnel to the address; only a TLS answer is read). Same-host
  redirects checked and pinned again (5 at most), another host's handed
  back; 5 MiB after decompression, 30 s, text types only; HTML to
  Markdown in one linear pass (entities 8.1.0 decodes references).
- Asks per host in Manual, Edit automatically and Auto; Bypass runs it;
  Plan and Restricted Mode refuse it. The page reaches the model between
  random markers as untrusted content. No billing.
- Muse Code: webFetch on the ide server, listed only in a trusted
  workspace without sandboxNetwork restricted, annotated open-world and
  not read-only, with the extension's own modal before every call.
- Row: the URL, the size and type, and what the model read; 23 strings in
  fifteen languages; harness scenario web-fetch.
- Tests over a fake resolver and transport, a loopback transport test, an
  integration test inside VS Code 1.139.1 and 1.125.0 with a loopback
  proxy; 28 red drills; live: public pages (no model) and one Muse Code
  turn (4 model attempts, contributor model).
- Docs: README (Web fetch, permission modes, privacy), PRIVACY, SECURITY,
  AGENTS layout, CONTRIBUTING (Networks), CHANGELOG, PLAN (M69 status, D3,
  M44b, §9), docs/certification/m69.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every finding of the three class reviews of c3d7702, in one pass:

- Muse Code's Stop reaches the ide webFetch call: IdeMcpServer aborts a
  call's signal when its request closes unanswered or
  notifications/cancelled names its id (both captured from Muse Code
  1.4.0); the modal is raced against it, the offer is checked again after
  the answer, the fetch gets the signal, one modal per URL at a time.
- Checked addresses are raced as RFC 8305 says (250 ms attempt delay,
  first TLS connection wins, the others stopped).
- Failures in web fetch's own words (host, addresses tried), not M56's
  Meta advice; a proxy's tunnel refusal recognised by the transport's code;
  the detail names error codes only, never a certificate's names.
- Server text outside the markers only as short tokens; the final URL, the
  title and a moved target inside them.
- The HTML converter is bounded (100,000 characters, prefix depth 4, body
  rows unpadded, title source capped).
- Every trailing dot stripped, empty labels refused.
- RFC 7050 NAT64 prefix discovery; answers under it judged by their IPv4.
- Damaged compression is the coding's failure; charset only from <meta>,
  an unknown label ignored.
- Model text says "this tool"; Model API rows localized for a moved page
  and Restricted Mode; side chats are not offered web fetch.
- sandboxNetwork described in fifteen manifest tables; docs narrowed where
  they overclaimed; PAC/noProxy seeing the pinned address documented.
- The integration proxy tests share one setup and judge only the page's
  tunnels (VS Code's own requests may use the window's proxy).

The full local gate did not finish under machine load; the record says
which parts passed (all of quality:gates, secrets, SAST, a11y for the
changed scenario) and leaves the full run to CI.

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

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-28T20:17:59.401260Z 83eedf2 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b1ac17f0a8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/host/web/webFetcher.ts Outdated
Comment thread src/core/backends/modelapi/ModelApiHost.ts
PR #52 review (Codex):

- NAT64 discovery asks the resolver the page's name used (getaddrinfo),
  and only its definite "no AAAA" (ENOTFOUND, or ENODATA) means no DNS64.
  A timeout, SERVFAIL or answers without an RFC 6052 prefix leave NAT64
  unknown: no IPv6 answer is used, and a name or IPv6 literal with only
  IPv6 answers is refused as nat64Unknown (one new string, 14 tables).
- A PermissionRequest hook's allow no longer replaces the per-host web
  fetch card; a hook may still deny or ask. Bypass and session-allowed
  hosts run without a card, as before. Muse Code's ide webFetch always asks
  in the extension's own modal and needed no change.
- Swept the other failed-lookup and failed-check paths: none allows.

Drills R32-R35 red and restored. Touched suites and the fast gates only
(machine overloaded); the full gate is CI's.

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

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ec34f25fc6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/core/backends/modelapi/ModelApiHost.ts Outdated
RandyNorthrup and others added 2 commits September 28, 2026 07:56
PR #52 second review (Codex): on the Model API backend, trust revoked
while the card or a PermissionRequest hook was pending did not stop the
fetch.

- After the card or hook resolves, the turn, trust and the mode are asked
  again before the fetch (a stopped turn cancels; Restricted Mode and a
  mode that now refuses refuse).
- The fetch takes an isStillAllowed check, asked before each hop's lookup
  and connection, redirects included; a failed check ends it as
  "withdrawn" (one new string, 14 tables).
- Once the page is in, it reaches the model only while fetch is still
  allowed.
- Muse Code's ide webFetch passes its offer (trust, sandboxNetwork) as
  that check and asks it, with the stop, once the page is in.

Swept every await on both paths. Drills R36-R43 red and restored.
Touched suites and fast gates run; the full gate runs after this commit.

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

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7ad1815540

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/host/web/webFetcher.ts Outdated
Comment thread src/core/web/htmlToMarkdown.ts Outdated
PR #52 third review (Codex):

- getaddrinfo's ENOTFOUND can stand for other lookup failures, so it no
  longer proves "no DNS64". Discovery asks the system resolver and a DNS
  query of its own (c-ares resolve6) together: a prefix comes from either
  one's answers; with none, only the DNS query's NXDOMAIN or NODATA means
  no NAT64. Everything else leaves it unknown and IPv6 answers unused.
- The converter now hides elements HTML closes without an end tag (<p>,
  <li>, <dt>, <dd>, cells, rows, ...) up to where a browser ends them: a
  closing start tag unless something open inside keeps them open, their
  own end tag, or the end tag of an element open around them. Counted
  open-element tracking keeps hostile pages linear.
- Sweep: a hidden image's alt text, a self-closed hidden element, an
  unopened dialog, rp and datalist are left out too; no other error in
  the fetch path maps to an allowing verdict.

Drills R44-R52 red and restored. Touched suites and fast gates run; the
full gate runs after this commit.

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

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 569dd869a0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/core/web/htmlToMarkdown.ts Outdated
PR #52 fourth review (Codex): a self-closed <template/> let its content
through, the third converter finding in a row. This round audits the
converter against the HTML parsing algorithm and follows it for
everything that decides what is hidden or left out.

- One stack of open elements with the algorithm's scopes, special and
  formatting elements, implied ends, a reduced adoption agency (a hidden
  formatting element reopens until its own end tag), </form>, foreign
  content (breakouts, integration points, CDATA), select, tables,
  headings and <body>/<html> attribute merging. Positions are kept per
  name and per tracked set, so every check is constant time and hostile
  pages stay linear.
- Tokenizer: comments end at <!-->, <!--->, --> or --!>; </ before a
  non-letter is a bogus comment; CDATA outside foreign content is one;
  scripts follow the escape states; a slash counts only right before >
  and only on void and foreign elements; attribute names may begin
  with =.
- parse5 8.0.1 (already in the tree) was measured and not adopted: it
  is quadratic on hostile nesting (40,000 nested lists in 73 s against
  127 ms here). No dependency changes.

Drills R53-R71 red and restored. Touched suites and fast gates run; the
full gate runs after this commit.

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

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5ef2eabeda

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/core/web/htmlToMarkdown.ts
Comment thread src/core/web/webFetch.ts Outdated
Comment thread src/core/web/htmlToMarkdown.ts Outdated
RandyNorthrup and others added 4 commits September 28, 2026 09:46
Conflicts kept both sides: the README settings table keeps web fetch's
sandboxNetwork row and takes main's environmentVariables row; the
certification index lists M69 and sign-in detection. en.ts and the
manifest tables merged on their own. Typecheck, check:l10n (0 problems)
and the unit suite (2,837 passed) ran on the merge.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The fifth review found three more gaps in the hand-written HTML path
(<base href> ignored, the charset sniff reading <meta> in comments and
other attributes, display:/**/none passing the hidden check): the fourth
round, so it is redesigned rather than patched.

- parse5 8.0.1 parses the page with the HTML standard's algorithm; the
  converter walks its tree with the existing Markdown writer, resolves
  links against the first <base href>, and leaves out what HTML hides
  (hidden, inert, aria-hidden, a dialog not opened, rp, template,
  noscript) and inline styles that hide, read with
  @csstools/css-tokenizer 4.0.1 (comments, escapes, case, !important,
  invalid later values).
- html-encoding-sniffer 6.0.0 decodes a page as HTML does (BOM, header,
  the standard's <meta> prescan), UTF-8 by default.
- parse5 is quadratic on hostile nesting, so the converter runs in its
  own bundle, dist/pageWorker.js (212 KiB, budget 300), on a worker
  thread started at the first page and stopped at 10 s or 512 MiB; such
  a page, or a worker that cannot start, is refused with the reason
  (three strings, 14 tables). The bundle-split gate keeps it off
  dist/extension.js (473 KiB, down from 490).
- Dependencies pinned exactly, already in the lockfile, MIT, audit clean;
  entities is no longer direct. Node 20: the production worker ran on a
  portable Node 20.18.3.

README and SECURITY say exactly what is left out and that text a
stylesheet hides still reaches the model, marked untrusted. Drills P1-P16
red and restored.

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

Two class reviewers and Grok Build over a49d373:

- Encodings: the sniffer's crash on a malformed <meta> content is read
  past; XHTML is sniffed as XML; a later <meta> changes a tentative
  encoding (the standard's reparse); x-user-defined and replacement are
  decoded by the Encoding standard's own definitions (Node 20.18 has no
  decoder for the first, no Node for the second); any other encoding a
  runtime cannot decode refuses the page ("undecodable"), never read as
  UTF-8; a text page's byte order mark decides first; the header's charset
  is a MIME parameter.
- Workers: at most two conversions at once, the wait honouring the
  fetch's signal; a deadline passing during conversion is named as the
  conversion's; failure details are short codes; crashes are logged by
  name, code and frames, never the message; the tests prove the worker
  thread ends.
- Hidden content: popover, closed <details> (all but its summary) and
  declarative shadow roots (children in their slots) read as they render;
  the title only from <head>; inline display by its real grammar, a
  bracket stack, and var() on a hiding property counted as hiding.
- Shipping: the .vsix check requires dist/pageWorker.js; an integration
  test converts through it in VS Code.
- Docs: a worker per page, the developer rows and layout.

Two strings (one new, one reworded) in 14 tables. Drills Q1-Q19, R1-R6.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Mac mini rig failed two htmlToMarkdown tests on 02e8ca1: their 5 s
limits, written for the hand-written converter, took 13.6 and 17.5 s on
parse5 there. On parse5 that time is the page worker's to bound (tested in
pageConverter.test.ts); the pure converter's tests now check the output
bound only, on inputs still past it.

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

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 19f843f33b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/core/web/htmlToMarkdown.ts
Comment thread src/core/web/htmlToMarkdown.ts Outdated
PR #52 review of 19f843f (Codex):

- XHTML went through parse5's HTML parser, which misreads its XML syntax
  (<script src="x"/> swallowed the text after it). No XML parser is
  bundled, so application/xhtml+xml is refused with its own reason (one
  string, 14 tables), dropped from the Accept header, and the XML sniffing
  path is removed.
- visibility is inherited: a hidden ancestor no longer drops its subtree.
  The walk carries the inherited value; a descendant with visibility:
  visible shows, an invisible element keeps its tags but writes no text or
  void element. display:none and content-visibility:hidden still take
  everything; swept: visibility was the only inherited hiding.

Drills V1-V6 red and restored.

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

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 83eedf262e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/core/web/htmlToMarkdown.ts Outdated
Codex found a <col style="visibility:collapse"> whose cells the converter
still wrote: the third round on the converter's hiding, so by the owner's
rule the area is redesigned rather than patched. Hiding cannot be worked
out completely without being a browser, and the attempt protects nothing
(the same words fit in visible small print); the untrusted markers around
everything a page returns are the defence.

The converter now leaves out only what its structure never makes page
text (the head, scripts, styles, template content, noscript, frames and
media, form controls, SVG, MathML) and reads no CSS and no hiding
attribute, hidden included, for one consistent rule. A declarative shadow
root's content is written where its template stands. inlineStyle.ts and
@csstools/css-tokenizer are removed (pageWorker.js 216.8 -> 201.2 KiB).
The tool description and the model's notice say the result is the page's
text as served, which can include text a browser would not show, all of
it untrusted; README, SECURITY, PRIVACY, PLAN, CHANGELOG and the
certification say the same, with red drills W1 to W9.

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

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

This branch has not been deployed

No deployments
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