From 5a7ca1bfee6604087e0b895340c2a21289d4866d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Og=C3=BCn=20Keskin?= Date: Mon, 1 Jun 2026 08:03:11 +0300 Subject: [PATCH] Add GitHub Actions Node 24 readiness check --- CHANGELOG.md | 6 ++++++ README.md | 10 ++++++---- contextforge-actions-audit.md | 4 ++-- contextforge-publish-readiness.md | 4 ++-- docs/actions-audit.md | 19 +++++++++++++++++++ docs/adoption.md | 2 +- docs/artifacts.md | 2 +- docs/github-action.md | 6 +++--- docs/launch-snapshot.md | 2 +- docs/release-checklist.md | 2 +- docs/research/adjacent-tools.md | 6 ++++++ docs/use-cases.md | 2 +- llms-full.txt | 2 +- llms.txt | 4 ++-- package.json | 2 +- src/analyzers/githubActions.ts | 25 +++++++++++++++++++++++-- src/cli.ts | 2 +- src/init/githubAction.ts | 2 +- src/report/adoptionBrief.ts | 2 +- src/report/artifactMap.ts | 2 +- src/report/launchSnapshot.ts | 2 +- tests/actionsAudit.test.ts | 29 +++++++++++++++++++++++++++++ tests/cli.test.ts | 2 +- tests/init.test.ts | 2 +- 24 files changed, 112 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2327fa..77f4ae9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.71.0 - 2026-06-01 + +- Add `actions-missing-node24-opt-in` to `contextforge actions-audit` so repositories using JavaScript actions can prepare for GitHub's Node 24 hosted-runner default. +- Keep ContextForge dogfood workflows passing by recognizing their existing `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true` opt-in. +- Document the known GitHub runner annotation behavior where Node 20 action metadata can still produce an informational warning even when the Node 24 runtime override is active. + ## 0.70.0 - 2026-06-01 - Add Claude Code settings, agentic workflow, and GitHub Actions hardening checks to `contextforge doctor`. diff --git a/README.md b/README.md index bb60106..69b0989 100644 --- a/README.md +++ b/README.md @@ -359,7 +359,7 @@ contextforge pack --task "review auth regression" --budget 20000 --sessions --ou Or use the GitHub Action before npm publishing is complete: ```yaml -- uses: grnbtqdbyx-create/contextforge@v0.70.0 +- uses: grnbtqdbyx-create/contextforge@v0.71.0 with: min-context-score: 60 min-cache-score: 60 @@ -448,7 +448,7 @@ and tuned for Codex/Claude repository work. | MCP findings should show up in GitHub Security. | `mcp-audit --sarif` writes `contextforge-mcp.sarif` with `mcp-exposure/*` rule ids for Code Scanning. | | Claude Code settings can over-trust a repo. | `claude-audit` checks shared `.claude/settings.json` permissions, hooks, bypass modes, and sensitive-file denies. | | Agentic GitHub workflows can ingest attacker-controlled text. | `workflow-audit` checks whether issue, PR, review, comment, title, workflow input, or branch/ref text flows into agentic jobs with write permissions or secrets. | -| Agent-authored CI can weaken the release path. | `actions-audit` checks workflow SHA pins, token permissions, `pull_request_target`, pwn-request checkout, and direct script interpolation. | +| Agent-authored CI can weaken the release path. | `actions-audit` checks workflow SHA pins, token permissions, Node 24 runtime opt-in, `pull_request_target`, pwn-request checkout, and direct script interpolation. | | Claude Code subagents and custom slash commands can hide powerful project prompts. | `security-audit`, context health, and context packs include `.claude/agents/**/*.md` and `.claude/commands/**/*.md`. | | Copilot hooks can run shell commands during agent workflows. | `security-audit` scans `.github/hooks/*.json` and committed `.github/copilot/settings*.json` for unsafe shell, exfiltration, hidden directives, and permission weakening. | | VS Code workspace settings can carry Copilot instructions. | `security-audit` scans `.vscode/settings.json` and committed `*.code-workspace` files for risky Copilot review, commit, and PR instruction text. | @@ -498,7 +498,7 @@ contextforge cost-estimate [--demo] [--json] [--summary contextforge-cost-estima contextforge review-kit [--demo] [--base main] [--output contextforge-review-kit.md] contextforge artifact-map [--output docs/artifacts.md] contextforge publish-readiness [--json] [--summary contextforge-publish-readiness.md] -contextforge init [--all] [--github-action] [--pr-comment-workflow] [--agents-md] [--claude-md] [--copilot-instructions] [--project-name "My App"] [--action-ref grnbtqdbyx-create/contextforge@v0.70.0] [--force] +contextforge init [--all] [--github-action] [--pr-comment-workflow] [--agents-md] [--claude-md] [--copilot-instructions] [--project-name "My App"] [--action-ref grnbtqdbyx-create/contextforge@v0.71.0] [--force] ``` Local session scans are bounded by default. Use `--max-session-files` and @@ -583,7 +583,7 @@ See [docs/research/adjacent-tools.md](docs/research/adjacent-tools.md). ## Current Status -ContextForge v0.70.0 is a public MVP CLI with: +ContextForge v0.71.0 is a public MVP CLI with: - Claude Code and Codex JSONL fixture scanners - bounded local session scanning fallbacks @@ -623,6 +623,7 @@ ContextForge v0.70.0 is a public MVP CLI with: - generated `contextforge publish-readiness` checks for npm Trusted Publishing preparation and GitHub tarball attestation setup - npm provenance metadata checks for repository, homepage, and issue tracker links - GitHub workflow Node 24 JavaScript action runtime opt-in for dogfood and generated workflows +- `actions-audit` checks for missing GitHub Actions Node 24 JavaScript runtime opt-ins before hosted-runner defaults change - generated `contextforge launch-kit` build-in-public launch posts - generated `contextforge compare` adjacent-tool positioning guides - `Public proof surfaces` doctor check for OSS trust/readiness files @@ -728,6 +729,7 @@ ContextForge v0.70.0 is a public MVP CLI with: - **v0.68.0:** workflow audits expand attacker-controlled coverage to titles and branch/ref text. - **v0.69.0:** GitHub Actions audits catch mutable action refs, pwn-request checkout, missing permissions, and direct script interpolation. - **v0.70.0:** doctor, proof-pack, and scorecard reports surface Claude settings, agentic workflow, and GitHub Actions hardening evidence in one readiness path. +- **v0.71.0:** GitHub Actions audits catch missing Node 24 JavaScript action runtime opt-ins and document the known runner annotation behavior. - **Next:** first approved npm publish and external launch outreach. Release preparation lives in [docs/release-checklist.md](docs/release-checklist.md). diff --git a/contextforge-actions-audit.md b/contextforge-actions-audit.md index d7b56df..1527039 100644 --- a/contextforge-actions-audit.md +++ b/contextforge-actions-audit.md @@ -8,8 +8,8 @@ Workflow files: `.github/workflows/ci.yml`, `.github/workflows/contextforge-audi | Type | Severity | File | Message | Suggestion | | --- | --- | --- | --- | --- | -| none | low | | No GitHub Actions hardening findings. | Keep workflows pinned, least-privilege, and isolated from untrusted PR code. | +| none | low | | No GitHub Actions hardening findings. | Keep workflows pinned, least-privilege, opted into Node 24, and isolated from untrusted PR code. | ## Next Actions -- Keep GitHub Actions workflows pinned to full SHAs and least-privilege by default. +- Keep GitHub Actions workflows pinned to full SHAs, least-privilege, and opted into the Node 24 JavaScript action runtime by default. diff --git a/contextforge-publish-readiness.md b/contextforge-publish-readiness.md index 9a8f7d4..a821f90 100644 --- a/contextforge-publish-readiness.md +++ b/contextforge-publish-readiness.md @@ -2,11 +2,11 @@ Status: **warn** -Package: `contextforge@0.70.0` +Package: `contextforge@0.71.0` | Check | Status | Detail | | --- | --- | --- | -| Package metadata | pass | contextforge@0.70.0 is public-package ready with bin dist/cli.js | +| Package metadata | pass | contextforge@0.71.0 is public-package ready with bin dist/cli.js | | Package provenance metadata | pass | repository, homepage, and issue tracker point at grnbtqdbyx-create/contextforge for npm provenance readers | | Trusted publishing workflow | pass | npm Trusted Publishing uses GitHub OIDC, manual dispatch, dry-run default, and environment approval | | Release artifact attestation | pass | GitHub artifact attestation covers the packed npm tarball before the same tarball is published | diff --git a/docs/actions-audit.md b/docs/actions-audit.md index c189f2b..93b74b1 100644 --- a/docs/actions-audit.md +++ b/docs/actions-audit.md @@ -21,6 +21,8 @@ The audit flags: - `pull_request_target` workflows - `pull_request_target` workflows that checkout attacker-controlled PR head code - untrusted GitHub contexts interpolated directly into `run:` shell steps +- workflows that use JavaScript actions without `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24` + while GitHub Actions transitions hosted runners from Node 20 to Node 24 Use the Markdown summary in PRs, launch issues, README proof surfaces, and ContextForge Audit artifacts when reviewers need a fast answer to: "Can this @@ -34,3 +36,20 @@ complete CI/CD threat model. It focuses on high-signal GitHub Actions footguns that are especially risky in agent-heavy repositories: mutable action refs, overbroad tokens, `pull_request_target`, direct script interpolation, and privileged release automation. + +## Node 24 Runtime Note + +GitHub began warning maintainers about JavaScript actions that still declare a +Node 20 runtime before the hosted-runner default moves to Node 24. Set a +workflow-level environment variable to opt in early: + +```yaml +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true +``` + +Some runner versions may still show an annotation that Node 20 actions are +being forced to run on Node 24. In that case the opt-in is working; the warning +is about the action metadata GitHub scanned before applying the runtime +override. ContextForge flags missing opt-ins so repositories can prepare before +the default changes. diff --git a/docs/adoption.md b/docs/adoption.md index 8cd1a9a..cc9a6fa 100644 --- a/docs/adoption.md +++ b/docs/adoption.md @@ -36,7 +36,7 @@ contextforge artifact-map --output docs/artifacts.md - Open `contextforge-mcp-audit.md` when the repo has MCP config files or agent tool setup; upload `contextforge-mcp.sarif` when GitHub Code Scanning should track those findings. - Open `contextforge-claude-audit.md` when the repo commits Claude Code project settings, hooks, or permissions. - Open `contextforge-workflow-audit.md` when GitHub workflows pass issue, PR, review, comment, title, workflow input, or branch/ref text into agent commands. -- Open `contextforge-actions-audit.md` when GitHub Actions workflows need SHA pinning, least-privilege permissions, pwn-request, or script-injection review. +- Open `contextforge-actions-audit.md` when GitHub Actions workflows need SHA pinning, least-privilege permissions, Node 24 runtime opt-in, pwn-request, or script-injection review. - Open `contextforge-trace-audit.md` when you want to see whether a Codex or Claude trace wasted context on repeated tools or bulky outputs. - Open `contextforge-cost-estimate.md` when you want to turn observed tokens into a configurable spend estimate without trusting stale hardcoded prices. - Open `docs/artifacts.md` when CI uploaded many files and you need the right next proof artifact. diff --git a/docs/artifacts.md b/docs/artifacts.md index 7578a41..db16bbb 100644 --- a/docs/artifacts.md +++ b/docs/artifacts.md @@ -27,7 +27,7 @@ Use this to decide which ContextForge artifact a maintainer, reviewer, CI bot, C | `contextforge-claude.sarif` | GitHub Code Scanning | you want Claude Code settings findings to appear beside code scanning alerts | `contextforge claude-audit --sarif contextforge-claude.sarif` | | `contextforge-workflow-audit.md` | Security reviewers and agent workflow maintainers | you need to see whether GitHub issue, PR, review, comment, title, workflow input, or branch/ref text flows into privileged AI workflows | `contextforge workflow-audit --summary contextforge-workflow-audit.md` | | `contextforge-workflow.sarif` | GitHub Code Scanning | you want agentic workflow injection findings to appear beside code scanning alerts | `contextforge workflow-audit --sarif contextforge-workflow.sarif` | -| `contextforge-actions-audit.md` | Security reviewers and release maintainers | you need to review GitHub Actions SHA pins, token permissions, pull_request_target risk, and direct script interpolation | `contextforge actions-audit --summary contextforge-actions-audit.md` | +| `contextforge-actions-audit.md` | Security reviewers and release maintainers | you need to review GitHub Actions SHA pins, token permissions, Node 24 runtime opt-in, pull_request_target risk, and direct script interpolation | `contextforge actions-audit --summary contextforge-actions-audit.md` | | `contextforge-actions.sarif` | GitHub Code Scanning | you want GitHub Actions hardening findings to appear beside code scanning alerts | `contextforge actions-audit --sarif contextforge-actions.sarif` | | `contextforge-trace-audit.md` | Codex and Claude operators | you need to review repeated tool calls, bulky tool output, and cache reuse before another long agent session | `contextforge trace-audit --summary contextforge-trace-audit.md` | | `contextforge-cost-estimate.md` | Maintainers and budget reviewers | you need a shareable cost estimate from observed session tokens without hardcoded provider pricing | `contextforge cost-estimate --summary contextforge-cost-estimate.md --input-price-per-mtok 2 --cached-input-price-per-mtok 0.2 --output-price-per-mtok 10` | diff --git a/docs/github-action.md b/docs/github-action.md index 68a1462..7ce4ff0 100644 --- a/docs/github-action.md +++ b/docs/github-action.md @@ -36,7 +36,7 @@ refuses to overwrite existing files by default: ```bash contextforge init --github-action --force -contextforge init --github-action --action-ref grnbtqdbyx-create/contextforge@v0.70.0 +contextforge init --github-action --action-ref grnbtqdbyx-create/contextforge@v0.71.0 ``` `contextforge init --pr-comment-workflow` writes a separate @@ -71,7 +71,7 @@ jobs: - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 with: fetch-depth: 0 - - uses: grnbtqdbyx-create/contextforge@v0.70.0 + - uses: grnbtqdbyx-create/contextforge@v0.71.0 with: min-context-score: 60 min-cache-score: 60 @@ -192,7 +192,7 @@ artifacts show whether GitHub issue, PR, review, comment, title, workflow input, or branch/ref text flows into agentic jobs with write permissions or secrets. The `contextforge-actions-audit.md` and `contextforge-actions.sarif` artifacts show whether GitHub Actions workflows have mutable action refs, missing -permissions, pwn-request checkout, or direct shell interpolation of untrusted +permissions, Node 24 runtime opt-in, pwn-request checkout, or direct shell interpolation of untrusted GitHub context. The `contextforge-trace-audit.md` artifact summarizes repeated tool calls, bulky tool output, tool-output-heavy traces, and cache reuse from available diff --git a/docs/launch-snapshot.md b/docs/launch-snapshot.md index 741189f..961fe2e 100644 --- a/docs/launch-snapshot.md +++ b/docs/launch-snapshot.md @@ -30,7 +30,7 @@ A short, shareable page for people deciding whether this project is worth trying | Are MCP configs risky? | `contextforge-mcp-audit.md` and `contextforge-mcp.sarif` | | Are Claude Code settings risky? | `contextforge-claude-audit.md` and `contextforge-claude.sarif` | | Can GitHub event text reach a privileged agent workflow? | `contextforge-workflow-audit.md` and `contextforge-workflow.sarif` | -| Are GitHub Actions pinned and least-privilege? | `contextforge-actions-audit.md` and `contextforge-actions.sarif` | +| Are GitHub Actions pinned, least-privilege, and Node 24-ready? | `contextforge-actions-audit.md` and `contextforge-actions.sarif` | | Did the last agent session waste context? | `contextforge-trace-audit.md` | | What would a long session cost? | `contextforge-cost-estimate.md` | | Is the first npm publish ready? | `contextforge-publish-readiness.md` | diff --git a/docs/release-checklist.md b/docs/release-checklist.md index 0a5fa2f..6881653 100644 --- a/docs/release-checklist.md +++ b/docs/release-checklist.md @@ -21,7 +21,7 @@ - [x] npm publish workflow packs, attests, uploads, and publishes the same release tarball. - [x] Launch snapshot gives README visitors a why-now, adjacent-category, and proof-first page. - [x] Agentic workflow audit catches untrusted GitHub event text flowing into privileged AI workflows. -- [x] GitHub Actions audit catches mutable action refs, missing permissions, pwn-request checkout, and direct script interpolation. +- [x] GitHub Actions audit catches mutable action refs, missing permissions, Node 24 runtime opt-in gaps, pwn-request checkout, and direct script interpolation. - [x] MCP exposure audit catches committed MCP config secrets, unsafe shell installers, unpinned package launches, auto-approval, broad tool permissions, and symlinked config files. - [x] MCP exposure findings can be exported as SARIF for GitHub Code Scanning. - [x] Claude Code project settings can be audited as Markdown and SARIF artifacts. diff --git a/docs/research/adjacent-tools.md b/docs/research/adjacent-tools.md index 3f0f78d..3c630c3 100644 --- a/docs/research/adjacent-tools.md +++ b/docs/research/adjacent-tools.md @@ -576,3 +576,9 @@ scorecard readers at the matching Markdown/SARIF rerun commands. The product reason is simple: a first-time maintainer, Codex session, or Claude session should not need to remember every specialized audit command before it can tell whether the repository is safe enough for agent-assisted work. +ContextForge v0.71.0 adds `actions-missing-node24-opt-in` because GitHub's +hosted runners are moving JavaScript actions from Node 20 to Node 24, and many +agent-edited repositories now see noisy runtime annotations. The audit keeps the +fix deterministic: set `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true` at workflow +level, then treat any remaining "target Node.js 20 but forced to Node.js 24" +message as a runner metadata annotation rather than a failed hardening state. diff --git a/docs/use-cases.md b/docs/use-cases.md index 2d91ec8..f76ee14 100644 --- a/docs/use-cases.md +++ b/docs/use-cases.md @@ -174,7 +174,7 @@ Success signal: title, workflow input, or branch/ref text flowing into privileged AI workflows. - Release reviewers can open `contextforge-actions-audit.md` or upload `contextforge-actions.sarif` to catch mutable action refs, missing - permissions, pwn-request checkout, and direct script interpolation before + permissions, Node 24 runtime opt-in, pwn-request checkout, and direct script interpolation before agent-authored workflow changes reach `main`. - Agent operators can open `contextforge-trace-audit.md` to see whether the demo trace wasted turns on repeated tools or bulky output before they try local diff --git a/llms-full.txt b/llms-full.txt index 859cd1a..d68b54c 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -155,7 +155,7 @@ cases so maintainers can see what the scanner is expected to catch. - `docs/artifacts.md`: generated artifact catalog and fast paths from `artifact-map` - `contextforge-artifact-map.md`: CI-uploaded artifact catalog from reusable and generated workflows - `contextforge-publish-readiness.md`: npm Trusted Publishing and package provenance readiness summary from `publish-readiness` -- Reusable GitHub Action and generated audit workflows upload `contextforge-proof-pack.md`, `contextforge-scorecard.md`, `contextforge-agent-surface-map.md`, `contextforge-agent-surface-inventory.md`, `contextforge-agent-surface-diff.md`, `contextforge-mcp-audit.md`, `contextforge-mcp.sarif`, `contextforge-claude-audit.md`, `contextforge-claude.sarif`, `contextforge-workflow-audit.md`, `contextforge-workflow.sarif`, `contextforge-actions-audit.md`, `contextforge-actions.sarif`, `contextforge-review-kit.md`, and `contextforge-artifact-map.md` alongside JSON, HTML, SARIF, summary, plan, comment, suggestions, and badge artifacts. Dogfood and generated workflows opt JavaScript actions into Node 24. +- Reusable GitHub Action and generated audit workflows upload `contextforge-proof-pack.md`, `contextforge-scorecard.md`, `contextforge-agent-surface-map.md`, `contextforge-agent-surface-inventory.md`, `contextforge-agent-surface-diff.md`, `contextforge-mcp-audit.md`, `contextforge-mcp.sarif`, `contextforge-claude-audit.md`, `contextforge-claude.sarif`, `contextforge-workflow-audit.md`, `contextforge-workflow.sarif`, `contextforge-actions-audit.md`, `contextforge-actions.sarif`, `contextforge-review-kit.md`, and `contextforge-artifact-map.md` alongside JSON, HTML, SARIF, summary, plan, comment, suggestions, and badge artifacts. Dogfood and generated workflows opt JavaScript actions into Node 24, and `actions-audit` flags missing `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24` opt-ins. - PR-ready comments embed a compact changed agent-surface summary and point reviewers at `contextforge-proof-pack.md`, `contextforge-review-kit.md`, and `contextforge-agent-surface-diff.md` so sticky review discussions can lead to the deeper doctor/audit proof packet, the changed-file review brief, and the changed agent-surface report. - `docs/launch-post.md`: generated build-in-public launch kit from `launch-kit` - `docs/comparison.md`: generated adjacent-tool positioning guide from `compare` diff --git a/llms.txt b/llms.txt index 8fcfb65..40ebdac 100644 --- a/llms.txt +++ b/llms.txt @@ -23,7 +23,7 @@ Codex and Claude can act on. - `contextforge mcp-audit --summary contextforge-mcp-audit.md --sarif contextforge-mcp.sarif`: scan committed MCP configs for hardcoded secrets, unsafe shell installers, unpinned package launches, auto-approval, broad tool permissions, and symlinked config files, with optional GitHub Code Scanning output. - `contextforge claude-audit --summary contextforge-claude-audit.md --sarif contextforge-claude.sarif`: scan committed Claude Code settings for risky default modes, broad Bash permissions, remote shell hooks, wildcard HTTP hooks, and missing sensitive-file denies. - `contextforge workflow-audit --summary contextforge-workflow-audit.md --sarif contextforge-workflow.sarif`: scan GitHub Actions workflows for untrusted issue, PR, review, comment, discussion, workflow input, title, or branch/ref text reaching privileged agentic jobs. -- `contextforge actions-audit --summary contextforge-actions-audit.md --sarif contextforge-actions.sarif`: scan GitHub Actions workflows for SHA pinning gaps, missing permissions, pwn-request checkout, and direct script interpolation. +- `contextforge actions-audit --summary contextforge-actions-audit.md --sarif contextforge-actions.sarif`: scan GitHub Actions workflows for SHA pinning gaps, missing permissions, Node 24 JavaScript runtime opt-in, pwn-request checkout, and direct script interpolation. - `contextforge security-audit --min-security-score 80`: scan repo instructions, Claude Code subagents, custom slash commands, Copilot prompts, hooks, and workspace settings for prompt/context poisoning. - `contextforge trace-audit --demo --summary contextforge-trace-audit.md`: summarize repeated tool calls, bulky tool output, tool-output dominance, and cache reuse from Codex/Claude traces. - `contextforge cost-estimate --demo --summary contextforge-cost-estimate.md --input-price-per-mtok 2 --cached-input-price-per-mtok 0.2 --output-price-per-mtok 10`: estimate session spend with caller-provided prices. @@ -52,7 +52,7 @@ Codex and Claude can act on. - [MCP Audit](docs/mcp-audit.md): Committed MCP config exposure checks and SARIF output for agent tool safety. - [Claude Audit](docs/claude-audit.md): Committed Claude Code settings checks and SARIF output for shared permissions and hooks. - [Agentic Workflow Audit](docs/workflow-audit.md): GitHub Actions checks for untrusted event text reaching privileged AI workflows. -- [GitHub Actions Audit](docs/actions-audit.md): GitHub Actions hardening checks for SHA pinning, permissions, pwn-request, and script injection. +- [GitHub Actions Audit](docs/actions-audit.md): GitHub Actions hardening checks for SHA pinning, permissions, Node 24 runtime opt-in, pwn-request, and script injection. - [Trace Audit](docs/trace-audit.md): Codex/Claude session efficiency checks for repeated tools, bulky outputs, and cache reuse. - [Cost Estimate](docs/cost-estimate.md): Configurable session cost estimates for input, cached input, and output tokens. - [Adoption Brief](docs/adoption.md): First-time evaluator page for maintainers deciding whether to try, star, or wire ContextForge into CI. diff --git a/package.json b/package.json index 908808a..01d5f5e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "contextforge", - "version": "0.70.0", + "version": "0.71.0", "description": "Agent context gate for Codex, Claude Code, GitHub Copilot, MCP, Cursor, Cline, Gemini, and Windsurf repos.", "type": "module", "packageManager": "pnpm@11.2.2", diff --git a/src/analyzers/githubActions.ts b/src/analyzers/githubActions.ts index be79a15..fa9a524 100644 --- a/src/analyzers/githubActions.ts +++ b/src/analyzers/githubActions.ts @@ -24,6 +24,7 @@ const PULL_REQUEST_TARGET_PATTERN = /(^|\n)\s*pull_request_target\s*:/i; const CHECKOUT_PATTERN = /^\s*-\s*uses:\s*actions\/checkout@/im; const PR_HEAD_REF_PATTERN = /github\.event\.pull_request\.head\.(sha|ref)|github\.head_ref/i; const SECRET_PATTERN = /\bsecrets\.[A-Z0-9_]+\b/i; +const NODE24_OPT_IN_PATTERN = /FORCE_JAVASCRIPT_ACTIONS_TO_NODE24\s*:\s*['"]?true['"]?/i; const UNTRUSTED_CONTEXTS = [ 'github.event.issue.title', 'github.event.issue.body', @@ -94,7 +95,7 @@ export function createGithubActionsSummary(audit: GithubActionsAudit): string { (finding) => `| ${escapeTableCell(finding.type)} | ${finding.severity} | ${escapeTableCell(finding.file ?? '')} | ${escapeTableCell(finding.message)} | ${escapeTableCell(finding.suggestion)} |` ) - : ['| none | low | | No GitHub Actions hardening findings. | Keep workflows pinned, least-privilege, and isolated from untrusted PR code. |']), + : ['| none | low | | No GitHub Actions hardening findings. | Keep workflows pinned, least-privilege, opted into Node 24, and isolated from untrusted PR code. |']), '', '## Next Actions', '', @@ -117,6 +118,7 @@ function findActionsRisks(file: string, content: string): Finding[] { const hasWriteAll = WRITE_ALL_PATTERN.test(content); const hasWritePermissions = WRITE_PERMISSION_PATTERN.test(content) || hasWriteAll; const usesSecrets = SECRET_PATTERN.test(content); + const usesExternalActions = workflowUsesExternalActions(content); if (!hasPermissions) { findings.push({ @@ -138,6 +140,16 @@ function findActionsRisks(file: string, content: string): Finding[] { }); } + if (usesExternalActions && !NODE24_OPT_IN_PATTERN.test(content)) { + findings.push({ + file, + type: 'actions-missing-node24-opt-in', + severity: 'low', + message: `${file} uses JavaScript actions without explicitly opting into the GitHub Actions Node 24 runtime.`, + suggestion: 'Set top-level `env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true` while the GitHub Actions ecosystem finishes migrating action metadata from node20 to node24.' + }); + } + findings.push(...findUnpinnedActions(file, content)); if (usesPullRequestTarget && CHECKOUT_PATTERN.test(content) && PR_HEAD_REF_PATTERN.test(content)) { @@ -172,6 +184,14 @@ function findActionsRisks(file: string, content: string): Finding[] { return findings; } +function workflowUsesExternalActions(content: string): boolean { + for (const match of content.matchAll(USES_PATTERN)) { + const action = match[1]; + if (!action.startsWith('./') && !action.startsWith('../') && !action.startsWith('docker://')) return true; + } + return false; +} + function findUnpinnedActions(file: string, content: string): Finding[] { const findings: Finding[] = []; for (const match of content.matchAll(USES_PATTERN)) { @@ -222,12 +242,13 @@ function statusForFindings(findings: Finding[]): GithubActionsStatus { } function nextActions(findings: Finding[]): string[] { - if (findings.length === 0) return ['Keep GitHub Actions workflows pinned to full SHAs and least-privilege by default.']; + if (findings.length === 0) return ['Keep GitHub Actions workflows pinned to full SHAs, least-privilege, and opted into the Node 24 JavaScript action runtime by default.']; const actions = ['Review every GitHub Actions hardening finding before trusting agent-authored or agent-triggered workflows.']; if (findings.some((finding) => finding.type === 'actions-unpinned-action')) actions.push('Pin actions to full commit SHAs and update them through review.'); if (findings.some((finding) => finding.type === 'actions-pwn-request-checkout')) actions.push('Remove PR-head checkout from pull_request_target workflows.'); if (findings.some((finding) => finding.type === 'actions-script-injection')) actions.push('Route untrusted GitHub contexts through env vars before shell use.'); if (findings.some((finding) => finding.type.includes('permissions'))) actions.push('Declare least-privilege `permissions:` for every workflow.'); + if (findings.some((finding) => finding.type === 'actions-missing-node24-opt-in')) actions.push('Opt GitHub Actions workflows into the Node 24 JavaScript action runtime.'); return actions; } diff --git a/src/cli.ts b/src/cli.ts index a06b196..6446191 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -920,7 +920,7 @@ Usage: contextforge surface-inventory [--json] [--output contextforge-agent-surface-inventory.md] contextforge surface-diff [--base main] [--json] [--output contextforge-agent-surface-diff.md] contextforge publish-readiness [--json] [--summary contextforge-publish-readiness.md] - contextforge init [--all] [--github-action] [--pr-comment-workflow] [--agents-md] [--claude-md] [--copilot-instructions] [--project-name "My App"] [--action-ref grnbtqdbyx-create/contextforge@v0.70.0] [--force] + contextforge init [--all] [--github-action] [--pr-comment-workflow] [--agents-md] [--claude-md] [--copilot-instructions] [--project-name "My App"] [--action-ref grnbtqdbyx-create/contextforge@v0.71.0] [--force] Session scan safety: --max-session-files 50 newest JSONL files to scan per provider diff --git a/src/init/githubAction.ts b/src/init/githubAction.ts index 78594b8..e81f2e6 100644 --- a/src/init/githubAction.ts +++ b/src/init/githubAction.ts @@ -1,7 +1,7 @@ import { access, mkdir, writeFile } from 'node:fs/promises'; import path from 'node:path'; -export const DEFAULT_GITHUB_ACTION_REF = 'grnbtqdbyx-create/contextforge@v0.70.0'; +export const DEFAULT_GITHUB_ACTION_REF = 'grnbtqdbyx-create/contextforge@v0.71.0'; export interface GithubActionScaffoldOptions { rootDir: string; diff --git a/src/report/adoptionBrief.ts b/src/report/adoptionBrief.ts index 045e83f..e4d6c90 100644 --- a/src/report/adoptionBrief.ts +++ b/src/report/adoptionBrief.ts @@ -45,7 +45,7 @@ export function createAdoptionBrief(options: AdoptionBriefOptions): string { '- Open `contextforge-mcp-audit.md` when the repo has MCP config files or agent tool setup; upload `contextforge-mcp.sarif` when GitHub Code Scanning should track those findings.', '- Open `contextforge-claude-audit.md` when the repo commits Claude Code project settings, hooks, or permissions.', '- Open `contextforge-workflow-audit.md` when GitHub workflows pass issue, PR, review, comment, title, workflow input, or branch/ref text into agent commands.', - '- Open `contextforge-actions-audit.md` when GitHub Actions workflows need SHA pinning, least-privilege permissions, pwn-request, or script-injection review.', + '- Open `contextforge-actions-audit.md` when GitHub Actions workflows need SHA pinning, least-privilege permissions, Node 24 runtime opt-in, pwn-request, or script-injection review.', '- Open `contextforge-trace-audit.md` when you want to see whether a Codex or Claude trace wasted context on repeated tools or bulky outputs.', '- Open `contextforge-cost-estimate.md` when you want to turn observed tokens into a configurable spend estimate without trusting stale hardcoded prices.', '- Open `docs/artifacts.md` when CI uploaded many files and you need the right next proof artifact.', diff --git a/src/report/artifactMap.ts b/src/report/artifactMap.ts index a5a5b1d..c6d60a2 100644 --- a/src/report/artifactMap.ts +++ b/src/report/artifactMap.ts @@ -135,7 +135,7 @@ const artifactRows: ArtifactMapRow[] = [ { artifact: 'contextforge-actions-audit.md', audience: 'Security reviewers and release maintainers', - useWhen: 'you need to review GitHub Actions SHA pins, token permissions, pull_request_target risk, and direct script interpolation', + useWhen: 'you need to review GitHub Actions SHA pins, token permissions, Node 24 runtime opt-in, pull_request_target risk, and direct script interpolation', producedBy: '`contextforge actions-audit --summary contextforge-actions-audit.md`' }, { diff --git a/src/report/launchSnapshot.ts b/src/report/launchSnapshot.ts index c055705..7ae7b18 100644 --- a/src/report/launchSnapshot.ts +++ b/src/report/launchSnapshot.ts @@ -39,7 +39,7 @@ export function createLaunchSnapshot(options: LaunchSnapshotOptions): string { '| Are MCP configs risky? | `contextforge-mcp-audit.md` and `contextforge-mcp.sarif` |', '| Are Claude Code settings risky? | `contextforge-claude-audit.md` and `contextforge-claude.sarif` |', '| Can GitHub event text reach a privileged agent workflow? | `contextforge-workflow-audit.md` and `contextforge-workflow.sarif` |', - '| Are GitHub Actions pinned and least-privilege? | `contextforge-actions-audit.md` and `contextforge-actions.sarif` |', + '| Are GitHub Actions pinned, least-privilege, and Node 24-ready? | `contextforge-actions-audit.md` and `contextforge-actions.sarif` |', '| Did the last agent session waste context? | `contextforge-trace-audit.md` |', '| What would a long session cost? | `contextforge-cost-estimate.md` |', '| Is the first npm publish ready? | `contextforge-publish-readiness.md` |', diff --git a/tests/actionsAudit.test.ts b/tests/actionsAudit.test.ts index 8626806..5aa9c86 100644 --- a/tests/actionsAudit.test.ts +++ b/tests/actionsAudit.test.ts @@ -55,6 +55,8 @@ describe('GitHub Actions audit', () => { 'name: CI', 'on:', ' pull_request:', + 'env:', + ' FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true', 'permissions:', ' contents: read', 'jobs:', @@ -71,4 +73,31 @@ describe('GitHub Actions audit', () => { expect(audit.status).toBe('pass'); expect(audit.findings).toHaveLength(0); }); + + it('warns when JavaScript actions run without the GitHub Node 24 opt-in', async () => { + const rootDir = await mkdtemp(path.join(os.tmpdir(), 'contextforge-actions-node24-')); + await mkdir(path.join(rootDir, '.github/workflows'), { recursive: true }); + await writeFile( + path.join(rootDir, '.github/workflows/ci.yml'), + [ + 'name: CI', + 'on:', + ' pull_request:', + 'permissions:', + ' contents: read', + 'jobs:', + ' test:', + ' runs-on: ubuntu-latest', + ' steps:', + ' - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3', + ' - run: pnpm test' + ].join('\n') + ); + + const audit = await auditGithubActions({ rootDir }); + + expect(audit.status).toBe('warn'); + expect(audit.findings.map((finding) => finding.type)).toEqual(['actions-missing-node24-opt-in']); + expect(createGithubActionsSummary(audit)).toContain('FORCE_JAVASCRIPT_ACTIONS_TO_NODE24'); + }); }); diff --git a/tests/cli.test.ts b/tests/cli.test.ts index 9f133f7..442aa8c 100644 --- a/tests/cli.test.ts +++ b/tests/cli.test.ts @@ -62,7 +62,7 @@ describe('CLI help command', () => { it('prints the current default GitHub Action ref in init examples', async () => { const { stdout } = await execFileAsync('pnpm', ['contextforge', 'help']); - expect(stdout).toContain('--action-ref grnbtqdbyx-create/contextforge@v0.70.0'); + expect(stdout).toContain('--action-ref grnbtqdbyx-create/contextforge@v0.71.0'); }); }); diff --git a/tests/init.test.ts b/tests/init.test.ts index c05f228..31377df 100644 --- a/tests/init.test.ts +++ b/tests/init.test.ts @@ -88,7 +88,7 @@ describe('GitHub Action init scaffold', () => { const rootDir = await mkdtemp(path.join(os.tmpdir(), 'contextforge-init-default-ref-')); const result = await scaffoldGithubActionWorkflow({ rootDir }); - expect(await readFile(result.path, 'utf8')).toContain('uses: grnbtqdbyx-create/contextforge@v0.70.0'); + expect(await readFile(result.path, 'utf8')).toContain('uses: grnbtqdbyx-create/contextforge@v0.71.0'); }); it('is available through the init CLI command', async () => {