Skip to content

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

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

security(deploy-pages): declare minimal secret contract#901
seonghobae wants to merge 10 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
  • keep both values required, preserve contents: read, caller checkout, action pins, and deployment behavior
  • retain a value-free defense-in-depth missing-secret diagnostic
  • add a test-first contract, migration/rollback doctoring, 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, the authoritative examples map both names explicitly, and the called workflow itself references only those two Cloudflare names.

A current central/organization search found no product caller requiring migration. This must be revalidated immediately before merge; any new thin caller must be repaired in its owning repository rather than widening this reusable contract.

Validation

  • RED contract probe against protected main (missing workflow_call.secrets)
  • GREEN direct execution of all four contract tests
  • PyYAML syntax parse of .github/workflows/deploy-pages.yml
  • Python compilation and git diff --check
  • exact-head CI, security, and supply-chain evidence: all 9 pull-request workflows succeeded at 1e87aa7ecad8e5aeb33d5483039199fe081f45d8

Exact current status

  • exact head: 1e87aa7ecad8e5aeb33d5483039199fe081f45d8;
  • all 9 pull-request workflows completed successfully;
  • the prior workflow-header finding is resolved and outdated after the precise named-interface wording;
  • the existing CodeRabbit COMMENTED review is predecessor-head evidence, not a formal approval.

Protected-main operational acceptance remains required: one mapped-secrets deployment and one missing-mapping negative control without value disclosure.

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

Warning

Review limit reached

@seonghobae, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 51 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 39197dcd-0e80-4078-9c83-ccd69b069a95

📥 Commits

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

📒 Files selected for processing (2)
  • .github/workflows/deploy-pages.yml
  • docs/doctoring/deploy-pages-secret-contract.md
📝 Walkthrough

Walkthrough

Cloudflare Pages 재사용 워크플로가 CLOUDFLARE_API_TOKENCLOUDFLARE_ACCOUNT_ID를 필수 시크릿으로 받습니다. 호출 예제와 문서가 명시적 매핑을 사용합니다. 계약 테스트가 시크릿 계약과 배포 입력 검증을 확인합니다.

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: 3 (Moderate) | ~20 minutes

Possibly related issues

  • ContextualWisdomLab/RankWeave issue 37: 다른 재사용 워크플로에서 광범위한 secrets: inherit 사용을 명시적 최소 시크릿 처리로 변경하는 목표가 유사합니다.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive 워크플로, 문서, 테스트는 대부분 요구사항을 반영하지만 조직 소비자 재검색과 일부 필수 검증의 수행 증거가 없습니다. 조직 소비자 재검색 결과와 RED, 전체 테스트, 구문·보안·공급망 검사 및 git diff --check 결과를 첨부하십시오.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 재사용 워크플로에 최소 시크릿 계약을 선언하는 주요 변경을 정확하고 간결하게 설명합니다.
Out of Scope Changes check ✅ Passed 변경된 워크플로, 문서, 변경 로그 및 계약 테스트는 모두 명시된 시크릿 계약 변경과 직접 관련됩니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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 on lines +85 to +87
declared = set(
re.findall(r"^ ([A-Z][A-Z0-9_]+):$", call_contract, re.MULTILINE)
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

유효한 YAML 변형도 차단하십시오.

현재 검사는 EXTRA_SECRET: # 설명 형태의 추가 시크릿을 수집하지 않습니다. 또한 secrets: inherit 또는 secrets: inherit # 설명은 금지 검사를 통과합니다. 이 경우 테스트는 통과하지만 명시적 매핑 계약은 위반됩니다.

  • tests/test_deploy_pages_secret_contract.py#L85-L87: 콜론 뒤의 공백·주석과 관계없이 6칸 들여쓰기의 시크릿 키를 수집하십시오.
  • tests/test_deploy_pages_secret_contract.py#L111-L114: 공백, 선택적 따옴표, 후행 주석을 허용하는 줄 단위 정규식으로 inherit를 거부하십시오.
수정 예시
-        re.findall(r"^      ([A-Z][A-Z0-9_]+):$", call_contract, re.MULTILINE)
+        re.findall(
+            r"^      ([A-Z][A-Z0-9_]+):(?:[ \t].*)?$",
+            call_contract,
+            re.MULTILINE,
+        )
...
-        assert "secrets: inherit" not in example
+        assert not re.search(
+            r"""(?m)^[ \t]*secrets[ \t]*:[ \t]*(?:inherit|["']inherit["'])[ \t]*(?:#.*)?$""",
+            example,
+        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
declared = set(
re.findall(r"^ ([A-Z][A-Z0-9_]+):$", call_contract, re.MULTILINE)
)
declared = set(
re.findall(
r"^ ([A-Z][A-Z0-9_]+):(?:[ \t].*)?$",
call_contract,
re.MULTILINE,
)
)
Suggested change
declared = set(
re.findall(r"^ ([A-Z][A-Z0-9_]+):$", call_contract, re.MULTILINE)
)
for example in examples:
assert not re.search(
r"""(?m)^[ \t]*secrets[ \t]*:[ \t]*(?:inherit|["']inherit["'])[ \t]*(?:#.*)?$""",
example,
)
assert "CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}" in example
assert "CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" in example
📍 Affects 1 file
  • tests/test_deploy_pages_secret_contract.py#L85-L87 (this comment)
  • tests/test_deploy_pages_secret_contract.py#L111-L114
🤖 Prompt for 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.

In `@tests/test_deploy_pages_secret_contract.py` around lines 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.

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