Skip to content

docs(release): add SECURITY.md and base image security runbook - #1572

Merged
thepagent merged 2 commits into
openabdev:mainfrom
antigenius0910:chore/issue-1567-security-md-runbook
Oct 23, 2026
Merged

thepagent merged 2 commits into
openabdev:mainfrom
antigenius0910:chore/issue-1567-security-md-runbook

Conversation

@antigenius0910

@antigenius0910 antigenius0910 commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

What problem does this solve?

The repo has no SECURITY.md, so there is no stated support policy and no private reporting path. RELEASING.md has no steps for a base-image security release. It also no longer matches the release workflows. It names build.yml, which does not exist, lists the deprecated per-agent image repositories, and shows a manual re-run command that builds main HEAD and tags the result as the requested version. #1567 asked for SECURITY.md and the runbook in one PR, together with the fixes for these outdated references.

Part of #1567 (step 3, third PR).

Merge gates.

  1. A maintainer enables private vulnerability reporting (Settings > Code security). SECURITY.md points reporters there, and the setting is currently off.
  2. Merge after chore(docker): pin Node.js base image via NODE22_VERSION/NODE24_VERSION #1570 and chore(ci): add daily node-security-check workflow #1571. The runbook documents their ARG pins, smoke tests, and node-security-check.yml.

Discord Discussion URL: https://discord.com/channels/1491295327620169908/1491365158868619404/1562955032012398712

Review Contract

Goal

A maintainer handling a Node.js (or other upstream) security release can follow RELEASING.md from detection to release note without guessing. A user or reporter can read SECURITY.md to learn which versions get fixes and how to report a vulnerability privately.

Non-goals

Accepted Residual Risks

  • SECURITY.md tells reporters to use Report a vulnerability on the Security tab. gh api repos/openabdev/openab/private-vulnerability-reporting returns {"enabled":false}, so that button does not exist until a maintainer enables it under Settings > Code security. If you prefer another channel, tell me in review and I will change the text.
  • The runbook references node-security-check.yml and the NODE22_VERSION/NODE24_VERSION ARGs from the companion PRs. Until those merge, the references point at files that do not exist yet.
  • The new step headings keep the file's existing ##### Step N — ... style.

