fix(policy): prevent implicit authorization inheritance - #2499
Conversation
johntmyers
left a comment
There was a problem hiding this comment.
gator-agent
PR Review Status
Validation: This PR is project-valid because it addresses the concrete fail-open policy-merge defect documented in #2497 and preserves OpenShell's default-deny authorization boundary.
Head SHA: 5e825768d55ea469e5e56d5816a9d6df50cf763c
Review findings:
- One critical and three warning findings are attached inline and require author changes.
- Warning —
crates/openshell-policy/src/merge.rs:648: The documented remediation to put partial authorization in a separate rule cannot work for an overlapping endpoint because the unchanged overlap fallback folds the newrule_nameinto the existing rule, where inheritance validation rejects it. This line is outside the current diff, so it cannot be anchored inline. Preserve the requested key when folding would cause an inheritance conflict, or provide an explicit no-fold path, with an end-to-end regression covering a new binary granted only one endpoint from an existing multi-endpoint rule. - Warning — agent workflow drift: This changes policy-update behavior, but
.agents/skills/openshell-cli/SKILL.mdstill recommends incremental--add-endpointupdates without explaining the whole-product requirement or when full YAML replacement is needed. Review and update it andgenerate-sandbox-policyusing thesync-agent-inframaintenance map.
Docs: The relevant Fern policy page is updated; its folder is already navigation-managed, so docs/index.yml does not need a new entry.
E2E: test:e2e will be required after the review findings are resolved because this changes policy-enforcement behavior.
Next state: gator:in-review
| || !ports_cover(loaded, proposed) | ||
| || !protocols_match(&loaded.protocol, &proposed.protocol) | ||
| || effective_tls(&loaded.tls) != effective_tls(&proposed.tls) | ||
| || effective_enforcement(&loaded.enforcement) |
There was a problem hiding this comment.
gator-agent
Critical: merge_endpoint treats an empty incoming enforcement as “retain the existing value,” but coverage converts it to effective audit and requires equality. Adding a rule to an existing enforce endpoint can therefore succeed while policy.local /wait never observes coverage and times out. Treat omitted retained fields as unspecified during coverage, or normalize/reject them consistently during merge, and add a regression with an enforce endpoint plus an incoming proposal that omits enforcement.
| && rule | ||
| .endpoints | ||
| .iter() | ||
| .any(|endpoint| endpoint_authorization_covers(endpoint, target_endpoint)) |
There was a problem hiding this comment.
gator-agent
Warning: This requires one loaded endpoint to cover every proposed port. A proposed [443, 8443] endpoint can be fully covered by two loaded rules for the same binary, one per port, yet still be reported uncovered despite the documented rule-union semantics. Evaluate atomic host/path/port pairs across the loaded union and add a split-port coverage test.
| )?; | ||
|
|
||
| existing_rule.endpoints = merged_endpoints; | ||
| append_unique_binaries(&mut existing_rule.binaries, &incoming_rule.binaries); |
There was a problem hiding this comment.
gator-agent
Warning: Elsewhere this change defines an empty incoming binary list as any-binary, but appending an empty list leaves an existing restricted scope unchanged. An any-binary AddRule folded into a restricted rule therefore succeeds without applying its requested scope, and coverage can never become true. Either promote the merged scope to any-binary after validating all endpoints or retain the proposal as a separate rule; add a regression test.
| && !incoming_rule | ||
| .endpoints | ||
| .iter() | ||
| .any(|declared| endpoint_authorization_covers(declared, endpoint)) |
There was a problem hiding this comment.
gator-agent
Warning: A full declaration may be split across incoming endpoints. If the existing endpoint covers [443, 8443] and the new binary declares separate [443] and [8443] endpoints, no single declaration covers the merged endpoint, so this rejects a complete Cartesian declaration. Aggregate incoming authorization by atomic endpoint/port pairs before validating, and test this shape.
|
This guards the This is admin-only, not agent-reachable — |
johntmyers
left a comment
There was a problem hiding this comment.
gator-agent
PR Review Status
Validation: This PR remains project-valid because it fixes the fail-open policy-merge defect in #2497.
Head SHA: 9c80c5b81a485fd98b8904843b219b26bc3910b8
Review findings:
- Two critical authorization-boundary findings and one warning are attached inline and require author changes.
- The six findings from the prior-head gator review are resolved for their reported scenarios; the new findings cover distinct multi-port, cross-endpoint MCP-contract, and any-binary cases.
Docs: The Fern policy page and routed agent skills are updated; no navigation change is needed.
E2E: test:e2e remains required for this policy-enforcement change, but it will be applied only after blocking review findings are resolved.
Next state: gator:in-review
| .any(|existing| authorization_unit_unchanged(existing, endpoint, *port)) | ||
| }) | ||
| .collect(); | ||
| if !changed_ports.is_empty() && !undeclared_binaries.is_empty() { |
There was a problem hiding this comment.
gator-agent
Critical (CWE-863): changed_ports is rejected only when an existing binary is missing from the request. With an existing [443, 8443] endpoint carrying GET, an AddRule that lists every existing binary but declares only port 443 with POST merges POST into the shared endpoint and authorizes it on undeclared port 8443. Require each changed port to be covered by the incoming declarations independently of binary-scope coverage, or split the endpoint before applying port-local changes. Add a regression covering L7 rules and other widened fields on multi-port endpoints.
| .unwrap_or(0); | ||
|
|
||
| let promotes_l4_to_mcp = promotes_l4_endpoint_to_mcp(existing, incoming); | ||
| ensure_mcp_contract_compatible(existing, incoming, operation_index, &host, port)?; |
There was a problem hiding this comment.
gator-agent
Critical (CWE-693): MCP compatibility is checked only after path-aware endpoint folding. A second MCP endpoint on the same host/port with another path is appended without validation, and provider-composed rules are excluded from overlap folding. Runtime selects one matching extended configuration by host/port, so conflicting strict-tool-name, method-profile, or body-limit contracts can be selected unpredictably. Enforce one effective MCP contract per host/port across the final effective policy, including provider-composed rules, and add same-rule, cross-rule, and provider-composition regressions.
| // declare every merged endpoint before the widening is allowed. | ||
| existing_rule.binaries.clear(); | ||
| } else { | ||
| append_unique_binaries(&mut existing_rule.binaries, &incoming_rule.binaries); |
There was a problem hiding this comment.
gator-agent
Warning: An empty existing binary list means any binary, but appending an incoming named list makes the list non-empty and silently narrows the rule. An additive AddRule can therefore revoke access for every other process, including through overlap fallback under a new rule name. Preserve an already-empty scope when incoming binaries are named; only clear a restricted scope when the incoming scope is empty. Add same-key and overlap-fallback regressions.
johntmyers
left a comment
There was a problem hiding this comment.
gator-agent
PR Review Status
Thanks @shiju-nv. I checked the latest author delta against the prior multi-port, MCP-contract, and any-binary obligations. The any-binary path is resolved for the reported scenario, and the two carried Critical proposals were downgraded by evidence normalization and are not being republished as blockers. One new, machine-validated Warning remains: the new undeclared-port error bypasses the separate-rule fallback and reintroduces the previously resolved narrow-update failure for multi-port endpoints.
Validation: This PR remains project-valid because it addresses the fail-open policy-merge defect in #2497.
Head SHA: b23bb432d8673c9b6f3ba22688eef92c8bee7f06
Base SHA: 39bf94e528a58da6e9112b5b2278c2a1957b29e3
Merge base SHA: 39bf94e528a58da6e9112b5b2278c2a1957b29e3
Patch ID: 53c34d612935f3d776bad27e35d15fff974787e9
Gator payload: 2
Review mode: follow_up
Previous reviewed SHA: 9c80c5b81a485fd98b8904843b219b26bc3910b8
Blocking findings:
GATOR-b23bb432-01: The newUndeclaredPortWouldChangeresult is not recognized as a fold-only authorization conflict, so a safe differently named narrow rule is rejected instead of being retained separately. The inline comment contains the complete reproducer and requested fix.
Carried findings:
- None republished. The two carried Critical proposals normalized as non-blocking hypotheses; the existing threads remain the durable history.
Non-blocking suggestions: omitted on follow-up.
Docs: The Fern policy page and routed agent skills remain updated; no navigation change is needed.
E2E: test:e2e remains required for this policy-enforcement change after blocking review feedback is resolved.
Next state: gator:in-review
| .filter(|port| !operation_names_port(incoming_rule, endpoint, *port)) | ||
| .collect(); | ||
| if !undeclared_changed_ports.is_empty() { | ||
| return Err(PolicyMergeError::UndeclaredPortWouldChange { |
There was a problem hiding this comment.
gator-agent
Warning — GATOR-b23bb432-01
Invariant: The endpoint-overlap fallback must retain the requested rule name whenever folding alone creates a scope conflict that disappears when the incoming authorization remains separate.
Prerequisite: An operator uses a distinct rule name to add a capability for only a subset of binaries on one port of an existing multi-port endpoint.
Entry point → sink: UpdateConfig.merge_operations / openshell policy update --rule-name → add_rule attempts convenience folding and returns UndeclaredPortWouldChange instead of inserting the requested separate rule.
Base → head: At the previous reviewed head, the attempted fold reached ExistingBinariesWouldInheritAuthorization; the fallback recognized that conflict and inserted the requested narrow rule. This delta introduces UndeclaredPortWouldChange first, but is_authorization_inheritance_conflict does not include the new variant, so the safe fallback is bypassed. This materially invalidates the prior resolution.
Impact: A supported, safe incremental update fails with FailedPrecondition even though retaining the requested rule would express exactly the requested binary, endpoint, port, and capability scope. The documented narrow-update remediation is therefore false for multi-port endpoints.
Reproducer: Start with rule broad for binaries [/usr/bin/a, /usr/bin/b], with one REST endpoint api.example.com on ports [443, 8443] allowing GET. Submit differently named rule narrow for /usr/bin/a on api.example.com:443 adding POST. The candidate fold changes undeclared port 8443 and returns UndeclaredPortWouldChange; because that variant is not classified as fold-only, narrow is never inserted separately.
PR ownership: This latest delta introduces the new error and places it before the prior existing-binary conflict without routing it through the fallback added by this PR.
Requested change: Include UndeclaredPortWouldChange in the fold-only authorization-conflict classification so a differently named rule remains separate; same-key updates should still return the error. Add a regression for a subset of existing binaries on one port of an existing multi-port rule.
b23bb43 to
4d7cf50
Compare
johntmyers
left a comment
There was a problem hiding this comment.
gator-agent
PR Review Status
Thanks @shiju-nv. I checked your August 3 author update against the prior narrow-update obligation. GATOR-b23bb432-01 is resolved: UndeclaredPortWouldChange is now included in the exhaustive fold-only classifier, with regressions for both differently named and same-key updates. The same delta introduces one new, machine-validated Warning by broadening the MCP-only conflict check to all L7 protocols without preserving supported path disambiguation.
Validation: This PR remains project-valid because it addresses the fail-open policy-merge defect in #2497.
Head SHA: 4d7cf50f8413b66df52928b9b5dd9a0e4c9039d1
Base SHA: fde96f0488d09d34472521d0dd59bdf20c5bdf61
Merge base SHA: 1959ea19be386a2a917c2b070e3d396f422bc7f2
Patch ID: a9b4e4259d6dfa3560563f84d682147e894b3e50
Gator payload: 2
Review mode: follow_up
Previous reviewed SHA: b23bb432d8673c9b6f3ba22688eef92c8bee7f06
Blocking findings:
GATOR-4d7cf50f-01: The new host/port-only L7 contract comparison rejects path-disambiguated protocols that the runtime and effective-policy validator support. The inline comment contains the complete reproducer and requested fix.
Carried findings:
GATOR-b23bb432-01: Resolved on this head.
Docs: The existing Fern docs are present, but the latest delta's host/port-only wording is part of GATOR-4d7cf50f-01 because it contradicts the documented and implemented path-aware routing contract.
E2E: test:e2e remains required for this policy-enforcement change after blocking review feedback is resolved.
Next state: gator:in-review
| continue; | ||
| } | ||
| let inspection = EffectiveInspection { | ||
| protocol: endpoint.protocol.to_ascii_lowercase(), |
There was a problem hiding this comment.
gator-agent
Warning — GATOR-4d7cf50f-01
Invariant: Incremental policy merges must permit path-disambiguated L7 protocols that the runtime and effective-policy validator support; protocol conflicts should be rejected only when endpoint selectors are actually ambiguous.
Prerequisite: An operator or approved policy.local proposal adds a more-specific inspected endpoint with a different protocol on a host and port already covered by a broader inspected endpoint.
Entry point → sink: UpdateConfig.merge_operations / approved policy proposal → merge_policy calls ensure_no_new_inspection_conflicts, which returns ConflictingInspectionContracts and is mapped to FAILED_PRECONDITION.
Base → head: At d863617e, the post-merge scan compared only MCP option contracts, so REST on /** plus GraphQL on /graphql passed merge and runtime path selection chose the most-specific endpoint. This head keys EffectiveInspection only by host and port and treats the protocol difference as a conflict, ignoring path specificity.
Impact: Valid incremental updates and approved proposals fail even though equivalent full policies are supported and documented; the new Fern wording also contradicts the path-aware contract and examples.
Reproducer: Start with one binary authorized to api.github.com:443, path /**, protocol rest. Submit AddRule for the same rule and binary with api.github.com:443, path /graphql, protocol graphql. The previous tree accepts the merge and runtime selects GraphQL only for /graphql; this head returns ConflictingInspectionContracts.
PR ownership: Commit 4d7cf50f replaces the MCP-only scan with this all-protocol host/port comparison and adds the conflicting documentation. The undeclared-port classifier fix does not require that broader behavior change.
Requested change: Keep UndeclaredPortWouldChange in the exhaustive fold-only classifier, but revert the unrelated all-protocol restriction or make conflict detection use the same path-specificity and ambiguity rules as effective-policy validation. Restore the path-aware docs and add a REST /** plus GraphQL /graphql merge regression.
A network rule authorizes every listed binary to reach every listed endpoint, so unioning an AddRule operation's binaries and endpoints independently grants binary-by-endpoint pairs the operation never declared. Require AddRule to declare the complete product before merging, and reject an operation that would give one host and port two different MCP inspection contracts. The rejection names the binaries the operation still has to declare. Fix proposal coverage on the same surface. An any-binary proposal was vacuously covered by a binary-restricted loaded rule, and a complete product split across several loaded rules was reported as uncovered. Coverage compares merge-widened endpoint fields by containment and exact-matches only the fields the merge never widens, so a policy the gateway just merged always reads back as covered and the sandbox policy.local /wait long-poll cannot spin to its deadline. Signed-off-by: Shiju <shiju@nvidia.com>
Coverage treated an unset proposal value for a field the merge retains as a request for the default, so a proposal that merged cleanly into an endpoint carrying enforcement, protocol, or tls read back as uncovered and left the policy.local /wait long poll spinning. Unset now means unspecified. Ports are a set on the wire but each port is an independent authorization. Coverage and the inheritance check both resolve one binary, host, path, and port at a time, so ports spread across loaded rules resolve and a complete declaration split across incoming endpoints is accepted. An incoming empty binary list means any binary. It now has to declare every merged endpoint like a new concrete path does, and once declared the promotion is applied instead of appending an empty list and leaving the restricted scope in place. The endpoint-overlap fallback folded a new rule name into an existing rule where inheritance validation then rejected it, leaving no way to grant a binary part of a rule. Folding now keeps the requested rule name when it would widen, and reports that it did. An MCP contract conflict still propagates because one host and port carry a single inspection contract. AddAllowRules and AddDenyRules select an endpoint by host and port alone and now reject a target that resolves more than once, including two paths on one rule. RemoveBinary rejects an any-binary rule rather than reporting a success that leaves the binary authorized. Signed-off-by: Shiju <shiju@nvidia.com>
…ary scope The changed-port check only rejected when the operation also left an existing binary undeclared. An operation listing every existing binary could therefore declare one port of a multi-port endpoint and have its widened fields land on the endpoint the merge shares across all of them, authorizing L7 rules on a port it never named. Each changed port must now be named by the operation on its own, independently of binary-scope coverage. MCP contract compatibility was checked inside the endpoint fold, which only compares endpoints agreeing on host, path, and a shared port. The sandbox resolves one extended configuration per host and port and never consults the path, so a second MCP endpoint under another path, or in another rule, left the effective strict-tool-name, method-profile, and body-limit contract decided by match order. Contract agreement is now enforced across the whole merged policy, including provider-composed rules. A conflict already present in the baseline is left alone so unrelated updates still apply. An empty binary list authorizes any binary. Appending an incoming named list made it non-empty and revoked every process the operation did not name, turning an additive update into a silent mass revocation. An already-empty scope is now kept and reported; only a restricted scope is replaced by an incoming any-binary scope. Warnings raised during a fold now name the rule that was actually modified rather than the rule name the operation requested, which differ when the endpoint-overlap fallback redirects the operation. Signed-off-by: Shiju <shiju@nvidia.com>
…e fallback The fold-only classifier decides which merge errors disappear when the incoming authorization stays on its own rule. UndeclaredPortWouldChange was added ahead of the existing-binary conflict but never classified, so a differently named narrow update against a multi-port endpoint failed outright instead of landing separately. A same-key update still returns the error, because there the operation chose the target. The classifier is now an exhaustive match rather than a matches! with an implicit false. A new variant defaulting to "not fold-only" is what withdrew the separate-rule remedy here, so adding one has to be an explicit decision. Inspection-contract agreement now covers protocol, not only MCP options. The sandbox resolves one extended configuration per host and port and never consults the path, so an MCP endpoint and a REST endpoint on the same host and port left the effective inspection protocol decided by match order. Endpoints with no protocol carry no contract and are skipped. Signed-off-by: Shiju <shiju@nvidia.com>
…icts The post-merge conflict scan was broadened to compare inspection protocol as well as MCP options on one host and port. The supervisor selects among matching endpoint configs by most-specific path, so a broad REST endpoint and a narrower GraphQL endpoint on the same host and port are unambiguous and supported. The broader comparison rejected those updates even though the equivalent full policy loads and serves correctly. MCP options are not selected that way, so the scan keeps comparing them: two MCP endpoints on one host and port still have to agree on strict tool names, method profile, and body limit, whatever paths or rules hold them. The policy page returns to describing the MCP-specific rule and the path-aware selection it sits alongside. Signed-off-by: Shiju <shiju@nvidia.com>
184bb71 to
37ee1ba
Compare
johntmyers
left a comment
There was a problem hiding this comment.
gator-agent
Review Convergence Checkpoint
Thanks @shiju-nv. I checked your August 3 update that narrows the inspection-conflict scan back to MCP contracts. It resolves GATOR-4d7cf50f-01 for the reported REST/GraphQL path-disambiguation case, but the same delta introduces the Critical mixed REST/MCP authorization bypass attached inline.
Head SHA: 184bb719cefed4be1f04563e39825808ce003f51
Base SHA: fde96f0488d09d34472521d0dd59bdf20c5bdf61
Merge base SHA: 1959ea19be386a2a917c2b070e3d396f422bc7f2
Patch ID: c3456c1522b2c8b3e04f130518109569dacdb0bf
Gator payload: 2
Review mode: human_checkpoint
Previous reviewed SHA: 4d7cf50f8413b66df52928b9b5dd9a0e4c9039d1
Three finding-bearing review rounds have completed. The new Critical introduced by this author delta bypasses the checkpoint once; Gator returns to the checkpoint after reporting it.
Root-cause findings:
- The initial Cartesian authorization, proposal-coverage, any-binary, and effective-field families were resolved or normalized as non-blocking in prior dispositions.
GATOR-b23bb432-01: resolved by the exhaustive fold-only classifier and its narrow-rule regressions.GATOR-4d7cf50f-01: resolved for non-MCP path-disambiguated protocols on this head.GATOR-184bb719-01: open Critical; a broader REST endpoint can authorize a request selected for a restrictive MCP endpoint.
Scope growth:
- Remediation has expanded from
AddRuleCartesian-product validation into cross-rule and provider-composed inspection-contract validation plus runtime path-selection and L7 authorization semantics.
Reviewer-quality signals:
- The ledger records three duplicate finding-ID occurrences and one finding repeated across review heads. The current bounded reviewer proposed one latest-delta Critical, with no duplicate-invariant or unchanged-code proposals. These are Gator telemetry, not author defects.
Maintainer action: after GATOR-184bb719-01 is addressed, accept the current scope, split follow-up work, waive an obligation, or explicitly authorize another autonomous review round. test:e2e remains required for this policy-enforcement change after the checkpoint is released.
Next state: gator:blocked (review_convergence_checkpoint)
| // narrower GraphQL endpoint on one host and port are unambiguous | ||
| // and supported. MCP options are not path-selected, so two MCP | ||
| // endpoints on one host and port stay ambiguous. | ||
| let Some(mcp) = effective_mcp_contract(endpoint) else { |
There was a problem hiding this comment.
gator-agent
Critical — GATOR-184bb719-01 (CWE-863)
Invariant: An MCP endpoint's method and tool authorization must remain authoritative for requests routed to it; a broader non-MCP endpoint on the same host and port must not authorize requests that the selected MCP endpoint rejects.
Prerequisite: An operator applies a supported incremental update that places a restrictive MCP endpoint under a path already covered by a broader REST endpoint for the same sandbox binary, after which that sandboxed process sends requests to the destination.
Entry point → sink: UpdateConfig.merge_operations (openshell policy update or an approved policy.local AddRule) → the supervisor selects the MCP parser for the narrower path, but sandbox-policy.rego evaluates authorization as a union of every path-matching endpoint; the broader REST POST rule can make allow_request true and relay a tool call the MCP endpoint did not authorize.
Base → head: The previous reviewed head included protocol in EffectiveInspection and rejected mixed MCP/REST endpoints on one host and port. This delta skips every non-MCP endpoint. REST /** plus MCP /mcp now passes merge and effective-policy ambiguity validation because the selectors have different specificity, even though REST authorization still participates for /mcp.
Impact: A sandboxed process can invoke an MCP method or tool excluded by the MCP endpoint's allow rules, and the request is forwarded upstream when the broader REST rule permits POST. This bypasses the policy's MCP authorization boundary.
Reproducer: Create an enforce-mode rule for /usr/bin/agent with REST svc.example.com:443, path /**, allowing POST /**. Submit AddRule for the same binary and host/port with path /mcp, protocol mcp, and only one tools/call rule for params.name=safe. This head accepts the merge. Evaluate POST /mcp with JSON-RPC method tools/call and params.name=forbidden: route selection chooses MCP, its rule does not match, but the broader REST endpoint makes allow_request true and the relay forwards the request. The previous reviewed head rejects the merge with ConflictingInspectionContracts.
PR ownership: This author delta removes protocol from EffectiveInspection, skips non-MCP endpoints, and deletes the mixed MCP/REST rejection regression while resolving GATOR-4d7cf50f-01, newly permitting this policy shape.
Requested change: Preserve path-disambiguated combinations where neither endpoint is MCP, but record all inspected protocols and reject a host/port when MCP coexists with a different inspected protocol; continue comparing effective contracts when both endpoints are MCP. Add a regression with REST /** plus restrictive MCP /mcp that must fail merge.
Sibling sites: The same bypass is reachable across separate rules and when one endpoint comes from a provider-composed rule.
Review Convergence CheckpointThanks @shiju-nv. I checked your August 3 update that rejects mixed MCP/non-MCP inspection on one host and port. It resolves Head SHA: Three finding-bearing review rounds have completed. Root-cause findings:
Scope growth:
Reviewer-quality signals:
Maintainer action: accept the current scope, split follow-up work, waive an obligation, or explicitly authorize another autonomous review round. Next state: |
48007a8 to
4596776
Compare
Narrowing the conflict scan back to MCP options let an MCP endpoint sit under a path already covered by a broader REST endpoint. The supervisor picks the parser by most-specific path, so the MCP endpoint parses the request, but _policy_allows_l7 is existential over every endpoint matching it. A plain REST rule on the overlapping path can therefore make allow_request true for a JSON-RPC tool call the MCP endpoint never allowed, and the relay forwards it. The scan now records every inspected protocol on a host and port. MCP may not share one with a differently inspected endpoint, and two MCP endpoints there still have to agree on one contract. Endpoints that are not inspected carry no contract and never compete, and two non-MCP endpoints stay supported because they share one method-and-path rule vocabulary. Signed-off-by: Shiju <shiju@nvidia.com>
4596776 to
1eaa5cd
Compare
Summary
Make policy merging fail closed when an
AddRuleoperation would implicitly widen authorization. One network rule authorizes the Cartesian product of its binaries and endpoints, so every resulting pair must be declared before the merge, and proposal coverage must confirm that the loaded policy contains the whole proposed product.Related Issue
#2497
Changes
AddRuleendpoint scopes in request order while preserving atomic failure through the private merge copy.Testing
mise run pre-commitpassesChecklist
docs/sandboxes/policies.mdxdocuments the declare-the-whole-scope requirement and the new rejection reasons foropenshell policy update