Skip to content

test: pin the userinfo and trim guards in profileImageUrl - #1784

Merged
mrbobbytables merged 1 commit into
mainfrom
quality/test-profile-image-userinfo-trim
Oct 22, 2026
Merged

mrbobbytables merged 1 commit into
mainfrom
quality/test-profile-image-userinfo-trim

Conversation

@hivecommons-hive

Copy link
Copy Markdown
Contributor

Test Improvement

Adds two behavioural tests to tests/profile-image.test.mjs. No other file is touched, and scripts/lib/profile-image.mjs is unchanged.

StrykerJS over scripts/lib/profile-image.mjs scored 87.76% with 6 surviving mutants at fd23cd7. Four of them sat on the two guards that make the resolved URL safe to publish:

  • line 58, url.username || url.password mutated to && and to false. Every existing userinfo case names a host the allowlist rejects anyway (https://www.cncf.io@evil.example/ada.png), so the suite passed with the userinfo guard disabled. The new case uses an allowlisted host — https://user@github.com/ada.png, https://:pw@github.com/ada.png, https://user:pw@www.cncf.io/ada.png — where nothing but that guard can reject it.
  • line 47, value.trim() mutated away. No input carried whitespace the URL parser does not itself strip. The new case uses U+00A0: trim() drops it, the URL parser percent-encodes it into the path, so https://github.com/ada.png\u00a0 resolves to https://github.com/ada.png with the trim and https://github.com/ada.png%C2%A0 without it.

After this change the file scores 93.88%, and the 3 remaining survivors are equivalent mutants that cannot be killed from tests — documented in the commit message and in #1783:

  • String(hostname || "") fallback string: every falsy hostname is rejected either way.
  • .some → .every: CNCF_HOST_SUFFIXES holds exactly one entry.
  • if (!trimmed) → if (false): new URL("") throws and is caught into the same null.

Evidence, reproducible locally: TZ=UTC npx stryker run --mutate "scripts/lib/profile-image.mjs", before 46/49 → after 46 killed of 49 with the three equivalent survivors; TZ=UTC node --test tests/profile-image.test.mjs passes 10/10; prettier --check and eslint --max-warnings 0 clean on the changed file.

No overlap with the open held PRs: #1761 (tests/validate-utils.test.mjs), #1765 (tests/svg-active-content.test.mjs), #1771 (tests/enduser-landscape.test.mjs), #1773 (tests/markdown-references.test.mjs, tests/npm-lifecycle-scripts.test.mjs), #1780 (tests/enduser-collector.test.mjs), #1782 (tests/mdx-escape.test.mjs), #1767 (scripts/lib/duplicate-check.mjs and four validators).

Related Issue

Fixes #1783


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

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

StrykerJS scores scripts/lib/profile-image.mjs at 87.76% with six
surviving mutants. Four sit on the two guards that make the resolved
URL safe to publish:

  - line 58, 'url.username || url.password' mutated to '&&' and to
    'false'. Every existing userinfo case names a host the allowlist
    rejects anyway, so it passes with the guard disabled. A userinfo
    component on an allowlisted host is what the guard exists for.
  - line 47, 'value.trim()' mutated away. No input carried whitespace
    the URL parser does not itself strip.

Add a userinfo case per arm on an allowlisted host, and a U+00A0 case
leading and trailing: trim() drops it, the URL parser percent-encodes
it into the path, so the two differ observably.

The file now scores 93.88%. The three remaining survivors are
equivalent mutants: the '|| ""' fallback string (every falsy hostname
is rejected either way), '.some' to '.every' (CNCF_HOST_SUFFIXES holds
one entry), and 'if (!trimmed)' to 'if (false)' (new URL("") throws
and is caught into the same null).

Fixes #1783

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

netlify Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for endusers ready!

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

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.

@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.

Read scripts/lib/profile-image.mjs and the full test file at head 401a2a3, and mechanically verified the mutant-kill claims. Looks correct to me.

  • Userinfo guard (profile-image.mjs:59). The existing userinfo cases use non-allowlisted hosts, so they pass even with the guard mutated — the body's claim is accurate. The new assertions use an allowlisted host: https://user@github.com/ada.png exercises username-only and https://:pw@github.com/ada.png password-only, so each arm of url.username || url.password is independently load-bearing. I re-derived the three mutants (||→&&, guard→false): both new single-component cases return the href under the && mutant and fail the assertion, so the mutants die. Verified by execution, not inspection.
  • Trim guard (profile-image.mjs:48). U+00A0 is trimmed by String.prototype.trim but percent-encoded by the URL parser: without trim() the trailing case resolves to …/ada.png%C2%A0 and the leading case throws into the null path, so both assertions fail the trim-removal mutant. Also verified by execution.
  • One assertion in the new userinfo test (and one in the pre-existing test above it) contains a credential-shaped literal that is masked on every read path available to me, so I could not verify that specific input; the two unmasked assertions already pin both guard arms, so nothing in this judgement depends on it. A human glancing at that one line at merge time is sufficient.
  • Scope matches the body: one test file touched, module under test unchanged, no file overlap with the other queued test PRs (#1761, #1765, #1771, #1773, #1780, #1782, #1767). Mutation-testing and full CI checks are green on this head.

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

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] profileImageUrl's userinfo and trim guards survive mutation — no test distinguishes them

1 participant