Skip to content

Auto-capture copies third-party personal data from any project, with no per-project opt-out, and can leave orphan artifacts #737

Open

Description

@BillOTheWild

Summary

post_tool_capture copies tool outputs, and the first 200 characters of Bash commands, into Cortex's stores for every project. The only control is the global CORTEX_CAPTURE_MODE. The redaction step targets secrets, not personal data.

A user who keeps other people's data in a project gets a second copy of it in ~/.claude/methodology/. Nothing tells them. They cannot exclude that project.

Everything below comes from reading main at 02c3cf0. The scenario is hypothetical.

What the code does

  • One global switch. _capture_enabled (mcp_server/hooks/post_tool_capture.py:310) reads CORTEX_CAPTURE_MODE (full by default). Nothing in the capture path checks the project, its working directory or the paths the tool touched. I found no project-level marker or ignore file.
  • Project rules are not read. CLAUDE.md is where Claude Code users write rules such as "never store client data in memory". The agent can follow it, but the hooks never read it, so the rule does not reach auto-capture.
  • Redaction covers secrets only. scrub_secrets (line 359) handles URL passwords, AWS keys, bearer tokens, key=value secrets and PEM blocks. Names, e-mail addresses, phone numbers and health information pass through.
  • Command lines are captured. _reference_line (line 91) keeps the first 200 characters of a Bash command. A grep whose pattern holds names copies those names, even when the output is empty.
  • Artifacts are written before the memory. _gist_or_full (line 364) writes the full output to artifacts/<yyyy-mm>/ through store_artifact. Only after that does _store_memory (line 372) run. If storing the memory fails, the hook logs it and the artifact stays on disk, with no memory pointing to it. Recall cannot surface it, and I found no sweep that removes it.

Hypothetical scenario

A freelancer keeps a client file in a project: names, addresses, notes. Or a carer keeps notes about patients. They ask Claude Code to sort the file, check a column, or find duplicates.

Each Read, Bash or Grep output that passes the capture filter is copied into Cortex: the memory row, and for long outputs a full artifact. The user believes the data lives in one file they control. In fact a second, unmanaged copy exists in a folder they have probably never opened. Under the GDPR, they are now holding personal data, health data included, in a store they did not choose and cannot easily purge.

Proposals

  1. Per-project opt-out, checked before any write, artifacts included. For example a .cortex-no-capture file honoured for its directory and subfolders, or a list of excluded paths in the plugin config. It should match both the session's working directory and the paths the tool touched.
  2. No orphan artifacts. Write the artifact only after the memory is stored, or delete it when storing fails. Plus a sweep for artifacts that no memory references.
  3. Purge by directory, covering memories, artifacts, entities and profile examples, for when a user realises a project should not have been captured.
  4. Document it in the README and PRIVACY. Tool outputs and command lines are copied, to these places, for this long, and here is how to exclude a project.
  5. Optionally, a pluggable personal-data scrubber next to scrub_secrets, off by default.

