For operators deciding what dtq is safe to expose and where it needs help from the surrounding infrastructure.
- Task payloads and results. Opaque JSON supplied by producers and returned by handlers. May contain business data; dtq treats it as opaque but stores it in Redis streams, task hashes, the DLQ stream, and result keys, and serves it back over the API and CLI.
- Execution capacity. Workers, concurrency slots, the retry schedule, and queue depth. Exhausting them denies service to legitimate tasks.
- Control plane state. Queue pause flags, the scheduler leader lock, worker registrations, the audit stream. Corrupting them disrupts or blinds operations.
- The API key. A single shared admin secret guarding destructive operations.
- Operational visibility. Metrics, logs, and the audit stream. Losing them removes the ability to detect the other attacks.
| Actor | Capability | Trust |
|---|---|---|
| Task producer | Submits tasks, reads tasks/queues/DLQ | Untrusted; validated at ingest only |
| Dashboard user | Same as producer plus the admin UI (key-gated actions) | Untrusted |
| Admin (API key holder) | Cancel, requeue, discard, purge, pause/resume | Trusted, but the key is shared and bearer |
| Handler author | Code loaded into workers at startup | Fully trusted; code runs with worker privileges |
| Redis network neighbor | Raw Redis protocol access | Must be prevented; no in-system defense |
| Passive network observer | Sees API traffic | Prevented only by external TLS |
API (FastAPI, port 8000). Ingest, reads, admin endpoints, WebSocket,
/metrics. Input validation is the main control; auth covers only
destructive ops. No rate limiting beyond the queue-depth gate.
Redis (internal network, no host port by default). All state lives here: streams, hashes, schedules, idempotency keys, worker registrations, audit stream, pub/sub. No dtq-level ACL; network reachability equals full control.
Dashboard (nginx SPA, port 3000). Static files plus proxied API calls. No credentials stored; no login. Whatever the network exposes, the UI exposes.
Worker processes. Load handler modules at startup and execute them per task. A worker that processes a hostile payload is safe as long as the handler validates its input; a worker loaded with a hostile module is fully compromised.
A producer submits a registered task type with a hostile or malformed payload, hoping the handler mishandles it.
- Present mitigation: the payload must be a JSON object within the size limit; unknown types fail permanently without executing; handlers are looked up, never imported or evaluated.
- Residual risk: shape validation is the handler author's job. A handler
that trusts
payload["url"]and fetches it has an SSRF problem dtq cannot fix. Validate every field in the handler.
A producer submits payloads at the size cap, or deeply nested structures, to burn CPU in JSON parsing or in a handler.
- Present mitigation: 256 KB cap at ingest (413 beyond it); 4 KB cap on
metadata; handler
timeout_ms(default 30s, max 1h) bounds one execution. - Residual risk: the cap is per task, not per producer; a fast producer can submit many max-size payloads. Depth/nesting of JSON is not limited. There is no per-producer rate limit.
Keys are scoped (queue, key) and created with SET NX. Anyone can claim
a key.
- Present mitigation: scope includes the queue, so a squatter must target
the right queue; keys expire after
DTQ_IDEM_TTL_S(24h). - Residual risk: a squatter who guesses or learns a producer's key
pattern can hold keys in
processingstate, forcing the legitimate producer's submissions to 409IDEMPOTENCY_IN_PROGRESSuntil expiry. There is no ownership check because submit is unauthenticated. Use unguessable keys (uuids) if this matters to you.
DLQ entries contain the failed task's payload plus error details, and DLQ reads are unauthenticated (like all reads).
- Present mitigation: none inside dtq; this is by design for debuggability.
- Residual risk: anyone who can reach the API can read every failed
payload via
GET /api/v1/dlq. Do not put secrets in task payloads, and restrict network access to the API.
delay_seconds parks tasks in the retry schedule, which the depth gate does
not count.
- Present mitigation: none; the gate sums stream
XLENonly. - Residual risk: a producer can grow
dtq:retry:schedulewithout bound and without triggering 429. The scheduler batch is capped at 100 per pass, so a flooded schedule also delays legitimate retries. Monitordtq_retry_scheduledand alert on it.
Cancel, requeue, discard, purge, pause, and resume change or destroy work.
- Present mitigation: API-key auth when
DTQ_API_KEYis set; every action is audit-logged with actor and request id; discards remove the DLQ entry but keep the task hash markedDEAD_LETTEREDwithmetadata.discarded=true, never silently destroying the record. - Residual risk: with the key unset (local-dev default), all of this is open. The key is shared, so the audit trail cannot attribute actions to a person.
Events carry task ids, types, queues, attempts, and worker ids.
- Present mitigation: none beyond network controls; the WS endpoint has no auth separate from the API's.
- Residual risk: a passive observer learns workload patterns and task
metadata. Events are not signed; a party that can publish to Redis can
forge
TASK_SUCCEEDEDevents the dashboard will display (task state in Redis is unaffected, since workers decide from the task hash, not events).
Workers --task-module load arbitrary Python modules at startup.
- Present mitigation: none inside dtq.
- Residual risk: this is the highest-privilege action in the system. Pin dependencies, review handler code, and treat the worker image build as a trusted pipeline.
What the operator still owns after deploying dtq as documented:
- Network isolation for Redis and, if the data is sensitive, for the API.
- TLS termination in front of the API.
- A strong, rotated
DTQ_API_KEYin any shared environment. - Per-producer rate limiting if producers are untrusted (dtq has none).
- Payload shape validation inside every handler.
- NTP on all hosts (schedule, heartbeats, TTLs, and the claim reaper are clock-dependent).
- Backup and restore for Redis (the only durable state).
- Review of every handler module loaded into workers.
- security.md: the controls that are implemented, and what is explicitly not protected.
- failure-modes.md: how these abuses manifest as operational failures and how to recover.
- deployment.md: concrete steps to close the network and TLS gaps.