Skip to content

ci: the outbound check drops what it cannot parse, and cannot see non-JS files #110

Description

@pitimon

Deferred from #109 on the reviewer's own advice ("the rest can follow as issues"). Both halves are real; neither is fixable without care that would have rushed that PR.

1. Unresolvable authorities are dropped, not reported

scripts/validate-outbound.cjs returns null when an authority cannot be reduced to a host, and collectHosts skips it. Three live consequences, all real requests the check never mentions:

  • https://[2606:4700:4700::1111]/… — bracketed IPv6
  • https://gіthub.com/… — Cyrillic і; the browser punycodes it to an attacker domain, the check drops it
  • https://intranet-host/ — single label, no dot

Failing closed is the right instinct and was implemented and then reverted inside #109, because it immediately fired on eight legitimate patterns: ${hostHeader} for the local bind, ${LOCAL_BIND_HOST}:${port}, ${values.HTTPSProxy}, git-remote parsing in rollout.js, and a log string with an ANSI reset after the URL.

A check that flags ordinary code is a check someone switches off, so this needs per-pattern handling — most likely a small declared set of known-local interpolations — rather than a flag flip.

2. The walker sees two directories and one extension set

Named places a host could live and be missed:

Where Why it matters
dashboard/index.html, dashboard/pet.html The served SPA shell. A <link rel=preconnect>, font CDN or <script src> here is a browser request. Today they load only local assets — verified.
dashboard/src/styles.css Inside a scanned directory, skipped by extension. CSS url() is a request. No url() today — verified.
dashboard/src/content/i18n/*.json Same: strings that flow into href/src props.
dashboard/vite.config.js Dev proxy target; a new one is invisible.
scripts/build-pricing-seed.cjs Fetches raw.githubusercontent.com outside the scan. Maintainer-run today.
Half B Covers only dashboard/src. A server file adding fetch(cfg.someUrl) beside an existing host literal is unchecked, though the README's promise covers the server.

Also: isTestFile exempts by filename, not by import graph. A module named analytics.test.js is importable by the app and exempt from the scan.

3. link_from is an unconditional per-file waiver

Once a file is on a host's link_from, any request to that host from it passes forever — including an <img src> added later.

The concrete scenario, which is the shape of #100: adding <img src={https://github.com/${skill.repoOwner}.png`} />toSkillDetailPanel.jsx— a plausible "show skill-author avatars" PR — passes today. That file holds agithub.com` waiver and already interpolates owner names.

Fix: pin each link_from entry to a line number or to the exact URL literal, so a new string in a waived file is a visible diff in the inventory rather than silently covered.

4. Trailing comments will produce false findings

COMMENT_LINE_RE matches only line-leading comments, so

const x = 5; // see https://github.com/nodejs/node/issues/123

classifies as a request. Reference links in trailing comments are common, and this is the likeliest source of pressure to disable the check.

Priority

4 first — it costs nothing and prevents the check being switched off, which would take the rest with it. Then 3, then 1, then 2.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions