Blog • Claude Code / Politique gateway

Après la mise à niveau vers Claude Code 2.1.316 avec managed.policy_mode layered sur Claude apps gateway, comment fusionner plusieurs JSON de permission policy correspondants dans les logs d audit ?

Le 31 octobre 2026, Anthropic a livré Claude Code 2.1.316. Les réglages gérés sur Claude apps gateway ajoutent managed.policy_mode layered : au lieu du premier match, chaque policy correspondante entre dans matchedPolicies et fusionne bottom-up ; les effects tool des couches supérieures remplacent allow, deny ou ask en dessous.

Le JSON de contexte d évaluation doit porter policyMode layered et matchedPolicies avec id, order et rules par tool et effect allow, deny ou ask. L audit post-appel : appliedPolicyIds, decision, reason layered_merge, winningPolicyId. layered_policy_gate reflète schema, not_layered, first_only_legacy, no_rule, allow, deny, ask au lieu de la seule première policy id.

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 la fusion layered 2.1.316, le contexte d évaluation, les champs d audit et les branches layered_policy_gate.

Gardez ceci : 2.1.316 change la fusion layered de plusieurs policies sur le gateway et les champs d audit ; il ne réécrit pas les formats de fichiers permission policy locaux. Ensuite : changelog, JSON settings gérés, contexte d évaluation, appels Write et Bash, logs d audit et table, source layered_policy_gate, validation dans JSONNote.

Comment 2.1.316 fusionne plusieurs policies correspondantes en layered

Les notes de version sont dans changelog Claude Code, page tag version v2.1.316. Par rapport à l ancien gateway, managed.policy_mode layered : ne s arrête plus au premier match, fusionne tout par order, les rules tool supérieures remplacent les inférieures.

La fusion intervient à l évaluation des appels tool. L audit garde appliedPolicyIds ordonnés, decision, reason layered_merge, winningPolicyId pour attribuer SIEM à la couche effective, pas seulement first_match policy id.

Comment écrire managed.policy_mode layered dans le JSON settings gérés

Dans les settings gérés du Claude apps gateway, policy_mode layered sous managed. Le gateway envoie toutes les policies correspondantes au pipeline ; si first ou sans layered, chemin first_only_legacy au lieu des champs layered_merge.

Settings gérés : managed.policy_mode layered.
{
  "managed": {
    "policy_mode": "layered"
  }
}

Casse incorrecte de policy_mode ou managed absent → schema d abord avant fusion layered_policy_gate. Ne pas confondre forme et not_layered.

Comment policyMode et matchedPolicies apparaissent dans le contexte d évaluation

Le gateway écrit policyMode layered et matchedPolicies dans le JSON de contexte. Chaque entrée : id, order, rules ; chaque rule lie tool et effect allow, deny ou ask. L ordre est la pile de fusion : les policies suivantes remplacent les rules tool antérieures.

Contexte d évaluation : policyMode layered et matchedPolicies.
{
  "policyMode": "layered",
  "matchedPolicies": [
    {
      "id": "org-baseline",
      "order": 0,
      "rules": [
        { "tool": "Bash", "effect": "deny" }
      ]
    },
    {
      "id": "repo-contributors",
      "order": 1,
      "rules": [
        { "tool": "Write", "effect": "allow" },
        { "tool": "Bash", "effect": "ask" }
      ]
    }
  ]
}

matchedPolicies au moins une entrée, id et order entiers, rules non vides ; policyMode absent ou vide → schema. policyMode pas layered → not_layered.

JSON Schema contexte d évaluation : forme avant layered_policy_gate.
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["policyMode", "matchedPolicies"],
  "properties": {
    "policyMode": { "type": "string", "enum": ["layered", "first"] },
    "matchedPolicies": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "required": ["id", "order", "rules"],
        "properties": {
          "id": { "type": "string", "minLength": 1 },
          "order": { "type": "integer" },
          "rules": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["tool", "effect"],
              "properties": {
                "tool": { "type": "string" },
                "effect": { "type": "string", "enum": ["allow", "deny", "ask"] }
              }
            }
          }
        }
      }
    }
  }
}

Comment les appels Write et Bash sont jugés après fusion layered

Exemple 1 : allow Write en bas et en haut → decision allow. Exemple 2 : Bash deny bas, ask haut → decision ask, winningPolicyId id supérieure. L audit garde le tool et les effects par couche avant fusion pour layered_policy_gate.

Après fusion allow : appel tool Write.
{
  "tool": "Write",
  "path": "/srv/repo/src/app.ts"
}
Après fusion ask : Bash ask haut remplace deny bas.
{
  "tool": "Bash",
  "command": "npm test"
}

Comment appliedPolicyIds et winningPolicyId apparaissent dans les logs d audit

Après fusion layered : appliedPolicyIds ids ordonnées, decision allow deny ou ask, reason layered_merge, winningPolicyId couche effective. Write allow souvent order max ; Bash ask winningPolicyId vers ask haut pas deny bas.

