Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
0a1853c
fix(ci): trust npm 12 Brev template
cjagwani Sep 5, 2026
a1a0a68
test(ci): bind npm 12 Brev trust evidence
cjagwani Sep 5, 2026
4206008
fix(ci): stage reviewed npm bootstrap before trust
cjagwani Sep 5, 2026
90ee876
refactor(ci): consolidate reviewed npm bootstrap
cjagwani Sep 5, 2026
87ee5c1
fix(ci): reject npm version drift before install
cjagwani Sep 5, 2026
2d3b536
fix(ci): complete reviewed npm trust handoff
cjagwani Sep 5, 2026
e02c839
fix(ci): qualify reviewed npm bootstrap changes
cjagwani Sep 5, 2026
0d112df
fix(ci): close npm bootstrap publication gaps
cjagwani Sep 5, 2026
3786d51
docs(security): describe npm archive verification
cjagwani Sep 5, 2026
559b35a
test(ci): complete reviewed npm fixture identity
cjagwani Sep 8, 2026
b7adea8
merge(main): refresh npm trust prerequisite
cjagwani Sep 8, 2026
9228ab9
test(ci): remove source-shape review coverage
cjagwani Sep 8, 2026
80fa85c
merge(main): refresh npm trust review repair
cjagwani Sep 8, 2026
f90f4aa
test(ci): exercise reviewed npm trust handoff
cjagwani Sep 8, 2026
da62bbb
merge(main): refresh npm trust review repair
cjagwani Sep 8, 2026
0425c5e
Merge branch 'main' into codex/npm12-base-trust
cv Sep 8, 2026
a838b44
refactor(ci): clarify npm audit terminology
cjagwani Sep 8, 2026
ed5527c
Merge remote-tracking branch 'refs/remotes/origin/main' into codex/np…
cjagwani Sep 8, 2026
6c5207b
fix(ci): require npm audit failure evidence
cjagwani Sep 9, 2026
f84bc2d
merge: sync current main for patched js-yaml
cjagwani Sep 9, 2026
c670367
fix(ci): preserve npm audit signatures
cjagwani Sep 9, 2026
450938d
fix(ci): complete reviewed npm audit handoff
cjagwani Sep 9, 2026
334cff2
test(ci): validate reviewed npm failure routing
cjagwani Sep 9, 2026
5c10dc1
merge: sync current main
cjagwani Sep 9, 2026
3cc61fa
chore(node): use default TypeScript stripping
cjagwani Sep 9, 2026
c6b6db9
fix(ci): classify npm audit failures by evidence
cjagwani Sep 9, 2026
120f429
fix(ci): close npm audit review gaps
cjagwani Sep 9, 2026
3885100
fix(ci): classify invalid npm archive metadata
cjagwani Sep 9, 2026
c317455
merge: sync current main for Hermes image digest
cjagwani Sep 9, 2026
15bd4df
fix(ci): remove stale audit correlation branch
cjagwani Sep 9, 2026
9e2a42a
fix(ci): bind audit evidence to installed npm
cjagwani Sep 10, 2026
1dcbb4b
merge: sync current main for smol-toml advisory
cjagwani Sep 10, 2026
c5607f8
fix(ci): pass audit identity through CLI cache
cjagwani Sep 10, 2026
62ef062
fix(ci): bind receipts to reviewed npm identity
cjagwani Sep 10, 2026
03a94e9
fix(ci): tighten reviewed npm audit validation
cjagwani Sep 10, 2026
c70a28a
docs(skills): distinguish durable dependency contracts
cjagwani Sep 10, 2026
04e87bf
merge: sync current main for formatter baseline
cjagwani Sep 10, 2026
27d3e14
chore(test): apply current formatter baseline
cjagwani Sep 10, 2026
1dce30c
test(skills): enforce review report prohibition
cjagwani Sep 10, 2026
3a71dfc
Merge remote-tracking branch 'origin/main' into codex/npm12-base-trus…
cjagwani Sep 10, 2026
14e15b2
fix(ci): trust current Node 24 Brev template
cjagwani Sep 10, 2026
677317f
refactor(ci): reduce npm trust test duplication
cjagwani Sep 10, 2026
233cfd9
Merge branch 'main' into codex/npm12-base-trust
cjagwani Sep 10, 2026
bf8492a
Merge branch 'main' into codex/npm12-base-trust
cjagwani Sep 10, 2026
d575033
Merge branch 'main' into codex/npm12-base-trust
cjagwani Sep 10, 2026
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
31 changes: 14 additions & 17 deletions .github/actions/ci-reviewed-npm-audit/action.yaml
Comment thread
cjagwani marked this conversation as resolved.
Resolved
Original file line number Diff line number Diff line change
Expand Up @@ -39,29 +39,26 @@ runs:
previous_bucket="$(( current_bucket - 1 ))"
node --experimental-strip-types --input-type=module - \
"$GITHUB_ACTION_PATH/../../../ci/reviewed-npm-audit.json" \
"$GITHUB_ACTION_PATH/../../../scripts/lib/repository-input-path.mts" <<'NODE'
"$GITHUB_ACTION_PATH/../../../scripts/lib/repository-input-path.mts" \
"$GITHUB_ACTION_PATH/../../../scripts/lib/reviewed-npm-audit.mts" <<'NODE'
import { createHash } from "node:crypto";
import { appendFileSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
const targetRoot = process.env.NEMOCLAW_REVIEWED_NPM_AUDIT_TARGET_ROOT;
const [configFile, resolverFile] = process.argv.slice(2);
const [configFile, resolverFile, reviewedNpmAuditFile] = process.argv.slice(2);
if (!targetRoot) {
throw new Error("reviewed npm audit target root is required");
}
const { resolvePathWithinRoot } = await import(pathToFileURL(resolverFile).href);
const { parseReviewedNpmIdentity } = await import(pathToFileURL(reviewedNpmAuditFile).href);
const configSource = readFileSync(configFile, "utf8");
const config = JSON.parse(configSource);
if (!/^(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)$/.test(config.npmVersion) || /[\r\n]/.test(config.npmVersion)) {
throw new Error("reviewed npm audit configuration has an invalid npmVersion");
}
if (!/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(config.npmIntegrity) || /[\r\n]/.test(config.npmIntegrity)) {
throw new Error("reviewed npm audit configuration has an invalid npmIntegrity");
}
const identity = parseReviewedNpmIdentity(config);
const directories = ["", ...config.lockedGraphs.map((graph) => graph.directory)].sort();
const hash = createHash("sha256");
hash.update(configSource);
hash.update(JSON.stringify({ argv: ["audit", "--registry=https://registry.yarnpkg.com", "--omit=dev", "--json"], npmVersion: config.npmVersion, registry: "https://registry.yarnpkg.com/", schemaVersion: 1 }));
hash.update(JSON.stringify({ argv: ["audit", "--registry=https://registry.yarnpkg.com", "--omit=dev", "--json"], ...identity, registry: "https://registry.yarnpkg.com/", schemaVersion: 2 }));
for (const directory of directories) {
for (const file of ["package.json", "package-lock.json"]) {
const relative = join(directory, file);
Expand All @@ -71,7 +68,7 @@ runs:
}
appendFileSync(
process.env.GITHUB_OUTPUT,
`input-digest=${hash.digest("hex")}\nnpm-version=${config.npmVersion}\nnpm-integrity=${config.npmIntegrity}\n`,
`input-digest=${hash.digest("hex")}\n`,
);
NODE
printf 'current=%s\nprevious=%s\n' "$current_bucket" "$previous_bucket" >> "$GITHUB_OUTPUT"
Expand All @@ -82,21 +79,21 @@ runs:
uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: ${{ inputs.cache-directory }}
key: reviewed-npm-audit-v1-${{ runner.os }}-${{ steps.cache-buckets.outputs.input-digest }}-${{ steps.cache-buckets.outputs.current }}
key: reviewed-npm-audit-v2-${{ runner.os }}-${{ steps.cache-buckets.outputs.input-digest }}-${{ steps.cache-buckets.outputs.current }}

- name: Restore previous reviewed npm audit cache bucket
if: steps.cache-current.outputs.cache-hit != 'true'
uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: ${{ inputs.cache-directory }}
key: reviewed-npm-audit-v1-${{ runner.os }}-${{ steps.cache-buckets.outputs.input-digest }}-${{ steps.cache-buckets.outputs.previous }}
key: reviewed-npm-audit-v2-${{ runner.os }}-${{ steps.cache-buckets.outputs.input-digest }}-${{ steps.cache-buckets.outputs.previous }}

- name: Download and verify production npm
shell: bash
env:
NEMOCLAW_REVIEWED_NPM_VERSION: ${{ steps.cache-buckets.outputs.npm-version }}
NEMOCLAW_REVIEWED_NPM_INTEGRITY: ${{ steps.cache-buckets.outputs.npm-integrity }}
run: env -u NODE_AUTH_TOKEN -u NPM_TOKEN -u NPM_CONFIG__AUTH_TOKEN "$GITHUB_ACTION_PATH/verify-and-install-npm.sh"
run: >-
env -u NODE_AUTH_TOKEN -u NPM_TOKEN -u NPM_CONFIG__AUTH_TOKEN
"$GITHUB_ACTION_PATH/../setup-reviewed-npm/verify-and-install-npm.sh"
"$GITHUB_ACTION_PATH/../../../ci/reviewed-npm-audit.json"

- name: Materialize and audit reviewed npm graphs
shell: bash
Expand All @@ -113,7 +110,7 @@ runs:
uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: ${{ inputs.cache-directory }}
key: reviewed-npm-audit-v1-${{ runner.os }}-${{ steps.cache-buckets.outputs.input-digest }}-${{ steps.cache-buckets.outputs.current }}
key: reviewed-npm-audit-v2-${{ runner.os }}-${{ steps.cache-buckets.outputs.input-digest }}-${{ steps.cache-buckets.outputs.current }}

- name: Upload reviewed npm audit reports
if: always()
Expand Down
33 changes: 0 additions & 33 deletions .github/actions/ci-reviewed-npm-audit/verify-and-install-npm.sh

This file was deleted.

81 changes: 81 additions & 0 deletions .github/actions/setup-reviewed-npm/verify-and-install-npm.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

set -euo pipefail

if [ "$#" -ne 1 ]; then
echo "ERROR: reviewed npm identity path is required." >&2
exit 1
fi

config_file="$1"
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
download_dir="$(mktemp -d "$RUNNER_TEMP/reviewed-npm.XXXXXX")"
trap 'rm -rf "$download_dir"' EXIT
identity_file="$download_dir/identity"

node --experimental-strip-types --input-type=module - \
"$config_file" \
"$script_dir/../../../scripts/lib/reviewed-npm-audit.mts" >"$identity_file" <<'NODE'
import { readFileSync } from "node:fs";
import { pathToFileURL } from "node:url";

const [configFile, reviewedNpmAuditFile] = process.argv.slice(2);
const { parseReviewedNpmIdentityConfig } = await import(pathToFileURL(reviewedNpmAuditFile).href);
const identity = parseReviewedNpmIdentityConfig(readFileSync(configFile, "utf8"));
process.stdout.write(`${identity.npmVersion}\n${identity.npmIntegrity}\n${identity.npmArchiveSha256}\n`);
NODE

IFS= read -r version <"$identity_file"
IFS= read -r expected_integrity < <(sed -n '2p' "$identity_file")
IFS= read -r expected_sha256 < <(sed -n '3p' "$identity_file")
[ -n "$version" ]
[ -n "$expected_integrity" ]
[ -n "$expected_sha256" ]

npm pack "npm@$version" \
--pack-destination "$download_dir" \
--userconfig /dev/null \
--registry https://registry.npmjs.org/ \
--ignore-scripts --no-audit --no-fund >/dev/null

archive="$download_dir/npm-$version.tgz"
actual_hashes="$download_dir/actual-hashes"
node -e '
const fs = require("node:fs");
const crypto = require("node:crypto");
const archive = fs.readFileSync(process.argv[1]);
process.stdout.write(
crypto.createHash("sha512").update(archive).digest("base64") + "\n" +
crypto.createHash("sha256").update(archive).digest("hex") + "\n",
);
' "$archive" >"$actual_hashes"
IFS= read -r actual_sha512 <"$actual_hashes"
IFS= read -r actual_sha256 < <(sed -n '2p' "$actual_hashes")
actual_integrity="sha512-$actual_sha512"
if [ "$actual_integrity" != "$expected_integrity" ] || [ "$actual_sha256" != "$expected_sha256" ]; then
echo "ERROR: npm@$version archive integrity mismatch." >&2
exit 1
fi

archive_version="$(
tar -xOf "$archive" package/package.json | node -e '
let source = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => { source += chunk; });
process.stdin.on("end", () => {
const version = JSON.parse(source).version;
if (typeof version !== "string") process.exit(1);
process.stdout.write(version);
});
'
)"
if [ "$archive_version" != "$version" ]; then
echo "ERROR: npm archive version $archive_version does not match reviewed npm@$version." >&2
exit 1
fi

