Skip to content

Security: palm-ER/self-hosted-asr

SECURITY.md

Security

Reporting a vulnerability

Please report security issues privately rather than opening a public issue. Open a private security advisory on this repository. We aim to acknowledge reports within five working days.

Threat model

The design assumes the API sits on a trusted network segment behind your own authentication. Specifically:

In scope. This repository does not intentionally persist audio or transcripts beyond a request. Client input must be bounded so one request or session cannot grow memory or disk use without limit. Untrusted client-supplied filenames and session IDs must not reach logs or filesystem paths unchanged.

Out of scope. Authentication, authorization, TLS, multi-tenant isolation, SIEM collection and organization-specific audit retention. The stack emits bounded access and application records but does not operate the collector. A caller who can reach the port can transcribe audio.

What the deployment does

  • Binds to loopback by default. generate_compose.py publishes on 127.0.0.1:8080. Changing BIND_ADDR exposes an unauthenticated, plaintext API to that network.
  • Keeps working audio in RAM. /tmp is a tmpfs in every service, so decoded audio and scratch WAVs are not written to persistent filesystem storage and are discarded on container stop. Starlette's accepted multipart uploads remain below an explicitly configured in-memory spool threshold. nginx streams uploads with proxy_request_buffering off; bounded response buffers use backpressure instead of a proxy temporary file.
  • Removes temp files on every exit path. Paths are bound before the first write, and cleanup runs in finally blocks that tolerate already-deleted files. tests/test_temp_files.py covers the failure paths, including a write that raises partway through.
  • Emits bounded audit records. nginx logs a JSON record with request ID, client address, method, URI without query parameters, status, byte count and timings. Bodies, transcripts, filenames, query strings, authorization headers and arbitrary headers are excluded. The same validated request ID is passed through the orchestrator to ASR and Sortformer. Orchestrator access logs and GPU structured application logs are enabled; GPU Uvicorn access logs are disabled. Docker log rotation is enabled with conservative example limits.
  • Drops privileges. Containers run as a non-root user with cap_drop: ALL, no-new-privileges, no core dumps, and memory and PID limits. The generated services also use read-only root filesystems; only explicit tmpfs mounts and model-cache volumes are writable.
  • Bounds client input. Upload bytes, decoded audio duration, concurrent orchestrator requests, WebSocket connections, frame duration, pending audio, total session duration, sample rate, session ID length and base64 validity are bounded.

Deployment boundaries

  • No authentication or TLS. Deploy behind a gateway that provides both.
  • Unlinked is not erased. With the default tmpfs /tmp, working audio lives in RAM. If you remove that tmpfs mount, os.unlink only drops the directory entry and the blocks remain recoverable on the host until reallocated.
  • Host swap can page RAM to disk. tmpfs contents and process memory are swappable like any other memory. On hosts handling sensitive audio, disable swap or use encrypted swap.
  • Model code is third-party. Audio is handed to NeMo's transcribe() and diarize(). NeMo is version-pinned, and the deployment confines its writable locations to tmpfs and model-cache volumes.
  • Image and model revisions are pinned. Updating a base-image digest, Python lock file, or Hugging Face commit is a security-sensitive dependency change and requires a rebuild, scan, and inference test.
  • GPU workers need writable model caches. These named volumes are intended for model artifacts, not request data. They persist until an operator removes them.
  • Builds and first startup need network access. Image builds download packages, and the first worker startup downloads model artifacts. Setting HF_HUB_OFFLINE=1 makes Hub calls fail locally after the cache is populated; enforce host-level egress controls when a software flag is insufficient.

Verifying the data-handling claims

pip install -r tests/requirements.txt
pytest tests/ -q

The temp-file tests assert that temporary files are cleaned up after an oversized upload, a failed write, or an empty upload. To spot-check a running stack, send a transcription request and confirm the worker's /tmp is empty afterwards:

docker exec parakeet-gpu0 ls -la /tmp

There aren't any published security advisories