ブログ • Claude Code / Gateway ポリシー

Claude Code 2.1.316 に上げ、Claude apps gateway で managed.policy_mode を layered にしたとき、複数一致する permission policy JSON はどうマージして監査ログに書くか?

2026年10月31日 Anthropic は Claude Code 2.1.316 を出した。Claude apps gateway の托管設定に managed.policy_mode layered を追加。旧版の最初の一致で止まらず、開発者に一致する policy は matchedPolicies に入り、リスト順に下から上へマージし、上層の tool effect が下層の allow、deny、ask を上書きする。

評価 context JSON には policyMode layered と matchedPolicies 配列(id、order、tool と effect allow、deny、ask の rules)を書く。tool 呼び出し後の監査 JSON には appliedPolicyIds、decision、reason layered_merge、winningPolicyId。运维は layered_policy_gate の schema、not_layered、first_only_legacy、no_rule、allow、deny、ask 七分支と照合し、最初の policy id だけを見ない。

allow と deny キューは Agent permission policy。Managed Agents の auto 評価文脈は Managed Agents auto 評価。セッション工作流 JSON は Claude Code workflow。本記事は 2.1.316 の layered 策略マージ、評価 context と監査ログフィールド、layered_policy_gate 分岐のみを書く。

先にこれだけ:2.1.316 が変えるのは gateway で複数 policy を layered マージする点と監査フィールドであり、ローカル permission policy ファイル形式の書き換えではない。以下 changelog、managed settings JSON、評価 context、Write と Bash 呼び出し、監査ログと表、layered_policy_gate 源码、JSONNote での形状検証。

2.1.316 は複数一致 policy を layered マージにどう変えるか

変更は Claude Code changelog、タグは v2.1.316 release。以前の gateway 行为比で目立つのは managed.policy_mode layered。最初の一致で止まらず order で全件マージし、上層 tool 规则が下層を上書きする。

マージは tool 呼び出し評価段階。監査 JSON に appliedPolicyIds 全序、decision、reason layered_merge、winningPolicyId を残し、first_match policy id だけでなく最終層で SIEM 帰属する。

managed.policy_mode layered を托管 settings JSON にどう書くか

Claude apps gateway からの托管 settings で managed に policy_mode layered を書く。gateway は開発者に一致する policy をすべて評価 pipeline へ送る。first のままや layered 未宣言なら layered_merge 監査ではなく first_only_legacy 路径。

托管 settings:managed.policy_mode layered。
{
  "managed": {
    "policy_mode": "layered"
  }
}

policy_mode の大小文字混在や managed 欠落なら schema が先に失敗し schema を返し、layered_policy_gate マージ前に止める。形状错误を not_layered と混同しない。

評価 context の policyMode と matchedPolicies の書き方

gateway は評価 context JSON に policyMode layered と matchedPolicies 配列を書く。各要素に id、order、rules。各 rule は tool と effect allow、deny、ask。リスト順がマージ栈で、後の policy が同 tool の先の rule を上書きする。

評価 context:policyMode layered と 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 は少なくとも 1 件、id と order は整数、rules は非空配列。policyMode 欠落や空 matchedPolicies なら layered_policy_gate は schema。policyMode が layered でなければ not_layered。

評価 context JSON Schema:形状を通してから 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"] }
              }
            }
          }
        }
      }
    }
  }
}

layered マージ後 Write と Bash の tool 呼び出しはどう判定されるか

サンプル1:下層・上層とも Write allow → マージ後 decision allow。サンプル2:Bash は下層 deny・上層 ask → decision ask、winningPolicyId は上層 id。監査 JSON に tool 名とマージ前各層 effect を残し layered_policy_gate と照合。

マージ後 allow:Write tool 呼び出し。
{
  "tool": "Write",
  "path": "/srv/repo/src/app.ts"
}
マージ後 ask:Bash は上層 ask が下層 deny を上書き。
{
  "tool": "Bash",
  "command": "npm test"
}

監査ログの appliedPolicyIds と winningPolicyId の書き方

layered マージ後、permission または tool 監査 JSON に appliedPolicyIds は参加 id の全序配列、decision は allow deny ask、reason は layered_merge、winningPolicyId は最終層 id。Write allow は通常最高 order、Bash ask は下層 deny ではなく上層 ask の id。

