video_monitor/tests/test_phase2_security.py

108 lines
4.9 KiB
Python
Raw Normal View History

2026-08-30 22:22:11 +08:00
import json
import logging
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
class CredentialProtectionTests(unittest.TestCase):
def setUp(self):
from app.utils import Secrets
self.secrets = Secrets
self.old_path = Secrets.RUNTIME_SECRETS_FILE
self.old_cache = Secrets._CACHE
self.temp = tempfile.TemporaryDirectory()
Secrets.RUNTIME_SECRETS_FILE = Path(self.temp.name) / "secrets.json"
Secrets._CACHE = None
def tearDown(self):
self.secrets.RUNTIME_SECRETS_FILE = self.old_path
self.secrets._CACHE = self.old_cache
self.temp.cleanup()
def test_encryption_round_trip_and_no_plaintext_at_rest(self):
from app.utils.Credentials import decrypt_credential, encrypt_credential
encrypted = encrypt_credential("sk-sensitive-value")
self.assertTrue(encrypted.startswith("enc:v1:"))
self.assertNotIn("sk-sensitive-value", encrypted)
self.assertEqual(decrypt_credential(encrypted), "sk-sensitive-value")
self.assertEqual(encrypt_credential(encrypted), encrypted)
def test_old_runtime_file_is_atomically_extended_with_encryption_key(self):
old = self.secrets._new_secret_values()
old.pop("credential_encryption_key")
self.secrets.RUNTIME_SECRETS_FILE.write_text(json.dumps(old), encoding="utf-8")
key = self.secrets.get_runtime_secret("credential_encryption_key")
self.assertGreaterEqual(len(key), 40)
saved = json.loads(self.secrets.RUNTIME_SECRETS_FILE.read_text(encoding="utf-8"))
self.assertEqual(saved["credential_encryption_key"], key)
def test_service_secret_rotation_preserves_data_encryption_key(self):
old_key = self.secrets.get_runtime_secret("credential_encryption_key")
old_internal = self.secrets.get_runtime_secret("internal_api_secret")
self.secrets.rotate_runtime_secrets()
self.assertEqual(self.secrets.get_runtime_secret("credential_encryption_key"), old_key)
self.assertNotEqual(self.secrets.get_runtime_secret("internal_api_secret"), old_internal)
class LoggingRedactionTests(unittest.TestCase):
def test_filter_redacts_mapping_and_url_userinfo(self):
from app.utils.Logger import SensitiveDataFilter
record = logging.LogRecord("test", logging.INFO, __file__, 1,
"params={'api_key': 'secret key with spaces', 'url': 'rtsp://u:p@host/x'}",
(), None)
SensitiveDataFilter().filter(record)
message = record.getMessage()
self.assertNotIn("secret key with spaces", message)
self.assertNotIn("u:p", message)
self.assertIn("[REDACTED]", message)
class ModelTrustTests(unittest.TestCase):
def test_pt_rejected_until_hash_is_allowlisted(self):
from app.utils import ModelTrust
with tempfile.TemporaryDirectory() as temp_dir:
model = Path(temp_dir) / "model.pt"
manifest = Path(temp_dir) / "trusted.json"
model.write_bytes(b"reviewed test model")
old_manifest = ModelTrust.MANIFEST_PATH
ModelTrust.MANIFEST_PATH = manifest
try:
with self.assertRaises(PermissionError):
ModelTrust.require_trusted_model(model)
digest = ModelTrust.sha256_file(model)
manifest.write_text(json.dumps({"trusted_sha256": [digest]}), encoding="utf-8")
self.assertEqual(ModelTrust.require_trusted_model(model), digest)
finally:
ModelTrust.MANIFEST_PATH = old_manifest
class SourceBoundaryTests(unittest.TestCase):
def test_production_security_defaults_and_csrf(self):
settings = (ROOT / "framework" / "settings.py").read_text(encoding="utf-8")
urls = (ROOT / "app" / "urls.py").read_text(encoding="utf-8")
self.assertIn('DEBUG = env_bool("MONITOR_DEBUG", False)', settings)
self.assertIn("X_FRAME_OPTIONS = 'SAMEORIGIN'", settings)
self.assertNotIn("csrf_exempt(LLMView.api_openTest)", urls)
def test_known_sql_inputs_are_parameterized(self):
stream = (ROOT / "app" / "views" / "StreamView.py").read_text(encoding="utf-8")
users = (ROOT / "app" / "views" / "UserView.py").read_text(encoding="utf-8")
self.assertNotIn("nickname like '%{search_text}%'", stream)
self.assertNotIn("username='%s'", users)
self.assertIn("query_params", stream)
def test_secret_fields_are_not_returned_or_exported(self):
llm = (ROOT / "app" / "views" / "LLMView.py").read_text(encoding="utf-8")
stream = (ROOT / "app" / "views" / "StreamView.py").read_text(encoding="utf-8")
self.assertIn('"api_key_masked"', llm)
self.assertIn('d.pop("api_key", "")', llm)
self.assertIn('"[REDACTED]" if stream.pull_stream_password', stream)
if __name__ == "__main__":
unittest.main()