Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/codex/prompts/node-version-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@ If it has drifted, prepare one focused change:

Do not commit, push, open a pull request, call GitHub, or modify repository
secrets. You may run the fast policy checker while editing, but do not spend
this run repeating the full lint and test suites; the workflow owns validation
and opens the draft pull request after you finish.
this run repeating the full lint and test suites. This job has read-only
repository access. The workflow exports the allowed edits as untrusted JSON;
separate jobs verify the proposal against the original workflow commit, run
repository validation, and publish the draft pull request. Only the publishing
job has repository write access, and it does not execute proposed code.

Your final response becomes the draft pull request body. Write concise Markdown
with these sections:
Expand Down
148 changes: 124 additions & 24 deletions .github/workflows/node-version-review.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# Asks Codex once a month to compare this repository with the official Node.js
# lifecycle, make any needed support-policy edits, validate them, and open a
# draft pull request. If nothing has drifted, the run makes no pull request.
# Because GitHub suppresses CI events for its own token, checks start when a
# maintainer marks the draft ready; human review is always required before merge.
# Human review is always required before merge.
name: Monthly Node.js version review

on:
Expand All @@ -14,36 +13,30 @@ concurrency:
group: monthly-node-version-review
cancel-in-progress: false

permissions:
contents: write
pull-requests: write
# Proposal generation and validation never receive repository write authority.
permissions: {}

jobs:
review:
propose:
if: github.repository == 'openai/openai-node' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
environment: ci
outputs:
artifact-id: ${{ steps.upload.outputs.artifact-id }}
steps:
- name: Check out main
- name: Check out the workflow commit
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: main
ref: ${{ github.sha }}
persist-credentials: false

- name: Set up pnpm
uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4

- name: Set up Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version-file: '.nvmrc'
cache: pnpm

- name: Install dependencies
run: |
pnpm install --frozen-lockfile
./scripts/steady/install

- name: Ask Codex to review Node.js support
id: codex
Expand All @@ -56,6 +49,73 @@ jobs:
permission-profile: ':workspace'
safety-strategy: drop-sudo

- name: Export the untrusted proposal as JSON
env:
EXPECTED_BASE_SHA: ${{ github.sha }}
PROPOSAL_PATH: ${{ runner.temp }}/node-review-export/proposal.json
REVIEW_BODY: ${{ steps.codex.outputs.final-message }}
run: |
python3 scripts/node-version-review.py export \
--base-sha "$EXPECTED_BASE_SHA" \
--proposal "$PROPOSAL_PATH"

- name: Upload the original proposal
id: upload
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: node-review-proposal-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/node-review-export/proposal.json
if-no-files-found: error
retention-days: 1

validate:
needs: propose
if: github.repository == 'openai/openai-node' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
steps:
- name: Check out the workflow commit
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ github.sha }}
persist-credentials: false

- name: Download the original proposal
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
artifact-ids: ${{ needs.propose.outputs.artifact-id }}
path: ${{ runner.temp }}/node-review-proposal
merge-multiple: true
digest-mismatch: error

- name: Verify and apply the proposal
id: proposal
env:
EXPECTED_BASE_SHA: ${{ github.sha }}
PROPOSAL_PATH: ${{ runner.temp }}/node-review-proposal/proposal.json
BODY_PATH: ${{ runner.temp }}/node-review-body.md
run: |
python3 scripts/node-version-review.py apply \
--base-sha "$EXPECTED_BASE_SHA" \
--proposal "$PROPOSAL_PATH" \
--body-output "$BODY_PATH"

- name: Set up pnpm
uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4

- name: Set up Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version-file: '.nvmrc'
cache: pnpm

- name: Install dependencies
run: |
pnpm install --frozen-lockfile
./scripts/steady/install

- name: Check Node.js version policy
run: node --experimental-strip-types scripts/check-node-version-policy.ts

Expand Down Expand Up @@ -89,22 +149,62 @@ jobs:
- name: Run tests
run: ./scripts/test

