test: cover P0 security boundaries
This commit is contained in:
parent
440413fcb7
commit
686ba03ded
3
.github/workflows/sonarqube.yml
vendored
3
.github/workflows/sonarqube.yml
vendored
@ -39,6 +39,9 @@ jobs:
|
|||||||
- name: Verify Python sources compile
|
- name: Verify Python sources compile
|
||||||
run: python -m compileall -q core routes scripts server.py ttsvc_cli.py
|
run: python -m compileall -q core routes scripts server.py ttsvc_cli.py
|
||||||
|
|
||||||
|
- name: Run Python security regressions
|
||||||
|
run: python -m unittest discover -s tests -p 'test_*.py'
|
||||||
|
|
||||||
- name: Verify JavaScript parses
|
- name: Verify JavaScript parses
|
||||||
run: |
|
run: |
|
||||||
for f in $(git ls-files 'static/js/*.js' 'scripts/*.mjs'); do
|
for f in $(git ls-files 'static/js/*.js' 'scripts/*.mjs'); do
|
||||||
|
|||||||
@ -16,6 +16,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
|||||||
### Changed
|
### Changed
|
||||||
- **API Boundary Hardening.** External API authentication is enabled by default, request and mutation limits are
|
- **API Boundary Hardening.** External API authentication is enabled by default, request and mutation limits are
|
||||||
enforced, and settings responses redact provider credentials for non-browser callers.
|
enforced, and settings responses redact provider credentials for non-browser callers.
|
||||||
|
- **Security Regression Coverage.** Added a Python test gate for character metadata validation, private-download URL
|
||||||
|
rejection and oversized JSON request rejection.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- **SSRF and Credential-Leak Paths.** User-supplied downloads are validated against private-network access settings,
|
- **SSRF and Credential-Leak Paths.** User-supplied downloads are validated against private-network access settings,
|
||||||
|
|||||||
53
tests/test_security.py
Normal file
53
tests/test_security.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
"""Fast, dependency-light regression tests for P0 request protections."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
os.environ.setdefault("CONFIG_DIR", tempfile.mkdtemp(prefix="ttvc-test-config-"))
|
||||||
|
|
||||||
|
from fastapi import HTTPException
|
||||||
|
|
||||||
|
from core.validation import _validate_character_record, _validate_http_url
|
||||||
|
from server import RequestSizeLimitMiddleware
|
||||||
|
|
||||||
|
|
||||||
|
class SecurityValidationTests(unittest.TestCase):
|
||||||
|
def test_character_record_rejects_missing_id_and_bad_sheet(self):
|
||||||
|
for record in (
|
||||||
|
{"name": "Missing id", "sheet": {}},
|
||||||
|
{"id": "c1", "name": "Bad sheet", "sheet": []},
|
||||||
|
):
|
||||||
|
with self.assertRaises(HTTPException):
|
||||||
|
_validate_character_record(record)
|
||||||
|
|
||||||
|
def test_character_record_accepts_minimal_record(self):
|
||||||
|
record = {"id": "c1", "book": "Book", "name": "Name", "tags": "", "sheet": {}}
|
||||||
|
self.assertIs(_validate_character_record(record), record)
|
||||||
|
|
||||||
|
def test_private_url_is_rejected_when_downloads_are_public_only(self):
|
||||||
|
with self.assertRaises(HTTPException):
|
||||||
|
_validate_http_url("http://127.0.0.1:8188/image", allow_private=False)
|
||||||
|
|
||||||
|
def test_request_size_middleware_rejects_oversized_json(self):
|
||||||
|
sent = []
|
||||||
|
|
||||||
|
async def downstream(scope, receive, send):
|
||||||
|
sent.append({"type": "downstream"})
|
||||||
|
|
||||||
|
async def send(message):
|
||||||
|
sent.append(message)
|
||||||
|
|
||||||
|
scope = {
|
||||||
|
"type": "http", "method": "POST", "path": "/api/test",
|
||||||
|
"headers": [(b"content-type", b"application/json"), (b"content-length", b"40000000")],
|
||||||
|
}
|
||||||
|
asyncio.run(RequestSizeLimitMiddleware(downstream)(scope, None, send))
|
||||||
|
self.assertEqual(sent[0]["status"], 413)
|
||||||
|
self.assertNotIn({"type": "downstream"}, sent)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Loading…
Reference in New Issue
Block a user