Skip to content

security(deploy-pages): declare minimal secret contract - #901

Open
seonghobae wants to merge 11 commits into
mainfrom
security/deploy-pages-explicit-secrets-current-main-20260809
Open

security(deploy-pages): declare minimal secret contract#901
seonghobae wants to merge 11 commits into
mainfrom
security/deploy-pages-explicit-secrets-current-main-20260809

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • replace blanket secrets: inherit guidance with an explicit reusable-workflow interface for exactly CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID
  • preserve contents: read, caller checkout, immutable action pins, and deployment behavior while keeping both Cloudflare values required
  • validate project_name, build_dir, and custom_domain before they reach Wrangler, Cloudflare API URLs, or step summaries; only sealed validator outputs cross those sinks
  • reject argument/path/domain injection, workspace escape through symlinks, and unsafe reusable-secret YAML variants without reflecting attacker-controlled values in diagnostics
  • add test-first contracts, migration/rollback doctoring, current packaging guidance, and changelog evidence

RCA and feasibility

The workflow consumed two named secrets but declared no workflow_call.secrets interface, making blanket inheritance the documented transport. The least-privilege root-cause repair is to declare the two consumed names and map them explicitly at callers. Removing the guard alone would not constrain exposure; retaining blanket inheritance with prose warnings would not change the trust boundary.

This declaration is a named interface and approved-caller policy, not a GitHub runtime allowlist. GitHub still permits same-organization or same-enterprise callers to use secrets: inherit; CWL policy prohibits that caller form, authoritative examples map both names explicitly, and the called workflow itself references only the two Cloudflare names.

Strix then exposed a second trust boundary: caller-controlled Pages inputs were interpolated into the Wrangler command and Cloudflare URL/summary paths. The repair therefore validates all three inputs before use, canonicalizes the build directory under GITHUB_WORKSPACE, rejects symlink escape, and passes only validator outputs downstream. A later CodeRabbit finding showed that the contract test itself could miss valid YAML spellings such as EXTRA_SECRET: # comment and quoted/whitespace secrets: inherit; the current exact-head tests cover those variants rather than relying on brittle literal matching.

A current central/organization search found no product caller requiring migration at the time of the secret-contract repair. This must be revalidated immediately before protected integration; any new thin caller must be repaired in its owning repository rather than widening this reusable contract.

Test-first evidence

  • RED secret-interface contract against protected main
  • RED Strix security finding for unsafe reusable deployment inputs
  • RED regression set for unsafe project/build/domain values and symlink escape
  • RED contract-meta tests for YAML declaration/inheritance variants
  • GREEN exact-head machine evidence at e7825b137cc359f8522c08794854fe9f39606c4f

Exact current status

  • exact current head: e7825b137cc359f8522c08794854fe9f39606c4f
  • base snapshot recorded by the PR: 6eb06cdd08c79a06f7b390069d4ffa49e2eb7dba; merge decisions must independently resolve the live protected base tip
  • all 9 pull-request workflows on this exact head completed successfully: CodeQL PR, Python Security, Security Scan, SAST Semgrep, Secret Scan, OSV-Scanner PR, Scorecard PR, SBOM Generation, and Strix Changed Path Quality CI
  • both CodeRabbit inline findings are resolved/outdated on the current source line
  • the OpenCode CHANGES_REQUESTED review was submitted against predecessor head 1e87aa7ecad8e5aeb33d5483039199fe081f45d8 because that older head still had the Strix finding; it is historical evidence and is not a current-head approval
  • a fresh current-head automated review and a qualifying independent non-author formal approval remain separate merge authorities

Protected-main operational acceptance remains required after integration: one approved caller with explicit secret mappings and bounded canonical deployment inputs, plus a missing-mapping/invalid-input negative control that stops without value disclosure. Do not infer that acceptance from PR checks.

Fixes #900

Summary by CodeRabbit

  • 변경 사항

    • Cloudflare Pages 배포 인증 정보가 명시적으로 매핑됩니다.
    • 필수 인증 정보가 누락되면 배포가 중단되며, 민감한 값은 오류 메시지에 표시되지 않습니다.
    • 프로젝트명, 빌드 경로, 사용자 지정 도메인 등 잘못되거나 안전하지 않은 입력이 차단됩니다.
  • 문서

    • 인증 정보 설정, 권한, 마이그레이션 및 검증 절차를 안내하는 문서가 추가·갱신되었습니다.
  • 테스트

    • 인증 정보 계약, 입력 검증 및 보안 정책을 자동으로 확인합니다.

@seonghobae
seonghobae enabled auto-merge (squash) August 9, 2026 13:37
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Cloudflare Pages 재사용 워크플로가 두 개의 필수 Cloudflare 시크릿을 명시적으로 받습니다. 배포 입력을 검증한 뒤 배포, 도메인 연결, 요약 단계에 전달합니다. 문서와 계약 테스트가 새 시크릿 계약과 입력 검증을 반영합니다.

Changes

Cloudflare Pages 배포 계약

