Skip to content
Closed
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ this file. The format follows Keep a Changelog, and versioned releases follow
Semantic Versioning where the repository publishes a release.

## [Unreleased]
- Validate final documentation PNG bytes through chunk, CRC, zlib-stream,
palette, dimension, and scanline contracts before granting the narrow
Pingora-policy documentation exception.
- Fix one more Devin Review finding on PR #1452, a genuine gap in the round-4
malformed-gateway-reply fix (`scripts/ci/contextual_orchestrator_review_sidecar.sh`,
`tests/test_contextual_orchestrator_review_runtime_preflight.py`):
Expand Down
11 changes: 7 additions & 4 deletions docs/adr/0019-cloudflare-pingora-edge-standard.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ so a governed shared implementation is required.
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. Only dedicated source fixtures and the policy scanner may contain denied Nginx
samples; executable integration and end-to-end test helpers remain candidates
5. Documentation prose, license text, dedicated source fixtures, the policy
scanner, and structurally validated PNG evidence beneath documentation directories
may contain denied Nginx samples. Image extensions alone do not establish the
exception; executable integration and end-to-end test helpers remain candidates
for enforcement.
6. Initial migration does not use Pingora's experimental cache integration.
7. PHP workloads move to an HTTP application server or reviewed FastCGI adapter
Expand Down Expand Up @@ -66,5 +68,6 @@ so a governed shared implementation is required.

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.
inspection, raster-format validation, and fail-closed malformed-evidence tests.
Product migrations require site/proxy behavior tests and deployment-specific
smoke tests before cutover.
6 changes: 4 additions & 2 deletions docs/doctoring/pingora-edge-standard.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ dependencies. The initial CWL implementation avoids experimental cache APIs.
- 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.
- Runtime evidence is bounded to one-megabyte regular-file bytes, with UTF-8
decoding for runtime candidates and complete PNG chunk/CRC/zlib/scanline
validation for documentation images; the maximum is 3,000 changed files and 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.
Expand Down
23 changes: 14 additions & 9 deletions docs/policies/PINGORA_EDGE_POLICY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ 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,
This is a runtime boundary, not a vocabulary ban. Documentation, recognized
non-executable image evidence beneath a documentation directory, license notices,
dedicated source fixtures under `tests/fixtures/`, the scanner source itself, and
migration histories may name Nginx. Executable integration and end-to-end test
helpers remain runtime candidates. 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.
migration histories may name Nginx. An image suffix alone is not an exception:
final bounded bytes must be a structurally valid, completely decodable PNG. Executable integration
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
and end-to-end test helpers remain runtime candidates. 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

Expand Down Expand Up @@ -51,10 +54,12 @@ route/site contracts. Product repositories do not fork proxy internals.

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.
changed-file metadata and final file bytes through GitHub's REST API; runtime
candidates must decode as UTF-8, while documentation PNG evidence must pass
chunk-order, CRC, zlib-stream, dimension, and scanline validation and match
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
its supported format. It does not check out or execute pull-request content and
receives only read permissions. Malformed, truncated, unrecognized binary,
symlink, oversized, or unavailable evidence fails closed.

## Exception process

Expand Down
226 changes: 226 additions & 0 deletions scripts/ci/pingora_edge_policy.py

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.

📝 Info: Image size remains deliberately bounded

_load_raw_file_bytes still rejects PNGs above one megabyte. The policy explicitly requires bounded evidence, so larger screenshots remain unsupported by contract.

(Refers to this code)

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import os
import re
import sys
import zlib
from dataclasses import dataclass
from pathlib import PurePosixPath
from typing import Callable, Mapping, Sequence
Expand All @@ -24,6 +25,7 @@

MAX_FILE_BYTES = 1_048_576
MAX_RESPONSE_BYTES = 16_777_216
MAX_IMAGE_DECODED_BYTES = MAX_RESPONSE_BYTES
REPOSITORY_RE = re.compile(r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$")
SHA_RE = re.compile(r"^[0-9a-f]{40}$")
GITHUB_API_ORIGIN = "https://api.github.com"
Expand All @@ -39,6 +41,7 @@
# (this org's own "attach the relevant paper PDF" convention) for a reason
# that has nothing to do with the Nginx runtime policy this module enforces.
BINARY_DOCUMENT_SUFFIXES = frozenset({".pdf"})
DOCUMENT_ASSET_SUFFIXES = frozenset({".png"})
Comment thread
seonghobae marked this conversation as resolved.
Resolved
SOURCE_TEST_SUFFIXES = frozenset({".py", ".pyi", ".js", ".mjs", ".cjs", ".ts", ".tsx", ".rs"})
LICENSE_NAMES = frozenset({"license", "license.md", "copying", "copyrights", "notice"})
DOCUMENTATION_DIRECTORIES = frozenset({"doc", "docs", "documentation"})
Expand Down Expand Up @@ -228,6 +231,218 @@ def _is_binary_documentation_pdf(changed: ChangedFile) -> bool:
)


