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.

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 settings: managed.policy_mode layered.
{
  "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.

Evaluationskontext: policyMode layered und 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 mindestens ein Eintrag mit integer id und order und nicht-leeren rules; fehlendes policyMode oder leer → schema zuerst. policyMode nicht layered → not_layered.

Evaluationskontext-JSON-Schema: Form vor 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"] }
              }
            }
          }
        }
      }
    }
  }
}

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.

Nach Merge allow: Write tool call.
{
  "tool": "Write",
  "path": "/srv/repo/src/app.ts"
}
Nach Merge ask: Bash oben ask überschreibt unten deny.
{
  "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.

Audit-Log: appliedPolicyIds, decision, reason layered_merge, winningPolicyId.
{
  "tool": "Bash",
  "policyMode": "layered",
  "appliedPolicyIds": ["org-baseline", "repo-contributors"],
  "decision": "ask",
  "reason": "layered_merge",
  "winningPolicyId": "repo-contributors"
}
Log- oder Aufrufsignallayered_policy_gate liefertOperator-Aktion
Write alle Schichten allow, reason layered_mergeallowLog decision allow; winningPolicyId höchste order.
Bash unten deny oben ask, reason layered_mergeaskLog decision ask; winningPolicyId obere ask policy id.
Finaler effect deny ohne ask-OverridedenyLog decision deny; appliedPolicyIds vollständig geordnet.
Settings noch first oder layered nicht aktivfirst_only_legacyKein 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.

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

Wie validiert man settings, context und Audit-Logs in JSONNote

  1. 1
    Context-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.

  2. 2
    Format 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.

  3. 3
    Diff: 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.

Zurück zum Blog