Blog • Claude Code / Permissions

Après la mise à niveau vers Claude Code 2.1.314 avec permissions.blockWritesOutsideWorkingDirectories activé, comment consigner les chemins Write et Edit refusés dans les logs JSON ?

Le 30 octobre 2026, Anthropic a livré Claude Code 2.1.314 avec permissions.blockWritesOutsideWorkingDirectories. Si true, Write, Edit et NotebookEdit sont refusés hors workingDirectories dans chaque mode permission, y compris default, acceptEdits et bypassPermissions.

Ce n'est pas la même couche que allow ou deny dans une permission policy : celles-ci gèrent types d'outils et approbation, les écritures hors répertoires sont bloquées par préfixe de path avant exécution. Le JSON d'audit doit garder le flag settings, workingDirectories de session, tool et path, decision denied et reason outside_working_directories pour write_path_gate.

La file allow et deny est dans Agent permission policy; le contexte auto evaluation Managed Agents dans évaluation auto Managed Agents; le JSON workflow de session dans Claude Code workflow. Cet article ne couvre que les blocages 2.1.314 hors répertoires de travail, la forme des champs de log et les branches de write_path_gate.

Gardez ceci : 2.1.314 ajoute un blocage dur des écritures aux limites des répertoires de travail et les champs de log associés ; il ne réécrit pas les tables allow ou deny d'une permission policy. Ensuite : changelog, JSON settings, workingDirectories de session, exemples Write Edit NotebookEdit, logs de refus et table, source write_path_gate, validation des formes dans JSONNote.

Comment 2.1.314 bloque Write, Edit et NotebookEdit hors répertoires de travail

Les notes de version sont dans changelog Claude Code, page tag version v2.1.314. Par rapport aux versions antérieures, permissions.blockWritesOutsideWorkingDirectories : activé, les tools d'écriture n'atteignent plus des chemins comme /etc via bypassPermissions ou acceptEdits.

Le blocage intervient après résolution du path et avant l'écriture. Les logs gardent le nom tool et le path absolu pour agréger sur reason outside_working_directories plutôt qu'un permission denied générique.

Comment écrire permissions.blockWritesOutsideWorkingDirectories dans le JSON settings

Ajoutez blockWritesOutsideWorkingDirectories sous permissions dans settings utilisateur ou gérés à true. Le drapeau est orthogonal au permission mode : même en bypassPermissions, les tools d'écriture passent le contrôle de préfixe workingDirectories.

Settings : permissions.blockWritesOutsideWorkingDirectories true.
{
  "permissions": {
    "blockWritesOutsideWorkingDirectories": true
  }
}

Si le drapeau est la chaîne true ou permissions manque, la validation schema échoue d'abord avec schema avant les branches métier de write_path_gate. Ne pas confondre erreur de forme et not_enabled.

Comment workingDirectories et blockOutside se lient en session

Le client écrit un tableau workingDirectories et blockOutside booléen dans le JSON de contexte de session. Comparaison de préfixe seulement si blockOutside true et le flag settings actif ; si blockOutside false → not_enabled même si settings true.

Contexte session : workingDirectories et blockOutside.
{
  "workingDirectories": ["/home/dev/myapp"],
  "blockOutside": true
}

workingDirectories exige au moins une chaîne non vide ; tableau vide → no_workdirs. Retirez les slash finaux des racines avant de tester égalité ou préfixe root/.

JSON Schema contexte session : forme avant write_path_gate.
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["workingDirectories", "blockOutside"],
  "properties": {
    "workingDirectories": {
      "type": "array",
      "minItems": 1,
      "items": { "type": "string", "minLength": 1 }
    },
    "blockOutside": { "type": "boolean" }
  }
}

Quels champs exige le JSON d'appel Write, Edit, NotebookEdit

Les échantillons d'audit exigent au minimum tool et path. Write, Edit ou NotebookEdit entrent dans la logique de blocage ; path doit être une chaîne absolue. Si le client résout des relatifs, le log garde la valeur absolue résolue pour les règles SIEM.

Autorisé : Write vise un fichier dans workingDirectory.
{
  "tool": "Write",
  "path": "/home/dev/myapp/src/main.ts"
}
Refusé : Edit vise un path hors workingDirectory.
{
  "tool": "Edit",
  "path": "/etc/hosts"
}

Comment écrire decision et reason sur un refus

Si path n'est sous aucun préfixe workingDirectory, le client n'écrit pas et logue decision denied et reason outside_working_directories en gardant tool et path d'origine. NotebookEdit partage la même enum reason qu'Edit pour une règle sur trois tools d'écriture.

Log de refus : decision denied, reason outside_working_directories.
{
  "tool": "Edit",
  "path": "/etc/hosts",
  "decision": "denied",
  "reason": "outside_working_directories"
}
Signal log ou appelwrite_path_gate renvoieAction opérateur
path sous préfixe workingDirectories, tool Write ou NotebookEditallowAutoriser ; pas de outside_working_directories dans le log.
path hors racines, tool Edit, blockOutside truedeny_outsideAligner sur decision denied et reason outside_working_directories du log client.
blockOutside false ou flag settings inactifnot_enabledNe pas signaler outside_working_directories à tort ; vérifier settings et blockOutside de session.
tool Bash, path n'importe oùnot_write_toolCompter à part des blocages d'écriture hors répertoire ; Bash reste soumis aux autres permission rules.

Comment write_path_gate choisit entre schema et sept branches métier

Ordre : valider le JSON Schema de contexte pour workingDirectories et blockOutside ; blockOutside false → not_enabled ; lire path de l'appel, non string → missing_path ; tool hors Write, Edit, NotebookEdit → not_write_tool ; workingDirectories vide → no_workdirs ; path correspond à un préfixe → allow ; sinon deny_outside et reason outside_working_directories dans le log.

