Security Gates
godotz.ai’s security model is fail-closed: anything not explicitly permitted is denied. This guide covers the four layers of the security gate chain — supply chain verification, secret management, privilege controls, and API key scoping.
1. The Security Gate Chain
Every plugin execution and tool call passes through three gates before running:
Plugin / Tool Call Request
↓
[Gate 1: plugin-eval] ← static analysis + signature verification
↓ PASS
[Gate 2: mcp-scan] ← CVE database check (CVE-2025-6514 class)
↓ PASS
[Gate 3: sandbox] ← read-only FS, network allowlist, CPU/RAM cap
↓ PASS
Execute
Any gate can block. Block events fire an ntfy urgent alert.
# config/security.yml
security_gates:
plugin_eval:
enabled: true
require_signature: true
signature_key: "/etc/omp/plugin-signing.pub"
block_unsigned: true
mcp_scan:
enabled: true
cve_database: "https://mcp-scan.omp-team.dev/cve-db"
update_interval: 6h
block_on_cve: true
sandbox:
enabled: true
filesystem: read_only
network:
allowlist:
- "100.64.0.0/10" # Tailscale CGNAT range
- "api.anthropic.com"
- "open.bigmodel.cn"
resource_limits:
cpu_cores: 2
ram_gb: 2
timeout_seconds: 300
2. .env.secrets Management
.env.secrets is the runtime secret store for Docker Compose stacks. It is flat, human-readable, and scoped to one node.
Rules
- Never commit
.env.secrets— add to.gitignoreproject-wide - Rotate on breach — all virtual keys, not just the exposed one
- Scope by role — worker
.env.secretsmust not contain provider API keys - Audit access —
auditdrule on every node watching.env.secretsreads
# Verify .gitignore contains both patterns
grep -q ".env.secrets" .gitignore && echo OK || echo MISSING
grep -q "*.secrets" .gitignore && echo OK || echo MISSING
# Verify no secrets are staged
git diff --cached --name-only | grep -E "(\.env|\.secrets)"
Control Plane vs Worker Split
Control plane .env.secrets holds all provider keys. Workers hold only virtual keys:
# Worker node .env.secrets — NO provider API keys here
LITELLM_VIRTUAL_KEY=sk-vk-glm-worker-...
NATS_PASSWORD=...
NODE_NAME=worker-01
TAILSCALE_AUTHKEY=tskey-auth-...
3. sops-nix Prep
.env.secrets is suitable for a single-operator setup. For team environments, migrate to sops-nix — secrets are age-encrypted in the repository, decrypted at boot by the node’s private key.
Migration Path
# Step 1: Install sops and age
nix-env -iA nixpkgs.sops nixpkgs.age
# Step 2: Generate a key per node
age-keygen -o /etc/omp/age-keys/worker-01.key
# Output: Public key: age1...
# Step 3: Create .sops.yaml at repo root
cat > .sops.yaml <<EOF
creation_rules:
- path_regex: secrets/.*\.yaml$
age:
- age1xyz... # control-01 public key
- age1abc... # worker-01 public key
- age1def... # operator key
EOF
# Step 4: Encrypt secrets
sops --encrypt secrets/control-01.yaml > secrets/control-01.enc.yaml
In the NixOS flake:
# flake.nix (excerpt)
inputs.sops-nix.url = "github:mic92/sops-nix";
# modules/secrets.nix
sops.defaultSopsFile = ./secrets/control-01.enc.yaml;
sops.secrets.anthropic_api_key = {};
sops.secrets.litellm_master_key = {};
Until sops-nix is in place, .env.secrets is acceptable. Do not block fleet setup waiting for it.
4. NOPASSWD Sudo Considerations
Some godotz.ai scripts request NOPASSWD sudo for service restarts. Understand the tradeoff before enabling.
What Requires It
| Script | Reason | Safer Alternative |
|---|---|---|
scripts/restart-stack.sh | docker compose restart | Create a docker group user |
scripts/snapshot.sh | Write to /var/backups/ | Pre-create writable backup dir |
scripts/tailscale-up.sh | tailscale up requires root | Use tailscaled socket auth |
Minimal NOPASSWD Entry
If NOPASSWD is needed, scope it to the exact commands — never grant blanket sudo:
# /etc/sudoers.d/omp-agent
omp-agent ALL=(ALL) NOPASSWD: /usr/bin/docker compose restart, /usr/bin/tailscale up --authkey=*
Preferred: systemd User Services
Run godotz.ai agent as a systemd user service that can manage its own scope without root:
# /etc/systemd/system/omp-agent.service
[Service]
User=omp-agent
Group=docker
ExecStart=/usr/local/bin/omp agent start
Restart=on-failure
Add omp-agent to the docker group and remove any NOPASSWD entry.
5. API Key Handling
Provider API keys are only held on the control plane inside LiteLLM Proxy. Workers receive virtual keys only.
Provider Key Flow:
ANTHROPIC_API_KEY → LiteLLM Proxy (control plane only)
↓
Virtual Key issued → Worker node
↓
Worker calls proxy → proxy calls provider
Key rotation procedure:
# 1. Generate new provider key in Anthropic console
# 2. Update control plane .env.secrets
nano /opt/omp-fleet/.env.secrets
# 3. Restart LiteLLM Proxy (zero-downtime reload)
docker compose exec litellm kill -HUP 1
# 4. Revoke old key in provider console
# 5. Audit Langfuse for any calls using old key
langfuse traces --filter api_key:sk-ant-OLD --last 72h
6. Security Checklist
Before going to production, verify each item:
-
.env.secretsis in.gitignoreand not in any git history - Workers hold only virtual keys, not provider API keys
- Tailscale ACL is deny-by-default (see Multi-Node)
-
mcp-scanis enabled and CVE database is reachable -
plugin-evalsignature verification is enabled - Budget limits are set on all virtual keys
- ntfy alerts fire on security gate blocks
- NOPASSWD sudo is either eliminated or scoped to exact commands
-
auditdwatches.env.secretsreads
Next Steps
- Model Routing — Virtual key scoping per role
- Hardcore Mode — Maximum security posture
- Fleet Setup — Integrate
.env.secretsinto Docker Compose