Log d audit : appliedPolicyIds, decision, reason layered_merge, winningPolicyId.
{
  "tool": "Bash",
  "policyMode": "layered",
  "appliedPolicyIds": ["org-baseline", "repo-contributors"],
  "decision": "ask",
  "reason": "layered_merge",
  "winningPolicyId": "repo-contributors"
}
Signal log ou appellayered_policy_gate renvoieAction opérateur
Write toutes couches allow, reason layered_mergeallowLog decision allow ; winningPolicyId order max.
Bash deny bas ask haut, reason layered_mergeaskLog decision ask ; winningPolicyId id policy ask haute.
Effect final deny sans override askdenyLog decision deny ; appliedPolicyIds ordonné complet.
Settings encore first ou layered inactiffirst_only_legacyNe pas écrire reason layered_merge ; comparer aux rapports first-match legacy.

Comment layered_policy_gate choisit entre schema et sept branches métier

Ordre : schema context d évaluation pour policyMode et matchedPolicies ; policyMode pas layered → not_layered ; settings first → first_only_legacy ; fusion effects tool bottom-up par order ; pas de rule → no_rule ; résultat allow deny ou ask ; mauvaise forme → schema.

Ne fusionnez pas deny et ask en un blocked. Comptez à part first_only_legacy, no_rule et vrais résultats layered_merge allow ask deny.

layered_policy_gate : schema, not_layered, first_only_legacy, no_rule, allow, deny, ask.
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 effect_for_tool(policies: list, tool: str, layered: bool) -> tuple[str, str | None]:
    ordered = sorted(policies, key=lambda p: p.get("order", 0))
    if not layered:
        first = ordered[0]
        for rule in first.get("rules") or []:
            if rule.get("tool") == tool:
                return str(rule.get("effect")), str(first.get("id"))
        return "no_rule", None
    effect: str | None = None
    winner: str | None = None
    for policy in ordered:
        for rule in policy.get("rules") or []:
            if rule.get("tool") == tool:
                effect = str(rule.get("effect"))
                winner = str(policy.get("id"))
    if effect is None:
        return "no_rule", None
    return effect, winner

def layered_policy_gate(ctx: dict, call: dict, schema: dict) -> str:
    if validate(ctx, schema):
        return "schema"
    mode = ctx.get("policyMode")
    if mode != "layered":
        return "not_layered"
    policies = ctx.get("matchedPolicies") or []
    if not policies:
        return "schema"
    tool = call.get("tool")
    if not isinstance(tool, str):
        return "schema"
    effect, _ = effect_for_tool(policies, tool, layered=True)
    if effect == "no_rule":
        return "no_rule"
    return effect

Comment valider settings, context et logs d audit dans JSONNote

  1. 1
    Fixer la forme context avec Schema

    Ouvrez JSON Schema. Schema context d évaluation au premier champ, policyMode layered et matchedPolicies au second. Erreurs required ou minItems d abord ; pas allow ou deny si forme mauvaise.

  2. 2
    Format pour les appels tool

    Ouvrez JSON Format, collez JSON Write allow ou Bash ask. Vérifiez tool avant de juger ask ou allow pour layered_policy_gate.

  3. 3
    Diff logs allow et ask

    Ouvrez JSON Diff. À gauche échantillon Write allow, à droite Bash ask. winningPolicyId surligné est la clé SIEM.

Questions fréquentes

En quoi layered diffère-t-il de l ancien first-match-only ?

L ancien s arrêtait au premier match, audit souvent une seule policy id. 2.1.316 layered liste tout dans appliedPolicyIds, fusionne par order, reason layered_merge et winningPolicyId. Gateway pas layered → first_only_legacy.

Quel lien entre layered et un fichier permission policy local ?

La fusion layered des settings gateway gérés tourne dans le pipeline cloud et produit matchedPolicies et champs d audit. Les fichiers permission policy locaux restent pour defaults Agent ; ids et order ne coïncident pas automatiquement. Validez settings gérés, contexte d évaluation et JSON policy local séparément.

Un deny bas peut-il être remplacé par un ask haut ?

Oui. La fusion layered empile bottom-up par order ; l effect final d un tool vient de la couche la plus haute. Bash deny bas ask haut → decision ask, winningPolicyId en haut. Sans rule haut, deny bas reste.

Comment SIEM utilise winningPolicyId pour l attribution ?

appliedPolicyIds porte l ordre complet, winningPolicyId la couche effective. SIEM relie owners et tickets via winningPolicyId et surveille la longueur appliedPolicyIds pour first-match-only. Si reason layered_merge, agréger decision allow deny ask.

Cet article est-il un tutoriel d attaque ou de contournement ?

Non. L article explique seulement les formes JSON, champs d audit et branches layered_policy_gate pour validation opérateur et conformité et attribution SIEM. Pas d étapes pour contourner les contrôles via ask ou deny multi-couches.

Conclusion

Claude Code 2.1.316 fusionne plusieurs policies avec managed.policy_mode layered sur Claude apps gateway et écrit appliedPolicyIds, decision, reason layered_merge et winningPolicyId dans l audit JSON. Pilotez layered_policy_gate avec settings gérés, contexte d évaluation, appels Write et Bash et logs d audit, pas seulement la première policy id.

Décision : context échoue schema → schema ; policyMode pas layered → not_layered ; settings first → first_only_legacy ; pas de rule tool → no_rule ; allow deny ou ask fusionné aligné sur audit reason layered_merge et winningPolicyId.

Ensuite : Schema pour context et settings, Format pour Write et Bash, Diff logs allow et ask, compter séparément les retours layered_policy_gate.

Retour au blog