監査ログ:appliedPolicyIds、decision、reason layered_merge、winningPolicyId。
{
  "tool": "Bash",
  "policyMode": "layered",
  "appliedPolicyIds": ["org-baseline", "repo-contributors"],
  "decision": "ask",
  "reason": "layered_merge",
  "winningPolicyId": "repo-contributors"
}
ログまたは呼び出し現象layered_policy_gate 戻り運用対応
Write 各層 allow、reason layered_mergeallowログ decision allow、winningPolicyId は最高 order。
Bash 下層 deny・上層 ask、reason layered_mergeaskログ decision ask、winningPolicyId は上層 ask policy id。
マージ後最終 effect deny、ask 上書きなしdenyログ decision deny、appliedPolicyIds は全序のまま。
settings が first のまままたは layered 未启用first_only_legacyreason layered_merge を書かず旧 first-match 报表と照合。

layered_policy_gate は schema と七业务分支をどう選ぶか

順序:評価 context JSON Schema で policyMode と matchedPolicies を検証;policyMode が layered でなければ not_layered;托管 settings が first なら first_only_legacy;order で下から上へ同 tool の effect をマージ;rule 無し no_rule;結果 allow deny ask;形状错误 schema。

deny と ask を blocked 一つに畳まない。レポートで first_only_legacy、no_rule、本当の 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

JSONNote で settings、context、監査ログをどう検証するか

  1. 1
    Schema で context 形状を固定

    開く JSON Schema。1欄に評価 context schema、2欄に policyMode layered と matchedPolicies サンプル。required や minItems 誤りを先に表示し、形状错误で allow や deny を返さない。

  2. 2
    フォーマットで tool 呼び出しを確認

    開く JSON フォーマット。Write allow または Bash ask 呼び出し JSON を貼る。tool キーを確認し、layered_policy_gate が ask か allow か照合する。

  3. 3
    Diff で allow と ask 監査ログを比較

    開く JSON Diff。左に Write allow サンプル、右に Bash ask サンプル。Diff で光る winningPolicyId が SIEM 帰属の键行。

よくある質問

layered と旧 first-match-only の違いは?

旧版は最初の一致 policy で止まり監査は 1 id のみ。2.1.316 layered は全一致を appliedPolicyIds に書き order でマージし reason layered_merge と winningPolicyId を残す。gateway が layered でなければ first_only_legacy。

layered とローカル permission policy ファイルの関係は?

gateway 托管 settings の layered マージはクラウド評価 pipeline で行われ matchedPolicies と監査フィールドを出す。ローカル permission policy は Agent 默认や离线对照用だが id と order は自動一致しない。managed settings、評価 context、ローカル policy JSON を別々に検証する。

下層 deny は上層 ask で上書きできるか?

はい。layered マージは order で下から上へ叠层し、同 tool の最終 effect は最上位の命中 rule。Bash で下層 deny・上層 ask なら decision ask、winningPolicyId は上層。上層に rule が無ければ下層 deny のまま。

SIEM は winningPolicyId をどう帰属に使うか?

appliedPolicyIds はマージ参加の全序、winningPolicyId は最終層。SIEM は winningPolicyId で owner と变更单に結び、appliedPolicyIds 長で first-match-only 残存を検知。reason layered_merge のとき decision allow deny ask を集計。

本篇は攻击や bypass 教程か?

いいえ。2.1.316 layered マージの JSON 形状、監査ログ、layered_policy_gate 分岐の説明のみで、运维と合规の検証と SIEM 帰属を助ける。多層 ask や deny で管控を bypass する手順は書かない。

まとめ

Claude Code 2.1.316 は Claude apps gateway で managed.policy_mode layered により複数 policy をマージし、監査 JSON に appliedPolicyIds、decision、reason layered_merge、winningPolicyId を書く。托管 settings、評価 context、Write と Bash 呼び出し、監査ログの四样本で layered_policy_gate を動かし最初の policy id だけを見ない。

結論:context が schema 不通 → schema。policyMode が layered でなければ not_layered。settings が first なら first_only_legacy。tool 规则なし no_rule。マージ後 allow deny ask は監査 reason layered_merge と winningPolicyId に合わせる。

次:Schema で context と settings を固定し、Write と Bash 呼び出しをフォーマットで見て、allow と ask 監査ログを Diff し、layered_policy_gate 戻り値をレポートで分ける。

ブログへ戻る