#230·flowsint

Deleting a sketch leaves its entire graph in Neo4j

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

Summary

DELETE /api/sketches/{id} returns 204 and removes the sketch from Postgres, but every Neo4j node carrying that sketch_id is left in place. The graph data of a "deleted" sketch survives indefinitely, invisible to the UI but fully present in the database.

Version

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

Reproduction

  1. Create an investigation and a sketch.

  2. Add a domain node and run an enricher that expands the graph. domain_to_subdomains on a domain with many subdomains makes the effect obvious — in my run it produced 22,248 subdomain nodes.

  3. Confirm the graph: GET /api/sketches/{sketch_id}/graph → 22,254 nodes.

  4. DELETE /api/sketches/{sketch_id}204 No Content.

  5. Query Neo4j directly:

    cypher
    MATCH (n) WHERE n.sketch_id IS NOT NULL RETURN count(n);

    22254

Every node is still there, still tagged with the deleted sketch's id. The same holds for a small sketch — I first saw it with a 3-node sketch, where the delete left all 3.

Expected

Deleting a sketch should delete the graph belonging to it, or the API should document that it does not and offer a way to purge. At present the only route is manual:

cypher
MATCH (n {sketch_id:'<id>'}) WITH n LIMIT 5000 DETACH DELETE n RETURN count(n);

(repeated until it returns 0 — a single unbatched DETACH DELETE on a large sketch is heavy).

Impact

Two concerns:

  1. Data retention. The docs describe Flowsint as local and privacy-first, which invites the reading that deleting an investigation disposes of its data. It does not. For anyone handling subject data under a retention policy, "delete" not deleting is a meaningful gap.
  2. Unbounded growth. Nothing reclaims these nodes, so a Neo4j instance accumulates the full graph of every sketch ever deleted. Subdomain enumeration produces tens of thousands of nodes per run, so this adds up quickly.

Note

Deleting the owning investigation (DELETE /api/investigations/{id}, also 204) does not clean them up either.