Layer / File(s) Summary
워크플로 계약과 입력 검증
.github/workflows/deploy-pages.yml
workflow_call이 두 Cloudflare 시크릿을 필수로 선언합니다. 배포 전에 프로젝트명, 빌드 경로, 사용자 지정 도메인을 검증하고 정규화합니다. 후속 단계는 검증된 출력만 사용합니다.
호출자 계약 문서화
infra/cloudflare/README.md, docs/doctoring/deploy-pages-secret-contract.md, CHANGELOG.md
호출자가 두 시크릿을 개별 매핑해야 하며 secrets: inherit를 사용하지 않아야 한다는 계약을 기록합니다. 입력 검증, 마이그레이션, 테스트 조건과 롤백 절차도 문서화합니다.
계약 및 보안 검증
tests/test_deploy_pages_secret_contract.py
시크릿 선언·참조, 명시적 매핑, 누락 시 값 비노출 실패, 검증 순서, 정상 입력, 명령어·옵션·경로·호스트·심볼릭 링크 탈출 입력 거부를 검증합니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller as 호출 워크플로
  participant Workflow as deploy-pages.yml
  participant Cloudflare as Cloudflare Pages
  participant DomainAPI as 사용자 지정 도메인 API
  participant Summary as 작업 요약
  Caller->>Workflow: 두 시크릿과 배포 입력 전달
  Workflow->>Workflow: 입력 검증 및 출력 생성
  Workflow->>Cloudflare: 검증된 프로젝트명과 빌드 경로로 배포
  Workflow->>DomainAPI: 검증된 도메인 연결
  Workflow->>Summary: 검증된 값으로 결과 기록
Loading

Possibly related issues

  • ContextualWisdomLab/RankWeave issue 37: 다른 GitHub Actions 워크플로에서도 광범위한 secrets: inherit를 명시적 최소 시크릿 처리로 변경하는 목표가 유사합니다.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed 두 Cloudflare 시크릿의 명시적 계약, 상속 금지 문서, fail-closed 테스트, 마이그레이션 및 롤백 문서 요구를 충족합니다.
Out of Scope Changes check ✅ Passed 워크플로 검증, 계약 테스트, 문서 및 변경 로그는 연결된 이슈의 시크릿 경계 강화와 직접 관련됩니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 deploy-pages 재사용 워크플로의 최소 시크릿 계약을 명시하는 주요 변경 사항을 정확하고 간결하게 설명합니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch security/deploy-pages-explicit-secrets-current-main-20260809

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copy link
Copy Markdown
Contributor Author

/oc Refetch and abort without writing unless exact head is still 4857bd873933278ba171b877d400fa5ecee87371. The core repair is directionally correct, but tighten one documentation/security claim before review. GitHub's current official reusable-workflow documentation explicitly states that when a caller uses secrets: inherit, inherited secrets can be referenced in the called workflow even if they were not declared under on.workflow_call.secrets. Therefore declaring exactly two secrets is an explicit named interface for compliant callers, not a runtime allowlist that technically prevents a caller in the same org/enterprise from using inheritance.

Keep the production change and tests that require exactly the two declared/consumed Cloudflare names and forbid secrets: inherit in authoritative central examples. Amend docs/doctoring/deploy-pages-secret-contract.md (and any PR/issue wording if needed) to state the precise boundary: this workflow references only two Cloudflare secret names; approved CWL callers MUST pass those names explicitly and MUST NOT use secrets: inherit; workflow_call.secrets declaration documents/enforces named mappings but cannot itself make GitHub's inherit keyword unavailable to a caller. Re-run organization code search immediately before merge and treat any real inherited caller as a leaf migration defect, not a reason to broaden the central interface.

Add a narrow contract assertion that the doctoring records this platform limitation and caller prohibition without weakening the existing no-inherit examples. Keep all current action pins/permissions/deployment behavior unchanged. Re-run the focused test suite and exact-head checks. Do not invent a YAML feature that blocks secrets: inherit, and do not claim that unrelated inherited secrets are cryptographically/runtime-inaccessible solely because the two names are declared.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/deploy-pages.yml:
- Line 6: Update the comment in the workflow to state that it references only
the two declared values, without claiming GitHub Actions imposes a runtime
restriction; add that approved callers must use explicit secret mapping.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4d826c52-aabf-4b84-b0c5-c183887a2e64

📥 Commits

Reviewing files that changed from the base of the PR and between 6eb06cd and 213449d.

📒 Files selected for processing (5)
  • .github/workflows/deploy-pages.yml
  • CHANGELOG.md
  • docs/doctoring/deploy-pages-secret-contract.md
  • infra/cloudflare/README.md
  • tests/test_deploy_pages_secret_contract.py

Comment thread .github/workflows/deploy-pages.yml Outdated
@opencode-agent
opencode-agent Bot disabled auto-merge August 9, 2026 14:47

Copy link
Copy Markdown
Contributor Author

