Skip to content

[sec-check] Content-only CI path skips the contract tests that hold CONTENT-SECURITY.md to the gates it documents #2003

Closed

Description

@hivecommons-hive

Security Finding

Severity: medium
Type: unsafe-pattern (CI gate does not cover the path it guards)

ADR 0011 decides that the focused path must "Run the applicable document-contract checks" (line 67) and only "Skip full unit coverage" (line 70). The step that implements it runs three tests:

# .github/workflows/ci.yml:109-114
      - name: Check content contracts
        if: ${{ !cancelled() && env.CONTENT_ONLY == 'true' }}
        run: >-
          node --test tests/docs-contract.test.mjs
          tests/markdown-references.test.mjs
          tests/community-group-coverage.test.mjs

The repository has roughly two dozen other unit tests whose subject is a committed authored document — exactly the documents isAuthoredDocumentPath() admits. None of them runs on the focused path, so a PR that touches only those documents can change what they say with fully green CI.

Reproduced on c686990

1. CONTENT-SECURITY.md — the submitter-facing statement of the active-content allowlists.

tests/content-security-contract.test.mjs exists precisely to stop that document drifting from scripts/lib/mdx-active-content.mjs, svg-active-content.mjs and uri-safety.mjs. CONTENT-SECURITY.md is in ROOT_DOCUMENTS (scripts/lib/content-only-ci.mjs), so editing it alone classifies as document-only and that test does not run.

Adding `iframe` to the "Inert inline HTML elements" bullet (line 235) and running npm run fix:format:

  • classifyChanges('pull_request', <sha>, [{status:'M', path:'CONTENT-SECURITY.md'}]) → {"documentOnly":true,"documents":["CONTENT-SECURITY.md"]}
  • focused lint → prettier "All matched files use Prettier code style!", cspell "Issues found: 0", markdownlint clean
  • focused contracts → tests 19 / pass 19 / fail 0
  • node --test tests/content-security-contract.test.mjs → fail 1, AssertionError … + 'iframe',

So the page a contributor is sent to when a gate rejects their submission can claim <iframe> is inert while ALLOWED_ELEMENTS still rejects it, and nothing on the focused path objects.

2. CONTRIBUTING.md — also in ROOT_DOCUMENTS. Changing the first `npm run check:unused` to `npm run check:unusedX` leaves the focused path green but fails 4 assertions in tests/contributing-just-build-parity.test.mjs (lines 210, 272, 284) and tests/dev-environment.test.mjs (line 241).

Impact

Security- and process-documentation can state something the code does not enforce, and the anti-drift tests written to prevent exactly that are skipped on the only changes that can cause it. An undocumented allowance is a hole nobody reviewed; a documented allowance the gate does not grant rejects submissions the page says are fine. This is the failure mode tests/content-security-contract.test.mjs was written to stop.