Activity

  1. cdeust commented on Nov 6, 2026

    @cdeust
    Owner

    CI autopilot : première tranche dans #907 (Refs #737, pas Closes).

    Tranche 1 (proposition 2, partie synchrone) : quand dispatch refuse ou lève, le hook supprime l'artefact qu'il vient de créer. Un artefact qui existait déjà (dédupliqué, peut-être référencé par une autre mémoire) n'est jamais supprimé. Commit 60d8b7b.

    Tranches restantes :

    1. Balayage des artefacts orphelins (proposition 2b) : couvre les mémoires remises au worker puis refusées par le write gate, et les fichiers déjà laissés par des captures passées. Réutilise artifact_gc.count_artifact_references.
    2. Exclusion par projet (proposition 1) : il faut d'abord trancher le mécanisme (fichier .cortex-no-capture hérité par les sous-dossiers, ou liste de chemins dans la config du plugin) et ce qui est comparé (cwd de la session, chemins touchés par l'outil, ou les deux).
    3. Purge par répertoire (proposition 3) : mémoires, artefacts, entités, exemples de profil.
    4. Documentation README / PRIVACY.md (proposition 4) : ce qui est copié, où, pour combien de temps, comment exclure un projet.
    5. Optionnel : un nettoyeur de données personnelles à côté de scrub_secrets, désactivé par défaut (proposition 5).

    Generated by Claude Code

  2. added 2 commits that reference this issue on Nov 6, 2026
  3. cdeust commented on Nov 10, 2026

    @cdeust
    Owner

    CI autopilot : tranche 4 (documentation) dans #968 (Refs #737).

    PRIVACY.md dit maintenant ce que copie l'auto-capture, outil par outil, et un test (tests_py/hooks/test_capture_privacy_disclosure.py) confronte ce texte au vrai hook. Précision sur le corps de l'issue : pour Read, NotebookRead, Glob et Grep, la ligne en base ne garde que le chemin ou le motif (_LIGHT_VALUE_TOOLS, post_tool_capture.py). Mais une réponse de plus de 3000 caractères est écrite en entier dans un artefact (constat de la revue Codex sur #968, reproduit, corrigé dans 05bb79a) : un long fichier lu est donc copié tel quel. Les réponses d'Edit, Write, MultiEdit, NotebookEdit, Bash, WebFetch et WebSearch sont, elles, copiées.

    Pourquoi pas le balayage des artefacts orphelins d'abord : un balayage sûr ne doit jamais supprimer l'artefact d'une capture en transit. Le spool sur disque (capture_spool) peut être relu, mais la file du worker résident est en mémoire, et je n'ai trouvé aucune borne sourcée sur le temps qu'un payload y passe avant d'être stocké. Un délai de grâce sans source enfreindrait la règle « no source, no implementation ».

    Tranches restantes :

    1. Balayage des artefacts orphelins : il faut une borne sourcée sur le temps de transit dans le worker résident, ou une décision (balayer seulement depuis consolidate, avec un âge minimum mesuré).
    2. Exclusion par projet : décision à prendre sur le mécanisme (fichier .cortex-no-capture hérité par les sous-dossiers, ou liste dans la config) et sur ce qui est comparé (cwd, chemins touchés, ou les deux).
    3. Purge par répertoire : mémoires, artefacts, entités, exemples de profil.
    4. Optionnel : nettoyeur de données personnelles, désactivé par défaut.

    Non vérifié : suite complète et pyright (dépendances de uv.lock non installables ici, téléchargement de torch refusé par le proxy).

    Précisions à ajouter à PRIVACY.md dans la prochaine tranche documentaire (notes de la revue 1 de #968) :

    • Write et MultiEdit stockent le JSON complet de la réponse, contenu écrit et originalFile compris ; Edit stocke les chaînes avant/après.
    • Une commande dont la sortie fait moins de 50 caractères n'est pas capturée du tout, sa ligne de commande non plus.
    • Les outils hors des trois listes (Task, TodoWrite, outils MCP) ne sont jamais capturés.

    Generated by Claude Code

  4. cdeust commented on Nov 10, 2026

    @cdeust
    Owner

    CI autopilot : tranche 5 (précisions documentaires) dans #976 (Refs #737).

    Les trois précisions notées après la revue 1 de #968 sont dans PRIVACY.md, chacune confrontée à process_event par un test : un appel Edit/Write/MultiEdit/NotebookEdit/Bash dont la réponse fait moins de 50 caractères n'est pas capturé du tout (ligne de commande comprise) ; Edit garde ses chaînes avant/après, Write et MultiEdit leur réponse JSON entière ; un outil hors des trois listes (Task, TodoWrite, outils MCP) n'est jamais capturé.

    Tranches restantes, toutes en attente d'une décision humaine (inchangées depuis le commentaire précédent) :

    1. Balayage des artefacts orphelins : il faut une borne sourcée sur le temps de transit dans le worker résident, ou décider de balayer seulement depuis consolidate avec un âge minimum mesuré.
    2. Exclusion par projet : choisir le mécanisme (fichier .cortex-no-capture hérité par les sous-dossiers, ou liste dans la config) et ce qui est comparé (cwd, chemins touchés, ou les deux).
    3. Purge par répertoire : mémoires, artefacts, entités, exemples de profil.
    4. Optionnel : nettoyeur de données personnelles, désactivé par défaut.

    Non vérifié : suite complète et pyright (torch refusé par le proxy, uv sync --locked impossible ici).


    Generated by Claude Code

  5. cdeust commented on Nov 11, 2026

    @cdeust
    Owner

    CI autopilot : issue mise en autopilot:hold. Les trois tranches livrées sont fusionnées (#907, #968, #976). Chaque tranche restante dépend d'une décision qui revient au propriétaire.

    Questions à trancher, une réponse de cdeust par point suffit :

    1. Exclusion par projet : quel mécanisme ? Un fichier .cortex-no-capture hérité par les sous-dossiers, ou une liste de chemins dans la config du plugin ? Et que compare-t-on : le cwd de la session, les chemins touchés par l'outil, ou les deux ?
    2. Balayage des artefacts orphelins : quelle borne pour ne pas supprimer une capture en transit dans le worker résident (file en mémoire, aucune borne sourcée trouvée) ? Faut-il balayer seulement depuis consolidate, avec un âge minimum à mesurer ?
    3. Purge par répertoire (mémoires, artefacts, entités, exemples de profil) : faut-il la rattacher au même critère de chemin que le point 1 ? Si oui, elle attend la réponse au point 1.
    4. Nettoyeur de données personnelles (optionnel) : à faire ou à écarter ?

    Le hold se lève au prochain commentaire de cdeust ou à la prochaine modification du corps de l'issue.


    Generated by Claude Code

  6. added
    autopilot:holdCI autopilot stood down: a human decision is required
    on Nov 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    autopilot:holdCI autopilot stood down: a human decision is required

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions