mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-05-15 10:03:31 +00:00
fix: address remaining Codacy security findings
Flask debug=True (real fix):
- web_interface/app.py: debug=True in __main__ block exposes the Werkzeug
interactive debugger (arbitrary code execution). Changed to
os.environ.get('FLASK_DEBUG', '0') == '1' — off by default, opt-in
via environment variable for local development.
nosec annotations (accepted risk with documented rationale):
- disk_cache.py: os.chmod(0o660) is intentional — web UI and LED matrix
service share a group, 660 gives group write while denying world access
(B103 + Semgrep insecure-file-permissions suppressed in Codacy)
- wifi_manager.py: urlopen to hardcoded connectivity-check.ubuntu.com URL
(B310 — no user input involved)
- font_manager.py: urlretrieve URL comes from user's own config file on
their local device (B310)
- start_web_conditionally.py: os.execvp with both sys.executable and a
fixed PROJECT_DIR-relative constant (B606)
Confirmed false positives suppressed via Codacy API (15 issues):
- SSRF (3x): client-side JS fetch — SSRF is server-side; browser fetch
is CORS-restricted to same origin
- B105 (3x): test fixtures use dummy secrets by design; store_manager
checks for the placeholder string, it is not itself a secret
- PMD numeric literal (2x): 10000000 is within Number.MAX_SAFE_INTEGER
- Prototype pollution (1x): read-only schema traversal, no writes
- no-unsanitized_method (1x): dynamic import() is CORS-restricted
- detect-unsafe-regex (1x): operates on server-controlled config values
- plugin-repos B103 (1x): vendor code chmod on executable
- Semgrep insecure-file-permissions (3x): same disk_cache 0o660 as above
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -111,7 +111,7 @@ def main():
|
||||
# Ensure PYTHONPATH is set correctly if web_interface.py has relative imports to src
|
||||
# The WorkingDirectory in systemd service should handle this for web_interface.py
|
||||
print(f"Launching web interface v3: {sys.executable} {WEB_INTERFACE_SCRIPT}")
|
||||
os.execvp(sys.executable, [sys.executable, WEB_INTERFACE_SCRIPT])
|
||||
os.execvp(sys.executable, [sys.executable, WEB_INTERFACE_SCRIPT]) # nosec B606 - both args are fixed constants
|
||||
except Exception as e:
|
||||
print(f"Failed to exec web interface: {e}")
|
||||
sys.exit(1) # Failed to start
|
||||
|
||||
6
src/cache/disk_cache.py
vendored
6
src/cache/disk_cache.py
vendored
@@ -183,7 +183,7 @@ class DiskCache:
|
||||
os.replace(tmp_path, cache_path)
|
||||
# Set proper permissions: 660 (rw-rw----) for group-readable cache files
|
||||
try:
|
||||
os.chmod(cache_path, 0o660)
|
||||
os.chmod(cache_path, 0o660) # nosec B103 - intentional; web UI and service share a group
|
||||
except OSError:
|
||||
pass # Non-critical if chmod fails
|
||||
finally:
|
||||
@@ -201,7 +201,7 @@ class DiskCache:
|
||||
os.fsync(cache_file.fileno())
|
||||
# Set proper permissions: 660 (rw-rw----) for group-readable cache files
|
||||
try:
|
||||
os.chmod(cache_path, 0o660)
|
||||
os.chmod(cache_path, 0o660) # nosec B103 - intentional; web UI and service share a group
|
||||
except OSError:
|
||||
pass # Non-critical if chmod fails
|
||||
self.logger.debug("Wrote cache for %s directly (non-atomic)", key)
|
||||
@@ -227,7 +227,7 @@ class DiskCache:
|
||||
json.dump(data, tmp_file, indent=4, cls=DateTimeEncoder)
|
||||
# Set proper permissions: 660 (rw-rw----) for group-readable cache files
|
||||
try:
|
||||
os.chmod(fallback_path, 0o660)
|
||||
os.chmod(fallback_path, 0o660) # nosec B103 - intentional; web UI and service share a group
|
||||
except OSError:
|
||||
pass # Non-critical if chmod fails
|
||||
self.logger.debug("Cache wrote to fallback location: %s", fallback_path)
|
||||
|
||||
@@ -267,7 +267,7 @@ class FontManager:
|
||||
|
||||
# Download font
|
||||
logger.info(f"Downloading font from {url}")
|
||||
urllib.request.urlretrieve(url, cache_path)
|
||||
urllib.request.urlretrieve(url, cache_path) # nosec B310 - URL from user's own config file on local device
|
||||
|
||||
# Handle zip files
|
||||
if url.endswith('.zip'):
|
||||
|
||||
@@ -937,7 +937,7 @@ class WiFiManager:
|
||||
pass
|
||||
try:
|
||||
import urllib.request as _ureq
|
||||
_ureq.urlopen("http://connectivity-check.ubuntu.com/", timeout=timeout)
|
||||
_ureq.urlopen("http://connectivity-check.ubuntu.com/", timeout=timeout) # nosec B310 - hardcoded URL, no user input
|
||||
logger.debug("Internet connectivity confirmed via HTTP check")
|
||||
return True
|
||||
except OSError:
|
||||
|
||||
@@ -737,6 +737,9 @@ def check_health_monitor():
|
||||
_threading.Thread(target=_run_startup_reconciliation, daemon=True).start()
|
||||
|
||||
if __name__ == '__main__':
|
||||
import os as _os
|
||||
# threaded=True is Flask's default since 1.0 but stated explicitly so that
|
||||
# long-lived /api/v3/stream/* SSE connections don't starve other requests.
|
||||
app.run(host='0.0.0.0', port=5000, debug=True, threaded=True)
|
||||
# Debug mode is off by default; opt in with FLASK_DEBUG=1 in the environment.
|
||||
_debug = _os.environ.get('FLASK_DEBUG', '0') == '1'
|
||||
app.run(host='0.0.0.0', port=5000, debug=_debug, threaded=True)
|
||||
|
||||
Reference in New Issue
Block a user