# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference) # Canonical Residual: https://corpusfetch.com/c/across-what-encoding-and-canonicalization-boundaries-must-an-instance-normalize-identifiers-and-paths-to-prevent-equivalence-collision-and-bypass.md # Remnant Warrant: https://corpusfetch.com/c/across-what-encoding-and-canonicalization-boundaries-must-an-instance-normalize-identifiers-and-paths-to-prevent-equivalence-collision-and-bypass.warrant.json # Markdown SHA-256: ed074fca5b7bc66d424c74a9d2e420af1d875e00c6e0ce598474256badddda32 # Code AST SHA-256: 88f515401ca9ca8d1fe8c3a2fd3143011490732ca70a251f0a9b70667ed4ec0c # Verified Classes: CanonicalizationBypassError, CanonicalPathVerdict, CanonicalBoundaryNormalizer # Defensive Guards: 7 fail-closed invariants (zero external deps) import unicodedata from dataclasses import dataclass from pathlib import Path from typing import Final from urllib.parse import unquote class CanonicalizationBypassError(PermissionError): '''Raised when an identifier or path exhibits double-encoding, homoglyph evasion, or sandbox escape.''' pass @dataclass(frozen=True) class CanonicalPathVerdict: raw_input: str resolved_canonical_path: Path sandbox_root: Path is_confined: bool class CanonicalBoundaryNormalizer: ''' Enforces strict canonicalize-before-validate invariants for paths and identifiers: 1. Rejects null bytes, control chars, and multi-pass percent-encoding (`unquote(unquote(x)) != unquote(x)`). 2. Applies Unicode NFKC normalization and blocks mixed-script confusables / zero-width chars. 3. Verifies physical path confinement via `Path.resolve()` and `Path.is_relative_to()`. ''' FORBIDDEN_CODEPOINTS: Final[frozenset[int]] = frozenset({ 0x0000, 0x200B, 0x200C, 0x200D, 0x202A, 0x202B, 0x202C, 0x202D, 0x202E, 0xFEFF }) @classmethod def canonicalize_identifier(cls, raw_id: str) -> str: if not raw_id: raise CanonicalizationBypassError("Identifier cannot be empty.") # Gate 1: Reject double-encoding and forbidden control/zero-width codepoints once = unquote(raw_id) if unquote(once) != once: raise CanonicalizationBypassError(f"Rejected double-encoded identifier: '{raw_id}'.") for ch in once: if ord(ch) in cls.FORBIDDEN_CODEPOINTS or unicodedata.category(ch).startswith("C"): raise CanonicalizationBypassError( f"Rejected forbidden control or zero-width codepoint U+{ord(ch):04X} in identifier." ) # Gate 2: NFKC normalization & mixed-script homoglyph check nfkc = unicodedata.normalize("NFKC", once).strip().casefold() scripts: set[str] = set() for ch in nfkc: if ch.isalpha(): name = unicodedata.name(ch, "") script = name.split(" ")[0] if name else "UNKNOWN" scripts.add(script) if len(scripts) > 1: raise CanonicalizationBypassError( f"Rejected mixed-script confusable identifier '{raw_id}' (scripts={sorted(scripts)})." ) return nfkc @classmethod def resolve_confined_path(cls, sandbox_root: Path, untrusted_rel_path: str) -> CanonicalPathVerdict: # Gate 1: Reject null bytes and double URL encoding if "\x00" in untrusted_rel_path: raise CanonicalizationBypassError("Null byte injection detected in path.") decoded = unquote(untrusted_rel_path) if unquote(decoded) != decoded: raise CanonicalizationBypassError(f"Double-encoded path traversal rejected: '{untrusted_rel_path}'.") norm_rel = unicodedata.normalize("NFC", decoded) # Gate 3 & Gate 4: Resolve physical realpath (following symlinks) and enforce is_relative_to resolved_root = sandbox_root.resolve() candidate_resolved = (resolved_root / norm_rel).resolve() if not candidate_resolved.is_relative_to(resolved_root): raise CanonicalizationBypassError( f"Sandbox Escape Blocked: '{untrusted_rel_path}' resolved to '{candidate_resolved}', " f"which is outside sandbox root '{resolved_root}'." ) return CanonicalPathVerdict( raw_input=untrusted_rel_path, resolved_canonical_path=candidate_resolved, sandbox_root=resolved_root, is_confined=True, )