publish:
needs: [propose, validate]
if: github.repository == 'openai/openai-node' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
pull-requests: write
steps:
- name: Check out the workflow commit
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ github.sha }}
persist-credentials: false

# create-pull-request resets the working branch to origin/main. Keep both
# refs at the validated commit so a concurrent main update cannot rebase it.
- name: Preserve the validated base
env:
EXPECTED_BASE_SHA: ${{ github.sha }}
run: |
test "$(git rev-parse HEAD)" = "$EXPECTED_BASE_SHA"
git checkout -B main "$EXPECTED_BASE_SHA"
git update-ref refs/remotes/origin/main "$EXPECTED_BASE_SHA"

- name: Download the original proposal
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
artifact-ids: ${{ needs.propose.outputs.artifact-id }}
path: ${{ runner.temp }}/node-review-proposal
merge-multiple: true
digest-mismatch: error

- name: Verify and apply the proposal
id: proposal
env:
EXPECTED_BASE_SHA: ${{ github.sha }}
PROPOSAL_PATH: ${{ runner.temp }}/node-review-proposal/proposal.json
BODY_PATH: ${{ runner.temp }}/node-review-body.md
run: |
python3 scripts/node-version-review.py apply \
--base-sha "$EXPECTED_BASE_SHA" \
--proposal "$PROPOSAL_PATH" \
--body-output "$BODY_PATH"

- name: Open or update the draft pull request
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7
with:
token: ${{ github.token }}
base: main
branch: codex/monthly-node-version-update
delete-branch: true
draft: always-true
commit-message: 'chore(node): review supported Node.js versions'
title: 'chore(node): review supported Node.js versions'
body: |
Automated monthly review of the Node.js support policy.

${{ steps.codex.outputs.final-message }}

Before marking this draft ready, choose the release classification
required by `NODE_VERSION_POLICY.md` and update the title.
body-path: ${{ runner.temp }}/node-review-body.md
add-paths: |
.nvmrc
package.json
Expand Down
9 changes: 6 additions & 3 deletions NODE_VERSION_POLICY.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,12 @@ type-checked `scripts/check-node-version-policy.ts` fails when those projections
drift and emits the matrix consumed by CI.

Each month, `.github/workflows/node-version-review.yml` asks Codex to research
the official Node.js schedule, reconcile the policy artifacts, run repository
validation, and open or update one draft pull request. Generated changes never
merge automatically.
the official Node.js schedule and propose policy edits in a job with read-only
repository access. Separate jobs verify the same JSON proposal against the
workflow commit, run repository validation, and open or update one draft pull
request. Only the publishing job has repository write access; it applies
verified policy files without installing dependencies or executing proposed
code. Generated changes never merge automatically.

