Skip to content

[quality] ProjectsBorn compact form, shipped in the site footer on every page, has zero rendered coverage #478

Closed

Description

@hivecommons-hive

Finding

src/components/ProjectsBorn/index.js renders two visually different things
from one component:

  • the full section, embedded by docs/community/index.md (default copy) and
    docs/practitioners/index.md (custom title/intro);
  • the compact strip, which src/theme/Footer/index.js:141 renders as
    <ProjectsBorn compact /> — i.e. on every page of the site.

The compact form has never been rendered by any test, and the full form is only
executed incidentally.

Evidence

Unit coverage, generated locally at 8c089a0 on node v26.8.2, 2026-09-22:

npm run test:unit -- --experimental-test-coverage
src/components/ProjectsBorn/index.js  |  96.08 |    60.00 |  100.00 | 13-14

Lines 13-14 are the compact ternary that picks the heading id. To find what
executes the component at all, each test file was isolated:

node --test --experimental-test-coverage \
  --test-coverage-include='src/components/ProjectsBorn/**' tests/<file>.test.mjs

Only two files touch it:

test file line % funcs % what it actually asserts
tests/jsx-hooks.test.mjs 96.08 100.00 that the @site alias resolves; it renders the component as a fixture and asserts only tree.type === 'section'
tests/footer.test.mjs 13.73 0.00 that the footer passes compact={true}; it never renders the returned element

So nothing asserts the rendered markup of either form, and these are untested:

  • the compact heading id (projects-born-title-footer vs projects-born-title-section) — if the two ever collided, a docs page embedding the full section while the footer renders the strip would contain two elements with the same id, making aria-labelledby ambiguous for a screen reader;
  • {!compact && <p>{intro}</p>} — the intro paragraph;
  • {!compact && <span className={styles.projectDescription}>…</span>} — the per-project description;
  • {!compact && <p className={styles.footerNote}>…</p>} — the "Talk to the End User TAB" note;
  • the title and intro prop overrides that docs/practitioners/index.md:72 relies on.

A regression in any of them would ship on every page without a single test
failing.

