Skip to content

🛡️ Sentinel: [MEDIUM] Enforce audio file size limit to prevent DoS#51

Open
Whamp wants to merge 1 commit intomainfrom
sentinel-audio-file-limit-12653216208219232376
Open

🛡️ Sentinel: [MEDIUM] Enforce audio file size limit to prevent DoS#51
Whamp wants to merge 1 commit intomainfrom
sentinel-audio-file-limit-12653216208219232376

Conversation

@Whamp
Copy link
Copy Markdown
Owner

@Whamp Whamp commented Feb 1, 2026

User description

🛡️ Sentinel: [MEDIUM] Enforce audio file size limit to prevent DoS

🚨 Severity: MEDIUM
💡 Vulnerability: Missing input length limits (DoS risk). The application allowed loading arbitrarily large audio files into memory via AudioFeedback, which could lead to memory exhaustion and application crash (Denial of Service).
🎯 Impact: A user or attacker with access to config.toml could specify a large file path, causing the application to crash due to OOM when _load_and_cache attempts to read the entire file into a numpy array.
🔧 Fix:

  • Added MAX_AUDIO_FILE_SIZE_BYTES = 5 * 1024 * 1024 (5MB) constant in src/chirp/audio_feedback.py.
  • Added a check in _load_and_cache using path.stat().st_size to reject files larger than 5MB.
  • Updated existing tests to mock pathlib.Path.stat.
  • Added new security tests in tests/test_audio_security.py to verify the limit is enforced.
    ✅ Verification:
  • uv run python -m unittest tests/test_audio_security.py passes.
  • uv run python -m unittest discover tests passes.

PR created automatically by Jules for task 12653216208219232376 started by @Whamp


PR Type

Bug fix, Tests


Description

  • Enforce 5MB file size limit on audio files to prevent DoS attacks

  • Added validation in _load_and_cache to reject oversized files

  • Created comprehensive security tests in test_audio_security.py

  • Updated existing tests to mock Path.stat calls properly


Diagram Walkthrough

flowchart LR
  A["Audio File Loading"] -->|Check file size| B["Size Validation"]
  B -->|Size > 5MB| C["Raise ValueError"]
  B -->|Size <= 5MB| D["Load and Cache"]
  C --> E["Prevent DoS"]
  D --> F["Normal Operation"]
Loading

File Walkthrough

Relevant files
Security enhancement
audio_feedback.py
Add audio file size limit validation                                         

src/chirp/audio_feedback.py

  • Added MAX_AUDIO_FILE_SIZE_BYTES constant set to 5MB
  • Implemented file size validation in _load_and_cache method
  • Raises ValueError if audio file exceeds size limit
  • Removed unused imports from type hints
+9/-1     
Tests
test_audio_feedback.py
Mock Path.stat in existing tests                                                 

tests/test_audio_feedback.py

  • Added @patch("pathlib.Path.stat") decorator to two test methods
  • Mocked stat().st_size to return valid file size (1000 bytes)
  • Ensures tests work with new file size validation logic
+6/-2     
test_audio_security.py
Add audio file size security tests                                             

tests/test_audio_security.py

  • New security test file with two test cases
  • test_load_and_cache_large_file_raises_error: Verifies ValueError is
    raised for files > 5MB
  • test_load_and_cache_valid_file_proceeds: Verifies files <= 5MB load
    successfully
  • Imports and tests MAX_AUDIO_FILE_SIZE_BYTES constant
+59/-0   
Formatting
test_audio_feedback_cache.py
Remove unused import                                                                         

tests/test_audio_feedback_cache.py

  • Removed unused Path import from pathlib
+0/-1     
Documentation
sentinel.md
Document security learning from DoS vulnerability               

.jules/sentinel.md

  • Added new security learning entry dated 2025-05-21
  • Documents vulnerability of missing audio file size limit in
    implementation
  • Highlights risk of documentation drift in security controls
  • Recommends automated testing to verify documented security controls
+5/-0     

Added `MAX_AUDIO_FILE_SIZE_BYTES` limit in `AudioFeedback` class.
Updated `_load_and_cache` to check file size before loading.
Added `tests/test_audio_security.py` to verify the fix.
Updated `tests/test_audio_feedback.py` to mock `stat` calls.
Updated `.jules/sentinel.md` with new learnings.

Co-authored-by: Whamp <[email protected]>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@qodo-code-review
Copy link
Copy Markdown

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
TOCTOU size check

