Security Model
- Network: Binds to
127.0.0.1by default on manual/source installs; the Docker/Podman image binds0.0.0.0internally (required for port-publishing) - actual exposure is controlled by the-p/port choice made when running the container - Input validation: All user inputs (IP, port, MD5, URL, filename) validated before use in subprocess calls or filesystem operations
- Path safety:
is_safe_path()prevents directory traversal viaos.path.realpath()comparison - Content validation: File type detected by magic bytes and routed accordingly - PCAPs get Suricata, log files get Zircolite, everything else gets YARA
- URL safety (SSRF):
validate_url_safety()/resolve_safe_ips()block localhost and private/internal IP ranges, and resolve the hostname once, connecting directly to the validated IP rather than letting the HTTP client re-resolve it - this closes a DNS-rebinding TOCTOU gap that a resolve-then-separately-connect design would have - Zip safety:
validate_zip_extraction()checks both zip-slip (every extracted path stays within the target directory) and zip-bomb (per-member and total decompressed-size limits). A ZIP member with a spoofed declared size (small metadata, large real decompressed content) is not a bypass of this check - CPython'szipfilemodule bounds every read to the declared size internally and then validates CRC32 against the original checksum, so a size lie surfaces aszipfile.BadZipFileon its own, independent of this check. See the docstring onvalidate_zip_extraction()invalidators.pyfor how this was verified, before re-investigating it again. - Error handling: Generic "Internal server error" - no stack traces or internal paths leaked
- Response headers:
_add_security_headers()sendsX-Frame-Options: DENY,X-Content-Type-Options: nosniff, andContent-Security-Policy: default-src 'self'; ...on every response -'unsafe-inline'is allowed forscript-src/style-srcsince the UI relies on inlineonclick/styleattributes by convention (seeAGENTS.md's frontend conventions) - No CORS: No
Access-Control-Allow-Originheader is ever sent - cross-origin access isn't allowed, not even a wildcard - Container privilege: The Docker/Podman image's final stage runs as
USER 1000:1000, never root - Startup network isolation:
main()callssetup_suricata_config()/setup_yara_rules()/setup_sigma_rules()withnetwork_allowed=Falseat boot - startup only ever uses baked-in rules or whatever's already cached on disk, and can never block on a slow or unreachable rule mirror. Refreshing rules over the network happens later, only as an explicit on-demand action from the Rules modal (POST /api/update-rules)
See Security for the user-facing summary of these same protections.