Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
59b4bb5
fix(security): update transitive nanoid to 3.3.17
seonghobae Aug 8, 2026
0b28347
docs(security): record nanoid remediation evidence
seonghobae Aug 8, 2026
f0b0252
fix(security): minimize nanoid lockfile remediation
seonghobae Aug 8, 2026
0153b55
docs(changelog): record nanoid security remediation
seonghobae Aug 8, 2026
5123462
docs(security): correct nanoid remediation evidence
seonghobae Aug 8, 2026
9b81b92
docs(security): define atomic write and toolchain evidence
seonghobae Aug 8, 2026
9af9d78
test(ci): require exact pull-request head checkout
seonghobae Aug 8, 2026
9e2606d
fix(ci): verify exact pull-request head
seonghobae Aug 8, 2026
b6f838c
docs(security): bind nanoid evidence to exact-head CI
seonghobae Aug 8, 2026
21dba32
test(ci): require reviewer exact-head binding
seonghobae Aug 8, 2026
aab81f7
fix(ci): bind reviewer checks to exact PR head
seonghobae Aug 8, 2026
e66f389
docs(security): classify exact-head scan evidence
seonghobae Aug 8, 2026
6160e7d
test(ci): pin exact Node and npm execution contract
seonghobae Aug 8, 2026
739753f
fix(ci): pin Node npm and install semantics
seonghobae Aug 8, 2026
a0b7c48
docs(security): pin nanoid reproduction toolchain
seonghobae Aug 8, 2026
e0106ce
test(ci): accept exact Node 24 patch versions
seonghobae Aug 8, 2026
51d5842
test(build): require deterministic toolchain on nanoid predecessor
seonghobae Aug 10, 2026
572f3ad
build(reproducibility): pin reviewed Node and npm identities
seonghobae Aug 10, 2026
39ea829
build(reproducibility): fail closed on unreviewed install scripts
seonghobae Aug 10, 2026
279945c
build(reproducibility): add exact lockfile change control
seonghobae Aug 10, 2026
769838c
ci(reproducibility): bind lockfile control to live predecessor
seonghobae Aug 10, 2026
6cb2ae4
test(reproducibility): verify exact lockfile change control
seonghobae Aug 10, 2026
7414c25
test(reproducibility): count lockfile control in production coverage
seonghobae Aug 10, 2026
7c7ae25
test(reproducibility): prove strict install-script policy
seonghobae Aug 10, 2026
b6e730d
test(reproducibility): cover lockfile security boundaries
seonghobae Aug 10, 2026
37ae4a1
test(reproducibility): close lockfile policy schema
seonghobae Aug 10, 2026
6604b4d
test(reproducibility): reject duplicate JSON keys
seonghobae Aug 10, 2026
485aca0
test(reproducibility): reject descriptor races
seonghobae Aug 10, 2026
8974391
test(reproducibility): preserve canonical array order
seonghobae Aug 10, 2026
0795cb2
test(reproducibility): tolerate explicit Node 24 patch pins
seonghobae Aug 10, 2026
6e09481
test(docs): require package-manager reproducibility doctoring
seonghobae Aug 10, 2026
63fe285
docs(reproducibility): record package-manager evidence contract
seonghobae Aug 10, 2026
96cdc4d
test(reproducibility): require package-manager changelog handoff
seonghobae Aug 10, 2026
adcc42d
docs(changelog): record integrated npm reproducibility controls
seonghobae Aug 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
131 changes: 127 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,141 @@ jobs:
contents: read
steps:
- name: checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
persist-credentials: false

- name: verify exact checkout
shell: bash
env:
NOEMA_EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
set -euo pipefail
if [[ ! "$NOEMA_EXPECTED_HEAD_SHA" =~ ^[0-9a-f]{40}$ ]]; then
printf '::error::Invalid expected head SHA.\n'
exit 1
fi
test "$(git rev-parse HEAD)" = "$NOEMA_EXPECTED_HEAD_SHA"

- name: setup node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "24"
node-version: "24.19.0"
cache: npm

- name: verify package-manager toolchain
shell: bash
run: |
set -euo pipefail
test "$(node --version)" = "v24.19.0"
test "$(npm --version)" = "11.17.0"

