mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-18 03:54:17 +02:00
Bound the scheme repetition in the log credential-redaction pattern. As an
unbounded repetition the match was quadratic in the subject length: on a run of scheme-legal characters the engine restarted at every offset and consumed to the end before failing to find "://". ffmpeg echoes the configured camera URL into its stderr and the whole blob reaches the pattern before any truncation, so the subject length is attacker-influenced.
This commit is contained in:
@@ -10,6 +10,7 @@ into the log.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
from backend.app.api.routes.camera import _read_ffmpeg_stderr, _summarize_ffmpeg_stderr
|
||||
from backend.app.core.logging_filters import redact_url_credentials
|
||||
@@ -71,6 +72,43 @@ class TestRedactUrlCredentials:
|
||||
assert redact_url_credentials("") == ""
|
||||
assert redact_url_credentials(None) is None
|
||||
|
||||
def test_a_long_scheme_like_run_does_not_blow_up(self):
|
||||
"""The scheme repetition is capped so the match stays linear.
|
||||
|
||||
Unbounded, the engine restarted at every offset of a run of
|
||||
scheme-legal characters and consumed to the end each time before
|
||||
failing to find ``://`` — quadratic in the length of the line, and
|
||||
ffmpeg echoes the operator's camera URL into the subject. An absolute
|
||||
timing bound would be flaky, so this pins the growth rate instead:
|
||||
doubling the input must not quadruple the work. Measured against the
|
||||
unbounded pattern, these two inputs took 550ms and 2187ms (ratio 3.97,
|
||||
so the assertion fails); bounded, 2.8ms and 5.4ms (ratio 1.98).
|
||||
"""
|
||||
small = "A" * 32_000 + "://@"
|
||||
large = "A" * 64_000 + "://@"
|
||||
|
||||
start = time.perf_counter()
|
||||
assert redact_url_credentials(small) == small
|
||||
small_elapsed = time.perf_counter() - start
|
||||
|
||||
start = time.perf_counter()
|
||||
assert redact_url_credentials(large) == large
|
||||
large_elapsed = time.perf_counter() - start
|
||||
|
||||
# Linear would be ~2x. Allow generous slack for a loaded CI box while
|
||||
# still failing the ~4x of a quadratic match.
|
||||
assert large_elapsed < max(small_elapsed * 3, 0.5)
|
||||
|
||||
def test_a_scheme_longer_than_the_cap_still_gets_its_secret_masked(self):
|
||||
"""The cap bounds backtracking; it must not create a redaction hole.
|
||||
|
||||
A pseudo-scheme longer than the cap simply matches from a later
|
||||
offset, so the password is still replaced.
|
||||
"""
|
||||
result = redact_url_credentials("Z" * 100 + "://user:hunter2@host/path")
|
||||
assert "hunter2" not in result
|
||||
assert result.endswith("://user:[REDACTED]@host/path")
|
||||
|
||||
|
||||
class TestFfmpegStderrFunnel:
|
||||
"""`_summarize_ffmpeg_stderr` is the one funnel every stderr log in the
|
||||
|
||||
Reference in New Issue
Block a user