Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 47 additions & 3 deletions docs/openshell-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,53 @@ Defaults follow the pinned [OpenShell v0.1.2 authored schema](https://github.com
and [conversion code](https://github.com/NVIDIA/OpenShell/blob/v0.1.2/crates/openshell-policy/src/lib.rs).
The [upstream schema reference](https://docs.nvidia.com/openshell/how-it-works/policies/schema)
describes runtime constraints beyond document inventory. This reader is not a
substitute for upstream policy validation. Semantic expansion/subset review,
Git dependency identity, gate integration, local composition and native proof
are separate implementation stages (#944–#948).
substitute for upstream policy validation. Git dependency identity, gate
integration, local composition and native proof are separate implementation
stages (#945–#948).

## Conservative declared-authority comparison

The shared host comparator reads normalized policy facts. It describes
declared authority, without establishing runtime enforcement or whether a
named file, executable or destination exists. JSON and Markdown use the same
before/after rows, expansion signals and explanations. `diff` publishes no
merge verdict. Mixed changes retain their proven expansions and narrowings;
their single overall direction remains unknown.

| Surface | Supported direction proof | Unproven cases |
|---|---|---|
| Filesystem | Exact canonical absolute read/write path sets; read-write also grants read; duplicates are neutral | Omitted filesystem, changed workdir inclusion, ancestor overlap, noncanonical/wildcard paths, edits to runtime-baseline paths while network policy is present |
| Landlock | `hard_requirement` to explicit/omitted `best_effort` weakens the compatibility requirement; reverse strengthens it | Actual kernel support or applied rules |
| Process | Unchanged identity is neutral | Changed identity, image user/group resolution and driver defaults |
| Network L4 | Exact binary × hostname × port selection without address/transport/request options | Wildcards, DNS/IP reach, executable resolution, credential options and endpoint path routing |
| REST | Exact method/path matchers; method `*`; read-only/read-write/full presets; compatible overlapping allows with deny precedence | Request-path globs or omitted paths, query constraints, encoded-slash changes and protocol/credential options |
| Other protocols | Unchanged normalized facts are neutral | Changes to MCP, GraphQL, WebSocket, JSON-RPC or middleware semantics |

For REST, a finite partition includes every literal method/path on either side
and an additional class for all other values. It evaluates the effective union
of matching allows minus matching denials, keeping each binary/destination
relationship. An unchanged covering grant makes an added grant redundant.
Each possible combination of up to eight exact binary selectors is evaluated
per destination, because a process may match both its own path and ancestor
paths. A denial or inspected rule can therefore affect grants from another
matching binary selector.
Removing one of two covering denials does not expand the allowed set. A matching
inspected rule suppresses request access from uninspected rules. Conflicting
enforcement modes in overlapping inspected endpoints are unproven.

Removing `enforcement: enforce` restores upstream `audit`, which permits
well-formed requests that violate request rules. Malformed requests and runtime
transport checks are outside this comparison. A full-access endpoint without
denials already permits the compared request domain, so that transition alone
is neutral. Named rules, collection order, duplicates and explicit spelling of
an unchanged default are not authority. Adding/removing an entire selected
document remains unknown because it establishes no replacement runtime policy.

Comparison stops with a named unknown result beyond eight exact binary selectors
per destination or 100,000 network reference
or request-partition cells. Unsupported semantics never become inferred safe
narrowing. An HTTP method or MCP tool name still supplies no business effect,
approval, argument restriction or deployed agent binding.

## Read limits and coverage

Expand Down
10 changes: 10 additions & 0 deletions src/agents_shipgate/core/capability_diff_rows.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
step_action_key,
)
from agents_shipgate.core.host_settings import rate_claude_setting, setting_value_text
from agents_shipgate.core.openshell_compare import compare_openshell_grants
from agents_shipgate.core.permission_lattice import (
exec_equivalent_argument,
permission_pairing_group,
Expand Down Expand Up @@ -593,6 +594,11 @@ def _grant_value(
if not grant:
return ABSENT
kind = str(grant.get("kind") or "")
if kind == "openshell_policy":
facts = grant["facts"]
policy = facts["policy"]
endpoints = sum(len(rule["endpoints"]) for rule in policy["network_policies"].values())
return f"{facts['role']}, OpenShell {facts['runtime_version']}, {endpoints} endpoint(s), facts {grant['config_sha256']}"
if kind == "permission_rule" and redact_permission_arguments:
from agents_shipgate.core.host_boundary import _safe_rule

Expand Down Expand Up @@ -1564,6 +1570,10 @@ def capability_diff_rows(
agent_reasons=agent_reasons,
)
kind = grant.get("kind")
if kind == "openshell_policy":
comparison = compare_openshell_grants(before_grant, after_grant)
direction = comparison.direction if comparison.direction in {"widened", "narrowed"} else CHANGED
why = comparison.explanation + "; declared policy only; runtime enforcement and freshness are unverified"
if (
kind == "plugin_or_app" and after_grant is not None
and not str(after_grant.get("name", "")).startswith("marketplace:")
Expand Down
14 changes: 13 additions & 1 deletion src/agents_shipgate/core/host_grants.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
parse_selection,
policy_field_paths,
)
from agents_shipgate.core.openshell_compare import compare_openshell_grants
from agents_shipgate.core.permission_lattice import (
exec_equivalent_argument,
permission_pairing_group,
Expand Down Expand Up @@ -6013,9 +6014,13 @@ def diff_host_grants(baseline: dict[str, Any], current: dict[str, Any]) -> list[
for grant_id in sorted(set(base_by_id) | set(current_by_id)):
before = base_by_id.get(grant_id)
after = current_by_id.get(grant_id)
same_openshell = bool(
before and after and before.get("kind") == after.get("kind") == "openshell_policy"
and compare_openshell_grants(before, after).direction == "equivalent"
)
if compared_grant(before) != compared_grant(after) and not _same_workflow_grant(
before, after
):
) and not same_openshell:
changes.append({"grant_id": grant_id, "baseline": before, "current": after})
return changes

Expand Down Expand Up @@ -6351,6 +6356,8 @@ def host_grant_direction_unknown(
"""

before, after = change.get("baseline"), change.get("current")
if (after or before or {}).get("kind") == "openshell_policy":
return compare_openshell_grants(before, after).direction in {"unknown", "mixed"}
if after is None or (before is not None and before.get("config_sha256") == after.get("config_sha256")):
return False
if host_grant_expansion_signals([change], comparison_changes=comparison_changes):
Expand Down Expand Up @@ -6383,6 +6390,11 @@ def host_grant_expansion_signals(
for change in changes:
before = change.get("baseline")
after = change.get("current")
if (after or before or {}).get("kind") == "openshell_policy":
for reason in compare_openshell_grants(before, after).widened:
grant = after or before
signals.append(f"openshell_authority_expanded: {grant['source']}: {reason}")
continue
if after is None:
if before and before.get("kind") == "permission_rule" and before.get("disposition") in {"deny", "ask"}:
signals.append(f"{before['disposition']}_rule_removed: {before['host']}:{before['rule']}")
Expand Down
Loading
Loading