diff --git a/home/coding/agents/opencode.nix b/home/coding/agents/opencode.nix index 6abed49..5f13825 100644 --- a/home/coding/agents/opencode.nix +++ b/home/coding/agents/opencode.nix @@ -49,6 +49,150 @@ }; }; + # Security: permission hardening for OpenCode + # Last matching rule wins. Glob patterns: * = any chars, ? = single char. + # ~ and $HOME are expanded to the user's home directory. + # external_directory gates paths outside the working directory. + permission = { + # External directory access: ask by default, allow safe paths + "external_directory" = { + "*" = "ask"; + "/nix/store/**" = "allow"; + "/tmp/**" = "allow"; + }; + + # Read access: allow by default, deny sensitive paths + "read" = { + "*" = "allow"; + "~/.ssh/**" = "deny"; + "~/.gnupg/**" = "deny"; + "~/.aws/**" = "deny"; + "~/.kube/**" = "deny"; + "~/.config/gh/**" = "deny"; + "~/.config/gcloud/**" = "deny"; + "~/.config/op/**" = "deny"; + "~/.config/sops/**" = "deny"; + "/run/agenix/**" = "deny"; + "~/.pi/agent/auth.json" = "deny"; + "~/.pi/agent/sessions/**" = "deny"; + "*.env" = "deny"; + "*.env.*" = "deny"; + "*.pem" = "deny"; + "*.key" = "deny"; + "*.p12" = "deny"; + "*.pfx" = "deny"; + "*id_rsa*" = "deny"; + "*id_ed25519*" = "deny"; + "*id_ecdsa*" = "deny"; + "*.example.env" = "allow"; + "*.sample.env" = "allow"; + "*.test.env" = "allow"; + ".env.example" = "allow"; + ".env.sample" = "allow"; + ".env.test" = "allow"; + "~/.ssh/*.pub" = "allow"; + "*.pub" = "allow"; + "*.csr" = "allow"; + }; + + # Edit access: ask by default, deny sensitive paths + "edit" = { + "*" = "ask"; + "~/.ssh/**" = "deny"; + "~/.gnupg/**" = "deny"; + "~/.aws/**" = "deny"; + "~/.kube/**" = "deny"; + "~/.config/gh/**" = "deny"; + "~/.config/gcloud/**" = "deny"; + "~/.config/op/**" = "deny"; + "~/.config/sops/**" = "deny"; + "/run/agenix/**" = "deny"; + "~/.pi/agent/auth.json" = "deny"; + "~/.pi/agent/sessions/**" = "deny"; + "*.env" = "deny"; + "*.env.*" = "deny"; + "*.pem" = "deny"; + "*.key" = "deny"; + "*.p12" = "deny"; + "*.pfx" = "deny"; + "*id_rsa*" = "deny"; + "*id_ed25519*" = "deny"; + "*id_ecdsa*" = "deny"; + "~/.ssh/*.pub" = "allow"; + "*.pub" = "allow"; + "*.csr" = "allow"; + }; + + # Glob patterns: same rules as read for file matching + "glob" = { + "*" = "allow"; + "~/.ssh/**" = "deny"; + "~/.gnupg/**" = "deny"; + "/run/agenix/**" = "deny"; + "*.env" = "deny"; + "*.env.*" = "deny"; + "*.pem" = "deny"; + "*.key" = "deny"; + "*.p12" = "deny"; + "*.pfx" = "deny"; + }; + + # Grep: allow search, but deny searching for secrets + "grep" = { + "*" = "allow"; + "~/.ssh/**" = "deny"; + "~/.gnupg/**" = "deny"; + "/run/agenix/**" = "deny"; + "*PASSWORD*" = "ask"; + "*SECRET*" = "ask"; + "*API_KEY*" = "ask"; + "*PRIVATE_KEY*" = "ask"; + }; + + # Bash: ask by default, deny dangerous and env-leak commands + "bash" = { + "*" = "ask"; + "git status*" = "allow"; + "git diff*" = "allow"; + "git log*" = "allow"; + "git branch*" = "allow"; + "git show*" = "allow"; + "git remote*" = "allow"; + "nix --version" = "allow"; + "nix eval*" = "allow"; + "nix build*" = "allow"; + "nix develop*" = "allow"; + "nix shell*" = "allow"; + "nix search*" = "allow"; + "alejandra*" = "allow"; + "git add*" = "allow"; + "git commit*" = "allow"; + "git push*" = "ask"; + "git pull*" = "allow"; + "rm *" = "ask"; + "rm -rf *" = "deny"; + "sudo *" = "ask"; + "env" = "deny"; + "printenv" = "deny"; + "cat /proc/*/environ" = "deny"; + "gpg *--export-secret*" = "deny"; + "ssh-add -D" = "deny"; + "docker run --privileged*" = "deny"; + "curl *| *sh" = "deny"; + "wget *| *sh" = "deny"; + }; + + # Web fetch: ask for sensitive URLs + "webfetch" = { + "*" = "ask"; + "https://api.github.com*" = "allow"; + "https://search.nixos.org*" = "allow"; + }; + + # Doom loop guard + "doom_loop" = "ask"; + }; + # AZ-Gruppe LiteLLM endpoint + available models provider = { litellm = { diff --git a/home/coding/agents/pi.nix b/home/coding/agents/pi.nix index d08745a..2cada13 100644 --- a/home/coding/agents/pi.nix +++ b/home/coding/agents/pi.nix @@ -54,6 +54,216 @@ defaultThinkingLevel = "high"; }; + # pi-guardrails: strict security config + # NOTE: Path access checks are lexical (not symlink-safe). + # NOTE: Local project .pi/extensions/guardrails.json can override same rule IDs. + # For immutable global policies, consider a wrapper or upstream patch. + guardrails = { + enable = true; + config = { + enabled = true; + applyBuiltinDefaults = true; + + onboarding = { + completed = true; + }; + + features = { + policies = true; + permissionGate = true; + pathAccess = true; + }; + + pathAccess = { + mode = "ask"; + allowedPaths = [ + "/nix/store/" + "/tmp/" + ]; + }; + + policies = { + rules = [ + # ── SSH keys ─────────────────────────────────────────── + { + id = "home-ssh"; + enabled = true; + protection = "noAccess"; + onlyIfExists = false; + patterns = [ + {pattern = "~/.ssh/**";} + {pattern = "~/.ssh/*_rsa";} + {pattern = "~/.ssh/*_ed25519";} + {pattern = "~/.ssh/*.pem";} + ]; + allowedPatterns = [ + {pattern = "~/.ssh/*.pub";} + ]; + } + + # ── GPG keys ───────────────────────────────────────── + { + id = "home-gpg"; + enabled = true; + protection = "noAccess"; + onlyIfExists = false; + patterns = [ + {pattern = "~/.gnupg/**";} + {pattern = "~/*.gpg";} + {pattern = "~/.gpg-agent.conf";} + ]; + } + + # ── AWS credentials ──────────────────────────────────── + { + id = "home-aws"; + enabled = true; + protection = "noAccess"; + onlyIfExists = false; + patterns = [ + {pattern = "~/.aws/**";} + {pattern = "~/.aws/credentials";} + {pattern = "~/.aws/config";} + ]; + } + + # ── Kubernetes configs ──────────────────────────────── + { + id = "home-kube"; + enabled = true; + protection = "noAccess"; + onlyIfExists = false; + patterns = [ + {pattern = "~/.kube/**";} + {pattern = "*kubeconfig*";} + ]; + } + + # ── Cloud CLI configs ──────────────────────────────── + { + id = "home-config"; + enabled = true; + protection = "noAccess"; + onlyIfExists = false; + patterns = [ + {pattern = "~/.config/gh/**";} + {pattern = "~/.config/gcloud/**";} + {pattern = "~/.config/op/**";} + {pattern = "~/.config/sops/**";} + ]; + } + + # ── agenix secrets ─────────────────────────────────── + { + id = "agenix-secrets"; + enabled = true; + protection = "noAccess"; + onlyIfExists = false; + patterns = [ + {pattern = "/run/agenix/**";} + ]; + } + + # ── Pi auth and sessions ──────────────────────────── + { + id = "pi-auth-sessions"; + enabled = true; + protection = "noAccess"; + onlyIfExists = false; + patterns = [ + {pattern = "~/.pi/agent/auth.json";} + {pattern = "~/.pi/agent/sessions/**";} + ]; + } + + # ── Environment files ───────────────────────────────── + { + id = "secret-files"; + enabled = true; + protection = "noAccess"; + onlyIfExists = true; + patterns = [ + {pattern = ".env";} + {pattern = ".env.*";} + {pattern = ".dev.vars";} + ]; + allowedPatterns = [ + {pattern = "*.example.env";} + {pattern = "*.sample.env";} + {pattern = "*.test.env";} + {pattern = ".env.example";} + {pattern = ".env.sample";} + {pattern = ".env.test";} + ]; + } + + # ── Private keys and certificates ─────────────────── + { + id = "private-keys"; + enabled = true; + protection = "noAccess"; + onlyIfExists = false; + patterns = [ + {pattern = "*.pem";} + {pattern = "*.key";} + {pattern = "*.p12";} + {pattern = "*.pfx";} + {pattern = "*id_rsa*";} + {pattern = "*id_ed25519*";} + {pattern = "*id_ecdsa*";} + ]; + allowedPatterns = [ + {pattern = "*.pub";} + {pattern = "*.csr";} + ]; + } + ]; + }; + + permissionGate = { + explainCommands = false; + # Auto-deny patterns: env leakage and credential dumping + autoDenyPatterns = [ + { + pattern = "\\benv\\b"; + regex = true; + description = "env command (may dump environment)"; + } + { + pattern = "\\bprintenv\\b"; + regex = true; + description = "printenv command (dumps environment variables)"; + } + { + pattern = "/proc/[0-9]+/environ"; + regex = true; + description = "reading process environment files"; + } + { + pattern = "gpg\\s+--export-secret-keys"; + regex = true; + description = "GPG secret key export"; + } + { + pattern = "gpg\\s+--export-secret-subkeys"; + regex = true; + description = "GPG secret subkey export"; + } + { + pattern = "ssh-add\\s+-D"; + regex = true; + description = "delete all SSH identities"; + } + { + pattern = "\\b(op|pass)\\s+(read|show|get)"; + regex = true; + description = "password manager read operations"; + } + ]; + }; + }; + }; + # MCP servers auto-inherited from programs.mcp in default.nix }; }