For upstream dates and lifecycle definitions, see the
[Node.js release schedule](https://github.com/nodejs/Release/blob/main/schedule.json)
Expand Down
20 changes: 12 additions & 8 deletions docs/architecture/security-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ SDK verifier before parsing (`src/client.ts:505-612`,
| Bedrock providers | Dependency-free bearer provider or AWS access key, secret key, session token, profile, or credential-provider chain | Explicit provider options select one bearer or AWS credential mode and a Bedrock endpoint/region | Normalized configured or region-derived Bedrock endpoint | Bedrock authority receives bearer or SigV4-signed request | Provider-owned Authorization, endpoint/region checks, replayable-body requirement, and manual redirects | `src/providers/bedrock.ts:14-50`, `src/providers/bedrock/aws.ts:26-64`, `src/providers/bedrock/aws.ts:161-249`, `src/providers/bedrock/aws.ts:252-289` |
| Stable and beta Realtime | API key, ephemeral key, or Azure credential in a WebSocket handshake; untrusted server frames | Stable defaults derive from the client but trusted `buildRealtimeURL` may supply any `wss:` URL; beta requires the configured origin | Stable trusted `wss:` URL or beta matching configured `wss:` origin | Configured or explicitly trusted Realtime authority; parsed events to application listeners | Stable requires `wss:`; legacy Bedrock clients additionally require the configured origin; beta requires `wss:` plus configured-origin equality; both `ws` transports protect credential redirects and both surfaces validate own string `type` | `src/realtime/internal-base.ts:155-166`, `src/realtime/internal-base.ts:276-281`, `src/realtime/websocket.ts:204-230`, `src/internal/bedrock.ts:247-263`, `src/realtime/ws.ts:63-105`, `src/beta/realtime/websocket.ts:132-143`, `src/beta/realtime/websocket.ts:210-258`, `src/beta/realtime/ws.ts:18-28`, `src/beta/realtime/ws.ts:68-117` |
| Trusted Castiron reporting | Lower-trust `workflow_run` metadata, trusted main reporter, status and PR-comment writes | Completed upstream custom-code workflow is checked against event, path, repository, head SHA, and current PR identity | Main checkout plus recomputed Git-object report | Commit statuses and one PR comment | Trusted main code recomputes reports; freshness and identity checks gate writes; candidate artifacts are not trusted inputs | `.github/workflows/castiron-custom-code-comment.yml:4-24`, `.github/workflows/castiron-custom-code-comment.yml:34-98`, `.github/workflows/castiron-custom-code-comment.yml:116-200` |
| Monthly Node version review | OpenAI API key plus repository and pull-request write authority for an automated Codex run | Scheduled or manual main-only workflow, `environment: ci`, fixed prompt file, workspace permission profile | Main checkout and `codex/monthly-node-version-update` draft PR | Codex action and GitHub draft-PR publisher | Main-only condition, pinned actions, `drop-sudo`, constrained add-paths, and draft PR; required human review and branch protection are external assumptions | `.github/workflows/node-version-review.yml:17-55`, `.github/workflows/node-version-review.yml:90-111` |
| Monthly Node version review | OpenAI API key in the proposal job; repository and pull-request write authority only in the publisher | Scheduled or manual main-only workflow; isolated proposal, validation, and publication jobs | Same immutable JSON artifact and workflow commit in fresh validation and publisher checkouts | Codex action, read-only validation, and GitHub draft-PR publisher | Pinned actions, strict policy-file allowlist, engines.node-only package reconstruction, independent verification before dependency execution and publication, fixed validated base, and draft PR; human review and branch protection are external assumptions | `.github/workflows/node-version-review.yml:16-117`, `.github/workflows/node-version-review.yml:152-213`, `scripts/node-version-review.py` |

## 2. Threat Model, Trust Boundaries, and Assumptions

Expand Down Expand Up @@ -308,15 +308,19 @@ artifact crosses into protected CI, release, or publication credentials.
`.github/workflows/create-releases.yml:288-346`).
- **Workflow metadata or automation prompts to privileged writes:** the
trusted Castiron comment workflow consumes lower-trust `workflow_run` state
before writing statuses and PR comments, while the monthly Node review gives
an automated Codex run an API key and repository/PR write authority. Their
main-only code, freshness/identity checks, constrained paths, and draft
status are repository-visible controls; required human review and branch
protection are external assumptions
before writing statuses and PR comments. The monthly Node review isolates
Codex with read-only repository access and an API key. Fresh validation and
publisher jobs independently verify the same JSON proposal against the
workflow commit, allowing only policy files and `package.json#engines.node`.
Validation has read-only access; the publisher has repository/PR write access
and applies verified data without installing dependencies or executing
proposed code. Its local base remains at the validated commit. Required human
review and branch protection are external assumptions
(`.github/workflows/castiron-custom-code-comment.yml:4-24`,
`.github/workflows/castiron-custom-code-comment.yml:116-200`,
`.github/workflows/node-version-review.yml:17-55`,
`.github/workflows/node-version-review.yml:90-111`).
`.github/workflows/node-version-review.yml:16-117`,
`.github/workflows/node-version-review.yml:152-213`,
`scripts/node-version-review.py`).
- **Dependencies to build execution:** frozen lockfiles and workspace dependency
policy reduce supply-chain risk, but dependency lifecycle behavior remains a
real boundary when independently mutable artifacts can reach a trusted build
Expand Down
Loading
Loading