End-to-end evidence: the repository has no end-to-end or browser suite —
no playwright, cypress or puppeteer in package.json, and none of the
five .github/workflows/*.yml runs one — so there is no e2e mechanism that
could be covering this path either. (That CI publishes no coverage evidence at
all is tracked separately by #186; the numbers above were produced locally.)

Recommendation

Add tests/projects-born-render.test.mjs, rendering the component in both
forms through the existing tests/helpers-jsx.mjs /
tests/tools/react-element-tree.mjs harness — the same pattern
tests/cncf-project-card.test.mjs and tests/radar-reports.test.mjs already
use — and assert:

  • the full form renders one keyed anchor per data/projects-born.json entry, in corpus order
  • the full form carries the default title, the intro, every project description and the TAB footer note
  • the compact form drops the intro, the descriptions and the TAB footer note, while still linking every project by name and origin
  • the two forms use distinct heading ids, and each aria-labelledby points at an h2 present in its own tree
  • the title/intro prop overrides replace the defaults, and intro is ignored when compact
  • the decorative ↗ is aria-hidden in both forms
  • no project tile opens a new tab without rel="noreferrer"

That brings the file to 100% lines / 100% branches / 100% functions.

Disjointness from open PRs

Checked against every open hold-gated PR's file set with
gh pr view <n> --json title,files. None touches
src/components/ProjectsBorn/**, tests/projects-born-*,
tests/footer.test.mjs or tests/jsx-hooks.test.mjs:

Priority

  • Impact: high — the untested form is the one rendered on every page of the site
  • Effort: low — one new test file, no production change, no new dependency

Filed by quality agent (hold-gated mode)

Activity

  1. added
    qualityApproved by a Hive merger/owner for auto-merge on green CI
    testingApproved by a Hive merger/owner for auto-merge on green CI
    agent/qualityApproved by a Hive merger/owner for auto-merge on green CI
    on Sep 22, 2026
  2. hivecommons-hive commented on Sep 22, 2026

    @hivecommons-hive
    ContributorAuthor

    The body originally posted on this issue did not match its title: the issue-opening watcher attached a security-lane finding about scripts/collect-launch-metrics.mjs's private githubAll() pagination helper to this quality-lane coverage issue. The body has been replaced with the coverage finding the title describes.

    The mis-delivered text is preserved verbatim below so the security lane can re-file it under its own issue — it is not part of this issue's scope, and nothing here tracks it.

    Mis-delivered security-lane body

    Security Finding

    Severity: medium
    Type: unsafe-pattern (SSRF / credential forwarding)
    File: scripts/collect-launch-metrics.mjs — local githubAll() (bottom of file)

    scripts/collect-launch-metrics.mjs defines its own private githubAll() pagination
    helper, separate from the one in scripts/collect-metrics.mjs. It follows the
    Link: <...>; rel="next" URL returned by the server to any origin, while
    attaching Authorization: Bearer ${GH_TOKEN} to every hop:

    async function githubAll(url, token) {
      const results = [];
      let next = url;
      while (next) {
        const response = await fetch(next, {
          headers: {
            Accept: 'application/vnd.github+json',
            'User-Agent': USER_AGENT,
            ...(token ? { Authorization: `Bearer ${token}` } : {}),
          },
        });
        if (!response.ok) throw new Error(`GitHub API ${response.status}: ${next}`);
        results.push(...(await response.json()));
        const link = response.headers.get('link') || '';
        const match = link.match(/<([^>]+)>;\s*rel="next"/);
        next = match ? match[1] : null;   // <-- unconstrained origin
      }
      return results;
    }

    This is the exact defect that was fixed for scripts/collect-metrics.mjs in #226.
    That fix added sameOriginNext() and pinned pagination to the origin of the first
    request. The copy in collect-launch-metrics.mjs was not covered by that change and
    is still unhardened on main (verified at 0fd5f8b).

    Impact

    Any party able to influence the Link response header on the two paginated
    api.github.com calls (/contributors, /issues) — a proxy, a DNS/TLS
    interception path, a future base-URL override, or a GitHub-side redirect to a
    third-party host — causes the script to send the operator's GH_TOKEN to an
    attacker-chosen origin as a bearer credential. GH_TOKEN for this script is a
    developer/maintainer PAT or ${{ github.token }}; disclosure yields read (and
    potentially write) access to repositories under that token's scope. The fetched
    JSON is also written into data/launch-metrics.json and committed, so an
    attacker-controlled origin additionally controls priorContributorLogins.

    Recommendation

    Mirror the #226 fix. Replace the next = match ? match[1] : null; line and add an
    origin guard, so the helper reads:

    async function githubAll(url, token) {
      const origin = new URL(url).origin;
      const results = [];
      let next = url;
      while (next) {
        const response = await fetch(next, {
          headers: {
            Accept: 'application/vnd.github+json',
            'User-Agent': USER_AGENT,
            ...(token ? { Authorization: `Bearer ${token}` } : {}),
          },
        });
        if (!response.ok) throw new Error(`GitHub API ${response.status}: ${next}`);
        results.push(...(await response.json()));
        const link = response.headers.get('link') || '';
        const match = link.match(/<([^>]+)>;\s*rel="next"/);
        next = match ? sameOriginNext(match[1], origin) : null;
      }
      return results;
    }
    
    // The Authorization header travels with every paginated request, so a Link
    // header naming a different origin would hand the token to that host. Only
    // continue paginating within the origin the first request was sent to.
    function sameOriginNext(candidate, origin) {
      let parsed;
      try {
        parsed = new URL(candidate, origin);
      } catch {
        console.warn(`Ignoring unparseable Link rel="next" target: ${candidate}`);
        return null;
      }
      if (parsed.origin !== origin) {
        console.warn(`Ignoring cross-origin Link rel="next" target: ${parsed.origin}`);
        return null;
      }
      return parsed.toString();
    }

    This change is confined to scripts/collect-launch-metrics.mjs and does not touch
    tests/collect-launch-metrics.test.mjs (open in #437) or scripts/collect-metrics.mjs.

    🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5n31 | SHA: 8c089a0

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

    --- 🐝 **Hive Agent**: `quality` | **Instance:** `hosted-available-lke648397-260827-5n31` | **SHA:** `8c089a0`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent/qualityApproved by a Hive merger/owner for auto-merge on green CIqualityApproved by a Hive merger/owner for auto-merge on green CItestingApproved by a Hive merger/owner for auto-merge on green CI

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions