#229·flowsint

Scan status reports COMPLETED after an enricher emits a FAILED event

Author: jameshenningCreated Sep 21, 2026Updated Sep 21, 2026

Summary

When an enricher fails, the scan it belongs to still ends in COMPLETED. The failure is recorded as a FAILED event on the sketch event log, but nothing propagates it to the scan record, and GET /api/scans/{id} exposes only {id, sketch_id, status} — no error field. From the API (and therefore anything built on it), a failed enrichment is indistinguishable from an enricher that ran fine and legitimately found nothing.

Version

v1.2.12 (docker-compose.prod.yml, ghcr.io/reconurge/flowsint-api:1.2.12)

Reproduction

Easiest trigger is any Docker-backed enricher when the API/celery container can't reach the Docker daemon — which is the out-of-the-box state if the host's docker.sock is root:docker 0660, since the image runs as uid 1001 and isn't in that group.

  1. Bring up the prod stack without granting the container the host's docker group.

  2. Add a domain node and launch domain_to_dns (or domain_to_tls, domain_to_subdomains).

  3. GET /api/events/sketch/{sketch_id}/logs

    json
    {"type": "FAILED", "payload": {"message": "[DNSX] Failed to initialize dnsx: Failed to connect to Docker daemon. Is Docker running? Error: ... PermissionError(13, 'Permission denied')"}}
    {"type": "COMPLETED", "payload": {"message": "Enricher domain_to_dns finished."}}
  4. GET /api/scans/{scan_id}

    json
    {"id": "...", "sketch_id": "...", "status": "COMPLETED"}

The FAILED event and the COMPLETED event are emitted ~10 ms apart for the same enricher.

Expected

A scan whose enricher emitted FAILED should end in a failed terminal state, and the scan record should carry enough detail (error message, or at least a failed flag) for a caller to tell "the tool never ran" from "the tool ran and found nothing".

Impact

This is the sort of tool where an empty result gets read as a finding. Silently downgrading "the enricher could not run" to "COMPLETED, no new nodes" means an investigator can conclude a domain has no subdomains / no DNS records / no TLS when in fact nothing was ever queried. Anyone automating against the API has no signal at all, since the failure only exists in the event log.

Notes

Granting the container the host docker gid (group_add) fixes the underlying Docker permission error and those enrichers then work correctly — but the reporting behaviour is independent of that particular cause: any enricher that emits FAILED will still land on COMPLETED.

Also observed while testing: celery logs a pydantic.ValidationError from the emit_event task (1 validation error for Event ... dict_type) around the same failure path, in case the two are related.