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

  1. Never commit .env.secrets — add to .gitignore project-wide
  2. Rotate on breach — all virtual keys, not just the exposed one
  3. Scope by role — worker .env.secrets must not contain provider API keys
  4. Audit accessauditd rule on every node watching .env.secrets reads
# 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

ScriptReasonSafer Alternative
scripts/restart-stack.shdocker compose restartCreate a docker group user
scripts/snapshot.shWrite to /var/backups/Pre-create writable backup dir
scripts/tailscale-up.shtailscale up requires rootUse 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.secrets is in .gitignore and not in any git history
  • Workers hold only virtual keys, not provider API keys
  • Tailscale ACL is deny-by-default (see Multi-Node)
  • mcp-scan is enabled and CVE database is reachable
  • plugin-eval signature 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
  • auditd watches .env.secrets reads

Next Steps