Add local agent sandbox policy
This commit is contained in:
parent
c7956638b3
commit
0b886295c8
12
README.md
12
README.md
|
|
@ -206,6 +206,18 @@ It summarizes risk, blast radius, required validation, rollback, and forbidden
|
|||
actions. The implementation lives under `infra/safety-cases` and reuses the
|
||||
same impact rules as `./jeannie impact`.
|
||||
|
||||
## Agent Sandbox
|
||||
|
||||
Future Jeannie AI actions should be checked before execution. The local sandbox
|
||||
policy starts with a read-only command classifier:
|
||||
|
||||
```bash
|
||||
./jeannie agent-sandbox check "./jeannie status"
|
||||
./jeannie agent-sandbox check "./jeannie nuke"
|
||||
```
|
||||
|
||||
Policy is stored in `infra/agent-sandbox/policy.tsv`.
|
||||
|
||||
## Deploying
|
||||
|
||||
From the Debian server:
|
||||
|
|
|
|||
|
|
@ -206,6 +206,18 @@ It summarizes risk, blast radius, required validation, rollback, and forbidden
|
|||
actions. The implementation lives under `infra/safety-cases` and reuses the
|
||||
same impact rules as `./{{ main_script }} impact`.
|
||||
|
||||
## Agent Sandbox
|
||||
|
||||
Future Jeannie AI actions should be checked before execution. The local sandbox
|
||||
policy starts with a read-only command classifier:
|
||||
|
||||
```bash
|
||||
./{{ main_script }} agent-sandbox check "./{{ main_script }} status"
|
||||
./{{ main_script }} agent-sandbox check "./{{ main_script }} nuke"
|
||||
```
|
||||
|
||||
Policy is stored in `infra/agent-sandbox/policy.tsv`.
|
||||
|
||||
## Deploying
|
||||
|
||||
From the Debian server:
|
||||
|
|
|
|||
|
|
@ -96,6 +96,10 @@ explicitly approved.
|
|||
: Generate a read-only safety case for a Git diff or explicit paths, including
|
||||
risk, blast radius, required validation, rollback, and forbidden actions.
|
||||
|
||||
`agent-sandbox {check|policy}`
|
||||
: Classify proposed commands against repo-managed agent action policy. The first
|
||||
version is read-only and reports `allow`, `approval`, or `deny`.
|
||||
|
||||
`scorecard`
|
||||
: Roll up backup readiness, GitOps health, DNS/RPi health, cluster health, edge
|
||||
health, capacity pressure, security posture, public certificates, inventory, and
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
# Local Agent Sandbox
|
||||
|
||||
The agent sandbox is a command policy layer for future Jeannie AI actions. The
|
||||
first implementation is read-only: it classifies proposed commands, explains why
|
||||
they are allowed or blocked, and writes no state.
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
./jeannie agent-sandbox check "./jeannie status"
|
||||
./jeannie agent-sandbox check "./jeannie nuke"
|
||||
```
|
||||
|
||||
Policy lives in `infra/agent-sandbox/policy.tsv`.
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
pattern decision reason
|
||||
./jeannie status allow Read-only health report.
|
||||
./jeannie doctor- allow Read-only focused doctor command.
|
||||
./jeannie capacity allow Read-only capacity report.
|
||||
./jeannie validate allow Read-only repository validation.
|
||||
./jeannie safety-case allow Read-only change safety review.
|
||||
./jeannie incident allow Read-only incident triage/replay.
|
||||
./jeannie ai-evals allow Read-only eval harness.
|
||||
./jeannie prompt-injection-lab allow Read-only security lab harness.
|
||||
./jeannie up approval High-blast-radius deployment.
|
||||
./jeannie apps approval Application deployment can mutate cluster state.
|
||||
./jeannie rpi-services approval Mutates RPi Docker services.
|
||||
./jeannie nuke deny Destructive cluster state path.
|
||||
tofu destroy deny Destructive infrastructure path.
|
||||
terraform destroy deny Destructive infrastructure path.
|
||||
rm -rf deny Destructive file deletion pattern.
|
||||
kubectl delete approval Kubernetes deletion needs explicit scope and approval.
|
||||
docker rm approval Docker deletion needs explicit scope and approval.
|
||||
|
||||
|
8
jeannie
8
jeannie
|
|
@ -6334,6 +6334,10 @@ safety_case() {
|
|||
"${REPO_ROOT}/scripts/safety-case" "${@:2}"
|
||||
}
|
||||
|
||||
agent_sandbox() {
|
||||
"${REPO_ROOT}/scripts/agent-sandbox" "${@:2}"
|
||||
}
|
||||
|
||||
validate_homelab() {
|
||||
"${REPO_ROOT}/scripts/validate-homelab"
|
||||
}
|
||||
|
|
@ -6529,6 +6533,7 @@ Operator View And Reports
|
|||
recover-power [--dry-run] Run or preview post-outage recovery.
|
||||
incident {list|replay|triage} Classify incident text and suggest read-only next steps.
|
||||
safety-case [--since REF] [PATH...] Generate risk, blast-radius, test, and rollback case.
|
||||
agent-sandbox {check|policy} Classify proposed commands against agent policy.
|
||||
impact [--since REF] [PATH...] Explain affected lab areas before changes.
|
||||
review-last-change [REF] Summarize changed files, impact, validation.
|
||||
map [--dot] Print the homelab dependency map.
|
||||
|
|
@ -6700,6 +6705,9 @@ case "${1:-}" in
|
|||
safety-case)
|
||||
safety_case "$@"
|
||||
;;
|
||||
agent-sandbox)
|
||||
agent_sandbox "$@"
|
||||
;;
|
||||
scorecard)
|
||||
scorecard
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Classify proposed agent commands against the homelab sandbox policy."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
POLICY_FILE = REPO_ROOT / "infra" / "agent-sandbox" / "policy.tsv"
|
||||
|
||||
|
||||
def load_policy() -> list[dict[str, str]]:
|
||||
with POLICY_FILE.open(encoding="utf-8", newline="") as handle:
|
||||
return list(csv.DictReader(handle, delimiter="\t"))
|
||||
|
||||
|
||||
def classify(command: str) -> dict[str, str]:
|
||||
normalized = " ".join(command.strip().split())
|
||||
best: dict[str, str] | None = None
|
||||
for rule in load_policy():
|
||||
pattern = rule["pattern"]
|
||||
if pattern in normalized:
|
||||
if best is None or len(pattern) > len(best["pattern"]):
|
||||
best = rule
|
||||
if best is None:
|
||||
return {
|
||||
"command": normalized,
|
||||
"decision": "approval",
|
||||
"pattern": "default",
|
||||
"reason": "Unknown command; require explicit approval before execution.",
|
||||
}
|
||||
return {
|
||||
"command": normalized,
|
||||
"decision": best["decision"],
|
||||
"pattern": best["pattern"],
|
||||
"reason": best["reason"],
|
||||
}
|
||||
|
||||
|
||||
def print_result(result: dict[str, str]) -> int:
|
||||
print("Agent Sandbox")
|
||||
print("=============")
|
||||
print(f"command: {result['command']}")
|
||||
print(f"decision: {result['decision']}")
|
||||
print(f"matched: {result['pattern']}")
|
||||
print(f"reason: {result['reason']}")
|
||||
if result["decision"] == "allow":
|
||||
return 0
|
||||
if result["decision"] == "approval":
|
||||
return 2
|
||||
return 3
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
check_parser = subparsers.add_parser("check")
|
||||
check_parser.add_argument("candidate", nargs="+")
|
||||
check_parser.add_argument("--json", action="store_true")
|
||||
subparsers.add_parser("policy")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "policy":
|
||||
for rule in load_policy():
|
||||
print(f"{rule['decision']:8} {rule['pattern']} - {rule['reason']}")
|
||||
return 0
|
||||
if args.command == "check":
|
||||
result = classify(" ".join(args.candidate))
|
||||
if args.json:
|
||||
print(json.dumps(result, indent=2, sort_keys=True))
|
||||
return 0 if result["decision"] == "allow" else 2
|
||||
return print_result(result)
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -34,7 +34,7 @@ EOF
|
|||
|
||||
is_read_only_command() {
|
||||
case "$1" in
|
||||
status|capacity|capacity-advisor|capacity-limits|recover-plan|incident|safety-case|scorecard|gitops-status|cert-check|backup-status|synthetic-checks|resource-budget|route-inventory|golden-ledger|workers|change-journal|map|validate|access-audit|doctor-versions|doctor-edge|doctor-gitea|doctor-rpi|doctor-cluster|preflight|doctor-preapply|inventory-check|secrets-check|tailnet-policy-check|ai-check|ask|ai-evals|impact|review-last-change|security-scan|security-host|security-trivy|security-secrets|security-nuclei|security-web|security-logs|security-attack-path|prompt-injection-lab|control-plane)
|
||||
status|capacity|capacity-advisor|capacity-limits|recover-plan|incident|safety-case|agent-sandbox|scorecard|gitops-status|cert-check|backup-status|synthetic-checks|resource-budget|route-inventory|golden-ledger|workers|change-journal|map|validate|access-audit|doctor-versions|doctor-edge|doctor-gitea|doctor-rpi|doctor-cluster|preflight|doctor-preapply|inventory-check|secrets-check|tailnet-policy-check|ai-check|ask|ai-evals|impact|review-last-change|security-scan|security-host|security-trivy|security-secrets|security-nuclei|security-web|security-logs|security-attack-path|prompt-injection-lab|control-plane)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
|
|
|
|||
Loading…
Reference in New Issue