Ne fusionnez pas deny_outside et missing_path en un seul blocked. Comptez séparément pour distinguer mauvais path, mauvais type tool et refus outside_working_directories réels.

write_path_gate : schema, not_enabled, missing_path, not_write_tool, no_workdirs, allow, deny_outside.
def actual_type(data: object) -> str:
    if data is None:
        return "null"
    if isinstance(data, bool):
        return "boolean"
    if isinstance(data, list):
        return "array"
    if isinstance(data, int):
        return "integer"
    if isinstance(data, float):
        return "number"
    if isinstance(data, str):
        return "string"
    if isinstance(data, dict):
        return "object"
    raise TypeError(type(data))

def validate(data: object, schema: dict) -> list[str]:
    errors: list[str] = []
    if not isinstance(schema, dict):
        return errors
    types = schema.get("type")
    if isinstance(types, str):
        types = [types]
    actual = actual_type(data)
    if types and not any(t == actual or (t == "number" and actual == "integer") for t in types):
        errors.append("type")
    if "enum" in schema and not any(value == data for value in schema["enum"]):
        errors.append("enum")
    if isinstance(data, str) and "minLength" in schema and len(data) < schema["minLength"]:
        errors.append("minLength")
    if isinstance(data, dict) and schema.get("properties"):
        for key in schema.get("required") or []:
            if key not in data:
                errors.append(f"required:{key}")
        if schema.get("additionalProperties") is False:
            for key in data:
                if key not in schema["properties"]:
                    errors.append(f"additional:{key}")
        for key, sub in schema["properties"].items():
            if key in data and data[key] is not None:
                errors.extend(validate(data[key], sub))
    return errors

def write_path_gate(ctx: dict, call: dict, schema: dict) -> str:
    if validate(ctx, schema):
        return "schema"
    if not ctx.get("blockOutside"):
        return "not_enabled"
    path = call.get("path")
    if not isinstance(path, str):
        return "missing_path"
    tool = call.get("tool")
    if tool not in {"Write", "Edit", "NotebookEdit"}:
        return "not_write_tool"
    roots = ctx.get("workingDirectories") or []
    if not roots:
        return "no_workdirs"
    for root in roots:
        base = root.rstrip("/")
        if path == base or path.startswith(base + "/"):
            return "allow"
    return "deny_outside"

Comment valider settings, context et logs de refus dans JSONNote

  1. 1
    Fixer la forme context avec Schema

    Ouvrez JSON Schema. Schema context session au premier champ, échantillon workingDirectories et blockOutside au second. Erreurs required ou minItems d'abord ; pas allow ou deny_outside si la forme est mauvaise.

  2. 2
    Format pour les appels tool

    Ouvrez JSON Format, collez le JSON d'appel Write, Edit ou NotebookEdit. Vérifiez tool et path et que path est string avant de juger missing_path pour write_path_gate.

  3. 3
    Diff logs autorisés et refusés

    Ouvrez JSON Diff. À gauche un échantillon autorisé sous workingDirectory, à droite un refus avec decision denied et reason outside_working_directories. Le reason surligné est la clé SIEM.

Questions fréquentes

Le mode bypassPermissions peut-il encore écrire hors répertoires de travail ?

Non. Dès 2.1.314 avec blockWritesOutsideWorkingDirectories true, Write, Edit et NotebookEdit restent bloqués hors répertoires de travail même en bypassPermissions ; les logs gardent reason outside_working_directories sans supposer que bypass ignore le contrôle de répertoire.

Les liens symboliques contournent-ils le préfixe workingDirectories ?

Le client compare les préfixes sur la chaîne path de la requête sans résoudre les cibles symlink ici. Fixez workingDirectories sur de vraies racines projet et loguez le path soumis par l'agent ; cet article décrit seulement la logique de validation, pas de contournement ni d'attaque.

Est-ce en conflit avec allow ou deny dans une permission policy ?

Pas de conflit, couches différentes. La permission policy décide allow ou file d'approbation par type tool ; blockWritesOutsideWorkingDirectories bloque encore hors répertoire par path. JSON séparés ; write_path_gate ne reflète que la couche répertoire, pas allow ou deny policy.

Read ou Grep hors répertoires de travail loguent-ils outside_working_directories ?

Non. outside_working_directories ne vise que les refus d'écriture Write, Edit, NotebookEdit. Les tools Read suivent d'autres permission et sandbox ; write_path_gate renvoie not_write_tool sans mélanger lecture et métriques de refus d'écriture.

Comment les logs séparent no_workdirs et deny_outside ?

no_workdirs est une branche locale quand workingDirectories manque ou est vide ; ne pas fabriquer reason outside_working_directories. deny_outside correspond à un path concret refusé par le client. Rapportez schema, no_workdirs et deny_outside séparément.

Conclusion

Claude Code 2.1.314 utilise permissions.blockWritesOutsideWorkingDirectories pour garder Write, Edit et NotebookEdit dans les préfixes de répertoires de travail et loguer decision denied et reason outside_working_directories au refus. Pilotez write_path_gate avec settings, contexte session, appels tool et logs, pas seulement les noms de permission mode.

Décision : context échoue schema → schema ; blockOutside false → not_enabled ; path non string → missing_path ; tool hors Write, Edit, NotebookEdit → not_write_tool ; workingDirectories vide → no_workdirs ; path sous préfixe → allow ; sinon deny_outside avec log decision denied et reason outside_working_directories.

Ensuite : Schema pour context et settings, Format pour tool et path, Diff logs autorisés et refusés, compter séparément les retours write_path_gate.

Retour au blog