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
15 changes: 12 additions & 3 deletions integrations/isolation/acq-kits/validate-kits.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,18 @@ def validate_kit(kit_dir: Path, schema: dict) -> tuple[list[str], list[str]]:
f"(got {type(c['background']).__name__})"
)

# Best-effort commands[] ↔ files[] consistency: a kit-owned payload path
# (e.g. /home/agent/foo.sh) referenced by a command but dropped by no
# files[] entry is almost always a re-home typo. WARN, don't ERROR: this is
# caps.network.tier field-level check (#300). Optional; when present it MUST
# be one of the neutral egress tiers. Omission is valid and means the default
# `balanced` posture (documented in the schema; not mutated here). The schema
# enum already rejects bad values — this adds a clearer, kit-scoped message.
_net = (spec.get("caps") or {}).get("network") or {}
if "tier" in _net and _net["tier"] not in ("strict", "balanced", "open"):
errors.append(
f"{kit_dir.name}: caps.network.tier must be one of "
f"strict|balanced|open (got {_net['tier']!r})"
)


# a heuristic (a command could legitimately create a script at runtime), so
# it flags for human eyes rather than failing the gate outright.
dropped = {f.get("path") for f in files if f.get("path")}
Expand Down
6 changes: 6 additions & 0 deletions schemas/kit-hybrid-v1.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@
"type": "object",
"additionalProperties": false,
"properties": {
"tier": {
"type": "string",
"enum": ["strict", "balanced", "open"],
"default": "balanced",
"description": "Neutral egress posture, mapped by each backend to its native primitive. ALL tiers are deny-by-default; the tier only sizes the baseline allowlist. `strict` = no baseline (kit `allow` only); `balanced` = a curated shared baseline ∪ the kit `allow` (the default when unspecified); `open` = unrestricted egress (testing only, never for GFE). The effective allowlist is: tier baseline ∪ this kit's `allow` ∪ per-sandbox additions."
},
"allow": {
"type": "array",
"description": "Outbound egress allow-list. Hostnames, optionally with :port (default 443). Each backend maps these to its native egress primitive.",
Expand Down
123 changes: 123 additions & 0 deletions scripts/tests/test_validate_kits_network_tier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""Tests for the neutral network.tier egress vocabulary (#300).

`caps.network.tier` (enum strict|balanced|open) is the neutral egress posture in
the hybrid/v1 kit schema. All tiers are deny-by-default; the tier only sizes the
baseline allowlist. Omission is valid and means the default `balanced` posture
(documented, not mutated). This locks in the schema enum + the validator's
field-level message + the coexistence with the existing `caps.network.allow`.
"""

from __future__ import annotations

import importlib.util
import json
from pathlib import Path

import jsonschema
import pytest

ROOT = Path(__file__).resolve().parents[2]
SCHEMA_PATH = ROOT / "schemas" / "kit-hybrid-v1.schema.json"
VALIDATOR_PATH = ROOT / "integrations" / "isolation" / "acq-kits" / "validate-kits.py"


def _load_schema() -> dict:
return json.loads(SCHEMA_PATH.read_text())


def _load_validate_kit():
spec = importlib.util.spec_from_file_location("validate_kits", VALIDATOR_PATH)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod.validate_kit


def _base(**extra) -> dict:
d = {
"schemaVersion": "hybrid/v1",
"kind": "mixin",
"name": "t",
"displayName": "T",
"description": "d",
}
d.update(extra)
return d


def _write_kit(kit_dir: Path, spec_yaml: str) -> Path:
kit_dir.mkdir(parents=True, exist_ok=True)
(kit_dir / "README.md").write_text("# t\n")
(kit_dir / "spec.yaml").write_text(spec_yaml)
return kit_dir


# --- schema-level -------------------------------------------------------------


class TestNetworkTierSchema:
def test_absent_tier_still_valid(self):
# Omission is valid → the default `balanced` posture (documented).
jsonschema.validate(instance=_base(), schema=_load_schema())

def test_absent_network_block_still_valid(self):
jsonschema.validate(instance=_base(), schema=_load_schema())

@pytest.mark.parametrize("tier", ["strict", "balanced", "open"])
def test_valid_tiers_accepted(self, tier):
inst = _base(caps={"network": {"tier": tier}})
jsonschema.validate(instance=inst, schema=_load_schema()) # must not raise

def test_tier_coexists_with_allow(self):
inst = _base(caps={"network": {"tier": "balanced", "allow": ["api.example.gov"]}})
jsonschema.validate(instance=inst, schema=_load_schema()) # must not raise

def test_bad_tier_rejected(self):
inst = _base(caps={"network": {"tier": "wideopen"}})
with pytest.raises(jsonschema.ValidationError):
jsonschema.validate(instance=inst, schema=_load_schema())

def test_tier_wrong_type_rejected(self):
inst = _base(caps={"network": {"tier": 1}})
with pytest.raises(jsonschema.ValidationError):
jsonschema.validate(instance=inst, schema=_load_schema())

def test_typo_key_on_network_rejected(self):
# additionalProperties:false on caps.network — a mis-spelled `teir` fails.
inst = _base(caps={"network": {"teir": "balanced"}})
with pytest.raises(jsonschema.ValidationError):
jsonschema.validate(instance=inst, schema=_load_schema())


# --- validator field-level message --------------------------------------------


class TestNetworkTierValidator:
def test_valid_tier_kit_passes(self, tmp_path):
validate_kit = _load_validate_kit()
kit = _write_kit(
tmp_path / "k",
"schemaVersion: hybrid/v1\nkind: mixin\nname: t\ndisplayName: T\n"
"description: d\ncaps:\n network:\n tier: strict\n",
)
errors, _ = validate_kit(kit, _load_schema())
assert not any("tier" in e for e in errors)

def test_absent_tier_kit_passes(self, tmp_path):
validate_kit = _load_validate_kit()
kit = _write_kit(
tmp_path / "k",
"schemaVersion: hybrid/v1\nkind: mixin\nname: t\ndisplayName: T\n"
"description: d\ncaps:\n network:\n allow:\n - api.example.gov\n",
)
errors, _ = validate_kit(kit, _load_schema())
assert not any("tier" in e for e in errors)

def test_bad_tier_flagged_with_message(self, tmp_path):
validate_kit = _load_validate_kit()
kit = _write_kit(
tmp_path / "k",
"schemaVersion: hybrid/v1\nkind: mixin\nname: t\ndisplayName: T\n"
"description: d\ncaps:\n network:\n tier: wideopen\n",
)
errors, _ = validate_kit(kit, _load_schema())
assert any("network.tier" in e and "strict|balanced|open" in e for e in errors)