ブログ • 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 分岐のみを書く。
- 托管 settings で managed.policy_mode は layered 必須。first や未設定で gateway が旧路径なら layered_policy_gate は first_only_legacy か not_layered を返し、layered_merge の winningPolicyId を書かない。
- 評価 context の matchedPolicies 各要素に id、order、rules(tool と effect allow、deny、ask)。マージ後 Write は allow、Bash は下層 deny・上層 ask なら deny ではなく ask。
- 監査ログに appliedPolicyIds 全序、decision allow deny ask、reason layered_merge、最終層の winningPolicyId。SIEM は最初の id だけでなく winningPolicyId で帰属する。
- tool に rule が無いとき layered_policy_gate は no_rule。context に policyMode が無いか matchedPolicies 形状错误ならマージ前に schema。
先にこれだけ: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 路径。
{
"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 を上書きする。
{
"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。
{
"$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 と照合。
{
"tool": "Write",
"path": "/srv/repo/src/app.ts"
}
{
"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。
{
"tool": "Bash",
"policyMode": "layered",
"appliedPolicyIds": ["org-baseline", "repo-contributors"],
"decision": "ask",
"reason": "layered_merge",
"winningPolicyId": "repo-contributors"
}
| ログまたは呼び出し現象 | layered_policy_gate 戻り | 運用対応 |
|---|---|---|
| Write 各層 allow、reason layered_merge | allow | ログ decision allow、winningPolicyId は最高 order。 |
| Bash 下層 deny・上層 ask、reason layered_merge | ask | ログ decision ask、winningPolicyId は上層 ask policy id。 |
| マージ後最終 effect deny、ask 上書きなし | deny | ログ decision deny、appliedPolicyIds は全序のまま。 |
| settings が first のまままたは layered 未启用 | first_only_legacy | reason 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 を分ける。
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、監査ログをどう検証するか
- 1Schema で context 形状を固定
開く JSON Schema。1欄に評価 context schema、2欄に policyMode layered と matchedPolicies サンプル。required や minItems 誤りを先に表示し、形状错误で allow や deny を返さない。
- 2フォーマットで tool 呼び出しを確認
開く JSON フォーマット。Write allow または Bash ask 呼び出し JSON を貼る。tool キーを確認し、layered_policy_gate が ask か allow か照合する。
- 3Diff で 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 戻り値をレポートで分ける。