Skip to content

EPBDS-16211: a security model and policy, a weekly audit of the bundled packages, and CI that keeps every master run - #205

Merged
AlexSamBY merged 1 commit into
masterfrom
security-and-ci
Oct 12, 2026
Merged

AlexSamBY merged 1 commit into
masterfrom
security-and-ci

Conversation

@AlexSamBY

Copy link
Copy Markdown
Member

What

The security and CI items of the 2026-10-08 audit.

What a meta can do, written down

The README gains a Security model section. Each claim in it was checked against the code, and the two strongest also in a test run:

  • A meta can render raw HTML. A Text node given dangerouslySetInnerHTML: {__html: '<b id="raw">raw</b>'} puts <b id="raw"> in the page: through the published entry, in jsdom, it was found by id. No URL scheme is checked in an attribute a meta sets.
  • A meta can call fetch. With mapArgs, the built-in fetch action calls the page's fetch with exactly the URL the meta names. With a stand-in fetch, the call arrived as ["https://example.test/mapped"]. With args, the click event goes first.
  • A data value is text, escaped by React, unless a meta binds it to an attribute with {name: 'path'}.
  • Upload checks run in the browser only. formats and maxSize filter in the browser, and uploadFile receives the files as chosen.
  • Downloads come from the host's downloadFile. The renderer only saves what that function answers.

SECURITY.md says how to report a vulnerability and what is in scope: anything a data value can do, the published package and what it bundles, the stylesheet leaving .ui-render, and the build and CI. What a meta may do is out of scope.

Before this merges, private vulnerability reporting needs turning on. SECURITY.md points reporters at it, and gh api repos/eisgroup/ui-render/private-vulnerability-reporting answers {"enabled":false}. It is under Settings → Code security. The same API shows Dependabot security updates, secret scanning and push protection off too. Those are repository settings, so they are not changed here.

The bundled packages, audited every week

npm run audit:bundled (scripts/audit-bundled.js) runs npm audit and keeps only the findings on the packages dist/sbom.cdx.json lists, at the version the bundle carries. That matters because:

  • dependencies is empty, so npm audit --omit=dev audits nothing;
  • a full npm audit reports the development tree.

It fails on a finding at moderate or above. .github/workflows/audit-bundled.yml runs it on Mondays and on demand, not on pull requests, so a new advisory cannot turn an unrelated change red.

CI

Measured

  • npm run audit:bundled on this branch: "npm audit: 36 finding(s) in the installed tree; 19 package(s) bundled, 0 of them with a finding.", exit 0. The plan's Appendix B now records 36, which it last counted as 35 on 2026-10-06.
  • scripts/__tests__/audit-bundled.contract.test.js, 4 tests, covering:
    • a finding on a bundled package at the bundled version is kept;
    • a vulnerable copy at another version, and an unbundled package, are not;
    • npm's severity order.
  • The workflows parse. ci.yml has 7 jobs, and cancel-in-progress reads ${{ github.event_name == 'pull_request' }}.
  • Local gates on HEAD, all exit 0. That is all 22 gates plus the manifest contract, on Node 24.14.0.

🤖 Generated with Claude Code

…ed packages, and CI that keeps every master run

Security and CI items of the 2026-10-08 audit:
- README: "Security model" says what a meta can do, each claim checked: a Text node renders the HTML
  of a dangerouslySetInnerHTML it is given, no URL scheme is checked, the built-in fetch action with
  mapArgs calls the page's fetch with the meta's URL, a data value is text unless a meta binds it to
  an attribute, and upload checks run in the browser only. SECURITY.md says how to report, and what
  is in scope.
- npm run audit:bundled (scripts/audit-bundled.js) keeps the npm audit findings on the packages
  dist/sbom.cdx.json lists, at the version bundled. dependencies is empty, so npm audit --omit=dev
  audits nothing; a full npm audit reports the development tree. On 2026-10-09: 36 findings in the
  tree, 0 in the 19 bundled packages. .github/workflows/audit-bundled.yml runs it every Monday.
- ci.yml cancels a superseded run on pull requests only. On master it cancelled the run before it:
  four PRs merged within a minute left three master commits with no finished run.
- Every action is pinned to the commit its major tag pointed to, with the release in a comment
  Dependabot updates.
@AlexSamBY
AlexSamBY merged commit 29b9a72 into master Oct 12, 2026
7 checks passed
@AlexSamBY
AlexSamBY deleted the security-and-ci branch October 12, 2026 08:29
AlexSamBY added a commit that referenced this pull request Oct 13, 2026
## What

Each push to master gets a CI run of its own. A push to master now has a
concurrency group named by its commit (`github.sha`). A pull request's
group is still its ref, and its newer push still cancels the run before
it.

## Why #205 was not enough

#205 stopped a push to master from cancelling the run in progress:
`cancel-in-progress` is on for pull requests only. But a group still
holds at most one running run and one pending run. GitHub cancels the
pending one when another is queued, whatever `cancel-in-progress` says.
From [GitHub's
documentation](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency):
"any existing `pending` job or workflow in the same concurrency group
will be canceled".

It happened on 2026-10-12, when #214, #215 and #216 merged within two
minutes:

| commit | its run was created | what happened to it |
|---|---|---|
| 1b16e4d (#214) | 16:12:35 | ran until 16:18:20, success |
| 5cb6bf6 (#215) | 16:13:29 | pending behind it; cancelled at 16:14:02
with no job started |
| 1710577 (#216) | 16:14:01 | success |

5cb6bf6 is the case #205 set out to prevent: a master commit with no
finished run.

## The change

- **`.github/workflows/ci.yml`:** the group is `ci-${{ github.workflow
}}-${{ github.event_name == 'push' && github.sha || github.ref }}`.
Master's runs no longer share a group, so none waits and none is
cancelled; merges in quick succession run in parallel. The comment says
why one group for the branch was not enough.
- **Why not `queue: max`:** it keeps up to 100 pending runs per group,
but GitHub's documentation says it cannot be combined with
`cancel-in-progress: true`, which pull requests use.
- **The changelog:** its entry said "CI cancels a run only when a newer
push to the same pull request supersedes it". It now says that each
master commit gets a run of its own.

The same fix for release/v0.34's `ci-0.34.yml` is #218.

## Verified

- The workflow parses (js-yaml).
- For a push, the group comes out as `ci-CI-<sha>`; for a pull request,
as `ci-CI-refs/pull/<n>/merge`. `&&` and `||` return one of their
operands, the common way to pick a value in a workflow expression. This
PR's own run evaluates the expression, so a syntax error would have kept
it from starting.
- This PR's own run uses the pull-request group, which is unchanged. The
push groups show on the next merges in quick succession: each commit
should get a finished run.

## Gates

All 22 gates and the manifest contract exit 0 on HEAD, on Node 24.14.0.

- The suite has 2875 tests. All pass on React 18 and 19.
- On React 16.14 and 17, 2871 pass and #207's 4 transition tests are
skipped, as before.
- In the browser, the demo's 62 tests pass in Chromium on React 18 and
19, and so do the packed tarball's on React 16.14 to 19.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.

1 participant