From 7a73e9e22d42d391c8d7a823bdf47ad2221cebc2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:13:55 +0900 Subject: [PATCH 01/19] test(ci): require Node 24 setup-node for fuzz workflow --- tests/unit/coverage-script-contract.test.mjs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/unit/coverage-script-contract.test.mjs b/tests/unit/coverage-script-contract.test.mjs index 149440e5..7c5ece1b 100644 --- a/tests/unit/coverage-script-contract.test.mjs +++ b/tests/unit/coverage-script-contract.test.mjs @@ -8,6 +8,10 @@ const packageJson = JSON.parse( readFileSync(new URL('../../package.json', import.meta.url), 'utf8'), ); const scripts = packageJson.scripts; +const fuzzWorkflow = readFileSync( + new URL('../../.github/workflows/fuzz.yml', import.meta.url), + 'utf8', +); assert.equal( scripts.coverage, @@ -45,4 +49,15 @@ assert.doesNotMatch( 'coverage cases never recursively invoke a coverage wrapper', ); +assert.match( + fuzzWorkflow, + /actions\/setup-node@820762786026740c76f36085b0efc47a31fe5020\s+# v7\.0\.0/, + 'property fuzz uses the immutable setup-node v7 runtime that declares node24', +); +assert.doesNotMatch( + fuzzWorkflow, + /actions\/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af/, + 'property fuzz no longer relies on the deprecated Node.js 20 setup-node runtime', +); + console.log('✓ coverage script contract tests passed'); From a92efde0ef0a4668432ef3c13466dcb948977345 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:16:38 +0900 Subject: [PATCH 02/19] fix(ci): run property fuzz setup action on Node 24 --- .github/workflows/fuzz.yml | 2 +- CHANGELOG.md | 4 ++ docs/doctoring/fuzz-setup-node-runtime.md | 45 +++++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 docs/doctoring/fuzz-setup-node-runtime.md diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 10f85b8b..41a91b55 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -38,7 +38,7 @@ jobs: persist-credentials: false - name: Set up Node.js - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '22.13.0' cache: 'npm' diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f784cc4..86f56b15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Moved the repository-owned property-fuzz setup action to immutable + `actions/setup-node` v7.0.0 so its JavaScript action runtime declares Node.js + 24 instead of relying on GitHub's compatibility override for deprecated + Node.js 20, while retaining Node.js 22.13.0 for ScopeWeave itself. - Accepted XML whitespace before exact Microsoft Project element delimiters while preserving the linear, regex-free import scanner and rejecting attributes, longer names, non-XML whitespace, nested unmatched blocks, and diff --git a/docs/doctoring/fuzz-setup-node-runtime.md b/docs/doctoring/fuzz-setup-node-runtime.md new file mode 100644 index 00000000..10341336 --- /dev/null +++ b/docs/doctoring/fuzz-setup-node-runtime.md @@ -0,0 +1,45 @@ +# Fuzz workflow Node.js action runtime + +## Status + +Implemented on active PR only until the change reaches protected `develop`. + +## Problem + +The repository-owned `Fuzz` workflow pinned `actions/setup-node` v4.1.0 at commit `39370e3970a6d050c480ffad4ff0ed4d3fdee5af`. Current GitHub-hosted runs warn that the action targets deprecated Node.js 20 and is being forced to run on Node.js 24. That compatibility override is runner behavior, not ScopeWeave-controlled evidence, and retaining the old action runtime creates avoidable future CI breakage risk. + +This is separate from the Node.js version used to execute ScopeWeave. The workflow continues to request Node.js `22.13.0` for the project-under-test; only the JavaScript runtime bundled by `actions/setup-node` changes. + +## Decision + +Pin the official `actions/setup-node` v7.0.0 release by immutable commit SHA `820762786026740c76f36085b0efc47a31fe5020` in `.github/workflows/fuzz.yml`. + +The official v7.0.0 `action.yml` declares `runs.using: node24`. The immutable pin preserves supply-chain provenance and avoids relying on mutable major-version tags. + +## Test-first evidence + +Test-only commit `7a73e9e22d42d391c8d7a823bdf47ad2221cebc2` added an executable workflow contract requiring the v7.0.0 immutable pin while production still used v4.1.0. Hosted Server Tests run `32019187595`, job `95355055643`, then failed at `tests/unit/coverage-script-contract.test.mjs:52` with the expected assertion that property fuzz must use the Node.js 24 setup-node runtime. + +The production repair changes only the setup-node action pin. It does not alter workflow permissions, the project Node version, npm install behavior, fuzz iteration budgets, or the fuzz command. + +## Verification contract + +The repaired exact PR head must prove all of the following before integration: + +- `unit-and-api` passes the workflow contract; +- `property fuzz` executes with the new immutable action pin and no Node.js 20 action-runtime deprecation warning attributable to `actions/setup-node`; +- the workflow still installs Node.js `22.13.0` for ScopeWeave; +- repository and organization-required security/review gates are evaluated on the same exact head; and +- any unrelated GitHub cache-service warning remains classified as infrastructure evidence rather than a source defect. + +## Rollback + +Reverting to the v4.1.0 pin would intentionally restore the deprecated action runtime and must not be used merely to silence an unrelated CI failure. If v7.0.0 exposes a verified compatibility defect, select a supported immutable setup-node revision that declares a current runner-supported JavaScript runtime and update this contract and evidence together. + +## References + +GitHub. (2025, September 19). *Deprecation of Node 20 on GitHub Actions runners*. GitHub Changelog. https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/ + +GitHub. (2026, July 14). *v7.0.0* [Software release]. GitHub, `actions/setup-node`. https://github.com/actions/setup-node/releases/tag/v7.0.0 + +GitHub. (2026). *actions/setup-node action metadata, v7.0.0 (`820762786026740c76f36085b0efc47a31fe5020`)* [Source code]. GitHub. https://github.com/actions/setup-node/blob/820762786026740c76f36085b0efc47a31fe5020/action.yml From 323cf370cd594e51eeef289e58496d5e0cb2a628 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:18:05 +0900 Subject: [PATCH 03/19] test(ci): require exact-head property fuzz checkout --- tests/unit/coverage-script-contract.test.mjs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/unit/coverage-script-contract.test.mjs b/tests/unit/coverage-script-contract.test.mjs index 7c5ece1b..10598aec 100644 --- a/tests/unit/coverage-script-contract.test.mjs +++ b/tests/unit/coverage-script-contract.test.mjs @@ -59,5 +59,18 @@ assert.doesNotMatch( /actions\/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af/, 'property fuzz no longer relies on the deprecated Node.js 20 setup-node runtime', ); +assert.ok( + fuzzWorkflow.includes('ref: ${{ github.event.pull_request.head.sha || github.sha }}'), + 'property fuzz checks out the exact contributor head rather than the synthetic pull-request merge', +); +assert.ok( + fuzzWorkflow.includes('EXPECTED_SHA: ${{ github.event.pull_request.head.sha || github.sha }}'), + 'property fuzz records the exact expected contributor SHA for checkout attestation', +); +assert.match( + fuzzWorkflow, + /git rev-parse HEAD[\s\S]*\$EXPECTED_SHA/, + 'property fuzz fails closed unless the checked-out commit matches the exact expected contributor SHA', +); console.log('✓ coverage script contract tests passed'); From b779d7c6c755fdd59ef0397e6b6510bdba37012f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:25:35 +0900 Subject: [PATCH 04/19] fix(ci): attest exact-head property fuzz checkout --- .github/workflows/fuzz.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 41a91b55..d037dc59 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -35,8 +35,19 @@ jobs: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} persist-credentials: false + - name: Verify exact checkout + env: + EXPECTED_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + run: | + actual_sha="$(git rev-parse HEAD)" + if [ "$actual_sha" != "$EXPECTED_SHA" ]; then + echo "::error::Fuzz checked out $actual_sha, expected $EXPECTED_SHA" + exit 1 + fi + - name: Set up Node.js uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: From d2546ca5831155c0f28a99ba4b46dcb6a4643431 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:37:50 +0900 Subject: [PATCH 05/19] test(ci): require exact-head OSV evidence --- tests/unit/coverage-script-contract.test.mjs | 30 +++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/tests/unit/coverage-script-contract.test.mjs b/tests/unit/coverage-script-contract.test.mjs index 10598aec..b2f7f268 100644 --- a/tests/unit/coverage-script-contract.test.mjs +++ b/tests/unit/coverage-script-contract.test.mjs @@ -12,6 +12,10 @@ const fuzzWorkflow = readFileSync( new URL('../../.github/workflows/fuzz.yml', import.meta.url), 'utf8', ); +const osvWorkflow = readFileSync( + new URL('../../.github/workflows/osvscanner.yml', import.meta.url), + 'utf8', +); assert.equal( scripts.coverage, @@ -73,4 +77,28 @@ assert.match( 'property fuzz fails closed unless the checked-out commit matches the exact expected contributor SHA', ); -console.log('✓ coverage script contract tests passed'); +assert.doesNotMatch( + osvWorkflow, + /osv-scanner-reusable-pr\.yml/, + 'OSV evidence must not delegate PR checkout authority to a reusable workflow that scans GITHUB_SHA', +); +assert.ok( + osvWorkflow.includes('HEAD_SHA: ${{ github.event.pull_request.head.sha }}'), + 'OSV scanning binds the new-code scan to the exact contributor head', +); +assert.ok( + osvWorkflow.includes('BASE_REF: ${{ github.event.pull_request.base.ref }}'), + 'OSV scanning records the protected base ref so it can resolve the live tip independently', +); +assert.match( + osvWorkflow, + /git fetch --no-tags origin[\s\S]*refs\/heads\/\$\{BASE_REF\}:refs\/remotes\/origin\/\$\{BASE_REF\}/, + 'OSV scanning resolves the live protected base tip rather than trusting the PR base snapshot', +); +assert.match( + osvWorkflow, + /git checkout --detach "\$HEAD_SHA"[\s\S]*git rev-parse HEAD[\s\S]*\$HEAD_SHA/, + 'OSV scanning checks out and attests the exact contributor head before the new-code scan', +); + +console.log('✓ coverage script contract tests passed'); \ No newline at end of file From 5ab1ad88fce00c4b8279f40170d7280f64e46120 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:39:35 +0900 Subject: [PATCH 06/19] fix(ci): bind OSV scans to live base and exact head --- .github/workflows/osvscanner.yml | 136 ++++++++++++++++++++++++++++--- 1 file changed, 126 insertions(+), 10 deletions(-) diff --git a/.github/workflows/osvscanner.yml b/.github/workflows/osvscanner.yml index 806c8086..3ab24bab 100644 --- a/.github/workflows/osvscanner.yml +++ b/.github/workflows/osvscanner.yml @@ -15,20 +15,136 @@ concurrency: jobs: osv-scan: if: github.event_name == 'pull_request' - # Companion SCA lane for manifest evidence. Central .github still owns the - # required review/security/scheduler workflows. - uses: google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@3a7550f43ba5b58905a821ce3a0ed24c4858b3f4 # v2.3.8 + export-results gate + runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write - with: - scan-args: |- - --maven-registry=https://maven-central.storage-download.googleapis.com/maven2 - --no-resolve - -r - ./ - fail-on-vuln: false + env: + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + BASE_REF: ${{ github.event.pull_request.base.ref }} + steps: + - name: Checkout exact contributor head + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + persist-credentials: false + + - name: Resolve live protected base and attest contributor head + id: revisions + shell: bash + run: | + set -euo pipefail + + if ! [[ "$HEAD_SHA" =~ ^[0-9a-f]{40}$ ]]; then + echo "::error::Invalid contributor head SHA" + exit 1 + fi + git check-ref-format --branch "$BASE_REF" >/dev/null + + actual_head="$(git rev-parse HEAD)" + if [ "$actual_head" != "$HEAD_SHA" ]; then + echo "::error::OSV checkout resolved $actual_head, expected contributor head $HEAD_SHA" + exit 1 + fi + + git fetch --no-tags origin \ + "refs/heads/${BASE_REF}:refs/remotes/origin/${BASE_REF}" + live_base_sha="$(git rev-parse "refs/remotes/origin/${BASE_REF}")" + if ! [[ "$live_base_sha" =~ ^[0-9a-f]{40}$ ]]; then + echo "::error::Could not resolve live protected base" + exit 1 + fi + echo "live_base_sha=$live_base_sha" >> "$GITHUB_OUTPUT" + + - name: Checkout live protected base + env: + LIVE_BASE_SHA: ${{ steps.revisions.outputs.live_base_sha }} + shell: bash + run: | + set -euo pipefail + git checkout --detach "$LIVE_BASE_SHA" + actual_sha="$(git rev-parse HEAD)" + if [ "$actual_sha" != "$LIVE_BASE_SHA" ]; then + echo "::error::OSV base checkout resolved $actual_sha, expected $LIVE_BASE_SHA" + exit 1 + fi + + - name: Scan live protected base + uses: google/osv-scanner-action/osv-scanner-action@8dc09193bb540e09b23da07ad7e30bd33bf87018 # v2.3.8 + continue-on-error: true + with: + scan-args: |- + --format=json + --output=old-results.json + --maven-registry=https://maven-central.storage-download.googleapis.com/maven2 + --no-resolve + -r + ./ + + - name: Checkout and attest exact contributor head + shell: bash + run: | + set -euo pipefail + git checkout --detach "$HEAD_SHA" + actual_sha="$(git rev-parse HEAD)" + if [ "$actual_sha" != "$HEAD_SHA" ]; then + echo "::error::OSV contributor checkout resolved $actual_sha, expected $HEAD_SHA" + exit 1 + fi + + - name: Scan exact contributor head + uses: google/osv-scanner-action/osv-scanner-action@8dc09193bb540e09b23da07ad7e30bd33bf87018 # v2.3.8 + continue-on-error: true + with: + scan-args: |- + --format=json + --output=new-results.json + --maven-registry=https://maven-central.storage-download.googleapis.com/maven2 + --no-resolve + -r + ./ + + - name: Compare OSV results + uses: google/osv-scanner-action/osv-reporter-action@8dc09193bb540e09b23da07ad7e30bd33bf87018 # v2.3.8 + with: + scan-args: |- + --output=results.sarif + --old=old-results.json + --new=new-results.json + --gh-annotations=true + --fail-on-vuln=false + + - name: Upload OSV SARIF artifact + if: ${{ !cancelled() }} + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: OSV Scanner SARIF file + path: results.sarif + retention-days: 5 + + - name: Upload live-base OSV JSON + if: ${{ !cancelled() }} + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: old-json-results + path: old-results.json + retention-days: 5 + + - name: Upload exact-head OSV JSON + if: ${{ !cancelled() }} + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: new-json-results + path: new-results.json + retention-days: 5 + + - name: Upload OSV results to code scanning + if: ${{ !cancelled() }} + uses: github/codeql-action/upload-sarif@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10 + with: + sarif_file: results.sarif manifest-pattern-coverage: if: github.event_name == 'workflow_dispatch' From 4d16ac8150a598182813684337a88069690c529d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:40:20 +0900 Subject: [PATCH 07/19] docs(ci): record exact-head OSV evidence boundary --- docs/doctoring/exact-head-osv-evidence.md | 64 +++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 docs/doctoring/exact-head-osv-evidence.md diff --git a/docs/doctoring/exact-head-osv-evidence.md b/docs/doctoring/exact-head-osv-evidence.md new file mode 100644 index 00000000..b801497d --- /dev/null +++ b/docs/doctoring/exact-head-osv-evidence.md @@ -0,0 +1,64 @@ +# Exact-head OSV evidence binding + +## Status + +Implemented on active PR only until the change reaches protected `develop`. + +## Problem + +ScopeWeave's repository-owned `OSV Scanner` wrapper delegated pull-request scanning to the pinned `google/osv-scanner-action` reusable PR workflow. That upstream workflow checks out the target branch for the old-code scan and then checks out `$GITHUB_SHA` for the new-code scan. + +For a workflow triggered by `pull_request`, GitHub defines `GITHUB_SHA` as the pull request's synthetic merge commit rather than the contributor branch head. A green reusable-workflow result therefore proved that OSV scanned a merge synthesis, not necessarily the unchanged contributor SHA used for review and release decisions. + +The same evidence path also relied on the reusable workflow's branch checkout for its old-code comparison. ScopeWeave's governance contract distinguishes a pull request's historical base snapshot from the independently resolved current protected-base tip, so the repository-owned wrapper must bind both identities explicitly. + +This is an evidence-integrity defect in ScopeWeave's wrapper, not a vulnerability in application source and not a reason to patch the upstream OSV Scanner project. + +## Decision + +Keep the immutable OSV Scanner action/reporter revisions, scan arguments, SARIF upload, and existing `fail-on-vuln=false` companion-SCA policy, but own the revision-selection steps locally in `.github/workflows/osvscanner.yml`. + +For every pull-request run the workflow now: + +1. reads the exact contributor SHA from `github.event.pull_request.head.sha`; +2. reads the protected base branch name from `github.event.pull_request.base.ref`; +3. checks out and attests the exact contributor SHA; +4. independently fetches the current protected base ref and resolves its live commit; +5. checks out and attests that live base before creating `old-results.json`; +6. checks out and attests the exact contributor SHA again before creating `new-results.json`; +7. compares those two explicit results with the pinned OSV reporter; and +8. uploads bounded SARIF and JSON evidence without persisting checkout credentials. + +The workflow remains on `pull_request`, not `pull_request_target`, and does not materialize repository secrets or broaden application authority. + +## Test-first evidence + +Test-only commit `d2546ca5831155c0f28a99ba4b46dcb6a4643431` strengthened `tests/unit/coverage-script-contract.test.mjs` before the OSV workflow changed. The contract rejected delegation to `osv-scanner-reusable-pr.yml` and required explicit exact-head and live-base resolution/attestation. + +Hosted Server Tests run `32021071089` then failed on that test-only head while the production OSV wrapper still delegated revision selection to the reusable workflow. This is the executed RED evidence for the defect. + +Production commit `5ab1ad88fce00c4b8279f40170d7280f64e46120` replaced only the repository-owned OSV wrapper's revision-selection/orchestration layer. Exact-current-head GREEN evidence must be regenerated after this documentation commit; predecessor runs do not authorize integration. + +## Security and failure contract + +- Invalid contributor SHA or base ref fails closed before scanning. +- A checkout that does not resolve to the expected immutable SHA fails closed. +- The live protected base is independently fetched immediately before the old-code scan. +- Scanner exit status remains input to the pinned reporter; scanner execution is not replaced with a synthetic success path. +- OSV evidence remains a companion SCA lane. Central organization security/review gates retain their separate authority. +- No credentials, tokens, provider payloads, or customer data are added to logs or artifacts by this repair. +- A later upstream reusable-workflow revision may be reconsidered only if its exact-head/live-base semantics are explicitly compatible with ScopeWeave's evidence contract. + +## Rollback + +Do not restore the old reusable PR workflow merely to obtain a green status. If the local orchestration exposes a verified incompatibility, keep exact identity binding fail-closed and repair the smallest affected checkout/scanner/reporter step while preserving the immutable action pins and evidence identities. + +## References + +GitHub. (n.d.). *Events that trigger workflows*. GitHub Docs. https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows + +GitHub. (n.d.). *Variables reference*. GitHub Docs. https://docs.github.com/en/actions/reference/workflows-and-actions/variables + +Google LLC. (2026). *OSV-Scanner PR scanning reusable workflow (`3a7550f43ba5b58905a821ce3a0ed24c4858b3f4`)* [Source code]. GitHub. https://github.com/google/osv-scanner-action/blob/3a7550f43ba5b58905a821ce3a0ed24c4858b3f4/.github/workflows/osv-scanner-reusable-pr.yml + +Google LLC. (2026). *OSV-Scanner GitHub Action* [Source code]. GitHub. https://github.com/google/osv-scanner-action From 4a9fd92bfdfaa90452df8327ff7b70f6f12a800b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:40:57 +0900 Subject: [PATCH 08/19] docs(ci): record exact-head OSV scan evidence --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86f56b15..137bc347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `actions/setup-node` v7.0.0 so its JavaScript action runtime declares Node.js 24 instead of relying on GitHub's compatibility override for deprecated Node.js 20, while retaining Node.js 22.13.0 for ScopeWeave itself. +- Bound repository-owned OSV old/new dependency scans to an independently + resolved live protected-base commit and the exact pull-request contributor + head, with SHA attestations before both scans, instead of treating a synthetic + pull-request merge commit as exact-head security evidence. - Accepted XML whitespace before exact Microsoft Project element delimiters while preserving the linear, regex-free import scanner and rejecting attributes, longer names, non-XML whitespace, nested unmatched blocks, and @@ -102,4 +106,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.1] - 2026-06-25 ### 성능 개선 (Performance) -- 드래그 앤 드롭 동작 중 `dragover` 이벤트에서 발생하는 O(N) 작업 리스트 검색 성능 병목 문제를, O(1) 해시맵(Map) 기반의 캐싱 조회 로직으로 개선하여 큰 크기의 WBS 리스트에서의 버벅임 현상을 해결했습니다. +- 드래그 앤 드롭 동작 중 `dragover` 이벤트에서 발생하던 O(N) 작업 리스트 검색 성능 병목 문제를 O(1) 해시맵(Map) 기반 캐싱 조회 로직으로 개선하여 큰 WBS 리스트에서의 버벅임을 줄였습니다. From ea7d747dc4aa084d2afd5710c4959e5a49e136f2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:41:31 +0900 Subject: [PATCH 09/19] fix(docs): preserve published 1.0.1 release note --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 137bc347..c28048ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -106,4 +106,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.1] - 2026-06-25 ### 성능 개선 (Performance) -- 드래그 앤 드롭 동작 중 `dragover` 이벤트에서 발생하던 O(N) 작업 리스트 검색 성능 병목 문제를 O(1) 해시맵(Map) 기반 캐싱 조회 로직으로 개선하여 큰 WBS 리스트에서의 버벅임을 줄였습니다. +- 드래그 앤 드롭 동작 중 `dragover` 이벤트에서 발생하는 O(N) 작업 리스트 검색 성능 병목 문제를, O(1) 해시맵(Map) 기반의 캐싱 조회 로직으로 개선하여 큰 크기의 WBS 리스트에서의 버벅임 현상을 해결했습니다. From 9ecccb04ab36d6687cd87c83dd72343227415908 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:42:55 +0900 Subject: [PATCH 10/19] test(ci): require exact-head CodeQL checkout --- tests/unit/coverage-script-contract.test.mjs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/unit/coverage-script-contract.test.mjs b/tests/unit/coverage-script-contract.test.mjs index b2f7f268..9e0c7dcb 100644 --- a/tests/unit/coverage-script-contract.test.mjs +++ b/tests/unit/coverage-script-contract.test.mjs @@ -16,6 +16,10 @@ const osvWorkflow = readFileSync( new URL('../../.github/workflows/osvscanner.yml', import.meta.url), 'utf8', ); +const codeqlWorkflow = readFileSync( + new URL('../../.github/workflows/codeql.yml', import.meta.url), + 'utf8', +); assert.equal( scripts.coverage, @@ -101,4 +105,18 @@ assert.match( 'OSV scanning checks out and attests the exact contributor head before the new-code scan', ); +assert.ok( + codeqlWorkflow.includes('ref: ${{ github.event.pull_request.head.sha || github.sha }}'), + 'CodeQL checks out the exact contributor head on pull requests rather than the synthetic merge', +); +assert.ok( + codeqlWorkflow.includes('EXPECTED_SHA: ${{ github.event.pull_request.head.sha || github.sha }}'), + 'CodeQL records the exact expected source SHA before initialization', +); +assert.match( + codeqlWorkflow, + /git rev-parse HEAD[\s\S]*\$EXPECTED_SHA[\s\S]*Initialize CodeQL/, + 'CodeQL attests the exact checkout before initializing the database', +); + console.log('✓ coverage script contract tests passed'); \ No newline at end of file From e9385e4ad5c3249fec13272d9831cc0b263fe02e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:43:23 +0900 Subject: [PATCH 11/19] fix(ci): attest exact-head CodeQL checkout --- .github/workflows/codeql.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1a9461d5..bf76f872 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -32,8 +32,21 @@ jobs: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} persist-credentials: false + - name: Verify exact checkout + env: + EXPECTED_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + shell: bash + run: | + set -euo pipefail + actual_sha="$(git rev-parse HEAD)" + if [ "$actual_sha" != "$EXPECTED_SHA" ]; then + echo "::error::CodeQL checked out $actual_sha, expected $EXPECTED_SHA" + exit 1 + fi + - name: Initialize CodeQL uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 with: From 124ba2d83836e68c675ad36ab2aab67c13ed671c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:43:50 +0900 Subject: [PATCH 12/19] docs(ci): record exact-head CodeQL evidence boundary --- docs/doctoring/exact-head-codeql-evidence.md | 58 ++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 docs/doctoring/exact-head-codeql-evidence.md diff --git a/docs/doctoring/exact-head-codeql-evidence.md b/docs/doctoring/exact-head-codeql-evidence.md new file mode 100644 index 00000000..7b22b507 --- /dev/null +++ b/docs/doctoring/exact-head-codeql-evidence.md @@ -0,0 +1,58 @@ +# Exact-head CodeQL evidence binding + +## Status + +Implemented on active PR only until the change reaches protected `develop`. + +## Problem + +ScopeWeave's repository-owned CodeQL workflow used the default `actions/checkout` ref. For a workflow triggered by `pull_request`, GitHub binds the default source revision to the pull request merge ref and exposes the synthetic merge commit through `GITHUB_SHA`. + +The protected branch rules require the two repository CodeQL contexts, `Analyze (javascript-typescript)` and `Analyze (python)`. Treating those checks as exact-head security evidence while their database was initialized from a synthetic merge revision creates the same evidence-authority mismatch already repaired in the repository-owned fuzz and OSV lanes. + +This is a ScopeWeave workflow-integrity defect, not a source vulnerability and not a reason to weaken CodeQL or branch protection. + +## Decision + +Before CodeQL initialization, `.github/workflows/codeql.yml` now: + +1. checks out `${{ github.event.pull_request.head.sha || github.sha }}` explicitly; +2. retains `persist-credentials: false`; +3. records the same expression as `EXPECTED_SHA`; +4. compares `git rev-parse HEAD` with that immutable expected revision; and +5. fails closed before `github/codeql-action/init` when the identities differ. + +For protected push and scheduled execution, the fallback remains the event's exact `github.sha`. The CodeQL language matrix, action revisions, categories, and permissions remain unchanged. + +## Test-first evidence + +Test-only commit `9ecccb04ab36d6687cd87c83dd72343227415908` strengthened `tests/unit/coverage-script-contract.test.mjs` while the production CodeQL workflow still used default checkout behavior. The regression requires explicit source ref selection and attestation before CodeQL initialization. + +The immediate production repair superseded the hosted Server Tests run for that test-only head before it completed, so cancelled predecessor evidence is not promoted as executed RED proof. The source-ordering remains test-first: the executable contract was committed before the workflow repair. + +Production commit `e9385e4ad5c3249fec13272d9831cc0b263fe02e` implements the narrow exact-checkout and pre-initialization attestation. Fresh exact-current-head repository and organization evidence must pass after this documentation commit; predecessor CodeQL results do not transfer. + +## Verification contract + +Before integration: + +- the workflow contract must pass under `unit-and-api`; +- both CodeQL matrix jobs must run against the exact current contributor head on the pull request; +- the SHA attestation must execute before CodeQL initialization; +- no workflow permission is broadened; +- exact-head review/security evidence remains separate from synthetic mergeability evidence; and +- any contributor-head or protected-base movement invalidates head/base-sensitive conclusions and triggers fresh evidence. + +## Rollback + +Do not restore default merge-ref checkout merely to make a required CodeQL status green. A verified action/runtime compatibility problem should be repaired while keeping explicit source identity and fail-closed attestation intact. + +## References + +GitHub. (n.d.). *Events that trigger workflows*. GitHub Docs. https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows + +GitHub. (n.d.). *Variables reference*. GitHub Docs. https://docs.github.com/en/actions/reference/workflows-and-actions/variables + +GitHub. (n.d.). *Configuring default setup for code scanning*. GitHub Docs. https://docs.github.com/en/code-security/code-scanning/enabling-code-scanning/configuring-default-setup-for-code-scanning + +GitHub. (2026). *CodeQL Action* [Source code]. GitHub. https://github.com/github/codeql-action From 5f3cfe8cf6da069b2ab6149ecc739b021b7d4bd4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:44:18 +0900 Subject: [PATCH 13/19] docs(ci): record exact-head CodeQL analysis --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c28048ab..767276fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 resolved live protected-base commit and the exact pull-request contributor head, with SHA attestations before both scans, instead of treating a synthetic pull-request merge commit as exact-head security evidence. +- Bound both required repository CodeQL matrix analyses to the exact contributor + head on pull requests and fail closed on a checkout mismatch before CodeQL + initialization instead of treating a synthetic merge checkout as exact-head + security evidence. - Accepted XML whitespace before exact Microsoft Project element delimiters while preserving the linear, regex-free import scanner and rejecting attributes, longer names, non-XML whitespace, nested unmatched blocks, and From 77e1997b20595f5eb566e5bd49b0ccbefdf39fc3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:45:20 +0900 Subject: [PATCH 14/19] chore(ci): keep OSV exact-head work on canonical PR 523 --- .github/workflows/osvscanner.yml | 136 +++---------------------------- 1 file changed, 10 insertions(+), 126 deletions(-) diff --git a/.github/workflows/osvscanner.yml b/.github/workflows/osvscanner.yml index 3ab24bab..806c8086 100644 --- a/.github/workflows/osvscanner.yml +++ b/.github/workflows/osvscanner.yml @@ -15,136 +15,20 @@ concurrency: jobs: osv-scan: if: github.event_name == 'pull_request' - runs-on: ubuntu-latest + # Companion SCA lane for manifest evidence. Central .github still owns the + # required review/security/scheduler workflows. + uses: google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@3a7550f43ba5b58905a821ce3a0ed24c4858b3f4 # v2.3.8 + export-results gate permissions: actions: read contents: read security-events: write - env: - HEAD_SHA: ${{ github.event.pull_request.head.sha }} - BASE_REF: ${{ github.event.pull_request.base.ref }} - steps: - - name: Checkout exact contributor head - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 0 - persist-credentials: false - - - name: Resolve live protected base and attest contributor head - id: revisions - shell: bash - run: | - set -euo pipefail - - if ! [[ "$HEAD_SHA" =~ ^[0-9a-f]{40}$ ]]; then - echo "::error::Invalid contributor head SHA" - exit 1 - fi - git check-ref-format --branch "$BASE_REF" >/dev/null - - actual_head="$(git rev-parse HEAD)" - if [ "$actual_head" != "$HEAD_SHA" ]; then - echo "::error::OSV checkout resolved $actual_head, expected contributor head $HEAD_SHA" - exit 1 - fi - - git fetch --no-tags origin \ - "refs/heads/${BASE_REF}:refs/remotes/origin/${BASE_REF}" - live_base_sha="$(git rev-parse "refs/remotes/origin/${BASE_REF}")" - if ! [[ "$live_base_sha" =~ ^[0-9a-f]{40}$ ]]; then - echo "::error::Could not resolve live protected base" - exit 1 - fi - echo "live_base_sha=$live_base_sha" >> "$GITHUB_OUTPUT" - - - name: Checkout live protected base - env: - LIVE_BASE_SHA: ${{ steps.revisions.outputs.live_base_sha }} - shell: bash - run: | - set -euo pipefail - git checkout --detach "$LIVE_BASE_SHA" - actual_sha="$(git rev-parse HEAD)" - if [ "$actual_sha" != "$LIVE_BASE_SHA" ]; then - echo "::error::OSV base checkout resolved $actual_sha, expected $LIVE_BASE_SHA" - exit 1 - fi - - - name: Scan live protected base - uses: google/osv-scanner-action/osv-scanner-action@8dc09193bb540e09b23da07ad7e30bd33bf87018 # v2.3.8 - continue-on-error: true - with: - scan-args: |- - --format=json - --output=old-results.json - --maven-registry=https://maven-central.storage-download.googleapis.com/maven2 - --no-resolve - -r - ./ - - - name: Checkout and attest exact contributor head - shell: bash - run: | - set -euo pipefail - git checkout --detach "$HEAD_SHA" - actual_sha="$(git rev-parse HEAD)" - if [ "$actual_sha" != "$HEAD_SHA" ]; then - echo "::error::OSV contributor checkout resolved $actual_sha, expected $HEAD_SHA" - exit 1 - fi - - - name: Scan exact contributor head - uses: google/osv-scanner-action/osv-scanner-action@8dc09193bb540e09b23da07ad7e30bd33bf87018 # v2.3.8 - continue-on-error: true - with: - scan-args: |- - --format=json - --output=new-results.json - --maven-registry=https://maven-central.storage-download.googleapis.com/maven2 - --no-resolve - -r - ./ - - - name: Compare OSV results - uses: google/osv-scanner-action/osv-reporter-action@8dc09193bb540e09b23da07ad7e30bd33bf87018 # v2.3.8 - with: - scan-args: |- - --output=results.sarif - --old=old-results.json - --new=new-results.json - --gh-annotations=true - --fail-on-vuln=false - - - name: Upload OSV SARIF artifact - if: ${{ !cancelled() }} - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - with: - name: OSV Scanner SARIF file - path: results.sarif - retention-days: 5 - - - name: Upload live-base OSV JSON - if: ${{ !cancelled() }} - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - with: - name: old-json-results - path: old-results.json - retention-days: 5 - - - name: Upload exact-head OSV JSON - if: ${{ !cancelled() }} - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - with: - name: new-json-results - path: new-results.json - retention-days: 5 - - - name: Upload OSV results to code scanning - if: ${{ !cancelled() }} - uses: github/codeql-action/upload-sarif@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10 - with: - sarif_file: results.sarif + with: + scan-args: |- + --maven-registry=https://maven-central.storage-download.googleapis.com/maven2 + --no-resolve + -r + ./ + fail-on-vuln: false manifest-pattern-coverage: if: github.event_name == 'workflow_dispatch' From 7318c5237d94676c3ec8d53114d0853cf6b58179 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:45:36 +0900 Subject: [PATCH 15/19] chore(ci): keep CodeQL exact-head work on canonical PR 523 --- .github/workflows/codeql.yml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index bf76f872..1a9461d5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -32,21 +32,8 @@ jobs: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} persist-credentials: false - - name: Verify exact checkout - env: - EXPECTED_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - shell: bash - run: | - set -euo pipefail - actual_sha="$(git rev-parse HEAD)" - if [ "$actual_sha" != "$EXPECTED_SHA" ]; then - echo "::error::CodeQL checked out $actual_sha, expected $EXPECTED_SHA" - exit 1 - fi - - name: Initialize CodeQL uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 with: From 88f3c004dc67acaa5607d218b43fc8d4d6e594c4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:45:55 +0900 Subject: [PATCH 16/19] chore(ci): keep shared exact-head contracts on canonical PR 523 --- tests/unit/coverage-script-contract.test.mjs | 46 -------------------- 1 file changed, 46 deletions(-) diff --git a/tests/unit/coverage-script-contract.test.mjs b/tests/unit/coverage-script-contract.test.mjs index 9e0c7dcb..e70ec67c 100644 --- a/tests/unit/coverage-script-contract.test.mjs +++ b/tests/unit/coverage-script-contract.test.mjs @@ -12,14 +12,6 @@ const fuzzWorkflow = readFileSync( new URL('../../.github/workflows/fuzz.yml', import.meta.url), 'utf8', ); -const osvWorkflow = readFileSync( - new URL('../../.github/workflows/osvscanner.yml', import.meta.url), - 'utf8', -); -const codeqlWorkflow = readFileSync( - new URL('../../.github/workflows/codeql.yml', import.meta.url), - 'utf8', -); assert.equal( scripts.coverage, @@ -81,42 +73,4 @@ assert.match( 'property fuzz fails closed unless the checked-out commit matches the exact expected contributor SHA', ); -assert.doesNotMatch( - osvWorkflow, - /osv-scanner-reusable-pr\.yml/, - 'OSV evidence must not delegate PR checkout authority to a reusable workflow that scans GITHUB_SHA', -); -assert.ok( - osvWorkflow.includes('HEAD_SHA: ${{ github.event.pull_request.head.sha }}'), - 'OSV scanning binds the new-code scan to the exact contributor head', -); -assert.ok( - osvWorkflow.includes('BASE_REF: ${{ github.event.pull_request.base.ref }}'), - 'OSV scanning records the protected base ref so it can resolve the live tip independently', -); -assert.match( - osvWorkflow, - /git fetch --no-tags origin[\s\S]*refs\/heads\/\$\{BASE_REF\}:refs\/remotes\/origin\/\$\{BASE_REF\}/, - 'OSV scanning resolves the live protected base tip rather than trusting the PR base snapshot', -); -assert.match( - osvWorkflow, - /git checkout --detach "\$HEAD_SHA"[\s\S]*git rev-parse HEAD[\s\S]*\$HEAD_SHA/, - 'OSV scanning checks out and attests the exact contributor head before the new-code scan', -); - -assert.ok( - codeqlWorkflow.includes('ref: ${{ github.event.pull_request.head.sha || github.sha }}'), - 'CodeQL checks out the exact contributor head on pull requests rather than the synthetic merge', -); -assert.ok( - codeqlWorkflow.includes('EXPECTED_SHA: ${{ github.event.pull_request.head.sha || github.sha }}'), - 'CodeQL records the exact expected source SHA before initialization', -); -assert.match( - codeqlWorkflow, - /git rev-parse HEAD[\s\S]*\$EXPECTED_SHA[\s\S]*Initialize CodeQL/, - 'CodeQL attests the exact checkout before initializing the database', -); - console.log('✓ coverage script contract tests passed'); \ No newline at end of file From 19ff9edba899c8743c1c21162aa41f5be2dc80f9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:46:09 +0900 Subject: [PATCH 17/19] chore(ci): defer OSV doctoring to canonical PR 523 --- docs/doctoring/exact-head-osv-evidence.md | 64 ----------------------- 1 file changed, 64 deletions(-) delete mode 100644 docs/doctoring/exact-head-osv-evidence.md diff --git a/docs/doctoring/exact-head-osv-evidence.md b/docs/doctoring/exact-head-osv-evidence.md deleted file mode 100644 index b801497d..00000000 --- a/docs/doctoring/exact-head-osv-evidence.md +++ /dev/null @@ -1,64 +0,0 @@ -# Exact-head OSV evidence binding - -## Status - -Implemented on active PR only until the change reaches protected `develop`. - -## Problem - -ScopeWeave's repository-owned `OSV Scanner` wrapper delegated pull-request scanning to the pinned `google/osv-scanner-action` reusable PR workflow. That upstream workflow checks out the target branch for the old-code scan and then checks out `$GITHUB_SHA` for the new-code scan. - -For a workflow triggered by `pull_request`, GitHub defines `GITHUB_SHA` as the pull request's synthetic merge commit rather than the contributor branch head. A green reusable-workflow result therefore proved that OSV scanned a merge synthesis, not necessarily the unchanged contributor SHA used for review and release decisions. - -The same evidence path also relied on the reusable workflow's branch checkout for its old-code comparison. ScopeWeave's governance contract distinguishes a pull request's historical base snapshot from the independently resolved current protected-base tip, so the repository-owned wrapper must bind both identities explicitly. - -This is an evidence-integrity defect in ScopeWeave's wrapper, not a vulnerability in application source and not a reason to patch the upstream OSV Scanner project. - -## Decision - -Keep the immutable OSV Scanner action/reporter revisions, scan arguments, SARIF upload, and existing `fail-on-vuln=false` companion-SCA policy, but own the revision-selection steps locally in `.github/workflows/osvscanner.yml`. - -For every pull-request run the workflow now: - -1. reads the exact contributor SHA from `github.event.pull_request.head.sha`; -2. reads the protected base branch name from `github.event.pull_request.base.ref`; -3. checks out and attests the exact contributor SHA; -4. independently fetches the current protected base ref and resolves its live commit; -5. checks out and attests that live base before creating `old-results.json`; -6. checks out and attests the exact contributor SHA again before creating `new-results.json`; -7. compares those two explicit results with the pinned OSV reporter; and -8. uploads bounded SARIF and JSON evidence without persisting checkout credentials. - -The workflow remains on `pull_request`, not `pull_request_target`, and does not materialize repository secrets or broaden application authority. - -## Test-first evidence - -Test-only commit `d2546ca5831155c0f28a99ba4b46dcb6a4643431` strengthened `tests/unit/coverage-script-contract.test.mjs` before the OSV workflow changed. The contract rejected delegation to `osv-scanner-reusable-pr.yml` and required explicit exact-head and live-base resolution/attestation. - -Hosted Server Tests run `32021071089` then failed on that test-only head while the production OSV wrapper still delegated revision selection to the reusable workflow. This is the executed RED evidence for the defect. - -Production commit `5ab1ad88fce00c4b8279f40170d7280f64e46120` replaced only the repository-owned OSV wrapper's revision-selection/orchestration layer. Exact-current-head GREEN evidence must be regenerated after this documentation commit; predecessor runs do not authorize integration. - -## Security and failure contract - -- Invalid contributor SHA or base ref fails closed before scanning. -- A checkout that does not resolve to the expected immutable SHA fails closed. -- The live protected base is independently fetched immediately before the old-code scan. -- Scanner exit status remains input to the pinned reporter; scanner execution is not replaced with a synthetic success path. -- OSV evidence remains a companion SCA lane. Central organization security/review gates retain their separate authority. -- No credentials, tokens, provider payloads, or customer data are added to logs or artifacts by this repair. -- A later upstream reusable-workflow revision may be reconsidered only if its exact-head/live-base semantics are explicitly compatible with ScopeWeave's evidence contract. - -## Rollback - -Do not restore the old reusable PR workflow merely to obtain a green status. If the local orchestration exposes a verified incompatibility, keep exact identity binding fail-closed and repair the smallest affected checkout/scanner/reporter step while preserving the immutable action pins and evidence identities. - -## References - -GitHub. (n.d.). *Events that trigger workflows*. GitHub Docs. https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows - -GitHub. (n.d.). *Variables reference*. GitHub Docs. https://docs.github.com/en/actions/reference/workflows-and-actions/variables - -Google LLC. (2026). *OSV-Scanner PR scanning reusable workflow (`3a7550f43ba5b58905a821ce3a0ed24c4858b3f4`)* [Source code]. GitHub. https://github.com/google/osv-scanner-action/blob/3a7550f43ba5b58905a821ce3a0ed24c4858b3f4/.github/workflows/osv-scanner-reusable-pr.yml - -Google LLC. (2026). *OSV-Scanner GitHub Action* [Source code]. GitHub. https://github.com/google/osv-scanner-action From 5d0a7b1cd5763085f0958c0a22e6c5a80bdf3bfd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:46:24 +0900 Subject: [PATCH 18/19] chore(ci): defer CodeQL doctoring to canonical PR 523 --- docs/doctoring/exact-head-codeql-evidence.md | 58 -------------------- 1 file changed, 58 deletions(-) delete mode 100644 docs/doctoring/exact-head-codeql-evidence.md diff --git a/docs/doctoring/exact-head-codeql-evidence.md b/docs/doctoring/exact-head-codeql-evidence.md deleted file mode 100644 index 7b22b507..00000000 --- a/docs/doctoring/exact-head-codeql-evidence.md +++ /dev/null @@ -1,58 +0,0 @@ -# Exact-head CodeQL evidence binding - -## Status - -Implemented on active PR only until the change reaches protected `develop`. - -## Problem - -ScopeWeave's repository-owned CodeQL workflow used the default `actions/checkout` ref. For a workflow triggered by `pull_request`, GitHub binds the default source revision to the pull request merge ref and exposes the synthetic merge commit through `GITHUB_SHA`. - -The protected branch rules require the two repository CodeQL contexts, `Analyze (javascript-typescript)` and `Analyze (python)`. Treating those checks as exact-head security evidence while their database was initialized from a synthetic merge revision creates the same evidence-authority mismatch already repaired in the repository-owned fuzz and OSV lanes. - -This is a ScopeWeave workflow-integrity defect, not a source vulnerability and not a reason to weaken CodeQL or branch protection. - -## Decision - -Before CodeQL initialization, `.github/workflows/codeql.yml` now: - -1. checks out `${{ github.event.pull_request.head.sha || github.sha }}` explicitly; -2. retains `persist-credentials: false`; -3. records the same expression as `EXPECTED_SHA`; -4. compares `git rev-parse HEAD` with that immutable expected revision; and -5. fails closed before `github/codeql-action/init` when the identities differ. - -For protected push and scheduled execution, the fallback remains the event's exact `github.sha`. The CodeQL language matrix, action revisions, categories, and permissions remain unchanged. - -## Test-first evidence - -Test-only commit `9ecccb04ab36d6687cd87c83dd72343227415908` strengthened `tests/unit/coverage-script-contract.test.mjs` while the production CodeQL workflow still used default checkout behavior. The regression requires explicit source ref selection and attestation before CodeQL initialization. - -The immediate production repair superseded the hosted Server Tests run for that test-only head before it completed, so cancelled predecessor evidence is not promoted as executed RED proof. The source-ordering remains test-first: the executable contract was committed before the workflow repair. - -Production commit `e9385e4ad5c3249fec13272d9831cc0b263fe02e` implements the narrow exact-checkout and pre-initialization attestation. Fresh exact-current-head repository and organization evidence must pass after this documentation commit; predecessor CodeQL results do not transfer. - -## Verification contract - -Before integration: - -- the workflow contract must pass under `unit-and-api`; -- both CodeQL matrix jobs must run against the exact current contributor head on the pull request; -- the SHA attestation must execute before CodeQL initialization; -- no workflow permission is broadened; -- exact-head review/security evidence remains separate from synthetic mergeability evidence; and -- any contributor-head or protected-base movement invalidates head/base-sensitive conclusions and triggers fresh evidence. - -## Rollback - -Do not restore default merge-ref checkout merely to make a required CodeQL status green. A verified action/runtime compatibility problem should be repaired while keeping explicit source identity and fail-closed attestation intact. - -## References - -GitHub. (n.d.). *Events that trigger workflows*. GitHub Docs. https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows - -GitHub. (n.d.). *Variables reference*. GitHub Docs. https://docs.github.com/en/actions/reference/workflows-and-actions/variables - -GitHub. (n.d.). *Configuring default setup for code scanning*. GitHub Docs. https://docs.github.com/en/code-security/code-scanning/enabling-code-scanning/configuring-default-setup-for-code-scanning - -GitHub. (2026). *CodeQL Action* [Source code]. GitHub. https://github.com/github/codeql-action From 75b4d86114b99b793a6d64afe070acffc0fd869e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:46:50 +0900 Subject: [PATCH 19/19] chore(ci): keep shared exact-head changelog on PR 523 --- CHANGELOG.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 767276fc..86f56b15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,14 +57,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `actions/setup-node` v7.0.0 so its JavaScript action runtime declares Node.js 24 instead of relying on GitHub's compatibility override for deprecated Node.js 20, while retaining Node.js 22.13.0 for ScopeWeave itself. -- Bound repository-owned OSV old/new dependency scans to an independently - resolved live protected-base commit and the exact pull-request contributor - head, with SHA attestations before both scans, instead of treating a synthetic - pull-request merge commit as exact-head security evidence. -- Bound both required repository CodeQL matrix analyses to the exact contributor - head on pull requests and fail closed on a checkout mismatch before CodeQL - initialization instead of treating a synthetic merge checkout as exact-head - security evidence. - Accepted XML whitespace before exact Microsoft Project element delimiters while preserving the linear, regex-free import scanner and rejecting attributes, longer names, non-XML whitespace, nested unmatched blocks, and