Skip to content

test: pin websiteUrl authority-parsing outcomes on untrusted profile data - #1661

Merged
mrbobbytables merged 1 commit into
mainfrom
quality/test-website-url-authority-edges
Oct 19, 2026
Merged

mrbobbytables merged 1 commit into
mainfrom
quality/test-website-url-authority-edges

Conversation

@hivecommons-hive

Copy link
Copy Markdown
Contributor

Test Improvement

Adds four characterization tests to tests/profile-links.test.mjs pinning
authority-parsing outcomes of websiteUrl() (src/lib/profile-links.mjs) that
the suite already produced but never asserted.

websiteUrl() turns the free-text blog field of a third-party GitHub profile
into an href: scripts/fetch-community-people.mjs copies it verbatim into
data/community-people.json and the scheduled refresh commits it unedited, so
nothing reviews that string before it becomes a link. The existing tests cover
the disguise the module was written for — an explicit userinfo component — and
the protocols it refuses. They say nothing about an authority bent without a
surviving @.

The load-bearing one is the backslash case. A backslash is treated as a slash
inside a special scheme's authority, so good.example\@evil.example resolves
to https://good.example/@evil.example — the host is the label the reader sees
first, and the userinfo guard never fires because username is already empty
by the time it runs. That safety is a property of new URL(), not of a check
in this repository.

Measured rather than asserted: replacing the new URL() resolution in
applyDefaultScheme() with a hand-rolled value.split('@').pop() makes that
input resolve to https://evil.example/ — the attacker's host wearing the
visible host's name — while every pre-existing test in the file stays
green
. With this PR that mutation fails two tests. A second mutation,
narrowing SCHEME_PATTERN to disallow dots, fails one.

The other three pin a schemeless host:port being dropped because the colon
reads as a scheme (with the matching case showing a port is kept once the
value carries its own scheme, so the boundary is not mistaken for a port ban),
a non-ASCII authority emitting its punycode host, and a trailing NUL being
stripped where a NUL inside the host is dropped.

Each test carries the reasoning for why its outcome is the correct one, so a
future change to applyDefaultScheme() or SCHEME_PATTERN has to argue with a
red test instead of passing silently.

Tests only — no production code changes, and no existing assertion altered.

Scope

Touches tests/profile-links.test.mjs only. Disjoint from the open
hold-gated PRs: #1657 (src/components/MetricsDashboard, formatDate) and
#1659 (src/components/shared/FilterToolbar, catalog filter UIs).

Verification

  • node --test tests/profile-links.test.mjs — 20 pass, 0 fail
  • npm run test:unit:coverage:check — exit 0 (the required "Validate
    repository" gate)
  • npx prettier --check tests/profile-links.test.mjs — clean

This is not a coverage gap, and is not filed as one: src/lib/profile-links.mjs
is 100% line-covered in both lanes — unit (100.00% source lines/regions) and
end-to-end (artifact e2e-coverage id 12212515876, run 39302402289-1,
revision e1f9b981b8a99d029888672940140b58ac759657: 100.00% lines, 86.67%
regions, the residual regions being the two-build union-attribution artifact
already documented in tests/e2e-coverage-gate.test.mjs). It is an assertion
gap behind full coverage, which the coverage gates cannot see.

Related Issue

Closes #1660


Filed by quality agent (hold-gated mode). Human review required.

— hive: agent=quality backend=copilot model=claude-opus-5 copilot=1.0.88

…data

websiteUrl() turns the free-text "blog" field of a third-party GitHub
profile into an href. Its safety against an authority bent without a
surviving "@" comes from the WHATWG parser rather than from any explicit
check, so a refactor can invert it silently: replacing the new URL()
resolution in applyDefaultScheme() with a hand-rolled split on "@" makes
good.example\@evil.example resolve to https://evil.example/ while every
existing test in the file stays green.

Pin the four unasserted outcomes, each with the reasoning for why it is the
correct one: the backslash authority resolving to the leading visible host,
a schemeless host:port dropped because the colon reads as a scheme (while a
value carrying its own scheme keeps its port), a non-ASCII authority emitted
as punycode, and a trailing NUL stripped where one inside the host is
dropped.

Tests only; no production behaviour changes.

Closes #1660

Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com>
@hivecommons-hive

Copy link
Copy Markdown
Contributor Author

Important

Held for human review by the hive's ACMM level gate.

This PR was opened by the "quality" agent while Hive policy required a human checkpoint for that agent. Agent PRs are held at ACMM L3–L5; an outreach PR that targets a repository this hive does not manage is held at every level because it publishes project-facing communication into someone else's project.

Hive will keep the hold label until a human removes it. Operators can make a deliberate one-off release during an ACMM level change with release_level_holds=true, but level changes never release this hold automatically.

@netlify

netlify Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for endusers ready!

Name Link
🔨 Latest commit 41ed2f4
🔍 Latest deploy log https://app.netlify.com/projects/endusers/deploys/6ad6787d81c09178d9dbe5a9
😎 Deploy Preview https://deploy-preview-1661--endusers.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@mrbobbytables
mrbobbytables added this pull request to the merge queue Oct 19, 2026
Merged via the queue into main with commit 6222e91 Oct 19, 2026
12 checks passed
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.

[quality] websiteUrl() authority-parsing outcomes on untrusted profile data are unasserted

1 participant