npm install --global "$archive" \
--userconfig /dev/null \
--ignore-scripts --no-audit --no-fund --offline
1 change: 1 addition & 0 deletions .github/workflows/base-image.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ on:
- "test/e2e/live/managed-image-activation-e2e.test.ts"
- "test/e2e/live/managed-image-activation-e2e-helpers.ts"
- ".github/actions/ci-reviewed-npm-audit/**"
- ".github/actions/setup-reviewed-npm/**"
- ".github/actions/publish-managed-image-digest/**"
- ".github/actions/build-base-image-platform/**"
- ".github/actions/publish-base-image-manifest/**"
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/managed-images.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ on:
pull_request:
paths:
- ".github/actions/ci-reviewed-npm-audit/**"
- ".github/actions/setup-reviewed-npm/**"
- ".github/workflows/base-image.yaml"
- ".github/actions/publish-managed-image-digest/**"
- ".github/workflows/managed-images.yaml"
Expand Down Expand Up @@ -96,6 +97,7 @@ jobs:
persist-credentials: false
sparse-checkout: |
.github/actions/ci-reviewed-npm-audit
.github/actions/setup-reviewed-npm
ci/npm-audit-exceptions.json
ci/reviewed-npm-audit.json
scripts/audit-reviewed-npm-graph.mts
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,7 @@ jobs:
persist-credentials: false
sparse-checkout: |
.github/actions/ci-reviewed-npm-audit
.github/actions/setup-reviewed-npm
ci/npm-audit-exceptions.json
ci/reviewed-npm-audit.json
scripts/audit-reviewed-npm-graph.mts
Expand Down
1 change: 1 addition & 0 deletions ci/reviewed-npm-audit.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"nodeVersion": "22.23.2",
"npmVersion": "10.9.4",
"npmIntegrity": "sha512-OnUG836FwboQIbqtefDNlyR0gTHzIfwRfE3DuiNewBvnMnWEpB0VEXwBlFVgqpNzIgYo/MHh3d2Hel/pszapAA==",
"npmArchiveSha256": "4bfba8a0c823024d1926ec9d97a37a00eb60fd2adf44b3d34a686fc32e8f51e4",
"registryOrigin": "https://registry.npmjs.org/",
"sourceRegistryPackage": {
"artifactName": "nvidia-openshell-sdk-0.0.106.tgz",
Expand Down
15 changes: 15 additions & 0 deletions ci/source-shape-test-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,16 @@
"test": "passes the cache identity target root without interpolating it into shell source",
"category": "security"
},
{
"file": "test/automation/releases/reviewed-npm-audit-workflow.test.ts",
"test": "uses the single JSON-bound reviewed npm bootstrap owner",
"category": "security"
},
{
"file": "test/automation/releases/reviewed-npm-audit-handoff.test.ts",
"test": "routes bootstrap-only changes through image qualification and publication",
"category": "security"
},
{
"file": "test/automation/releases/reviewed-npm-audit-workflow.test.ts",
"test": "rejects the removed plural source-registry package shape",
Expand Down Expand Up @@ -256,6 +266,11 @@
"test": "accepts the reviewed %s OpenShell 0.0.106 installer template",
"category": "security"
},
{
"file": "test/install/installer-brev-npm12-template-trust.test.ts",
"test": "binds the exact reviewed npm 12 Brev successor and rejects bootstrap drift",
"category": "security"
},
{
"file": "test/install/installer-supervisor-manifest-trust.test.ts",
"test": "accepts the prospective shared gateway state resolver template (#10544)",
Expand Down
11 changes: 4 additions & 7 deletions scripts/audit-reviewed-npm-graph.mts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
type AuditPolicyResult,
NPM_AUDIT_REGISTRY,
assertExceptionGraphs,
parseReviewedNpmIdentity,
readAuditExceptionRegistry,
runReviewedNpmAudit,
type Severity,
Expand Down Expand Up @@ -57,6 +58,7 @@ type AuditConfig = Readonly<{
exceptionFile: string;
lockedGraphs: readonly LockedGraph[];
nodeVersion: string;
npmArchiveSha256: string;
npmIntegrity: string;
npmVersion: string;
registryOrigin: string;
Expand Down Expand Up @@ -166,6 +168,7 @@ function run(command: string, args: readonly string[], cwd: string) {

export function parseAuditConfig(contents: string): AuditConfig {
const parsed = JSON.parse(contents) as AuditConfig;
const reviewedNpmIdentity = parseReviewedNpmIdentity(parsed);
if (
parsed.schemaVersion !== 2 ||
!SEVERITIES.includes(parsed.severityThreshold) ||
Expand All @@ -174,12 +177,6 @@ export function parseAuditConfig(contents: string): AuditConfig {
parsed.archiveTarVersion !== "7.5.21" ||
typeof parsed.exceptionFile !== "string" ||
!parsed.exceptionFile ||
typeof parsed.npmVersion !== "string" ||
!/^(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)$/.test(parsed.npmVersion) ||
/[\r\n]/.test(parsed.npmVersion) ||
typeof parsed.npmIntegrity !== "string" ||
!/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(parsed.npmIntegrity) ||
/[\r\n]/.test(parsed.npmIntegrity) ||
typeof parsed.registryOrigin !== "string" ||
!parsed.registryOrigin ||
!Array.isArray(parsed.archivePackages) ||
Expand Down Expand Up @@ -246,7 +243,7 @@ export function parseAuditConfig(contents: string): AuditConfig {
) {
throw new Error("ci/reviewed-npm-audit.json is invalid");
}
return parsed;
return { ...parsed, ...reviewedNpmIdentity };
}

function readConfig(): AuditConfig {
Expand Down
3 changes: 3 additions & 0 deletions scripts/checks/extract-installer-pins.mts
Original file line number Diff line number Diff line change
Expand Up @@ -351,9 +351,12 @@ const TRUSTED_OPENSHELL_RELEASES: readonly OpenShellReleaseTrust[] = [
version: "0.0.103",
},
{
// The third template authorizes the reviewed Node 24.18.1 and npm 12.0.2
// successor constructed and verified by installer-brev-npm12-template-trust.test.ts.
brevTemplateSha256: [
"c0a4ddf25a02a9fe02b2df53a60942ea887610f04d4ce16a121b6e79a5aeff1a",
"56fc6482d1508b73604099e6fd6c16daea16275cf36cc25c1c5366c82a4394e3",
"5674b528f6604b30b31fbf3877e4f5d53abc08e88c0a352070a898cfd7eaa7bf",
Comment thread
cjagwani marked this conversation as resolved.
Resolved
],
formula: {
asset: "openshell.rb",
Expand Down
20 changes: 2 additions & 18 deletions scripts/lib/npm-audit-receipt.mts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
parseAuditExceptionRegistry,
parseAuditReport,
NPM_AUDIT_ARGV,
parseReviewedNpmIdentityConfig,
} from "./reviewed-npm-audit.mts";

export const AUDIT_ARGV = NPM_AUDIT_ARGV;
Expand Down Expand Up @@ -212,24 +213,7 @@ export function canonicalAuditReceipt(receipt: AuditReceipt): string {
}

export function reviewedNpmVersionFromConfig(contents: string): string {
let parsed: unknown;
try {
parsed = JSON.parse(contents);
} catch {
throw new Error("reviewed npm audit configuration is not valid JSON");
}
const npmVersion =
typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
? (parsed as Record<string, unknown>).npmVersion
: undefined;
if (
typeof npmVersion !== "string" ||
!/^(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)$/.test(npmVersion) ||
/[\r\n]/.test(npmVersion)
) {
throw new Error("reviewed npm audit configuration has an invalid npmVersion");
}
return npmVersion;
return parseReviewedNpmIdentityConfig(contents).npmVersion;
}

function cli(args: readonly string[]): void {
Expand Down
Loading
Loading