- name: verify live pull-request base before lockfile control
if: github.event_name == 'pull_request'
shell: bash
env:
GH_TOKEN: ${{ github.token }}
NOEMA_PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
NOEMA_PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
set -euo pipefail
if [[ ! "$NOEMA_PR_BASE_SHA" =~ ^[0-9a-f]{40}$ ]]; then
printf '::error::Invalid pull-request base SHA.\n'
exit 1
fi
if [ -z "$NOEMA_PR_BASE_REF" ]; then
printf '::error::Pull-request base ref is unavailable.\n'
exit 1
fi
repository_name="${GITHUB_REPOSITORY#*/}"
live_base_sha="$(
gh api graphql \
-f query='query($owner:String!,$name:String!,$qualifiedName:String!){repository(owner:$owner,name:$name){ref(qualifiedName:$qualifiedName){target{oid}}}}' \
-F owner="$GITHUB_REPOSITORY_OWNER" \
-F name="$repository_name" \
-F qualifiedName="refs/heads/${NOEMA_PR_BASE_REF}" \
--jq '.data.repository.ref.target.oid'
)"
if [[ ! "$live_base_sha" =~ ^[0-9a-f]{40}$ ]]; then
printf '::error::Live pull-request base ref did not resolve to a full commit SHA.\n'
exit 1
fi
if [ "$live_base_sha" != "$NOEMA_PR_BASE_SHA" ]; then
printf '::error::Pull-request base branch advanced from %s to %s.\n' \
"$NOEMA_PR_BASE_SHA" "$live_base_sha"
exit 1
fi
test "$live_base_sha" = "$NOEMA_PR_BASE_SHA"

- name: verify lockfile change control
if: github.event_name == 'pull_request'
shell: bash
env:
NOEMA_PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
set -euo pipefail
if [[ ! "$NOEMA_PR_BASE_SHA" =~ ^[0-9a-f]{40}$ ]]; then
printf '::error::Invalid pull-request base SHA.\n'
exit 1
fi
base_lock="$RUNNER_TEMP/noema-package-lock-base.json"
git show "${NOEMA_PR_BASE_SHA}:package-lock.json" >"$base_lock"
NOEMA_LOCKFILE_BASE_PATH="$base_lock" \
NOEMA_LOCKFILE_BASE_SHA="$NOEMA_PR_BASE_SHA" \
node --input-type=module <<'NODE'
import { runLockfileChangeControl } from "./scripts/lockfile-change-control.mjs";

const result = runLockfileChangeControl();
if (!result.passed) {
for (const failure of result.failures) {
console.error(`::error::${failure}`);
}
process.exit(1);
}
console.log(`Lockfile change control passed for ${result.changedPackages.length} changed package node(s).`);
NODE

- name: install
run: npm ci
run: npm ci --legacy-peer-deps=false --install-links=false

- name: release verify
run: npm run release:verify

- name: refuse pull-request base drift after verification
if: github.event_name == 'pull_request'
shell: bash
env:
GH_TOKEN: ${{ github.token }}
NOEMA_PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
NOEMA_PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
set -euo pipefail
if [[ ! "$NOEMA_PR_BASE_SHA" =~ ^[0-9a-f]{40}$ ]]; then
printf '::error::Invalid pull-request base SHA.\n'
exit 1
fi
if [ -z "$NOEMA_PR_BASE_REF" ]; then
printf '::error::Pull-request base ref is unavailable.\n'
exit 1
fi
repository_name="${GITHUB_REPOSITORY#*/}"
live_base_sha="$(
gh api graphql \
-f query='query($owner:String!,$name:String!,$qualifiedName:String!){repository(owner:$owner,name:$name){ref(qualifiedName:$qualifiedName){target{oid}}}}' \
-F owner="$GITHUB_REPOSITORY_OWNER" \
-F name="$repository_name" \
-F qualifiedName="refs/heads/${NOEMA_PR_BASE_REF}" \
--jq '.data.repository.ref.target.oid'
)"
if [[ ! "$live_base_sha" =~ ^[0-9a-f]{40}$ ]]; then
printf '::error::Live pull-request base ref did not resolve to a full commit SHA.\n'
exit 1
fi
if [ "$live_base_sha" != "$NOEMA_PR_BASE_SHA" ]; then
printf '::error::Pull-request base branch advanced during verification from %s to %s.\n' \
"$NOEMA_PR_BASE_SHA" "$live_base_sha"
exit 1
fi
test "$live_base_sha" = "$NOEMA_PR_BASE_SHA"
13 changes: 13 additions & 0 deletions .github/workflows/reviewer-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,21 @@ jobs:
- name: checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
persist-credentials: false

- name: verify exact checkout
shell: bash
env:
NOEMA_EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
set -euo pipefail
if [[ ! "$NOEMA_EXPECTED_HEAD_SHA" =~ ^[0-9a-f]{40}$ ]]; then
printf '::error::Invalid expected head SHA.\n'
exit 1
fi
test "$(git rev-parse HEAD)" = "$NOEMA_EXPECTED_HEAD_SHA"

- name: setup python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
Expand Down
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
strict-allow-scripts=true
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Changelog