Description: The size check uses path.stat().st_size before wave.open(...), which can be bypassed via a
TOCTOU race (e.g., swapping the file or symlink target after stat but before open) to load
a larger file than MAX_AUDIO_FILE_SIZE_BYTES, potentially reintroducing memory-exhaustion
DoS in adversarial local-filesystem scenarios.
audio_feedback.py [136-145]

Referred Code
if path.stat().st_size > MAX_AUDIO_FILE_SIZE_BYTES:
    raise ValueError(
        f"Audio file too large: {path} (max {MAX_AUDIO_FILE_SIZE_BYTES} bytes)"
    )

if self._use_sounddevice:
    # Load as numpy array for volume-controlled playback via sounddevice
    with wave.open(str(path), "rb") as wf:
        samplerate = wf.getframerate()
        channels = wf.getnchannels()
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing audit log: Oversized audio file rejections raise an exception but are not explicitly logged as a
security-relevant validation outcome, so it is unclear whether attempted DoS inputs are
captured in audit trails.

Referred Code
if path.stat().st_size > MAX_AUDIO_FILE_SIZE_BYTES:
    raise ValueError(
        f"Audio file too large: {path} (max {MAX_AUDIO_FILE_SIZE_BYTES} bytes)"
    )

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Path leaked in error: The raised ValueError includes the full filesystem path, which may be surfaced to end
users depending on call sites and could leak internal system details.

Referred Code
raise ValueError(
    f"Audio file too large: {path} (max {MAX_AUDIO_FILE_SIZE_BYTES} bytes)"
)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Sensitive path in logs: The oversized-file ValueError message includes path and may be logged by the surrounding
exception handler, potentially exposing sensitive filesystem layout in logs.

Referred Code
if path.stat().st_size > MAX_AUDIO_FILE_SIZE_BYTES:
    raise ValueError(
        f"Audio file too large: {path} (max {MAX_AUDIO_FILE_SIZE_BYTES} bytes)"
    )

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Security
Fix race condition vulnerability

To prevent a race condition vulnerability, open the audio file once, check the
size from the file object, and then pass that same file object for processing,
instead of checking the path and then reopening it.

src/chirp/audio_feedback.py [135-145]

 def _load_and_cache(self, path: Path, key: str) -> Any:
-    if path.stat().st_size > MAX_AUDIO_FILE_SIZE_BYTES:
-        raise ValueError(
-            f"Audio file too large: {path} (max {MAX_AUDIO_FILE_SIZE_BYTES} bytes)"
-        )
-
     if self._use_sounddevice:
         # Load as numpy array for volume-controlled playback via sounddevice
-        with wave.open(str(path), "rb") as wf:
-            samplerate = wf.getframerate()
-            channels = wf.getnchannels()
+        with path.open("rb") as f:
+            # Check file size first to prevent loading large files into memory
+            f.seek(0, 2)  # Move to the end of the file
+            file_size = f.tell()
+            if file_size > MAX_AUDIO_FILE_SIZE_BYTES:
+                raise ValueError(
+                    f"Audio file too large: {path} ({file_size} > {MAX_AUDIO_FILE_SIZE_BYTES} bytes)"
+                )
+            f.seek(0)  # Rewind to the beginning for wave.open
 
+            with wave.open(f, "rb") as wf:
+                samplerate = wf.getframerate()
+                channels = wf.getnchannels()
+
  • Apply / Chat
Suggestion importance[1-10]: 10

__

Why: The suggestion correctly identifies a critical TOCTOU (Time-of-check to time-of-use) race condition vulnerability in the newly added security check and provides a robust solution, significantly improving the security of the PR.

High
General
Simplify test by removing boilerplate

Simplify the test case by removing the try...except self.fail() block; a direct
call to the function is sufficient, as any unhandled exception will
automatically fail the test.

tests/test_audio_security.py [53-56]

-try:
-    af._load_and_cache(Path("/fake/valid_file.wav"), "key")
-except ValueError:
-    self.fail("_load_and_cache raised ValueError unexpectedly!")
+# If _load_and_cache raises an unexpected ValueError, the test will fail.
+af._load_and_cache(Path("/fake/valid_file.wav"), "key")
  • Apply / Chat
Suggestion importance[1-10]: 4

__

Why: The suggestion correctly points out that the try...except...fail block is redundant and offers a more concise, readable alternative that improves the quality of the new test code.

Low
  • More

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant