5.0 KiB
Outbound Safety Check
What This Checks
The static safety check prevents accidental bypass of the outbound sanitization funnel by detecting:
- Direct use of raw
contextfields in POST payloads - Missing
sanitize_outbound_payload()calls - Dangerous fallback patterns like
sanitized_X or X json.dumps()on sensitive data
Why This Matters
ComfyUI-Doctor sends error context to LLM providers for analysis. To protect user privacy, all outbound payloads MUST go through the sanitization funnel in outbound.py.
Provider-specific LLM request construction is centralized in services/llm_provider_adapters.py, but route handlers remain responsible for sanitizing the completed payload immediately before network transmission.
The runtime sanitization boundary always redacts values associated with
Authorization and X-API-Key, case-insensitively. This applies to header-like
text, JSON/dictionary text, nested mappings/lists, request and response header
objects, recent log-buffer reads, and outbound payloads even when privacy mode
is none. Ordinary prose that merely uses the word "authorization" and
non-sensitive headers remain intact.
Sensitive Fields:
context.traceback- Raw Python stack trace with user pathscontext.workflow_json- Full workflow with potential pathscontext.system_info- System environment datacontext.settings- App settings
How to Fix Violations
❌ Wrong: Direct Field Access
payload = {"error": context.traceback}
await session.post(url, json=payload)
✅ Right: Sanitize Before Use
sanitizer, _ = get_outbound_sanitizer(base_url, privacy_mode)
error_text = sanitizer.sanitize(context.traceback).sanitized_text
payload = {"error": error_text}
payload = sanitize_outbound_payload(payload, sanitizer)
await session.post(url, json=payload)
❌ Wrong: Dangerous Fallback
data = {
"traceback": context.sanitized_traceback or context.traceback # BAD!
}
✅ Right: Fail Safe
if not context.sanitized_traceback:
logger.warning("Sanitized traceback missing")
context.llm_context = None
return context
data = {
"traceback": context.sanitized_traceback # Only use sanitized
}
❌ Wrong: POST Without Sanitization
async def handler():
sanitizer, _ = get_outbound_sanitizer(url, mode)
payload = {"data": "test"}
await session.post(url, json=payload) # Never sanitized!
✅ Right: Always Sanitize
async def handler():
sanitizer, _ = get_outbound_sanitizer(url, mode)
payload = {"data": "test"}
payload = sanitize_outbound_payload(payload, sanitizer)
await session.post(url, json=payload)
Suppressing False Positives
If you have a legitimate exception (e.g., local debug endpoint), add a suppression comment:
# nosec: outbound-bypass-allowed - Reason: local debugging only
payload = {"raw": context.traceback}
Important: Use suppressions sparingly. Most cases should use the proper sanitization pattern.
Running Locally
python scripts/check_outbound_safety.py
Output Example:
Outbound Funnel Safety Check
Scanning: /path/to/ComfyUI-Doctor
Checking 42 Python files...
❌ 1 violation(s) detected:
File: tests/fixtures/outbound_violations.py:12:8
Rule: DANGEROUS_FALLBACK
Message: Dangerous fallback: context.sanitized_traceback or context.traceback
10: payload = {
11: "traceback": context.sanitized_traceback or context.traceback,
12: }
--------------------------------------------------------------------------------
🚫 1 outbound safety violation(s) found
CI Integration
This check runs automatically on all Python file changes via .github/workflows/outbound-safety.yml.
Exit Codes:
0= All checks passed1= Violations detected (fails CI)2= Script error
Whitelisted Files
The following files are excluded from checking:
tests/- Tests need to verify behavioroutbound.py- Sanitization module itselfsanitizer.py- PII sanitization modulepipeline/stages/sanitizer.py- Stage that reads raw fields
Detection Rules
| Rule | Description | Example |
|---|---|---|
RAW_FIELD_IN_PAYLOAD |
Direct assignment of raw context field to payload | payload["error"] = context.traceback |
DANGEROUS_FALLBACK |
Fallback to raw field if sanitized missing | context.sanitized_traceback or context.traceback |
POST_WITHOUT_SANITIZATION |
POST without prior sanitize_outbound_payload() |
await session.post(url, json=payload) |
JSON_DUMPS_RAW_FIELD |
json.dumps() on raw context field |
json.dumps(context.system_info) |
Resources
- Sanitization Funnel:
outbound.py - LLM Provider Adapters:
services/llm_provider_adapters.py - Privacy Modes:
sanitizer.py - Test Fixtures:
tests/fixtures/outbound_violations.py - Test Suite:
tests/test_outbound_safety_gate.py
Questions?
If you're unsure whether your code violates the policy, run the check locally before pushing.