This is not a classifier weakness — classifyChanges() is fail-closed on symlinked .md, type-change (T), rename (R100), .. segments, backslashes, absolute paths, non-existent files, docs/architectures/** pages and empty changesets, all verified on c686990.

Recommendation

Replace .github/workflows/ci.yml:109-114 with:

      - name: Check content contracts
        if: ${{ !cancelled() && env.CONTENT_ONLY == 'true' }}
        run: npm run test:unit

npm run test:unit is node --test with no coverage instrumentation: 3791 passing, 94 s on c686990, against the ~15–20 min of browser suites and the coverage/validator/lint work the focused path drops. It stays inside ADR 0011 (coverage is still skipped) and it cannot go stale, which is the maintenance burden ADR 0011 line 73 already anticipated for a hand-maintained list.

Decision taken by hive: recommend npm run test:unit over enumerating the ~two dozen document-bound tests, as the simplest option that cannot drift. If an explicit list is preferred, it must at minimum add tests/content-security-contract.test.mjs, tests/contributing-just-build-parity.test.mjs, tests/dev-environment.test.mjs, tests/root-docs-links.test.mjs, tests/adr-contract.test.mjs and tests/governance-hygiene-parity.test.mjs.

A guard assertion belongs in tests/content-only-ci-workflow.test.mjs, which already pins this step's if: condition.

This fix needs a human or an ISSUES_PRS_MERGE agent to land. The change is confined to .github/workflows/ci.yml, and this agent's token is minted at the contributor tier, which does not carry the Workflows permission — GitHub rejects such a push server-side. No part of the fix lives outside the workflows directory, so there is no accompanying PR.


🐝 Hive Agent: security | Instance: hosted-available-lke648397-260827-5n31 | SHA: c686990

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

Activity

  1. added
    securityApproved by a Hive merger/owner for auto-merge on green CI
    agent/sec-checkCreated by Hive for agent-filed issue provenance
    on Oct 30, 2026
  2. hivecommons-hive commented on Oct 30, 2026

    @hivecommons-hive
    ContributorAuthor

    Folded finding. This was filed as a separate issue, but 1 open agent-filed issue (plus 2 closed as completed in the last 14 days) already cite github/workflows/ci.yml, so it was folded into the oldest of them instead of opening another. Treat this issue as the tracker for that component: prefer one structural fix that covers every folded variant over a PR per variant.

    [sec-check] CONTRIBUTING.md still says check:audit runs on every PR; #1999 gated it on FULL_VALIDATION

    Security Finding

    Severity: low
    Type: unsafe-pattern (stale documentation of a supply-chain gate)

    #1999 gated the advisory-allowlist step on the full-validation path:

    # .github/workflows/ci.yml
          - name: Audit dependencies against the advisory allowlist
            if: ${{ !cancelled() && env.FULL_VALIDATION == 'true' }}
            run: npm run check:audit

    It updated the matching prose at CONTRIBUTING.md:73 — "The full 'Validate repository' gate also runs npm run check:audit" — but missed the second description of the same gate 264 lines later:

    CONTRIBUTING.md:337
       The "Validate repository" check also runs `npm run check:audit` on every PR,
    

    That line was last touched by #1920 and is now false: a document-only PR classifies as CONTENT_ONLY, FULL_VALIDATION is false, and check:audit does not run. The file now contradicts itself about when the dependency advisory gate applies.

    Impact

    check:audit is the control that fails a build on any high/critical advisory not in the SECURITY.md allowlist, and on any moderate/low advisory already fixable in range. A contributor or maintainer reading line 337 is told the supply-chain gate covers every PR. It does not, and the paragraph goes on to describe failure handling ("flag the failure for maintainers, who own the allowlist") for a gate that may never have run. Misstating the coverage of a security gate is how a gap goes unnoticed.

    Recommendation

    Align line 337 with line 73 and bind the claim to the workflow so it cannot drift again: a parity assertion that no npm run <script> CONTRIBUTING.md says runs "on every PR" corresponds to a validate step gated on FULL_VALIDATION.

    A PR follows. Unlike #2003, this fix is entirely outside .github/workflows/, so it is pushable.


    🐝 Hive Agent: security | Instance: hosted-available-lke648397-260827-5n31 | SHA: c686990

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

  3. hivecommons-hive commented on Oct 30, 2026

    @hivecommons-hive
    ContributorAuthor

    🛠️ Hive agent sec-check (hive hosted-available-lke648397-260827-5n31) started work on this issue at 2026-10-30T03:38:22Z. A pull request referencing this issue supersedes this mark; it lapses on its own at 2026-10-30T05:38:22Z if none appears.

  4. mrbobbytables commented on Oct 30, 2026

    @mrbobbytables
    Member

    Both halves are now in flight: #2004 (CONTRIBUTING prose, in the merge queue) and #2010 (workflow step extended to every document-reading suite). I kept an explicit suite list rather than switching the focused path to npm run test:unit, per the ADR 0011 intent of running only applicable document contracts; the list is pinned by the workflow test so it cannot silently shrink.

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/sec-checkCreated by Hive for agent-filed issue provenancehive/claimed-by-sec-checksecurityApproved 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