## Unreleased
- 개발 의존성 체인의 transitive `nanoid` lockfile resolution을 `3.3.16`에서 `3.3.17`로 최소 갱신하여 GHSA-2v37-7h3g-55p8 / CVE-2026-67213 보안 게이트를 복구한다. PostCSS의 선언 범위 `^3.3.16`과 다른 package metadata는 변경하지 않으며 audit waiver·ignore·severity 완화 없이 `npm ci`/`npm audit --audit-level=high`가 exact head에서 재검증되도록 유지한다.
- lockfile 재생성 도구 체인을 Node.js 24.19.0/npm 11.17.0으로 정확히 고정하고, `strict-allow-scripts=true` 아래 승인된 install-script identity만 실행하며 schema v2 exact-base lockfile change control로 package metadata drift를 실패-폐쇄한다. #76의 `nanoid@3.3.17` 보안 수정과 explicit `npm ci --legacy-peer-deps=false --install-links=false` 계약을 보존하고, package-manager/toolchain·install-script authority·vulnerability audit·review/merge authority를 별도 증거 계층으로 유지한다.
- `hourly-product-development`가 `NVIDIA_NIM_API_KEY`뿐 아니라 `NOEMA_MAINTAINER_APP_CLIENT_ID`와 `NOEMA_MAINTAINER_APP_PRIVATE_KEY` 존재를 checkout·OpenCode 설치·NVIDIA 호출 전에 검증한다. 게시 경로가 준비되지 않았으면 `maintainer_app_unavailable`로 실패 폐쇄하여 알려진 실패에 추론 비용을 쓰지 않으며, `dry_run`은 credential 없이 queue와 task contract를 검토하는 경로로 유지한다. 기존 reviewer App 및 `NOEMA_LLM_API_KEY`·`contextual-orchestrator` reviewer credential 경계는 변경하지 않는다.
- zero open pull requests일 때만 `NVIDIA_NIM_API_KEY` 전용 OpenCode 1.17.13 세션을 실행하는 proposal-only `hourly-product-development` 루프를 추가. minute-47 schedule·non-cancelling single flight·OpenCode binary SHA-256 pin·NVIDIA NIM model fallback·후보 실패 시 clean reset·GitHub/OIDC credential 제거·reviewer key 비참조·full release verification·40-file/500,000-byte proposal budget·trusted one-PR packaging을 강제한다. 각 후보 실행은 900초와 30초 kill grace로 제한하고, 실패 후 `npm ci --ignore-scripts` 재설치는 별도 60초와 10초 kill grace로 제한한다. 재설치가 실패하거나 시간 초과되면 불완전한 dependency tree로 다음 후보를 실행하지 않고 실패 폐쇄한다. 세 후보의 실행·종료 2,790초, 두 번의 후보 간 재설치 140초, 300초 setup/diagnostic reserve를 합친 3,230초가 55분(3,300초) job budget에 들어가며 70초 여유를 남긴다. 마지막 후보가 실패하면 불필요한 reset·clean·재설치를 생략하고 안정적인 전체 후보 실패 진단으로 곧바로 종료한다. 모델 실행, 제안 코드 검증, publication credential을 각각 별도의 GitHub-hosted runner로 분리하고, immutable artifact의 exact ID·workflow-run ID·archive digest와 patch SHA-256·base SHA·file/byte count를 교차 검증하며 symlink(`120000`)와 gitlink(`160000`)를 세 경계 모두에서 차단한다. 제안 코드를 실행한 runner에는 Maintainer App secret/token을 절대 제공하지 않고, 세 번째 non-executing publisher에서만 late-bound repository-scoped App token을 발급한다. merge/release/deploy authority는 기존 `hourly-commercial-readiness` exact-head governance에 유지하며, 운영 Runbook과 OpenCode/NVIDIA/GitHub Actions/NIST SP 800-218 근거를 APA 7th doctoring에 기록했다. package version은 release·deployment·production KPI evidence를 발행하지 않으므로 유지한다.
- `/health` liveness와 분리된 unauthenticated `GET`/`HEAD /ready` runtime readiness endpoint를 추가. GitHub Actions OIDC issuer·audience·organization/workflow binding·exact workflow ref·GitHub Cloud API origin·GitHub App identifiers·PKCS#8 private key를 외부 호출 없이 검증하며, 불완전한 설정은 secret/config value를 반사하지 않는 deterministic failure codes와 `503 ERR_SERVICE_NOT_READY`, `Retry-After`, no-store/nosniff/trace/latency headers로 실패-폐쇄한다. exact workflow named ref는 Git `check-ref-format`의 모호성·유효성 경계(`..`, `//`, dot-leading/`.lock` component, revision-expression 문자, trailing dot/slash 등)를 만족해야 하므로 GitHub가 실제로 표현할 수 없는 ref에서 false-ready가 발생하지 않는다. 배포 smoke contract가 liveness·runtime readiness·unauthenticated exchange challenge를 모두 요구하도록 확장하고 Kubernetes probe separation, RFC 9110, NIST SSDF, Git ref-format 근거를 APA 7th doctoring에 기록했다.
Expand Down
Loading
Loading