fix: sync drifted policies.cedar copies, restore unknown-MCP-tool deny - #1596
Conversation
The repo carries 4 copies of the base Cedar policy set. src/policies.cedar
(the one PolicyEngine::init("policies.cedar") actually loads at gateway
runtime/test time) and helm/aegis-gateway/files/policies.cedar had silently
drifted from the canonical root policies.cedar, missing the
"mcp-unknown-tool-forbid" rule entirely. This silently downgraded the
fail-closed default for any unregistered MCP tool from deny to
require_approval (via the critical-risk-requires-approval override), which
is exactly what
routes::authorize::tests::authorize_denies_unknown_mcp_tools_by_default and
authorize_denies_unknown_mcp_tool_with_encoded_or_cased_identifier were
catching — these were failing on Gateway (beta)/(1.88) CI on every PR,
unrelated to whatever that PR's own diff touched.
Syncs both stale copies to the canonical content and adds
policies_cedar_copies_stay_byte_identical (src/src/routes/policy.rs) so
future drift fails CI immediately with a clear cause, instead of as two
opaque test failures days or weeks later.
Also includes 36 round-trip tests (lib/policy/src/compiler.rs) and rustfmt
fixes for the new test code carried over from #1328's branch, since this
fix was found while investigating that PR's CI run.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR. |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughAdds an early deny rule for unknown MCP tool calls in the Cedar policy copies, adds a byte-equality regression test for deployed policy copies, and reformats policy compiler test inputs. ChangesMCP tool policy gating
Policy compiler test reflow
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request synchronizes the mcp-unknown-tool-forbid rule across the various copies of the Cedar policy files in the repository and adds a unit test to ensure they remain identical. Feedback suggests normalizing line endings in the new test to prevent failures on Windows environments due to CRLF/LF differences.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| fn policies_cedar_copies_stay_byte_identical() { | ||
| let manifest_dir = env!("CARGO_MANIFEST_DIR"); | ||
| let canonical = std::fs::read_to_string(format!("{manifest_dir}/../policies.cedar")) | ||
| .expect("root policies.cedar must exist"); | ||
|
|
||
| for (label, path) in [ | ||
| ("src/policies.cedar", format!("{manifest_dir}/policies.cedar")), | ||
| ( | ||
| "lib/policy/policies.cedar", | ||
| format!("{manifest_dir}/../lib/policy/policies.cedar"), | ||
| ), | ||
| ( | ||
| "helm/aegis-gateway/files/policies.cedar", | ||
| format!("{manifest_dir}/../helm/aegis-gateway/files/policies.cedar"), | ||
| ), | ||
| ] { | ||
| let copy = std::fs::read_to_string(&path) | ||
| .unwrap_or_else(|e| panic!("{label} must exist at {path}: {e}")); | ||
| assert_eq!( | ||
| copy, canonical, | ||
| "{label} has drifted from the canonical root policies.cedar — \ | ||
| every copy must be byte-identical, since the gateway loads \ | ||
| {label} at runtime/test time, not the root copy" | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
Comparing files directly using std::fs::read_to_string without normalizing line endings can cause the test to fail on Windows or other environments where Git's core.autocrlf or different editors might checkout/save files with CRLF (\r\n) instead of LF (\n). Normalizing the line endings to \n before asserting equality makes the test robust across different platforms.
fn policies_cedar_copies_stay_byte_identical() {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let canonical = std::fs::read_to_string(format!("{manifest_dir}/../policies.cedar"))
.expect("root policies.cedar must exist")
.replace("\r\n", "\n");
for (label, path) in [
("src/policies.cedar", format!("{manifest_dir}/policies.cedar")),
(
"lib/policy/policies.cedar",
format!("{manifest_dir}/../lib/policy/policies.cedar"),
),
(
"helm/aegis-gateway/files/policies.cedar",
format!("{manifest_dir}/../helm/aegis-gateway/files/policies.cedar"),
),
] {
let copy = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("{label} must exist at {path}: {e}"))
.replace("\r\n", "\n");
assert_eq!(
copy,
canonical,
"{label} has drifted from the canonical root policies.cedar — \
every copy must be identical, since the gateway loads \
{label} at runtime/test time, not the root copy"
);
}
}…eny rule PR #1596 correctly restored the mcp-unknown-tool-forbid Cedar rule (synced src/ and helm/ policies.cedar copies to the canonical root), but this broke 53 tests that relied on never-registered fixture tool/action pairs (filesystem/read_file, github/read_issue, github/push_commit, github/read_file, github/delete_branch, quarantine_canary/trigger) silently falling through to a permissive default instead of hitting a named permit rule. Forbid always wins over permit, so once unknown tools are correctly denied, those fixtures need to be registered like every other test action. - Add register_default_test_fixtures(), called once from setup_state_with_events_capacity, registering the generic fixture tool/action pairs as known skill actions with default_decision: "policy" so Cedar's own rules still govern the actual decision. - Tag the mcp-unknown-tool-forbid rule with @id("mcp_unknown_tool") and surface it via PolicyEngine::authorize's matched_policies, since Cedar's auto-numbered policy ids ("policy0", ...) gave the two originally-targeted regression tests no way to assert which rule fired.
…eny rule (#1597) * fix: sync drifted policies.cedar copies, restore unknown-MCP-tool deny The repo carries 4 copies of the base Cedar policy set. src/policies.cedar (the one PolicyEngine::init("policies.cedar") actually loads at gateway runtime/test time) and helm/aegis-gateway/files/policies.cedar had silently drifted from the canonical root policies.cedar, missing the "mcp-unknown-tool-forbid" rule entirely. This silently downgraded the fail-closed default for any unregistered MCP tool from deny to require_approval (via the critical-risk-requires-approval override), which is exactly what routes::authorize::tests::authorize_denies_unknown_mcp_tools_by_default and authorize_denies_unknown_mcp_tool_with_encoded_or_cased_identifier were catching — these were failing on Gateway (beta)/(1.88) CI on every PR, unrelated to whatever that PR's own diff touched. Syncs both stale copies to the canonical content and adds policies_cedar_copies_stay_byte_identical (src/src/routes/policy.rs) so future drift fails CI immediately with a clear cause, instead of as two opaque test failures days or weeks later. Also includes 36 round-trip tests (lib/policy/src/compiler.rs) and rustfmt fixes for the new test code carried over from #1328's branch, since this fix was found while investigating that PR's CI run. * fix(gateway): repair fixture fallout from restored unknown-MCP-tool deny rule PR #1596 correctly restored the mcp-unknown-tool-forbid Cedar rule (synced src/ and helm/ policies.cedar copies to the canonical root), but this broke 53 tests that relied on never-registered fixture tool/action pairs (filesystem/read_file, github/read_issue, github/push_commit, github/read_file, github/delete_branch, quarantine_canary/trigger) silently falling through to a permissive default instead of hitting a named permit rule. Forbid always wins over permit, so once unknown tools are correctly denied, those fixtures need to be registered like every other test action. - Add register_default_test_fixtures(), called once from setup_state_with_events_capacity, registering the generic fixture tool/action pairs as known skill actions with default_decision: "policy" so Cedar's own rules still govern the actual decision. - Tag the mcp-unknown-tool-forbid rule with @id("mcp_unknown_tool") and surface it via PolicyEngine::authorize's matched_policies, since Cedar's auto-numbered policy ids ("policy0", ...) gave the two originally-targeted regression tests no way to assert which rule fired.
Summary
Root-caused the
Gateway (beta)/Gateway (1.88)CI failures that have been showing up red on every PR in this repo regardless of what that PR touched.The repo carries 4 copies of the base Cedar policy set:
policies.cedar(root) — canonicallib/policy/policies.cedar— already in sync with rootsrc/policies.cedar— the onePolicyEngine::init("policies.cedar")actually loads at gateway runtime/test timehelm/aegis-gateway/files/policies.cedar— Helm deployment copysrc/policies.cedarand the Helm copy had silently drifted from the canonical root file, missing themcp-unknown-tool-forbidrule entirely. This downgraded the fail-closed default for any unregistered MCP tool fromdenytorequire_approval(via the critical-risk-requires-approval override) — exactly whatauthorize_denies_unknown_mcp_tools_by_defaultandauthorize_denies_unknown_mcp_tool_with_encoded_or_cased_identifierwere catching on every CI run.(Production Docker deployments were unaffected —
docker-compose.ymlbuild context is the repo root and also bind-mounts the rootpolicies.cedarat runtime — but everycargo test --workspacerun, and any barecargo run/dev invocation pointed at the wrong copy, were exposed to this.)Changes
src/policies.cedarandhelm/aegis-gateway/files/policies.cedarto the canonical root content.policies_cedar_copies_stay_byte_identical(src/src/routes/policy.rs) so future drift between the 4 copies fails CI immediately and obviously, instead of as opaque test failures days later.Test plan
src/policies.cedarwas missing the forbid rule present in rootpolicies.cedarandlib/policy/policies.cedar(byte-diff).cargo testlocally — no Rust toolchain in this environment) — expectGateway (beta)/Gateway (1.88)to go green;Gateway (stable)'scargo fmt --checkwill still show pre-existing, unrelated drift in files this PR doesn't touch (cedar.rs,decisions.rs,playbooks.rs,tenant.rs,tenant_bloom.rs,lib.rsmod ordering) — flagging separately, not fixed here.Summary by CodeRabbit
New Features
Tests