Blog • Claude Code / Gateway-Richtlinie
Nach Upgrade auf Claude Code 2.1.316 mit managed.policy_mode layered am Claude apps gateway: wie werden mehrere passende permission-policy-JSONs zusammengeführt und in Audit-Logs geschrieben?
Am 31. Oktober 2026 lieferte Anthropic Claude Code 2.1.316. Managed settings am Claude apps gateway ergänzen managed.policy_mode layered: statt beim ersten Treffer zu stoppen, landen alle passenden Policies in matchedPolicies und werden bottom-up gemergt; obere tool effects überschreiben unten allow, deny oder ask.
Evaluationskontext-JSON braucht policyMode layered und matchedPolicies mit id, order und rules pro tool plus effect allow, deny oder ask. Audit-JSON nach tool calls: appliedPolicyIds, decision, reason layered_merge, winningPolicyId. layered_policy_gate spiegelt schema, not_layered, first_only_legacy, no_rule, allow, deny, ask statt nur der ersten policy id.
Allow- und deny-Queue in Agent permission policy; Managed Agents auto-Evaluationskontext in Managed Agents auto-Evaluation; Session-Workflow-JSON in Claude Code workflow. Dieser Artikel behandelt nur 2.1.316 layered Policy-Merge, Evaluationskontext und Audit-Log-Felder sowie layered_policy_gate-Verzweigung.
- Managed settings brauchen managed.policy_mode layered; bei first oder fehlend während legacy-Pfad layered_policy_gate first_only_legacy oder not_layered, kein layered_merge winningPolicyId.
- Jeder matchedPolicies-Eintrag braucht id, order und rules mit tool plus effect allow, deny oder ask; nach Merge Write allow, Bash bei unten deny und oben ask → ask nicht deny.
- Audit-Logs: appliedPolicyIds in Reihenfolge, decision allow deny oder ask, reason layered_merge, winningPolicyId der wirksamen Schicht; SIEM attributiert über winningPolicyId, nicht nur erste id.
- Keine rule für tool → layered_policy_gate no_rule; fehlt policyMode oder schlechte matchedPolicies-Form → schema vor Merge.
Zuerst dies: 2.1.316 ändert layered Merge mehrerer Policies am gateway und Audit-Felder; lokale permission-policy-Dateiformate werden nicht neu geschrieben. Unten: Changelog, managed settings JSON, Evaluationskontext, Write- und Bash-Beispiele, Audit-Logs und Tabelle, layered_policy_gate-Quelltext, Formprüfung in JSONNote.
Wie macht 2.1.316 aus mehreren passenden Policies layered Merge
Änderungen stehen in Claude Code changelog, Tag-Seite Release v2.1.316. Gegenüber früherem Gateway-Verhalten fällt managed.policy_mode layered auf: nicht beim ersten Match stoppen, sondern alle Treffer per order mergen, obere tool rules überschreiben unten.
Merge erfolgt bei tool-call evaluation. Audit-JSON: appliedPolicyIds in Reihenfolge, decision, reason layered_merge, winningPolicyId für SIEM-Attribution zur wirksamen Schicht statt nur first_match policy id.
Wie schreibt man managed.policy_mode layered in managed settings JSON
In managed settings vom Claude apps gateway policy_mode layered unter managed setzen. Das gateway schickt alle passenden Policies in die Evaluation; bei first oder ohne layered first_only_legacy statt layered_merge Audit-Felder.
{
"managed": {
"policy_mode": "layered"
}
}
Falsche Schreibweise von policy_mode oder fehlendes managed → schema zuerst, bevor layered_policy_gate merged. Formfehler nicht als not_layered werten.
Wie policyMode und matchedPolicies im Evaluationskontext stehen
Das gateway schreibt policyMode layered und matchedPolicies ins Evaluationskontext-JSON. Einträge mit id, order, rules; jede rule tool plus effect allow, deny oder ask. Listenreihenfolge ist Merge-Stack: spätere Policies überschreiben frühere rules für dasselbe tool.
{
"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 mindestens ein Eintrag mit integer id und order und nicht-leeren rules; fehlendes policyMode oder leer → schema zuerst. policyMode nicht 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"] }
}
}
}
}
}
}
}
}
Wie Write- und Bash-Aufrufe nach layered Merge entschieden werden
Beispiel 1: unten und oben allow für Write → decision allow. Beispiel 2: Bash unten deny, oben ask → decision ask, winningPolicyId obere id. Audit-JSON: tool-Name und Effects pro Schicht vor Merge für layered_policy_gate.
{
"tool": "Write",
"path": "/srv/repo/src/app.ts"
}
{
"tool": "Bash",
"command": "npm test"
}
Wie appliedPolicyIds und winningPolicyId in Audit-Logs stehen
Nach layered Merge: appliedPolicyIds geordnete teilnehmende ids, decision allow deny oder ask, reason layered_merge, winningPolicyId wirksame Schicht. Write allow oft höchste order; Bash ask winningPolicyId zur oberen ask-Schicht nicht unten deny.
{
"tool": "Bash",
"policyMode": "layered",
"appliedPolicyIds": ["org-baseline", "repo-contributors"],
"decision": "ask",
"reason": "layered_merge",
"winningPolicyId": "repo-contributors"
}
| Log- oder Aufrufsignal | layered_policy_gate liefert | Operator-Aktion |
|---|---|---|
| Write alle Schichten allow, reason layered_merge | allow | Log decision allow; winningPolicyId höchste order. |
| Bash unten deny oben ask, reason layered_merge | ask | Log decision ask; winningPolicyId obere ask policy id. |
| Finaler effect deny ohne ask-Override | deny | Log decision deny; appliedPolicyIds vollständig geordnet. |
| Settings noch first oder layered nicht aktiv | first_only_legacy | Kein reason layered_merge; mit legacy first-match Reports vergleichen. |
Wie layered_policy_gate zwischen schema und sieben Fachzweigen wählt
Reihenfolge: Evaluationskontext-Schema für policyMode und matchedPolicies; policyMode nicht layered → not_layered; settings noch first → first_only_legacy; Effects pro tool bottom-up per order mergen; keine rule → no_rule; Ergebnis allow deny oder ask; Formfehler → schema.
deny und ask nicht zu einem blocked-Bit zusammenlegen. Getrennt zählen für first_only_legacy, no_rule und echte 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
Wie validiert man settings, context und Audit-Logs in JSONNote
- 1Context-Form per Schema fixieren
Öffnen Sie JSON Schema. Evaluationskontext-Schema ins erste Feld, policyMode layered und matchedPolicies ins zweite. required oder minItems zuerst; bei schlechter Form kein allow oder deny von layered_policy_gate.
- 2Format für tool-Aufrufe
Öffnen Sie JSON formatieren, Write-allow- oder Bash-ask-JSON einfügen. tool-Key prüfen, dann ob layered_policy_gate ask oder allow liefern soll.
- 3Diff: allow vs ask Audit-Logs
Öffnen Sie JSON Diff. Links Write-allow-Beispiel, rechts Bash-ask-Beispiel. Hervorgehobenes winningPolicyId ist die SIEM-Attributionszeile.
Häufige Fragen
Worin unterscheidet sich layered vom alten first-match-only?
Legacy stoppte beim ersten Match, Audits oft nur eine policy id. 2.1.316 layered schreibt alle Treffer in appliedPolicyIds, merged per order, reason layered_merge und winningPolicyId. Gateway nicht layered → first_only_legacy.
Wie hängt layered mit einer lokalen permission-policy-Datei zusammen?
layered Merge in managed gateway settings läuft in der Cloud-Evaluation und liefert matchedPolicies und Audit-Felder. Lokale permission-policy-Dateien bleiben für Agent-Defaults, ids und order sind nicht automatisch gleich. managed settings, Evaluationskontext und lokales policy JSON getrennt prüfen.
Kann unten deny durch oben ask überschrieben werden?
Ja. layered Merge stapelt bottom-up per order; finaler effect pro tool von der höchsten treffenden Schicht. Bash unten deny oben ask → decision ask, winningPolicyId oben. Ohne rule oben bleibt deny unten.
Wie nutzt SIEM winningPolicyId zur Attribution?
appliedPolicyIds enthält die volle Merge-Reihenfolge, winningPolicyId die wirksame Schicht. SIEM soll Owner und Tickets über winningPolicyId verknüpfen und appliedPolicyIds-Länge für first-match-only prüfen. Bei reason layered_merge decision allow deny ask aggregieren.
Enthält dieser Artikel Angriffs- oder Bypass-Tutorials?
Nein. Der Artikel erklärt nur JSON-Formen, Audit-Felder und layered_policy_gate-Zweige für Betrieb und Compliance sowie SIEM-Attribution. Keine Schritte zum Ausnutzen multi-layer ask oder deny zum Umgehen von Controls.
Fazit
Claude Code 2.1.316 merged mit managed.policy_mode layered am Claude apps gateway mehrere Policies und schreibt appliedPolicyIds, decision, reason layered_merge und winningPolicyId ins Audit-JSON. layered_policy_gate mit managed settings, Evaluationskontext, Write- und Bash-Aufrufen und Audit-Logs füttern, nicht nur die erste policy id.
Entscheidung: context scheitert schema → schema; policyMode nicht layered → not_layered; settings first → first_only_legacy; keine tool rule → no_rule; merged allow deny oder ask passt zu Audit reason layered_merge und winningPolicyId.
Als Nächstes: context und settings per Schema, Format für Write- und Bash-Aufrufe, Diff allow vs ask Audit-Logs, layered_policy_gate-Rückgaben getrennt berichten.