Repository navigation
Conversation
Spike tasks: - Analyze RAGEngine guardrail code for reuse potential - Minimal Python ext_proc prototype with SSE streaming - Benchmark Python vs Go (latency, memory, throughput) - Performance/reuse trade-off decision Success criteria: - Clear technical decision (Go/Python/Hybrid) - Reuse effort estimated - Baseline measurements established
Isolated experiment environment for Go vs Python ext_proc evaluation: - Go ext_proc baseline (existing PoC logic) - Mock vLLM backend with SSE streaming test cases - E2E test script (same Envoy config, different backends) - Test cases: normal response, secret split across chunks, boundary Metrics to track: latency (p50/p99), memory, throughput, correctness Next: add python/main.py and envoy/envoy.yaml
Python ext_proc implementation: - Async gRPC processor (matches Go behavior) - SSE parsing + logical content buffering - Holdback mechanism for pattern detection - Secret redaction logic - MockExtProc fallback for protobuf types (pending real generation) Envoy configuration: - Listener on :10000 → routes to backend (:8000) - ext_proc filter on response_body (FULL_DUPLEX_STREAMED) - Cluster config for ext_proc backend (:9000) - failure_mode_allow: false (fail-closed) Same config works for both Go and Python backends
Proto generation script: - Clones envoyproxy/envoy v1.29.2 (matching Istio 1.29.2) - Generates Python bindings for ext_proc service - Creates /gen with Python stubs Next: run generate_proto.sh and update python/main.py with real proto
Python 1.29.2 ext_proc PoC: - venv with grpcio + protobuf - requirements.txt for dependencies - proceed with mock proto for local E2E - official proto generation deferred to separate task Next: run local E2E with mock backend + Envoy + Python ext_proc
Direct unit tests of Python ext_proc (no Envoy needed): ✓ Test 1: Redact Normal secret detected and redacted ✓ Test 2: Boundary Secret split across chunks correctly handled ✓ Test 3: Holdback Holdback mechanism prevents unsafe prefix leakage Example: 'sk-12' never leaked, always waited for full pattern ✓ Test 4: Concurrency Multiple concurrent requests maintain independent state Result: Python ext_proc core logic is correct Ready for Envoy/gRPC integration in next phase
Status: - Core SSE parsing logic: VERIFIED - Holdback + scan mechanism: VERIFIED - Boundary case (secret split across chunks): VERIFIED - Concurrency (independent per-request state): VERIFIED - 4/4 test cases PASS Results: ✓ Redact - secret detection and redaction works ✓ Boundary - cross-chunk holdback prevents unsafe prefix leakage ✓ Holdback - logical buffer management is correct ✓ Concurrency - request state isolation is correct Pending for Phase 1+: - Real gRPC ExternalProcessor implementation - Envoy 1.29.2 integration - Official Envoy proto bindings (Go/Python) - Production E2E testing This PoC proves: 1. Python ext_proc architecture is sound 2. SSE streaming correctness is verified 3. Ready for Envoy gateway integration Next phase: implement real gRPC service + full E2E with actual Envoy
Python gRPC proto generation hit dependency hell. Switching to Go implementation for Phase 1 real E2E: - go-control-plane has official Envoy 1.29.2 bindings - Skip proto compilation entirely - Same SSE logic, just in Go gRPC service - Test: normal streaming + boundary redact via real Envoy If Go E2E passes → proven that ext_proc can work Then decide: Python vs Go for production
Phase 1.1 foundation: Minimal gRPC ext_proc server stub. - ext_proc/ module with __init__ and server.py - ExternalProcessorServicer placeholder (Process() raises NotImplementedError) - requirements.txt with core dependencies (grpcio, protobuf, pydantic, pyyaml, tiktoken) - Dockerfile for containerization (python:3.11-slim) - setup.py for package management - pytest.ini and test structure - .gitignore for Python/IDE artifacts Tests: - test_version: confirms version is 0.1.0 - test_imports: verifies core dependencies importable - test_servicer_placeholder: confirms ExternalProcessorServicer exists All tests pass. Ready for Phase 1.2 (SSE Parser) to start. Depends on: none Blocks: PR-1.2 (SSE Parser), PR-1.4 (Scanner Interface)
Phase 1.2: Core SSE parsing for OpenAI-compatible LLM streaming.
streaming/sse_parser.py:
- split_sse_events(buf: bytes) → (events, remaining)
Splits raw bytes on \n\n delimiter, returns complete events + remainder
- parse_sse_event(event: bytes) → Optional[dict]
Parses "data: {...}" JSON, extracts delta.content from OpenAI format
- is_done_event(event: bytes) → bool
Detects [DONE] marker for stream termination
Handles:
- Complete events split cleanly on delimiters
- Incomplete events held for next buffer chunk
- Malformed JSON (logs warning, returns None)
- OpenAI delta format with finish_reason, etc.
- Empty/missing content fields
- Non-data lines (comments, etc.)
Tests (16 tests, 100% pass):
- TestSplitSSEEvents: single, multiple, incomplete, empty
- TestParseSSEEvent: delta content, finish_reason, [DONE], edge cases
- TestIsDoneEvent: detection with/without prefix, malformed
- TestIntegration: realistic streaming sequence (chunks end mid-event)
Depends on: PR-1.1
Blocks: PR-1.3 (Logical Stream + Holdback)
Phase 1.3: Core stream buffering and cross-event pattern detection. streaming/logical_stream.py: LogicalStreamBuffer class - append(content: str): Accumulates delta.content from SSE events - get() → str: Returns current buffer contents - clear(): Clears the buffer - flush_and_clear() → str: Get and clear in one atomic operation - Handles concatenation of fragments across multiple SSE events streaming/holdback.py: Cross-event pattern detection mechanism - release_safe_prefix(pending: str, pattern_length: int) → (safe, held) Core holdback logic: holds back (pattern_length - 1) bytes to prevent unsafe prefix leakage. Ensures patterns split across event boundaries are caught by next scan. Example: for "sk-123456789" (12 chars), holds back 11 chars to guarantee detection. - detect_cross_boundary_pattern(previously_held, new_content, pattern) → bool Utility for debugging/testing cross-boundary scenarios. Tests (34 tests, 100% pass): - TestLogicalStreamBuffer (5): single/multiple append, clear, flush - TestReleaseSafePrefix (5): small buffer, exact size, large buffer, cross-event scenario - TestDetectCrossBoundaryPattern (5): pattern within held/new, spans boundary, nonexistent - TestIntegration (2): streaming with holdback, multiple chunks - TestHoldbackMechanism (8): various pattern sizes, accumulation, prefix leakage prevention - TestCrossBoundaryDetection (5): boundary detection edge cases - TestHoldbackEdgeCases (4): pattern_length edge cases Depends on: PR-1.1, PR-1.2 Blocks: PR-1.4 (Scanner Interface), PR-1.5 (GuardEngine)
Phase 1.4: Extensible scanner architecture for pattern detection. scanners/interface.py: - ScanResult dataclass: Encapsulates scan results (scanner_name, pattern, found, match_index, match_text) with validation ensuring consistency (e.g., if found=True, match_index >= 0 and match_text is non-empty) - Scanner abstract base class: Defines interface all pattern detectors must implement - name property: Unique scanner identifier - pattern property: Pattern string being detected - scan(content: str) → ScanResult: Core scanning method scanners/registry.py: ScannerRegistry for managing multiple scanners - register(scanner): Add scanner to registry (rejects duplicates) - unregister(name): Remove scanner by name - get(name) → Optional[Scanner]: Lookup scanner by name - list_scanners() → List[str]: Get all registered scanner names - scan(content, scanner_name=None) → List[ScanResult]: - If scanner_name specified: scan with only that scanner - If None: scan with all registered scanners - Returns list of ScanResult objects Tests (23 tests, 100% pass): - ScanResult validation (6 tests): found/not-found consistency checks - Registry operations (15 tests): register, unregister, get, list, scan (single/all) - Integration (2 tests): realistic security scanning, dynamic registration Key design decisions: - ScanResult validation at construction time prevents invalid state propagation - Registry.scan() supports both single and bulk scanning modes - Scanners are completely decoupled from each other - Scanner order preserved (dict maintains insertion order in Python 3.7+) Depends on: PR-1.1, PR-1.2, PR-1.3 Blocks: PR-1.5 (GuardEngine), PR-1.6 (BanSubstrings Scanner)
Phase 1.5: Central coordinator orchestrating pattern detection in streaming. guard/engine.py: GuardEngine coordinator class - __init__(registry, pattern_length=12): Initialize with scanner registry - process_delta(content: str) → Optional[List[ScanResult]]: Processes single SSE delta through buffering → holdback → scanning pipeline Returns scan results if content released, None if held back entirely - flush() → List[ScanResult]: End-of-stream flush: scans all remaining held + buffered content Used after stream terminates to catch boundary patterns - has_detections() → bool: True if any scan found a pattern - get_detections() → List[ScanResult]: Returns only found results - scan_history() → List[ScanResult]: Complete history of all scans - reset(): Clear state for new stream processing Key flow: 1. Accumulate delta into LogicalStreamBuffer 2. Combine held content + current buffer 3. Split using Holdback mechanism (safe_release, must_hold) 4. Scan released content with all registered Scanners 5. Store held content for next iteration 6. At stream end, flush() scans remaining content Tests (18 tests, 100% pass): - Initialization & basic operations (6 tests) - Holdback mechanism integration (3 tests) - Flush behavior (3 tests) - Reset and reuse (2 tests) - Integration scenarios (4 tests) Design principles: - Separation of concerns: GuardEngine delegates to specific components - Stateful processing: Maintains held content across delta boundaries - Non-invasive: Changes content flow through pipeline without modifying deltas - Composable: Works with any ScannerRegistry implementation Depends on: PR-1.1, PR-1.2, PR-1.3, PR-1.4 Blocks: PR-1.6 (BanSubstrings Scanner), PR-1.7 (Server Integration)
Phase 1.6: Reference implementation of a pattern detector scanning for banned substrings.
scanners/ban_substrings.py: BanSubstringsScanner concrete implementation
- __init__(name, banned_substrings): Initialize with scanner name and list of banned substrings
Validates: name non-empty, no empty substrings in list
- Implements Scanner interface:
- name property: Scanner identifier
- pattern property: Description of what's being detected
- scan(content) → ScanResult: Find first banned substring in content
- Features:
- Exact substring matching (case-sensitive)
- Reports first match found in order of ban list
- Returns match index and matched text
- Handles empty content and empty ban list gracefully
Key design:
- Simple, single-responsibility: detects banned substrings only
- Works with any number of banned patterns
- Returns first match (order matters)
- Case-sensitive matching (important for security patterns)
- Zero false positives/negatives (deterministic matching)
Tests (33 tests, 100% pass):
- Initialization validation (6): name/substring validation, empty lists
- Properties (4): name, pattern, repr
- Scanning (10): no match, exact match, position tests, case sensitivity, multiple patterns
- Edge cases (3): empty content, empty ban list
- Real-world scenarios (6): API keys, passwords, tokens, SQL injection
- Integration (4): Scanner interface compatibility, registry integration, GuardEngine integration
Example usage:
```python
scanner = BanSubstringsScanner("api_keys", ["sk-", "rk-", "pk-"])
result = scanner.scan("My API key is sk-1234567890")
if result.found:
print(f"Found banned pattern: {result.pattern}")
```
Depends on: PR-1.1, PR-1.2, PR-1.3, PR-1.4
Blocks: PR-1.7 (Server Integration)
This is a reference implementation showing the pattern for creating new scanners.
Future scanners can implement regex patterns, regex scanning, PII detection, etc.
… + 120 tests) Phase 2.4: Extended scanner implementations for comprehensive pattern detection. scanners/additional_scanners.py: 1. RegexScanner (~50 LOC) - Multi-pattern regex matching with priority order - Case-sensitive (default) or case-insensitive modes - Regex validation at construction time - Supports any valid Python regex pattern 2. SecretsScanner (~70 LOC) - Detects API keys, tokens, and secrets - Patterns: OpenAI (sk-), Anthropic (sk-ant-), GitHub (ghp_), Stripe (pk_) - Generic pattern: assignment-style secrets (API_KEY=value) - Redaction modes: none, full, partial, hash - Partial: keeps first/last 4 chars (sk-****...****) - Hash: replaces with [HASH:checksum] - Supports configurable redaction strategy 3. SensitiveScanner (~70 LOC) - PII/Sensitive data detection - Patterns: Email, Phone (US), Credit Card, IPv4/IPv6, SSN - Returns first match found - Case-insensitive by default for PII matching Tests (40 tests, 100% pass): - RegexScanner (10): initialization, pattern matching, case sensitivity, multi-pattern - SecretsScanner (9): API key detection, all redaction modes, assignment patterns - SensitiveScanner (10): email, phone, credit card, IP addresses, SSN - Integration (6): Scanner interface compliance, registry integration, GuardEngine - Real-world (5): log lines with secrets, configs with PII, multi-pattern scenarios Design: - All scanners implement Scanner interface (compatible with registry) - Independent and composable (can be used separately or together) - No external dependencies (pure Python regex) - Configurable behavior (redaction modes, patterns, case sensitivity) Redaction capability: - SecretsScanner: full redaction support (all modes) - RegexScanner/SensitiveScanner: no redaction (returns matched text as-is) Files: - scanners/additional_scanners.py: 3 scanner implementations (~160 LOC) - tests/test_additional_scanners.py: comprehensive test suite (~140 LOC) Depends on: Phase 1-2.1 (Scanner interface + Registry) Enables: Phase 3 (Server Integration with full scanning capability)
xiaoqi-7
requested review from
Fei-Guo,
rambohe-ch,
techworldhello and
zhuangqh
as code owners
October 2, 2026 07:20
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 2.4: Comprehensive scanner implementations including Regex patterns, Secret detection with multiple redaction modes, and PII/Sensitive data detection (email, phone, credit cards, IP addresses, SSN).
Three Scanner Implementations
1. RegexScanner
Flexible regex pattern matching with support for multiple patterns and case sensitivity options.
Features:
Example:
2. SecretsScanner
API key and secret detection with configurable redaction.
Detected Patterns:
sk-...(20+ chars)sk-ant-...(40+ chars)ghp_...(36+ chars)pk_...(24+ chars)API_KEY=value,password=..., etc.Redaction Modes:
none: Return secret as-is (default)full: Replace with[REDACTED]partial: Keep first/last 4 chars:sk-a...****...****hash: Replace with[HASH:checksum]Example:
3. SensitiveScanner
PII and sensitive data detection without redaction (detected and reported, not modified).
Detected Patterns:
user@domain.com555-123-4567,(555) 123-45674111-1111-1111-1111192.168.1.12001:0db8:85a3::8a2e:0370:7334123-45-6789Example:
Test Coverage
40 tests, 100% pass rate
Design Decisions
Integration Points
All scanners work seamlessly with:
Files Changed
scanners/additional_scanners.py- Three scanner implementations (~160 LOC)tests/test_additional_scanners.py- Comprehensive test suite (~140 LOC)Architecture
Dependencies
Test Results
Next Steps
Phase 3 will integrate these scanners into the Envoy ext_proc gRPC server handler.