Repository navigation
fix(pingora): exempt documentation raster evidence #1420
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
Changes from 12 commits
0888c31
e29a96c
f67d22a
5422af7
5e08f53
25f4d2d
40bf13d
a8be56e
c1d1495
d43980f
7ee9662
78f890e
212ade1
a3c7986
d84d65c
a8dcb8d
b4dfa61
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
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. 📝 Info: Image size remains deliberately bounded
(Refers to this code) Was this helpful? React with 👍 or 👎 to provide feedback. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
|
@@ -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" | ||
|
|
@@ -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"}) | ||
|
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"}) | ||
|
|
@@ -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 | ||
| ) | ||
|
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
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. |
||
|
|
||
|
|
||
| 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 | ||
|
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) | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
Resolved
|
||
| ): | ||
|
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: | ||
|
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 | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
Resolved
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 | ||
|
seonghobae marked this conversation as resolved.
Resolved
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.""" | ||
|
|
||
|
|
@@ -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: | ||
|
|
@@ -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 | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.