Exact-head review requested: 1e87aa7ecad8e5aeb33d5483039199fe081f45d8.

The named-interface/platform-inheritance boundary, both authoritative explicit-mapping examples, four contract tests, and workflow-header wording are aligned. All 9 exact-head pull-request workflows succeeded, and the prior line-6 thread is resolved/outdated.

@cwl-noema-review @opencode-agent Please review this exact head. Do not carry forward evidence from 7514fc4 or earlier commits. A COMMENTED result or check/status is not a qualifying independent formal approval.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

OpenCode could not approve from deterministic current-head evidence because GitHub Checks have failed.

Findings

1. HIGH Current-head GitHub Checks - Fix failed required checks before approval

  • Problem: Failed same-head checks remain for 1e87aa7ecad8e5aeb33d5483039199fe081f45d8.
  • Root cause: The model-unavailable evidence fallback is allowed only when peer GitHub Checks are complete and clean.
  • Fix: Read and fix the failed check logs below, then rerun the current-head checks.
  • Regression test: Keep the model-unavailable fallback gated on an empty failed-check rollup.

Failed checks:

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Workflow: deploy-pages.yml"]
  S1 --> I1["GitHub Actions review job"]
  I1 --> R1["Review risk: Workflow: deploy-pages.yml"]
  R1 --> V1["actionlint plus required checks"]
  Evidence --> S2["Changed file (2 files)"]
  S2 --> I2["repository behavior"]
  I2 --> R2["Review risk: Changed file (2 files)"]
  R2 --> V2["required checks"]
  Evidence --> S3["Docs: deploy-pages-secret-contract.md"]
  S3 --> I3["operator or user guidance"]
  I3 --> R3["Review risk: Docs: deploy-pages-secret-contract.md"]
  R3 --> V3["docs review"]
  Evidence --> S4["Test: test_deploy_pages_secret_contract.py"]
  S4 --> I4["regression suite"]
  I4 --> R4["Review risk: Test: test_deploy_pages_secret_contract.py"]
  R4 --> V4["targeted test run"]
Loading

@opencode-agent

opencode-agent Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

OpenCode Review Overview

  • Head SHA: 1e87aa7ecad8e5aeb33d5483039199fe081f45d8
  • Workflow run: 31321513264
  • Workflow attempt: 1
  • Gate result: REQUEST_CHANGES (approval step)

Pull request overview

OpenCode could not approve from deterministic current-head evidence because GitHub Checks have failed.

Findings

1. HIGH Current-head GitHub Checks - Fix failed required checks before approval

  • Problem: Failed same-head checks remain for 1e87aa7ecad8e5aeb33d5483039199fe081f45d8.
  • Root cause: The model-unavailable evidence fallback is allowed only when peer GitHub Checks are complete and clean.
  • Fix: Read and fix the failed check logs below, then rerun the current-head checks.
  • Regression test: Keep the model-unavailable fallback gated on an empty failed-check rollup.

Failed checks:

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Workflow: deploy-pages.yml"]
  S1 --> I1["GitHub Actions review job"]
  I1 --> R1["Review risk: Workflow: deploy-pages.yml"]
  R1 --> V1["actionlint plus required checks"]
  Evidence --> S2["Changed file (2 files)"]
  S2 --> I2["repository behavior"]
  I2 --> R2["Review risk: Changed file (2 files)"]
  R2 --> V2["required checks"]
  Evidence --> S3["Docs: deploy-pages-secret-contract.md"]
  S3 --> I3["operator or user guidance"]
  I3 --> R3["Review risk: Docs: deploy-pages-secret-contract.md"]
  R3 --> V3["docs review"]
  Evidence --> S4["Test: test_deploy_pages_secret_contract.py"]
  S4 --> I4["regression suite"]
  I4 --> R4["Review risk: Test: test_deploy_pages_secret_contract.py"]
  R4 --> V4["targeted test run"]
Loading

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_deploy_pages_secret_contract.py`:
- Around line 85-87: Update tests/test_deploy_pages_secret_contract.py lines
85-87 so the declared-secret regex captures six-space-indented keys even when
the colon is followed by whitespace or a comment, including forms such as
EXTRA_SECRET: # 설명. Update lines 111-114 so the inherit check uses a line-based
regex allowing whitespace, optional quotes, and trailing comments, thereby
rejecting both secrets: inherit and secrets: inherit # 설명; both changes belong
to the existing contract test logic.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ecd5fb7e-4a99-4d76-842a-fb0433fd6a3c

📥 Commits

Reviewing files that changed from the base of the PR and between 213449d and 66be94b.

📒 Files selected for processing (3)
  • .github/workflows/deploy-pages.yml
  • docs/doctoring/deploy-pages-secret-contract.md
  • tests/test_deploy_pages_secret_contract.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/deploy-pages.yml
  • docs/doctoring/deploy-pages-secret-contract.md

Comment thread tests/test_deploy_pages_secret_contract.py Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

security(deploy-pages): replace blanket inherited secrets with an explicit reusable-workflow contract

1 participant