-
Notifications
You must be signed in to change notification settings - Fork 0
feat(edge): standardize organization runtimes on Cloudflare Pingora #1123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
bab7296
5ecd3ea
4468365
8a0a9c9
34b699b
cb014ca
4bf12a7
119acce
bbbbda4
067cd17
92bc7f6
2bc3f62
a04ddc7
4044b75
bda9e9d
58ee96c
6d31d3f
251b168
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ concurrency: | |
|
|
||
| permissions: | ||
| contents: read | ||
| pull-requests: read | ||
|
|
||
| jobs: | ||
| required-workflow-bootstrap: | ||
|
|
@@ -31,6 +32,126 @@ jobs: | |
| echo "Required OpenCode workflow materialized without checking out or | ||
| executing pull-request content." | ||
|
|
||
| - name: Resolve immutable central policy source | ||
| id: trusted_source | ||
| env: | ||
| JOB_CONTEXT_JSON: ${{ toJSON(job) }} | ||
| WORKFLOW_SHA: ${{ github.workflow_sha }} | ||
| WORKFLOW_REF: ${{ github.workflow_ref }} | ||
| run: | | ||
| set -euo pipefail | ||
| python3 <<'PY' >>"$GITHUB_OUTPUT" | ||
| import json | ||
| import os | ||
| import re | ||
| import sys | ||
|
|
||
| try: | ||
| job_context = json.loads(os.environ.get("JOB_CONTEXT_JSON") or "{}") | ||
| except json.JSONDecodeError as exc: | ||
| print(f"::error::Could not parse GitHub workflow context JSON: {exc}", file=sys.stderr) | ||
| raise SystemExit(1) | ||
|
|
||
| expected_repository = "ContextualWisdomLab/.github" | ||
| expected_file = ".github/workflows/opencode-review.yml" | ||
| workflow_sha = str( | ||
| job_context.get("workflow_sha") or os.environ.get("WORKFLOW_SHA") or "" | ||
| ).strip() | ||
| workflow_ref = str( | ||
| job_context.get("workflow_ref") or os.environ.get("WORKFLOW_REF") or "" | ||
| ).strip() | ||
| workflow_ref_head, separator, _ = workflow_ref.partition("@") | ||
| if not separator: | ||
| print("::error::Required workflow ref is missing its immutable ref separator.", file=sys.stderr) | ||
| raise SystemExit(1) | ||
| ref_parts = workflow_ref_head.split("/", 2) | ||
| if len(ref_parts) < 2 or not ref_parts[0] or not ref_parts[1]: | ||
| print("::error::Required workflow ref does not identify a repository.", file=sys.stderr) | ||
| raise SystemExit(1) | ||
| workflow_repository = "/".join(ref_parts[:2]) | ||
| workflow_file_path = str(job_context.get("workflow_file_path") or "").strip() | ||
|
|
||
| if not workflow_file_path: | ||
| prefix = f"{expected_repository}/{expected_file}@" | ||
| if workflow_ref.startswith(prefix): | ||
| workflow_file_path = expected_file | ||
|
|
||
| if workflow_repository != expected_repository: | ||
| print( | ||
| f"::error::Required workflow repository resolved to {workflow_repository}, expected {expected_repository}.", | ||
| file=sys.stderr, | ||
| ) | ||
| raise SystemExit(1) | ||
| if not re.fullmatch(r"[0-9a-fA-F]{40}", workflow_sha): | ||
| print("::error::Required workflow SHA is missing or malformed.", file=sys.stderr) | ||
| raise SystemExit(1) | ||
| if workflow_file_path != expected_file: | ||
| print("::error::Required workflow file path is missing or unexpected.", file=sys.stderr) | ||
| raise SystemExit(1) | ||
| expected_ref_prefix = f"{expected_repository}/{expected_file}@" | ||
| if not workflow_ref.startswith(expected_ref_prefix): | ||
| print("::error::Required workflow ref is missing or inconsistent.", file=sys.stderr) | ||
| raise SystemExit(1) | ||
|
Comment on lines
+91
to
+94
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔍 workflow_ref prefix match depends on exact org-name casing The file-path and ref validation build Was this helpful? React with 👍 or 👎 to provide feedback. |
||
|
|
||
| print(f"repository={workflow_repository}") | ||
| print(f"sha={workflow_sha}") | ||
| print(f"workflow_file_path={workflow_file_path}") | ||
| PY | ||
|
|
||
| - name: Materialize trusted central policy source | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| TRUSTED_SOURCE_REF: ${{ steps.trusted_source.outputs.sha }} | ||
| run: | | ||
| set -euo pipefail | ||
| if [[ ! "$TRUSTED_SOURCE_REF" =~ ^[0-9a-fA-F]{40}$ ]]; then | ||
| echo "::error::Trusted central policy source ref must resolve to the immutable workflow commit SHA before archive materialization." | ||
| exit 1 | ||
| fi | ||
| trusted_archive="${RUNNER_TEMP}/trusted-opencode-policy-source.tar.gz" | ||
| trusted_source_dir="${GITHUB_WORKSPACE}/.cwl-required-source" | ||
| api_url="${GITHUB_API_URL:-https://api.github.com}" | ||
| mkdir -p "$trusted_source_dir" | ||
| curl -fsSL \ | ||
| -H "Authorization: Bearer ${GH_TOKEN}" \ | ||
| -H "Accept: application/vnd.github+json" \ | ||
| -o "$trusted_archive" \ | ||
| "${api_url}/repos/ContextualWisdomLab/.github/tarball/${TRUSTED_SOURCE_REF}" | ||
| tar -xzf "$trusted_archive" -C "$trusted_source_dir" --strip-components=1 | ||
|
|
||
| - name: Verify immutable central policy source | ||
| env: | ||
| EXPECTED_FILE: ${{ steps.trusted_source.outputs.workflow_file_path }} | ||
| run: | | ||
| set -euo pipefail | ||
| trusted_source_dir="$GITHUB_WORKSPACE/.cwl-required-source" | ||
| if [ ! -f "$trusted_source_dir/$EXPECTED_FILE" ] || [ -L "$trusted_source_dir/$EXPECTED_FILE" ]; then | ||
| printf '::error::Required workflow source file is missing or symlinked: %s.\n' \ | ||
| "$EXPECTED_FILE" | ||
| exit 1 | ||
| fi | ||
| if [ ! -f "$trusted_source_dir/scripts/ci/pingora_edge_policy.py" ] || [ -L "$trusted_source_dir/scripts/ci/pingora_edge_policy.py" ]; then | ||
| echo "::error::Trusted Pingora edge policy helper is missing or symlinked." | ||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Enforce Cloudflare Pingora edge policy | ||
| if: ${{ github.event_name == 'pull_request_target' }} | ||
| env: | ||
| GITHUB_TOKEN: ${{ github.token }} | ||
| TARGET_REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.repository }} | ||
| PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number || 0 }} | ||
| PULL_REQUEST_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} | ||
| EVENT_ACTION: ${{ github.event.action || 'unknown' }} | ||
| run: | | ||
| set -euo pipefail | ||
| python3 .cwl-required-source/scripts/ci/pingora_edge_policy.py \ | ||
| --repository "$TARGET_REPOSITORY" \ | ||
| --pull-request "$PULL_REQUEST_NUMBER" \ | ||
| --head-sha "$PULL_REQUEST_HEAD_SHA" \ | ||
| --event-action "$EVENT_ACTION" \ | ||
| --api-url "https://api.github.com" | ||
|
|
||
|
seonghobae marked this conversation as resolved.
|
||
| coverage-source-tree: | ||
| name: coverage-source-tree | ||
| needs: [required-workflow-bootstrap] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| # ADR-0019: Standardize the CWL edge runtime on Cloudflare Pingora | ||
|
|
||
| - **Status:** Accepted | ||
| - **Date:** 2026-08-18 | ||
| - **Decision owners:** ContextualWisdomLab platform and product maintainers | ||
|
|
||
| ## Context | ||
|
|
||
| CWL repositories currently carry several unrelated Nginx images, configuration | ||
| files, ingress annotations, service scripts, and host runbooks. The duplication | ||
| creates drift in headers, TLS, timeouts, WebSocket handling, non-root operation, | ||
| metrics, and security patching. It also makes every product repository an edge | ||
| runtime maintainer. | ||
|
|
||
| Cloudflare Pingora supplies an Apache-2.0, Rust-based framework for HTTP/1 and | ||
| HTTP/2 proxying, TLS, gRPC/WebSocket forwarding, graceful reload, failover, and | ||
| observability. It is a framework, not a drop-in parser for Nginx configuration, | ||
| so a governed shared implementation is required. | ||
|
|
||
| ## Decision | ||
|
|
||
| 1. Pingora is the only approved CWL public HTTP reverse-proxy, load-balancer, and | ||
| static edge runtime. | ||
| 2. The shared implementation pins Pingora `0.8.1`; updates use a reviewed version | ||
| bump, security advisory review, compatibility tests, and exact-current-head CI. | ||
| 3. Product repositories consume versioned static/proxy artifacts and declarative | ||
| contracts. Environment deployment remains in `linux-cluster-ops`. | ||
| 4. The organization required workflow rejects active Nginx runtime artifacts in | ||
| changed final files without executing pull-request code. | ||
| 5. Initial migration does not use Pingora's experimental cache integration. | ||
| 6. PHP workloads move to an HTTP application server or reviewed FastCGI adapter | ||
| behind Pingora before the public listener changes. | ||
|
|
||
| ## Consequences | ||
|
|
||
| ### Positive | ||
|
|
||
| - One memory-safe, programmable edge framework and patch stream. | ||
| - Reusable security headers, request limits, metrics, graceful shutdown, and | ||
| connection-management behavior. | ||
| - Product repositories stop maintaining bespoke proxy configuration. | ||
| - Exact-head organization enforcement prevents regression. | ||
|
|
||
| ### Costs and risks | ||
|
|
||
| - Nginx configuration cannot be translated mechanically; behavior must be tested. | ||
| - The organization owns Rust proxy code and its release lifecycle. | ||
| - TLS/SNI, FastCGI, caching, and advanced ingress features need explicit modules. | ||
| - A faulty shared artifact has broad blast radius, so canary, digest pinning, | ||
| rollback, and independent review are mandatory. | ||
|
|
||
| ## Alternatives rejected | ||
|
|
||
| - **Keep Nginx with templates:** preserves configuration drift and C-runtime risk. | ||
| - **Traefik as the universal gateway:** useful off-the-shelf controller, but does | ||
| not meet the user-mandated Pingora standard and creates a second edge runtime. | ||
| - **Per-repository Pingora binaries:** duplicates security logic and fragments the | ||
| upgrade path. | ||
| - **Immediate host replacement without behavior tests:** creates unacceptable | ||
| outage and certificate risk. | ||
|
|
||
| ## Validation | ||
|
|
||
| The policy scanner has 100% production statement and branch coverage, bounded | ||
| GitHub API evidence, path/control escaping, pagination limits, exact-head content | ||
| inspection, and fail-closed malformed-evidence tests. Product migrations require | ||
| site/proxy behavior tests and deployment-specific smoke tests before cutover. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| # Doctoring record: Cloudflare Pingora edge standard | ||
|
|
||
| ## Decision trace | ||
|
|
||
| CWL standardizes public HTTP edge behavior on Cloudflare Pingora and prohibits | ||
| active Nginx runtime artifacts. Pingora is treated as a programmable framework; | ||
| shared binaries and declarative contracts prevent each product from becoming a | ||
| proxy implementation owner. | ||
|
|
||
| The minimum allowed Pingora line is `0.8.x`. Versions through `0.7.0` were affected | ||
| by a critical HTTP request-smuggling flaw caused by ambiguous HTTP/1 framing; | ||
| `0.8.0` patched it. The selected `0.8.1` release additionally bounded default | ||
| HTTP/2 server limits and updated security-sensitive Rustls development | ||
| dependencies. The initial CWL implementation avoids experimental cache APIs. | ||
|
|
||
| ## Standards and controls | ||
|
|
||
| - HTTP parsing and proxy behavior must follow the patched Pingora framing model | ||
| and RFC 9112 semantics referenced by the upstream advisory. | ||
| - The shared artifact is Apache-2.0 compatible with CWL permissive-license policy. | ||
| - Required-workflow code is bound to its immutable central SHA and never executes | ||
| pull-request content. | ||
| - Runtime evidence is bounded to one-megabyte UTF-8 regular files and a maximum of | ||
| 3,000 changed files; missing or malformed evidence fails closed. | ||
| - Exact-head product tests cover host/path routing, SPA fallback, security headers, | ||
| WebSocket/streaming, body limits, health, metrics, TLS, and graceful shutdown as | ||
| applicable. | ||
|
|
||
| ## APA 7th references | ||
|
|
||
| Cloudflare, Inc. (2026, June 4). *Pingora 0.8.1* [Software release]. GitHub. | ||
| https://github.com/cloudflare/pingora/releases/tag/0.8.1 | ||
|
|
||
| Cloudflare, Inc. (2026, March 5). *HTTP request smuggling via HTTP/1.0 and | ||
| Transfer-Encoding misparsing* (GHSA-hj7x-879w-vrp7) [Security advisory]. GitHub. | ||
| https://github.com/cloudflare/pingora/security/advisories/GHSA-hj7x-879w-vrp7 | ||
|
|
||
| Cloudflare, Inc. (n.d.). *Pingora* [Computer software]. GitHub. Retrieved August | ||
| 18, 2026, from https://github.com/cloudflare/pingora | ||
|
|
||
| Cloudflare, Inc. (n.d.). *Pingora user guide*. GitHub. Retrieved August 18, 2026, | ||
| from https://github.com/cloudflare/pingora/tree/main/docs/user_guide |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| # Cloudflare Pingora Edge Runtime Policy | ||
|
|
||
| ## Binding rule | ||
|
|
||
| ContextualWisdomLab production and test edge runtimes use **Cloudflare Pingora**. | ||
| Active Nginx containers, packages, commands, configuration files, Kubernetes | ||
| Nginx ingress annotations/classes, and host-service units are prohibited. | ||
|
|
||
| This is a runtime boundary, not a vocabulary ban. Documentation, license notices, | ||
| source-level scanner tests, and migration histories may name Nginx. Pull requests | ||
| that modify a runtime candidate are evaluated against the final exact head file, | ||
| so deleting a legacy artifact is allowed while preserving it or introducing a new | ||
| one fails closed. | ||
|
|
||
| ## Why this is not a search-and-replace | ||
|
|
||
| Pingora is a programmable Rust framework rather than an Nginx configuration | ||
| interpreter. The organization therefore maintains reusable, versioned Pingora | ||
| static-serving and proxy artifacts and gives product repositories only declarative | ||
| route/site contracts. Product repositories do not fork proxy internals. | ||
|
|
||
| ## Required migration contract | ||
|
|
||
| 1. Inventory the current listener, host/path matching, TLS ownership, static root, | ||
| upstream protocol, WebSocket/streaming behavior, body/timeout limits, headers, | ||
| health probes, metrics, and rollback path. | ||
| 2. Reproduce those behaviors with the approved Pingora artifact and a versioned | ||
| route/site manifest. | ||
| 3. Add behavior-level tests before deleting the old runtime artifact. | ||
| 4. Pin Pingora to an exact release at or above `0.8.0`; the shared baseline is | ||
| `0.8.1`. Do not use the experimental Pingora cache integration in the initial | ||
| migration. | ||
| 5. Preserve certificate data and rollback evidence, but never keep a runnable | ||
| Nginx fallback after cutover. Rollback means redeploying the prior application | ||
| release behind Pingora, not reintroducing Nginx. | ||
| 6. Treat PHP/FastCGI workloads as application-runtime migrations: place an | ||
| HTTP-capable PHP application server or a reviewed FastCGI adapter behind | ||
| Pingora before cutover. Pingora must remain the public HTTP/TLS edge. | ||
|
|
||
| ## Ownership | ||
|
|
||
| - `.github` owns the binding policy, scanner, shared contracts, and required gate. | ||
| - `linux-cluster-ops` owns environment-specific listeners, certificates, routes, | ||
| service units, backups, rollout, and host cutover. | ||
| - Each product owns its static build or upstream application behavior and tests. | ||
| - Keyverse remains the identity authority; an edge runtime never becomes the | ||
| identity system of record. | ||
|
|
||
| ## Enforcement and evidence | ||
|
|
||
| The organization-required `required-workflow-bootstrap` job runs trusted | ||
| base-branch scanner code at the immutable required-workflow SHA. It reads bounded | ||
| changed-file metadata and final UTF-8 content through GitHub's REST API. It does | ||
| not check out or execute pull-request content and receives only read permissions. | ||
| Malformed, truncated, binary, symlink, oversized, or unavailable evidence fails | ||
| closed. | ||
|
|
||
| ## Exception process | ||
|
|
||
| There is no standing Nginx exception. A temporary exception requires a public ADR | ||
| with an owner, exact affected asset, buyer impact, security controls, removal date, | ||
| and an approved Pingora migration PR. The central scanner remains unchanged; the | ||
| exception is implemented by completing the migration before merge. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -213,9 +213,9 @@ packaging==26.2 \ | |
| # via | ||
| # pip-audit | ||
| # pip-requirements-parser | ||
| pip==26.1.2 \ | ||
| --hash=sha256:382ff9f685ee3bc25864f820aa50505825f10f5458ffff07e30a6d96e5715cab \ | ||
| --hash=sha256:f49cd134c61cf2fd75e0ce2676db03e4054504a5a4986d00f8299ae632dc4605 | ||
| pip==26.2.1 \ | ||
| --hash=sha256:71138adf1f4ca900cdb7d289c21b7494329f2332b6d85f0e1c42108c0384ed3e \ | ||
| --hash=sha256:f6ad667e89a1fe78046c8f13232b247200f5258d7828f3f7883d660878e0813f | ||
|
Comment on lines
+216
to
+218
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔍 Unrelated pip lock bump in an edge-policy PR requirements-pip-audit-ci-hashes.txt bumps pip 26.1.2 -> 26.2.1, unrelated to the Pingora edge policy. CLAUDE.md requires Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| # via pip-api | ||
| pip-api==0.0.34 \ | ||
| --hash=sha256:8b2d7d7c37f2447373aa2cf8b1f60a2f2b27a84e1e9e0294a3f6ef10eb3ba6bb \ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 Info: Workflow context resolution relies on env fallback since job context lacks workflow_ keys*
The
Resolve immutable central policy sourcestep readsJOB_CONTEXT_JSON = toJSON(job)and thenjob_context.get("workflow_sha"/"workflow_ref"/"workflow_file_path"). The GitHubjobcontext only exposesstatus,container, andservices, so these.get()calls always return None and the code relies entirely on theWORKFLOW_SHA/WORKFLOW_REFenv fallbacks (fromgithub.workflow_sha/github.workflow_ref) and the ref-prefix derivation forworkflow_file_path. This is harmless (the fallbacks are correct), but thejob_contextlookups are effectively dead code — the intent may have been to read a different context (e.g.github).Was this helpful? React with 👍 or 👎 to provide feedback.