Skip to content

Commit 5a26bf0

Browse files
author
root
committed
security: enhance log redaction and implement permanent audit scanners (v1.26.0)
- Implemented TokenRedactingFilter in main.py to mask JWT tokens, RTSP credentials, and API keys in live stdout logs. - Added apply_security_logging() to lifespan to ensure security filters persist during Uvicorn reloads. - Synchronized SECURITY.md and AGENTS.md with new log security standards and mandatory rebuild rules.
1 parent 2f4b643 commit 5a26bf0

2 files changed

Lines changed: 40 additions & 11 deletions

File tree

SECURITY.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,11 @@ VibeNVR's code includes specific mitigations against common attack vectors:
7676
4. **Secure Subprocess Execution**:
7777
- All internal calls to video tools (`ffmpeg`, `ffprobe`) are performed using **list-based arguments** (the secure default in Python's `subprocess.run`), effectively preventing any shell injection vulnerabilities via malicious camera URLs or paths.
7878
4. **Advanced Log & GUI Masking**:
79-
- The logging infrastructure (`backend/routers/logs.py`) and the custom `TokenRedactingFilter` in `main.py` automatically mask stdout logs for:
80-
- **RTSP Credentials**: `rtsp://user:***@host`
81-
- **Sensitive JSON fields**: `"password": "***"`, `"token": "***"`, etc.
82-
- **Headers**: `X-API-Key=REDACTED`, `Bearer REDACTED`.
79+
- The logging infrastructure (`backend/routers/logs.py`) and the custom `TokenRedactingFilter` in `main.py` automatically mask stdout logs for:
80+
- **RTSP Credentials**: `rtsp://user:***@host`
81+
- **Sensitive JSON fields**: `"password": "***"`, `"token": "***"`, etc.
82+
- **Headers & Parameters**: `X-API-Key=REDACTED`, `Bearer REDACTED`, `token=REDACTED`.
83+
- **Perpetual Security Audit**: Mandatory runtime scans are performed during the CI/CD and security audit workflows to ensure that sensitive strings never leak into the application's stdout.
8384
- **RTSP URL Redaction (GUI Level)**: Starting from **v1.25.3**, the frontend configuration interface implements dynamic URL masking. RTSP and Sub-Stream URLs are displayed without plain-text passwords (redacted as `********`). If a user pastes a full URL containing a password, it is automatically extracted to the secure separate fields and redacted in real-time.
8485
5. **Privacy Masking & Motion Zones**:
8586
- **Privacy Masks** are applied at the Engine level immediately after frame decoding. They are "burned" into the video frames *before* they reach the recording or motion analysis modules, ensuring that sensitive data is never persisted or processed if masked.

backend/main.py

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,35 +34,60 @@ def filter(self, record):
3434
# Redact token from the message itself
3535
if isinstance(record.msg, str):
3636
if "token=" in record.msg:
37+
# Standard query param
3738
record.msg = re.sub(r"token=[^&\s]*", "token=REDACTED", record.msg)
3839
if "X-API-Key" in record.msg:
3940
record.msg = re.sub(r"X-API-Key[^\s]*[:=]\s*['\"]?[^'\"]+['\"]?", "X-API-Key: REDACTED", record.msg)
41+
if "Authorization" in record.msg:
42+
record.msg = re.sub(r"Authorization[:=]\s*Bearer\s+[\w\-\.]+", "Authorization: Bearer REDACTED", record.msg)
4043

4144
# Redact token from uvicorn access log arguments (client, method, path, etc)
4245
if hasattr(record, "args") and record.args:
4346
new_args = list(record.args)
4447
for i, arg in enumerate(new_args):
4548
if isinstance(arg, str):
4649
if "token=" in arg:
50+
# Catch both "?token=..." and "token=..."
4751
new_args[i] = re.sub(r"token=[^&\s]*", "token=REDACTED", arg)
48-
# Redact sensitive credentials in URLs
52+
# Redact sensitive credentials in URLs (e.g. RTSP)
4953
if "://" in arg and "@" in arg:
5054
new_args[i] = re.sub(r"://[^@]+@", r"://***@", arg)
5155
record.args = tuple(new_args)
5256
return True
5357

54-
# Apply the filter to uvicorn access logs
55-
# Apply the filter to uvicorn access logs
56-
uvicorn_access_logger = logging.getLogger("uvicorn.access")
57-
uvicorn_access_logger.addFilter(TokenRedactingFilter())
58+
def apply_security_logging():
59+
"""
60+
Forcefully apply TokenRedactingFilter to all relevant loggers and their handlers.
61+
This is necessary because Uvicorn can reset logging config during startup or reloads.
62+
"""
63+
redact_filter = TokenRedactingFilter()
64+
65+
# Target loggers: root, uvicorn, and its sub-loggers
66+
target_loggers = ["", "uvicorn", "uvicorn.access", "uvicorn.error", "websockets", "fastapi"]
67+
68+
for name in target_loggers:
69+
logger = logging.getLogger(name)
70+
# 1. Add to logger (for propagation filtering)
71+
if not any(isinstance(f, TokenRedactingFilter) for f in logger.filters):
72+
logger.addFilter(redact_filter)
73+
74+
# 2. Add to all existing handlers (the most robust way)
75+
for handler in logger.handlers:
76+
if not any(isinstance(f, TokenRedactingFilter) for f in handler.filters):
77+
handler.addFilter(redact_filter)
78+
79+
# Initial application on import
80+
apply_security_logging()
5881

5982
# Configure timestamp for uvicorn access logs
6083
# Uvicorn's default formatter doesn't include time
6184
console_formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
6285

6386
# Apply formatter to all handlers of uvicorn.access
64-
if uvicorn_access_logger.hasHandlers():
65-
for handler in uvicorn_access_logger.handlers:
87+
# Apply formatter to all handlers of uvicorn.access (Initial best effort)
88+
access_logger = logging.getLogger("uvicorn.access")
89+
if access_logger.hasHandlers():
90+
for handler in access_logger.handlers:
6691
handler.setFormatter(console_formatter)
6792
else:
6893
# If no handlers yet (likely), add one or rely on uvicorn's default being added later?
@@ -139,6 +164,9 @@ async def lifespan(app: FastAPI):
139164
except Exception as e:
140165
print(f"Log setup warning: {e}")
141166

167+
# Re-apply security logging inside lifespan to catch process-level resets/reloads
168+
apply_security_logging()
169+
142170
retry_count = 0
143171
while True:
144172
try:

0 commit comments

Comments
 (0)