def _is_documentation_image_path(path: str) -> bool:
"""Return whether *path* claims to be raster evidence below documentation."""

pure = PurePosixPath(path)
return (
len(pure.parts) > 1
and any(part.lower() in DOCUMENTATION_DIRECTORIES for part in pure.parts[:-1])
and pure.suffix.lower() in DOCUMENT_ASSET_SUFFIXES
)
Comment thread
seonghobae marked this conversation as resolved.
Resolved


def _paeth_predictor(left: int, above: int, upper_left: int) -> int:
"""Return the PNG Paeth predictor for three reconstructed bytes."""

estimate = left + above - upper_left
distances = (abs(estimate - left), abs(estimate - above), abs(estimate - upper_left))
return (left, above, upper_left)[distances.index(min(distances))]


def _png_rows_are_valid(
pixels: bytes,
pass_layouts: list[tuple[int, int, int, int]],
*,
bytes_per_pixel: int,
bit_depth: int,
palette_entries: int | None,
) -> bool:
"""Validate filters and indexed samples for every decoded PNG pass."""

for start, stride, rows, pass_width in pass_layouts:
previous = bytes(stride - 1)
for row in range(rows):
row_start = start + row * stride
filter_type = pixels[row_start]
if filter_type > 4:
return False
encoded = pixels[row_start + 1 : row_start + stride]
reconstructed = bytearray(len(encoded))
for index, value in enumerate(encoded):
left = reconstructed[index - bytes_per_pixel] if index >= bytes_per_pixel else 0
above = previous[index]
upper_left = previous[index - bytes_per_pixel] if index >= bytes_per_pixel else 0
predictor = (0, left, above, (left + above) // 2, _paeth_predictor(left, above, upper_left))[filter_type]
reconstructed[index] = (value + predictor) & 0xFF
previous = bytes(reconstructed)
if palette_entries is None:
continue
samples_seen = 0
for byte in reconstructed:
for shift in range(8 - bit_depth, -1, -bit_depth):
if samples_seen == pass_width:
break
if ((byte >> shift) & ((1 << bit_depth) - 1)) >= palette_entries:
return False
samples_seen += 1
return True
Comment on lines +253 to +289

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.

📝 Info: Interlaced scanlines use correct geometry

_png_rows_are_valid resets prior rows per Adam7 pass and uses the defined filter byte distance. Indexed checks exclude only padding samples.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.



def _is_recognized_documentation_image(path: str, raw: bytes) -> bool:
"""Accept only bounded bytes with a structurally complete PNG envelope."""

suffix = PurePosixPath(path).suffix.lower()
if suffix != ".png" or len(raw) < 33 or not raw.startswith(b"\x89PNG\r\n\x1a\n"):
return False
offset = 8
width = height = bit_depth = color_type = interlace = None
idat_parts: list[bytes] = []
saw_plte = False
palette_entries = None
saw_trns = False
saw_idat = False
finished_idat = False
while offset + 12 <= len(raw):
length = int.from_bytes(raw[offset : offset + 4], "big")
end = offset + 12 + length
if end > len(raw):
return False
chunk_type = raw[offset + 4 : offset + 8]
chunk_data = raw[offset + 8 : offset + 8 + length]
if (
len(chunk_type) != 4
or any(not (65 <= byte <= 90 or 97 <= byte <= 122) for byte in chunk_type)
or chunk_type[2] & 0x20
):
return False
if chunk_type[0] & 0x20 == 0 and chunk_type not in {
b"IHDR",
b"PLTE",
b"IDAT",
b"IEND",
}:
return False
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Resolved
declared_crc = int.from_bytes(raw[offset + 8 + length : end], "big")
if zlib.crc32(chunk_type + chunk_data) & 0xFFFFFFFF != declared_crc:
return False
if offset == 8 and (chunk_type != b"IHDR" or length != 13):
return False
if chunk_type == b"IHDR":
if offset != 8 or width is not None:
return False
width = int.from_bytes(chunk_data[:4], "big")
height = int.from_bytes(chunk_data[4:8], "big")
bit_depth, color_type, compression, filtering, interlace = chunk_data[8:13]
if (
width == 0
or height == 0
or width > MAX_IMAGE_DECODED_BYTES
or height > MAX_IMAGE_DECODED_BYTES
or compression != 0
or filtering != 0
or interlace not in {0, 1}
or bit_depth
not in {
0: {1, 2, 4, 8, 16},
2: {8, 16},
3: {1, 2, 4, 8},
4: {8, 16},
6: {8, 16},
}.get(color_type, set())
):
return False
elif chunk_type == b"PLTE":
if (
width is None
or saw_plte
or saw_idat
or color_type in {0, 4}
or length == 0
or length % 3
or length > 768
or (color_type == 3 and length // 3 > 2**bit_depth)
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Resolved
):
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
return False
saw_plte = True
palette_entries = length // 3
elif chunk_type == b"tRNS":
valid_length = {
0: length == 2,
2: length == 6,
3: saw_plte and 0 < length <= (palette_entries or 0),
}.get(color_type, False)
if width is None or saw_trns or saw_idat or not valid_length:
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Resolved
return False
saw_trns = True
elif chunk_type == b"IDAT":
if width is None or finished_idat or (color_type == 3 and not saw_plte):
return False
saw_idat = True
idat_parts.append(chunk_data)
elif saw_idat:
finished_idat = True
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Resolved
Comment thread
seonghobae marked this conversation as resolved.
Resolved
if chunk_type == b"IEND":
if (
length != 0
or not saw_idat
or end != len(raw)
or width is None
or height is None
or (color_type == 3 and not saw_plte)
):
return False
channels = {0: 1, 2: 3, 3: 1, 4: 2, 6: 4}[color_type]
passes = (
[(0, 0, 1, 1)]
if interlace == 0
else [
(0, 0, 8, 8),
(4, 0, 8, 8),
(0, 4, 4, 8),
(2, 0, 4, 4),
(0, 2, 2, 4),
(1, 0, 2, 2),
(0, 1, 1, 2),
]
)
pass_layouts: list[tuple[int, int, int, int]] = []
expected_size = 0
for x_start, y_start, x_step, y_step in passes:
pass_width = 0 if width <= x_start else (width - x_start + x_step - 1) // x_step
pass_height = 0 if height <= y_start else (height - y_start + y_step - 1) // y_step
if not pass_width or not pass_height:
continue
row_bytes = (pass_width * channels * bit_depth + 7) // 8
stride = row_bytes + 1
pass_size = pass_height * stride
if pass_size > MAX_IMAGE_DECODED_BYTES - expected_size:
return False
pass_layouts.append((expected_size, stride, pass_height, pass_width))
expected_size += pass_size
try:
decoder = zlib.decompressobj()
pixels = decoder.decompress(b"".join(idat_parts), expected_size + 1)
pixels += decoder.flush()
except zlib.error:
return False
return (
decoder.eof
and not decoder.unused_data
and not decoder.unconsumed_tail
and len(pixels) == expected_size
and _png_rows_are_valid(
pixels,
pass_layouts,
bytes_per_pixel=max(1, (channels * bit_depth + 7) // 8),
bit_depth=bit_depth,
palette_entries=palette_entries if color_type == 3 else None,
)
)
offset = end
return False
Comment thread
seonghobae marked this conversation as resolved.
Resolved
Comment thread
seonghobae marked this conversation as resolved.
Resolved


def _runtime_path_rule(path: str) -> str | None:
"""Return a path-level violation rule for active Nginx runtime artifacts."""

Expand Down Expand Up @@ -460,6 +675,8 @@ def _needs_content_scan(changed: ChangedFile) -> bool:

if changed.status == "removed" or _is_documentation_or_source_fixture(changed.path):
return False
if _is_documentation_image_path(changed.path):
return True
if _is_binary_documentation_pdf(changed):
return False
if not changed.patch_available:
Expand Down Expand Up @@ -499,6 +716,15 @@ def evaluate_pull_request(
changed_files = _load_changed_files(api_url.rstrip("/"), repository, pull_request, token, opener)
violations: list[Violation] = []
for changed in changed_files:
if changed.status != "removed" and _is_documentation_image_path(changed.path):
raw = _load_raw_file_bytes(
api_url.rstrip("/"), repository, changed.path, head_sha, token, opener
)
if not _is_recognized_documentation_image(changed.path, raw):
raise PolicyError(
f"Documentation image evidence for {changed.path} is not a recognized image"
)
continue
# A claimed binary documentation PDF gets its own network-verified
# check ahead of _needs_content_scan's patch-presence-only signal:
# a missing patch does not by itself prove binary content (GitHub
Expand Down
Loading
Loading