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" 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 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 diff --git a/CHANGELOG.md b/CHANGELOG.md index eea696893..70a6d785a 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 before dependency lifecycle execution in the `security-audit`, `release`, and `bandit` workflows. + ## [0.1.3] - 2026-04-29 ### Fixed 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 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..5a15b31f3 --- /dev/null +++ b/services/analysis-engine/tests/test_security_audit_workflow_credentials.py @@ -0,0 +1,42 @@ +"""Dependency-workflow checkout credential-boundary regression tests.""" + +from pathlib import Path + +REPOSITORY_ROOT = Path(__file__).resolve().parents[3] +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] + + +def test_dependency_workflow_checkout_does_not_persist_github_credentials() -> None: + """Dependency lifecycle code must not inherit persisted checkout credentials.""" + 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, workflow_path + + +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" not in _checkout_step(workflow_text)