From f51a51ee4c1ee208ec4f60064cf7b80e1cda3018 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 10:36:08 +0900 Subject: [PATCH 01/16] test(security): require non-persistent audit checkout credentials --- ...est_security_audit_workflow_credentials.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 services/analysis-engine/tests/test_security_audit_workflow_credentials.py diff --git a/services/analysis-engine/tests/test_security_audit_workflow_credentials.py b/services/analysis-engine/tests/test_security_audit_workflow_credentials.py new file mode 100644 index 000000000..31e837f1d --- /dev/null +++ b/services/analysis-engine/tests/test_security_audit_workflow_credentials.py @@ -0,0 +1,21 @@ +"""Security-audit workflow credential-boundary regression tests.""" + +from __future__ import annotations + +from pathlib import Path + + +REPOSITORY_ROOT = Path(__file__).resolve().parents[3] +SECURITY_AUDIT_WORKFLOW = REPOSITORY_ROOT / ".github" / "workflows" / "security-audit.yml" + + +def test_security_audit_checkout_does_not_persist_github_credentials() -> None: + """Dependency lifecycle scripts must not inherit persisted checkout credentials.""" + workflow_text = SECURITY_AUDIT_WORKFLOW.read_text(encoding="utf-8") + checkout_marker = ( + "- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0" + ) + checkout_offset = workflow_text.index(checkout_marker) + checkout_block = workflow_text[checkout_offset : checkout_offset + 240] + + assert "persist-credentials: false" in checkout_block From 0f70859496ecb6274c83ca257e143b8c835afb41 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 10:37:46 +0900 Subject: [PATCH 02/16] fix(security): disable audit checkout credential persistence --- .github/workflows/security-audit.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index 7d880c1a1..90ce48fd3 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -24,6 +24,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22.22.3 From cee9387f311b8664b821ac11e0b3e6d262691338 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 10:41:25 +0900 Subject: [PATCH 03/16] docs(changelog): record audit credential hardening --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eea696893..29cb90522 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ - Display the analyzed song tempo (BPM) as a badge in the rehearsal workspace. - 각 합주 역할(Role)별 개인 연습 진행도를 0~100% 범위로 기록 및 시각화할 수 있는 연습 진척도(`practiceProgress`) 트래커 기능 추가. UI 컨트롤(슬라이더 및 +/- 버튼)과 한/영 다국어 지원 포함. +### Fixed + +- Disable persisted GitHub checkout credentials in the `security-audit` workflow before dependency installation and audit execution. + ## [0.1.3] - 2026-04-29 ### Fixed From 949db7dbf11db09952a9e950d8f1d28d8e429bd8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 10:42:57 +0900 Subject: [PATCH 04/16] docs(security): require non-persistent audit checkouts --- docs/security/dependency-policy.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/security/dependency-policy.md b/docs/security/dependency-policy.md index f7271e68d..11a1b7f7f 100644 --- a/docs/security/dependency-policy.md +++ b/docs/security/dependency-policy.md @@ -44,6 +44,7 @@ Because of that, dependency review, security audit, SBOM generation, and supply- - dependency graph or dependency submission coverage must stay enabled wherever GitHub supports it for the repository state - GitHub dependency review must gate PRs into `develop` and `main` - GitHub Actions workflows that affect the supply chain must stay SHA pinned and least-privilege +- dependency-install and audit jobs must disable persisted checkout credentials before untrusted dependency lifecycle code can execute - third-party actions require source-trust review, maintenance review, permission review, and commit-SHA pinning before admission ## New dependency admission rule From bdf4628fab57bfe70c36840b02a1c6df4a5a58ba Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 11:01:19 +0900 Subject: [PATCH 05/16] test(security): cover dependency workflow credential boundaries --- ...est_security_audit_workflow_credentials.py | 52 +++++++++++++++---- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/services/analysis-engine/tests/test_security_audit_workflow_credentials.py b/services/analysis-engine/tests/test_security_audit_workflow_credentials.py index 31e837f1d..7b0bc98cb 100644 --- a/services/analysis-engine/tests/test_security_audit_workflow_credentials.py +++ b/services/analysis-engine/tests/test_security_audit_workflow_credentials.py @@ -1,21 +1,51 @@ -"""Security-audit workflow credential-boundary regression tests.""" +"""Dependency-workflow checkout credential-boundary regression tests.""" from __future__ import annotations from pathlib import Path +import pytest + REPOSITORY_ROOT = Path(__file__).resolve().parents[3] -SECURITY_AUDIT_WORKFLOW = REPOSITORY_ROOT / ".github" / "workflows" / "security-audit.yml" +CHECKOUT_MARKER = ( + "- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0" +) +DEPENDENCY_LIFECYCLE_WORKFLOWS = ( + ".github/workflows/security-audit.yml", + ".github/workflows/release.yml", + ".github/workflows/bandit.yml", +) + + +def _checkout_step(workflow_text: str) -> str: + """Return the first checkout step without accepting options from later steps.""" + checkout_offset = workflow_text.index(CHECKOUT_MARKER) + checkout_tail = workflow_text[checkout_offset:] + next_step_offset = checkout_tail.find("\n - ", len(CHECKOUT_MARKER)) + if next_step_offset == -1: + return checkout_tail + return checkout_tail[:next_step_offset] + + +@pytest.mark.parametrize("workflow_path", DEPENDENCY_LIFECYCLE_WORKFLOWS) +def test_dependency_workflow_checkout_does_not_persist_github_credentials( + workflow_path: str, +) -> None: + """Dependency lifecycle code must not inherit persisted checkout credentials.""" + workflow_text = (REPOSITORY_ROOT / workflow_path).read_text(encoding="utf-8") + checkout_step = _checkout_step(workflow_text) + + assert "persist-credentials: false" in checkout_step -def test_security_audit_checkout_does_not_persist_github_credentials() -> None: - """Dependency lifecycle scripts must not inherit persisted checkout credentials.""" - workflow_text = SECURITY_AUDIT_WORKFLOW.read_text(encoding="utf-8") - checkout_marker = ( - "- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0" - ) - checkout_offset = workflow_text.index(checkout_marker) - checkout_block = workflow_text[checkout_offset : checkout_offset + 240] +def test_checkout_step_does_not_accept_credentials_from_a_later_step() -> None: + """A later step option must not satisfy the checkout credential contract.""" + workflow_text = f"""steps: + {CHECKOUT_MARKER} + - uses: actions/setup-node@example + with: + persist-credentials: false +""" - assert "persist-credentials: false" in checkout_block + assert "persist-credentials: false" not in _checkout_step(workflow_text) From 7173e75cd3614389a843049ddf0286e15eda27d4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 11:04:56 +0900 Subject: [PATCH 06/16] test(security): keep credential contract formatter-clean --- .../test_security_audit_workflow_credentials.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/services/analysis-engine/tests/test_security_audit_workflow_credentials.py b/services/analysis-engine/tests/test_security_audit_workflow_credentials.py index 7b0bc98cb..baae70fbe 100644 --- a/services/analysis-engine/tests/test_security_audit_workflow_credentials.py +++ b/services/analysis-engine/tests/test_security_audit_workflow_credentials.py @@ -4,8 +4,6 @@ from pathlib import Path -import pytest - REPOSITORY_ROOT = Path(__file__).resolve().parents[3] CHECKOUT_MARKER = ( @@ -28,15 +26,13 @@ def _checkout_step(workflow_text: str) -> str: return checkout_tail[:next_step_offset] -@pytest.mark.parametrize("workflow_path", DEPENDENCY_LIFECYCLE_WORKFLOWS) -def test_dependency_workflow_checkout_does_not_persist_github_credentials( - workflow_path: str, -) -> None: +def test_dependency_workflow_checkout_does_not_persist_github_credentials() -> None: """Dependency lifecycle code must not inherit persisted checkout credentials.""" - workflow_text = (REPOSITORY_ROOT / workflow_path).read_text(encoding="utf-8") - checkout_step = _checkout_step(workflow_text) + for workflow_path in DEPENDENCY_LIFECYCLE_WORKFLOWS: + workflow_text = (REPOSITORY_ROOT / workflow_path).read_text(encoding="utf-8") + checkout_step = _checkout_step(workflow_text) - assert "persist-credentials: false" in checkout_step + assert "persist-credentials: false" in checkout_step, workflow_path def test_checkout_step_does_not_accept_credentials_from_a_later_step() -> None: From 587abfe240dee2e1675c4455af3b855aa5677a07 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 11:08:24 +0900 Subject: [PATCH 07/16] test(security): satisfy formatter before RED policy assertion --- .../tests/test_security_audit_workflow_credentials.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/services/analysis-engine/tests/test_security_audit_workflow_credentials.py b/services/analysis-engine/tests/test_security_audit_workflow_credentials.py index baae70fbe..ef1998e63 100644 --- a/services/analysis-engine/tests/test_security_audit_workflow_credentials.py +++ b/services/analysis-engine/tests/test_security_audit_workflow_credentials.py @@ -1,7 +1,5 @@ """Dependency-workflow checkout credential-boundary regression tests.""" -from __future__ import annotations - from pathlib import Path From 35d434ec7e7a050c3c153709112d5263d2468b4e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 11:09:46 +0900 Subject: [PATCH 08/16] fix(security): drop persisted credentials before release dependencies --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 84ace55d4..1c356b122 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,6 +29,8 @@ jobs: contents: read steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22.22.3 From 74b6cf940de1e1fb1d865f88b788c3f3e21f4b7f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 11:10:00 +0900 Subject: [PATCH 09/16] fix(security): drop persisted credentials before Bandit dependencies --- .github/workflows/bandit.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/bandit.yml b/.github/workflows/bandit.yml index 6db7276da..6c6d4900a 100644 --- a/.github/workflows/bandit.yml +++ b/.github/workflows/bandit.yml @@ -24,6 +24,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 with: version: "0.8.6" From be1305ef68a3ede00a42333ceb6ef6f88cb871df Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 11:10:40 +0900 Subject: [PATCH 10/16] docs(changelog): cover dependency lifecycle credential hardening --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29cb90522..d724e937e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ ### Fixed -- Disable persisted GitHub checkout credentials in the `security-audit` workflow before dependency installation and audit execution. +- Disable persisted GitHub checkout credentials before dependency lifecycle execution in the `security-audit`, `release`, and `bandit` workflows. ## [0.1.3] - 2026-04-29 @@ -53,7 +53,7 @@ - Issue #38: Added cross-architecture build support (Windows/macOS arm64+amd64) - Issue #40: Enforced 100% Python docstring and test coverage - Issue #32: Implemented local analysis orchestration and secure IPC boundaries -- Issue #33: Implemented secure local audio intake and project bootstrap +- Issue #33: Engineered section, form, and cue anchor extraction pipeline - Issue #35: Engineered section, form, and cue anchor extraction pipeline - Issue #34: Implemented role extraction targets and part graph - Issue #31: Added role-specific harmony, range, overlap, and confidence metrics From f4f2e2ebf24ac33468035a67389d06e998eaf1c3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 11:10:56 +0900 Subject: [PATCH 11/16] docs(changelog): preserve existing release history From 0ae19685cb813c606faf48bccf937f18dc3e1071 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 11:34:20 +0900 Subject: [PATCH 12/16] test(security): satisfy Ruff import-block formatting --- .../tests/test_security_audit_workflow_credentials.py | 1 - 1 file changed, 1 deletion(-) diff --git a/services/analysis-engine/tests/test_security_audit_workflow_credentials.py b/services/analysis-engine/tests/test_security_audit_workflow_credentials.py index ef1998e63..a84b94274 100644 --- a/services/analysis-engine/tests/test_security_audit_workflow_credentials.py +++ b/services/analysis-engine/tests/test_security_audit_workflow_credentials.py @@ -2,7 +2,6 @@ from pathlib import Path - REPOSITORY_ROOT = Path(__file__).resolve().parents[3] CHECKOUT_MARKER = ( "- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0" From 3d2c022761dca5948c37f71c302457b79dedc696 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 11:34:51 +0900 Subject: [PATCH 13/16] docs(changelog): restore protected release-history entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d724e937e..70a6d785a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,7 +53,7 @@ - Issue #38: Added cross-architecture build support (Windows/macOS arm64+amd64) - Issue #40: Enforced 100% Python docstring and test coverage - Issue #32: Implemented local analysis orchestration and secure IPC boundaries -- Issue #33: Engineered section, form, and cue anchor extraction pipeline +- Issue #33: Implemented secure local audio intake and project bootstrap - Issue #35: Engineered section, form, and cue anchor extraction pipeline - Issue #34: Implemented role extraction targets and part graph - Issue #31: Added role-specific harmony, range, overlap, and confidence metrics From 4116a0d108f719e1a4382bdbe588ce762843f871 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 12:04:55 +0900 Subject: [PATCH 14/16] fix(test): restore formatter-clean workflow credential regression --- .../tests/test_security_audit_workflow_credentials.py | 1 + 1 file changed, 1 insertion(+) diff --git a/services/analysis-engine/tests/test_security_audit_workflow_credentials.py b/services/analysis-engine/tests/test_security_audit_workflow_credentials.py index a84b94274..ef1998e63 100644 --- a/services/analysis-engine/tests/test_security_audit_workflow_credentials.py +++ b/services/analysis-engine/tests/test_security_audit_workflow_credentials.py @@ -2,6 +2,7 @@ from pathlib import Path + REPOSITORY_ROOT = Path(__file__).resolve().parents[3] CHECKOUT_MARKER = ( "- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0" From 07160db4dd4559d03b9b095395beb2f19a1ee254 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 13:04:17 +0900 Subject: [PATCH 15/16] test(security): satisfy Ruff import ordering --- .../tests/test_security_audit_workflow_credentials.py | 1 - 1 file changed, 1 deletion(-) diff --git a/services/analysis-engine/tests/test_security_audit_workflow_credentials.py b/services/analysis-engine/tests/test_security_audit_workflow_credentials.py index ef1998e63..a84b94274 100644 --- a/services/analysis-engine/tests/test_security_audit_workflow_credentials.py +++ b/services/analysis-engine/tests/test_security_audit_workflow_credentials.py @@ -2,7 +2,6 @@ from pathlib import Path - REPOSITORY_ROOT = Path(__file__).resolve().parents[3] CHECKOUT_MARKER = ( "- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0" From 475adeba62109e1e34981baed22ce89728211108 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 13:08:41 +0900 Subject: [PATCH 16/16] style(security): apply Ruff formatter output --- .../tests/test_security_audit_workflow_credentials.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/services/analysis-engine/tests/test_security_audit_workflow_credentials.py b/services/analysis-engine/tests/test_security_audit_workflow_credentials.py index a84b94274..5a15b31f3 100644 --- a/services/analysis-engine/tests/test_security_audit_workflow_credentials.py +++ b/services/analysis-engine/tests/test_security_audit_workflow_credentials.py @@ -3,9 +3,7 @@ from pathlib import Path REPOSITORY_ROOT = Path(__file__).resolve().parents[3] -CHECKOUT_MARKER = ( - "- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0" -) +CHECKOUT_MARKER = "- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0" DEPENDENCY_LIFECYCLE_WORKFLOWS = ( ".github/workflows/security-audit.yml", ".github/workflows/release.yml",