Skip to content

test: pin the decodeEntities bound, bare named reference and remoteTarget anchors in uri-safety - #1792

Closed
hivecommons-hive[bot] wants to merge 1 commit into
mainfrom
quality/test-uri-safety-anchors-boundaries
Closed

hivecommons-hive[bot] wants to merge 1 commit into
mainfrom
quality/test-uri-safety-anchors-boundaries

Conversation

@hivecommons-hive

Copy link
Copy Markdown
Contributor

Test Improvement

Tests only — no production change. Touches one file,
tests/uri-safety.test.mjs, adding four cases and importing remoteTarget
alongside the exports the file already pulls from scripts/lib/uri-safety.mjs.

scripts/lib/uri-safety.mjs is the URI classifier both content gates share, and
it is already at 100% lines / 100% regions — the gap is assertion strength, not
execution. StrykerJS scored it 90.91% with 10 of 110 mutants surviving.
Five were behaviour nothing in the suite pinned:

  • lines 59 and 65 — code <= 0x10ffff weakened to < in both the hex and
    the decimal decoder. The suite asserted only the rejected side of the bound
    (&#x110000; stays as written) and never that the highest valid code point
    decodes. New case: &#x10ffff; and &#1114111; both resolve to
    String.fromCodePoint(0x10ffff).
  • line 70 — /&([a-z]+);?/gi narrowed so the semicolon is mandatory. Every
    named-reference case in the suite wrote the semicolon, so nothing failed when
    &colon stopped decoding — and that is the form a value uses to hide a scheme
    from a gate decoding only the semicolon spelling. New case asserts &colon
    and &sol resolve bare, and that
    normalizeUri('java&#115;cript&colon alert(1)') collapses to
    javascript:alert(1).
  • lines 148 and 166 — the ^ anchors deleted from the two authority
    patterns in remoteTarget. Verified against the mutated patterns directly:
    remoteTarget('/img/http:logo.png') is null with the line-148 anchor and
    'http://http:logo.png' without it; remoteTarget('/img/https://evil.example/a.png')
    is null with the line-166 anchor and the whole relative path without it.
    Both mutants turn a local reference into a remote finding, which the SVG and
    MDX gates surface as a build failure the author cannot act on. Every remote
    case in the suite today is absolute, so nothing reached past the anchors.

The file now scores 95.45% with 5 survivors, and the file header comment is
updated to say what remoteTarget is and is not asserted on here.

Why the other five survivors are left alone

  • line 86, [\u0000-\u0020\u007f]+ without the +: equivalent under a global
    replace with the empty string.
  • line 111, /^data:([^,;]*)/ unanchored: reached only after the scheme has
    been read as data at index 0, so the leftmost match is the same one.
  • line 163, /^(https?:)?[/\\]{2,}/ unanchored: a match past index 0 cannot
    make the rewritten value start with //, so the return value never
    changes. Exhaustive search over http:, https:, /, \, a, ?, #,
    ., : to length 4 finds no distinguishing input.
  • line 163 (https?:)? → (https:)? and line 166 /^https?:\/\// →
    /^https:\/\//: the same search distinguishes these only on host-less
    values, http:// and http:///. remoteTarget('http://') returns
    'http://' today — a target with no host — while the comment above line 148
    states such a value has nothing to report. Killing these two means pinning a
    behaviour the source says should not happen, so this PR does not. Resolving
    that inconsistency is a production change in remoteTarget and is recorded
    in [quality] pin the decodeEntities code-point boundary, the optional-semicolon named reference, and the two remoteTarget anchors in uri-safety.mjs #1791 rather than worked around here.

Verification

Run locally at 761fa39 (node v26.12.0):

  • TZ=UTC npx stryker run --mutate scripts/lib/uri-safety.mjs — 90.91% / 10
    survivors before, 95.45% / 5 survivors after
  • TZ=UTC node --test tests/uri-safety.test.mjs — 15/15 pass
  • npx prettier --check and npx eslint --max-warnings 0 clean on the changed
    file

Related Issue

Closes #1791

Scope

Touches only tests/uri-safety.test.mjs, which no open held PR modifies:
#1782 is tests/mdx-escape.test.mjs (scripts/lib/mdx-escape.mjs), #1784 is
tests/profile-image.test.mjs (scripts/lib/profile-image.mjs), and #1786 is
tests/validate-utils.test.mjs (scripts/lib/validate-utils.mjs). None of
them touch scripts/lib/uri-safety.mjs or its test.


Human review required.

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

…et anchors

scripts/lib/uri-safety.mjs is at 100% line and region coverage, so what was
missing was assertions, not execution. StrykerJS scored it 90.91% with 10 of
110 mutants surviving; five of those were behaviour no test pinned:

- lines 59/65, `code <= 0x10ffff` weakened to `<`. The suite asserted only the
  rejected side of the bound, never that the highest valid code point decodes.
- line 70, `/&([a-z]+);?/gi` narrowed so the semicolon is mandatory. Every
  named-reference case wrote the semicolon, so nothing noticed `&colon`
  ceasing to decode -- the form a value uses to hide a scheme from a gate that
  only decodes the semicolon spelling.
- lines 148 and 166, the `^` anchors deleted from the two authority patterns in
  remoteTarget. Without them a relative reference that merely contains `http:`
  or `https://` further along its path is reported as a fetch to another host,
  which the SVG and MDX gates turn into a build failure against a local asset.
  Every remote case in the suite is absolute, so nothing reached past them.

Four cases close those five mutants; the file now scores 95.45%.

The remaining five are left alone deliberately. Line 86's `+` is equivalent
under a global replace with the empty string, and line 111's anchor is reached
only once the scheme has been read as `data` at index 0. The unanchored line
163 mutant has no distinguishing input at all, and the two surviving scheme
mutants (`(https?:)?` and `/^https?:\/\//`) are distinguished only by host-less
values: `remoteTarget('http://')` returns `'http://'` today, a target with no
host, while the comment above line 148 says such a value has nothing to report.
Killing them means pinning a behaviour the source says should not happen, so no
test here does.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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 22, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for endusers ready!

Name Link
🔨 Latest commit cce669b
🔍 Latest deploy log https://app.netlify.com/projects/endusers/deploys/6ada5dbbe7535ae293723350
😎 Deploy Preview https://deploy-preview-1792--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.

@hivecommons-hive hivecommons-hive Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Verified against the tree at cce669b and this looks correct to me. The bound at scripts/lib/uri-safety.mjs:59/:65 is code <= 0x10ffff, the named-reference regex at :70 is /&([a-z]+);?/gi, and both authority patterns at :148/:166 carry the ^ anchors the new cases pin — each test asserts real current behaviour, and TZ=UTC node --test tests/uri-safety.test.mjs passes 15/15 locally. The diff implements the four checkboxes of #1791 exactly, so Closes #1791 is the right link.

One thing a maintainer should know before merging: this PR conflicts with #1790. The Scope section here says no open PR touches tests/uri-safety.test.mjs, but #1790 (copilot/mutants-group-d) edits the same file — it rewrites the same header comment (lines 8–16) and appends tests for the same three behaviours (max code point &#x10ffff;/&#1114111;, bare &colon, and the mid-path remoteTarget anchors, e.g. remoteTarget('/img/http:a.png') === null). I read both diffs in full: they are semantically overlapping but not textually identical, so whichever merges second will hit a git conflict and need a rebase that drops or reconciles the duplicated cases. #1790 is a fork PR the hive cannot push to, so if #1790 lands first, this PR is largely redundant; if this one lands first, #1790's author needs to rebase its uri-safety.test.mjs hunk.

No defects found. I have not approved or closed anything — merge ordering is for a maintainer to settle.

— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.88

@hivecommons-hive

Copy link
Copy Markdown
Contributor Author

⚠️ Hold-gate mergeability: this PR has a real conflict with its base

This PR is hold-gated and has fallen behind its base branch. The hive tried to merge the base in to keep it mergeable while it waits on review, but that merge conflicts and the hive cannot resolve it automatically.

It will stay held and will NOT merge on its own even once reviewed — removing hold alone will not be enough. Someone will need to merge or rebase the base branch in by hand (or resolve the conflict another way) before this PR can merge.

@mrbobbytables

Copy link
Copy Markdown
Member

Superseded by #1790 (Group D of #1788), which extended tests/uri-safety.test.mjs and merged earlier today. The full-tree mutation run on main afterwards (https://github.com/cncf/endusers/actions/runs/39845001035) shows the five mutants this change targets are already killed: lines 59, 65 and 70 of uri-safety.mjs and both remoteTarget anchors no longer survive. The three remaining survivors in the file (regex mutants at lines 86, 111 and 163) are not the ones this PR addresses, and the branch now conflicts with main. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[quality] pin the decodeEntities code-point boundary, the optional-semicolon named reference, and the two remoteTarget anchors in uri-safety.mjs

1 participant