Acceptance Criteria

  • Every workflow name in RELEASING.md exists in .github/workflows/, except node-security-check.yml until its PR merges.
  • Every relative link and anchor resolves (SECURITY.md, docs/image-tags.md, #base-image-security-release, #release-flow-tag-driven).
  • Each changed statement about the release flow matches build-operator.yml, release-pr.yml, build-binaries.yml, or build-oabctl.yml. The Validation section lists the evidence for each one.
  • SECURITY.md states the policy from RFC: rebuild agent images for Node.js security releases (Node 22.23.4 / 24.22.1) #1567. Only the latest minor line is supported, and fixes are not backported.

Follow-ups

  • Enable private vulnerability reporting, or replace the reporting section with the channel the maintainers prefer.
  • Add a node-security-check.yml row to the workflow table after its PR merges.
  • docs/image-tags.md omits devin from its agent list. This PR leaves that file unchanged.

At a Glance

N/A. Documentation only.

Prior Art & Industry Research

Not applicable. This is a documentation and policy change.

Proposed Solution

  • SECURITY.md (new). It covers four topics.

    • Supported versions. Only the latest minor line, which includes its stable release and the betas toward the next release, gets fixes. Fixes are not backported.
    • Private reporting through GitHub.
    • Upstream CVEs (Node.js base image, agent CLIs, Debian packages). They ship as a new beta and then a stable promote. Existing version tags are never overwritten, the floating tags move, nightly has no guarantee, and the per-agent repositories receive no updates and will be deleted.
    • A pointer to node-security-check.yml and the runbook.
  • RELEASING.md, new "Base Image Security Release" runbook. It has five steps.

    1. Detection.
    2. Bump the ARG in every Dockerfile that declares it.
    3. Cut the next beta and verify node --version for every Node agent. The list comes from Dockerfile.unified. If verification fails, cut the next beta. Never re-push an existing tag.
    4. Promote, then verify again on stable-*.
    5. Prepend the release note to the openab-<version> GitHub Release. A <!-- security-note --> marker keeps a re-run from adding the note twice, and the install instructions that release.yml appends are kept.

    An English release-note template follows. It holds only the Security section. A release that also carries breaking changes adds its own migration section.

  • RELEASING.md, outdated references.

    • build.yml becomes build-operator.yml everywhere.
    • Manual re-runs gain --ref <tag>.
    • The Step 2 and Step 3 diagrams use the real job names and the per-agent tags.
    • The Image Variants section uses the unified openab:<version>-<agent> scheme, has no latest, and links docs/image-tags.md.
    • The workflow table gains build-binaries.yml and build-oabctl.yml.
    • The Version 同步 section now says that Cargo.toml carries only the stable version.
    • The Version Scheme section notes that auto bump on a beta increments the beta number.

Why this approach?

The runbook sits next to the release flow it extends, and its prose follows the file's existing 繁中 + English style. The release-note template is in English because GitHub release notes are. SECURITY.md states the policy as a rule with one example, not as a version table that would go stale with every release.

Alternatives Considered

  • A separate docs/security-releases.md. Rejected because the steps depend on the release flow, so they belong in RELEASING.md where maintainers already look.
  • Listing supported versions in a table. Rejected because the table would need an edit for every minor release.

Validation

  • The --ref fix rests on two facts. gh workflow run --help documents --ref as the branch or tag that contains the workflow version to run. build-operator.yml checks out with no ref and tags ${GITHUB_SHA:0:7}. So -f tag=v0.7.0-beta.1 alone builds main HEAD and labels it 0.7.0-beta.1.
  • The tag names are copied from the imagetools create calls in build-operator.yml. merge-manifests creates ${VERSION}-${AGENT}, beta-${AGENT}, and ${SHORT_SHA}-${AGENT}. promote-stable creates ${CHART_VERSION}-${AGENT}, ${MAJOR_MINOR}-${AGENT}, and stable-${AGENT}.
  • The workflow table follows the triggers. build-binaries.yml runs on push: tags: v*. build-oabctl.yml runs on Chart.yaml pushes to main. Both upload to openab-<version> (see the openab-0.10.0-beta.5 assets).
  • The Cargo.toml statement follows release-pr.yml, which writes version = "${STABLE}".
  • A script checked relative links, anchors, table column counts, fence balance (including the 4-backtick template fence), and that every *.yml name exists. Everything passed except node-security-check.yml, which comes from the companion PR. git diff --check is clean.

🤖 Generated with Claude Code

Add SECURITY.md with the support policy decided in openabdev#1567: only the
latest minor line gets fixes, with no backports. It covers private
vulnerability reporting and how upstream CVEs in the Node.js base
image, agent CLIs, and Debian packages ship as new releases.

Add a "Base Image Security Release" runbook to RELEASING.md with a
release-note template that has separate sections for breaking-change
migration and the deprecated per-agent repositories.

Fix outdated references in RELEASING.md. The release build is
build-operator.yml, not build.yml. Images use the unified
openab:<version>-<agent> tags. Cargo.toml carries only the stable
version. Manual re-runs need --ref <tag>; without it the workflow
builds main HEAD and tags the result as the requested version.

Refs openabdev#1567

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chaodu-agent

This comment has been minimized.

- Remove the no_cache rebuild step. It re-pushed an existing version
  tag, which contradicts SECURITY.md. A failed verification now leads
  to the next beta.
- The bump step says each Dockerfile declares only its own Node major
  ARG and that the smoke tests read the expected value from the
  Dockerfiles, so a bump does not edit any workflow.
- Make the release-note step idempotent with a <!-- security-note -->
  marker, and use VERSION=0.10.0 as the example.
- Reduce the reusable template to the Security section. Breaking-change
  migration and the per-agent repository notice belong in the specific
  release note.
- Derive the Node agent list from Dockerfile.unified instead of keeping
  two hand-written lists.

Refs openabdev#1567

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@antigenius0910

Copy link
Copy Markdown
Contributor Author

Thanks for the review. 5742a5d addresses the text findings.

Finding Status
F1 private reporting disabled Needs a maintainer. Listed as merge gate 1 in the body: enable private vulnerability reporting (Settings > Code security) before merge. I did not add an email address because I do not know one the maintainers would want published. If you prefer a different channel, tell me and I will change SECURITY.md.
F2 merge order Merge gate 2 in the body: #1570, then #1571, then #1572.
F3 Step 3.3 vs immutability Step 3.3 is removed. A failed verification now leads to the next beta, and existing version tags are never re-pushed.
F4 smoke-test pins Fixed in #1570 (5ae1753): validate-packaged-pins reads the pins from the Dockerfiles. Step 2 now says that each Dockerfile declares only its own major and that a bump edits no workflow.
F5 Step 5 idempotency The template starts with <!-- security-note -->. The command prepends the note only when the marker is absent and keeps a blank line before the existing body. The example is VERSION=0.10.0. Tested against a fake gh: the second run is a no-op.
F6 one-off template content The template holds only the Security section. The migration section and the per-agent repository notice belong in the specific release note, such as 0.10.0.
F7 hard-coded agent list The verify loop derives NODE_AGENTS from Dockerfile.unified, and the template uses a placeholder.

@chaodu-agent

Copy link
Copy Markdown
Collaborator

Note

LGTM ✅ - All ten round-1 findings are resolved. Private vulnerability reporting is now enabled, the runbook no longer overwrites tags, and every release-flow statement I re-checked matches the workflows. One cross-PR dependency is noted below. It is tracked as F1 on #1570, not as a blocker here.

What This PR Does

The repo had no SECURITY.md, and RELEASING.md described a release flow that no longer exists: build.yml, the per-agent image repositories, and a manual re-run that builds main under a release tag. This PR adds a security policy, adds a five-step "Base Image Security Release" runbook, and fixes the outdated references (#1567, step 3).

How It Works

  • SECURITY.md: only the latest minor line is supported, and fixes are not backported. Reports go through GitHub private vulnerability reporting. For upstream CVEs, the fix ships as a new beta and is then promoted. Version tags are never overwritten. The deprecated per-agent repositories get no updates.
  • Runbook: detection (node-security-check.yml), the ARG bump, the next beta with a verify loop that derives the Node.js agent list from Dockerfile.unified, promote and re-verify, then an idempotent release-note prepend guarded by a <!-- security-note --> marker.
  • Reference fixes: build-operator.yml, --ref <tag> for manual re-runs, real job names and tags, build-binaries.yml and build-oabctl.yml in the workflow table, and accurate version-sync and beta auto-bump statements.

Findings

# Severity Finding Location
1 🟢 Round-1 F1 resolved: private vulnerability reporting is now enabled, so the reporting path in SECURITY.md works SECURITY.md:11-19
2 🟢 Round-1 F3/F5/F6/F7 resolved: no tag overwrite, idempotent release-note step, template limited to security content, agent list derived from Dockerfile.unified RELEASING.md:120-205
3 🟢 Release-flow claims re-verified against the workflows at this head RELEASING.md
4 🟢 The derived NODE_AGENTS loop yields exactly the eight Node.js agents when run on the #1570 Dockerfile.unified RELEASING.md:150
Finding Details

🟢 F1-F2: Round-1 resolution

Round 1 Status at 5742a5d
🔴 F1: private reporting channel disabled Resolved. GET /repos/openabdev/openab/private-vulnerability-reporting now returns {"enabled":true}.
🟡 F2: merge order with #1570 / #1571 #1571 is merged. #1570 is still open, so this PR's references to the NODE2x_VERSION ARGs and the smoke-test pin check become true when #1570 lands. Merging this PR before #1570 is fine for a document. Merging it after is cleaner.
🟡 F3: Step 3.3 overwrote an existing tag Resolved. A failed verification now means cutting the next beta and never re-pushing an existing version tag.
🟡 F4: Step 2 omitted the smoke-test pins Resolved in this PR's text: Step 2.3 says a bump changes only Dockerfile*. That is true only once #1570 F1 is fixed. Today #1570 still hard-codes the two pin lines in validate-packaged-pins, and a Dockerfile-only bump fails (reproduced). The fix belongs in #1570. No change is needed here if #1570 adopts it.
🟡 F5: Step 5 not idempotent Resolved with the <!-- security-note --> marker check, an explicit separator newline, and the VERSION=0.10.0 example.
🟡 F6: one-off content in the template Resolved. The template is Security-only, and migration notes are added per release.
🟡 F7: hard-coded agent list Resolved. NODE_AGENTS is derived from Dockerfile.unified.

🟢 F3: Claims re-verified

  • build-operator.yml runs on v* tags and workflow_dispatch. The AGENTS env lists 15 agents. Checkout has no ref:, so --ref <tag> is required for a correct manual re-run.
  • build-binaries.yml runs on push: tags: v*. build-oabctl.yml runs on charts/openab/Chart.yaml pushes to main.
  • release-pr.yml writes the full version to Chart.yaml and the stable version to Cargo.toml. On a beta, auto bump increments the beta number.
  • The anchors #release-flow-tag-driven and #base-image-security-release exist, and SECURITY.md and docs/image-tags.md resolve.

🟢 F4: Verify loop

The awk in Step 3.2, run with mawk 1.3.4 on the Dockerfile.unified from #1570, prints claude codex kimi copilot gemini mimocode opencode pi. That matches the eight Node.js agents in AGENTS.

Non-blocking Notes
Baseline Check
  • PR opened: 2026-10-23
  • Head SHA: 5742a5d2b5db9170e3871e1325ceb65793986be8. Base: main (not stacked). Merge-base: 8b6bf293d8de. Diff: 2 files, +151/-24.
  • Main already has: node-security-check.yml (chore(ci): add daily node-security-check workflow #1571), no SECURITY.md, and a RELEASING.md that references build.yml and the per-agent repositories.
  • Net-new value: the security policy, the security release runbook, and accurate release-flow documentation.
What's Good (🟢)
  • Round-1 feedback was addressed point by point, including the idempotency marker and the derived agent list.
  • The --ref fix corrects a real mis-tagging hazard in the manual re-run path.
  • SECURITY.md states the policy as a rule rather than as a version table that would go stale.

5. Three Reasons We Might Not Need This PR

  1. Docs can lag the automation - chore(ci): add daily node-security-check workflow #1571 already opens an issue with next steps, so a separate runbook duplicates it. Rebuttal: the issue text points to this runbook, and the beta, promote and release-note steps are not automated anywhere.
  2. Mixed scope - The reference fixes could land separately from the security policy. Rebuttal: RFC: rebuild agent images for Node.js security releases (Node 22.23.4 / 24.22.1) #1567 asked for them in one PR, and they are small.
  3. Policy before process maturity - A support policy commits maintainers to a cadence they have not tested yet. Rebuttal: the policy, which supports only the latest minor line with no backports, is the lightest one possible and matches the decision already made in RFC: rebuild agent images for Node.js security releases (Node 22.23.4 / 24.22.1) #1567.

antigenius0910 added a commit to antigenius0910/openab that referenced this pull request Oct 23, 2026
The derived loop in validate-packaged-pins already enforces that every
Dockerfile built FROM node:${NODE<n>_VERSION} carries the same pin as
Dockerfile.unified. The two literal assert_line lines added only the
version itself, which forced a workflow edit on every Node bump. That
contradicts the Dockerfile-only bump described in RELEASING.md (openabdev#1572)
and in the node-security-check issue text (openabdev#1571).

Verified with the extracted step script: the tree as committed passes,
bumping all nine NODE22 Dockerfiles to 22.23.5 passes, and bumping only
Dockerfile.pi fails.

Refs openabdev#1567

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thepagent pushed a commit that referenced this pull request Oct 23, 2026
…ON (#1570)

* chore(docker): pin Node.js base image via NODE22_VERSION/NODE24_VERSION

Replace the floating node:22-trixie-slim and node:24-trixie-slim bases
with global build ARGs that hold the full Node.js version (22.23.4 and
24.22.1, the October 2026 security releases). The pin makes the Node.js
version shipped in each image visible in the repo and changes it only
through a reviewed bump, as decided in #1567.

The smoke tests now check that node --version in each Node-based image
matches the ARG default. A new assert_line keeps the ARG value
identical across every Dockerfile that declares it.

Nightly keeps floating as the Node.js canary. It passes only the major
version (NODE22_VERSION=22, NODE24_VERSION=24), so its base resolves to
the floating tags, and it does not run the version assertion.

Also fix docs/opencode.md, which still named node:22-bookworm-slim.

Refs #1567

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(review): derive Node.js pin checks from Dockerfiles, record nightly Node version

Address round-1 review findings on #1570:

- F1: Replace the hardcoded 8-agent allowlist and kimi case mapping in both
  smoke-test workflows with .github/scripts/node_base_arg.sh, which resolves
  the NODE<n>_VERSION ARG from the (target) stage's base image, following
  stage inheritance. Non-Node images skip; an unpinned FROM node: fails.
  validate-packaged-pins now also requires every Dockerfile built FROM
  node:${NODE<n>_VERSION} to carry the same pin as Dockerfile.unified, so a
  new Node-based Dockerfile cannot fail open or ship a different version.
- F2: Nightly records the Node.js version that actually shipped for each
  Node-based variant/arch in the job summary (warn-only; the float is
  intentional).
- F3: Generalize the Dockerfile.opencode base-image comment instead of
  listing a partial set of sibling Dockerfiles.

* chore(docker): drop hard-coded Node pin assertions

The derived loop in validate-packaged-pins already enforces that every
Dockerfile built FROM node:${NODE<n>_VERSION} carries the same pin as
Dockerfile.unified. The two literal assert_line lines added only the
version itself, which forced a workflow edit on every Node bump. That
contradicts the Dockerfile-only bump described in RELEASING.md (#1572)
and in the node-security-check issue text (#1571).

Verified with the extracted step script: the tree as committed passes,
bumping all nine NODE22 Dockerfiles to 22.23.5 passes, and bumping only
Dockerfile.pi fails.

Refs #1567

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(docker): make node_base_arg.sh the only Node FROM parser

validate-packaged-pins found Node-based Dockerfiles and unpinned bases
with its own ^FROM node: regex. node_base_arg.sh already skips
--platform and matches FROM in any case, and the parity gate's regex
did neither. An unpinned FROM --platform=... node:<tag> or
from node:<tag> in Dockerfile.package, which has no PR-time smoke
test, therefore passed silently.

node_base_arg.sh --all now checks every FROM line in a file. It prints
each NODE<n>_VERSION in use and fails on any unpinned node: base. The
parity gate runs it on every tracked Dockerfile, including those in
subdirectories, so the gate and the runtime check share one parser.
The file list is assigned before the loop, so a git failure stops the
step instead of skipping every check. Inline fixtures pin the two
reported forms and a pinned --platform base as regression cases.

Verified with the extracted step in ubuntu:24.04:
- passes as committed and after a full NODE22 bump
- fails on a partial bump, a missing Dockerfile.unified pin, an
  unpinned --platform or lowercase from base in Dockerfile.package,
  an unpinned base in openshell/Dockerfile, and outside a git repo

Refs #1567

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(review): make node_base_arg.sh the single fail-closed Node base parser

Address round-3 F1 on #1570: the parity gate kept its own `^FROM node:`
regex, so `FROM --platform=... node:<tag>`, lowercase `from node:<tag>`,
digest-only, registry-qualified, or build-arg Node bases passed silently,
and the resolver itself skipped several of those forms.

- node_base_arg.sh classifies every FROM in the file. Any reference to a
  Node.js image (node:, node@, library/node, docker.io/library/node) that
  is not node:${NODE<n>_VERSION}-<suffix> fails, as does FROM ${VAR} unless
  VAR is allowlisted (BUILDER_IMAGE). A missing tag suffix gets its own
  message. Stage names are matched case-insensitively, as Docker does.
- New --check mode prints "<file> NODE<n>_VERSION" per pin used.
  validate-packaged-pins now uses it over every tracked Dockerfile
  (including nested ones) instead of a second grep parser, fails if no
  Node-based Dockerfile is found, requires a full x.y.z pin in
  Dockerfile.unified, and names the file and expected pin on mismatch.
- Add test_node_base_arg.sh (21 fixtures plus the current repository
  targets) and run it in validate-packaged-pins.

* fix(review): recognize any registry Node base and continued FROM lines

Address round-4 findings on #1570: node_base_arg.sh --check still passed
FROM index.docker.io/library/node:22, FROM mirror.gcr.io/library/node:22,
and a FROM split across lines with "\" (parsed as an empty image).

- Treat any image whose repository name is "node", under any registry or
  namespace, as a Node.js base that must be node:${NODE<n>_VERSION}-<suffix>.
- Join "\" continuation lines (skipping comment lines) before parsing, as
  Docker does; reject a FROM with no image and the "# escape=" parser
  directive, which would change the continuation character.
- Add 9 fixtures (30 total) covering these forms.

* fix(review): reject unresolvable build args in base images; trigger on nested Dockerfiles

Address round-4 findings on #1570:

- node_base_arg.sh: any build arg other than ${NODE<n>_VERSION} inside a
  base image reference (node${TAG}-slim, ${IMG}:22, ${REG}/node:...) now
  fails, since it cannot be resolved statically and could hide an unpinned
  Node.js base. Document the RUN-heredoc limitation (false error only).
- docker-smoke-test.yml: also trigger on '**/Dockerfile*', so a PR that only
  changes a nested Dockerfile still runs the validate-packaged-pins check
  that scans every tracked Dockerfile.
- Add 4 fixtures (34 total).

* fix(review): fail repository-target tests when node_base_arg.sh exits non-zero

The repository-target checks compared command-substitution output only, so
a non-Node target whose lookup crashed (empty output, non-zero exit) passed
as "not Node-based". Check the exit status explicitly.

* fix(review): check the declared default of allowlisted base-image build args

FROM ${BUILDER_IMAGE} was trusted by name, so ARG BUILDER_IMAGE=node:22
would pass. Classify the declared ARG default as well (CI overrides are not
statically known), and guard against a self-referencing default. Add
fixtures for the default, parameter-expansion and bare-composite forms
(39 total).

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: chaodu-agent <chaodu-agent@users.noreply.github.com>
@thepagent
thepagent merged commit 3568d59 into openabdev:main Oct 23, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants