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.
- Les settings gérés exigent managed.policy_mode layered ; si first ou absent avec chemin legacy, layered_policy_gate renvoie first_only_legacy ou not_layered sans winningPolicyId layered_merge.
- Chaque matchedPolicies exige id, order et rules tool plus effect allow, deny ou ask ; après fusion Write allow, Bash avec deny bas et ask haut → ask pas deny.
- Logs d audit : appliedPolicyIds ordonnés, decision allow deny ou ask, reason layered_merge, winningPolicyId de la couche effective ; SIEM attribue via winningPolicyId, pas seulement la première id.
- Aucune rule pour le tool → layered_policy_gate no_rule ; policyMode absent ou matchedPolicies mal formé → schema avant fusion.
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.
{
"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.
{
"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.
{
"$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.
{
"tool": "Write",
"path": "/srv/repo/src/app.ts"
}
{
"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.
{
"tool": "Bash",
"policyMode": "layered",
"appliedPolicyIds": ["org-baseline", "repo-contributors"],
"decision": "ask",
"reason": "layered_merge",
"winningPolicyId": "repo-contributors"
}
| Signal log ou appel | layered_policy_gate renvoie | Action opérateur |
|---|---|---|
| Write toutes couches allow, reason layered_merge | allow | Log decision allow ; winningPolicyId order max. |
| Bash deny bas ask haut, reason layered_merge | ask | Log decision ask ; winningPolicyId id policy ask haute. |
| Effect final deny sans override ask | deny | Log decision deny ; appliedPolicyIds ordonné complet. |
| Settings encore first ou layered inactif | first_only_legacy | Ne 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.
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
- 1Fixer 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.
- 2Format 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.
- 3Diff 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.