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
|
actions. The implementation lives under `infra/safety-cases` and reuses the
|
||||||
same impact rules as `./jeannie impact`.
|
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
|
## Deploying
|
||||||
|
|
||||||
From the Debian server:
|
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
|
actions. The implementation lives under `infra/safety-cases` and reuses the
|
||||||
same impact rules as `./{{ main_script }} impact`.
|
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
|
## Deploying
|
||||||
|
|
||||||
From the Debian server:
|
From the Debian server:
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,10 @@ explicitly approved.
|
||||||
: Generate a read-only safety case for a Git diff or explicit paths, including
|
: Generate a read-only safety case for a Git diff or explicit paths, including
|
||||||
risk, blast radius, required validation, rollback, and forbidden actions.
|
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`
|
`scorecard`
|
||||||
: Roll up backup readiness, GitOps health, DNS/RPi health, cluster health, edge
|
: Roll up backup readiness, GitOps health, DNS/RPi health, cluster health, edge
|
||||||
health, capacity pressure, security posture, public certificates, inventory, and
|
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}"
|
"${REPO_ROOT}/scripts/safety-case" "${@:2}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
agent_sandbox() {
|
||||||
|
"${REPO_ROOT}/scripts/agent-sandbox" "${@:2}"
|
||||||
|
}
|
||||||
|
|
||||||
validate_homelab() {
|
validate_homelab() {
|
||||||
"${REPO_ROOT}/scripts/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.
|
recover-power [--dry-run] Run or preview post-outage recovery.
|
||||||
incident {list|replay|triage} Classify incident text and suggest read-only next steps.
|
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.
|
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.
|
impact [--since REF] [PATH...] Explain affected lab areas before changes.
|
||||||
review-last-change [REF] Summarize changed files, impact, validation.
|
review-last-change [REF] Summarize changed files, impact, validation.
|
||||||
map [--dot] Print the homelab dependency map.
|
map [--dot] Print the homelab dependency map.
|
||||||
|
|
@ -6700,6 +6705,9 @@ case "${1:-}" in
|
||||||
safety-case)
|
safety-case)
|
||||||
safety_case "$@"
|
safety_case "$@"
|
||||||
;;
|
;;
|
||||||
|
agent-sandbox)
|
||||||
|
agent_sandbox "$@"
|
||||||
|
;;
|
||||||
scorecard)
|
scorecard)
|
||||||
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() {
|
is_read_only_command() {
|
||||||
case "$1" in
|
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
|
return 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue