feat(agent): isolate from erik — dedicated overlord-agent user

The agent service was running as User=erik, which meant:
- Sessions polluted erik's ~/.claude/projects/
- erik's .claude/settings.local.json (months of accumulated dev permissions
  for docker/git/dotnet/etc.) was loaded by the production agent, defeating
  the --allowed-tools whitelist
- Subscription rate quota mingled between human-erik's interactive Claude
  Code use and the production assistant
- Theoretical access to /home/erik/.ssh, .bash_history, .gitconfig

Now:
- User=overlord-agent (system account, no shell, /var/lib/overlord-agent home)
- HOME=/var/lib/overlord-agent — claude state fully isolated from erik
- /home/erik/.claude permissions tightened to 0700 (was 0755)
- group=overlord-agent on the repo + /etc/overlord/agent.env (read-only)

Project settings:
- New strict committed .claude/settings.json: deny Bash/Read/Write/Edit/
  Glob/Grep/NotebookEdit/WebSearch; allow only WebFetch(domain:acpedia.org)
- .claude/settings.local.json now gitignored (was leaking dev permissions
  to the server through the deploy)
This commit is contained in:
Erik 2026-04-25 21:50:57 +02:00
parent 49ae4369e0
commit f894399165
3 changed files with 42 additions and 14 deletions

19
.claude/settings.json Normal file
View file

@ -0,0 +1,19 @@
{
"permissions": {
"allow": [
"WebFetch(domain:acpedia.org)"
],
"deny": [
"Bash",
"Write",
"Edit",
"Read",
"Glob",
"Grep",
"NotebookEdit",
"WebSearch"
],
"ask": []
},
"enableAllProjectMcpServers": true
}

5
.gitignore vendored
View file

@ -2,3 +2,8 @@
__pycache__ __pycache__
static/v2/ static/v2/
frontend/node_modules/ frontend/node_modules/
# Claude Code per-machine permissions (do NOT deploy to server — production
# agent must run with the strict permissions in committed .claude/settings.json)
.claude/settings.local.json
.claude/settings.local.json.*

View file

@ -5,18 +5,23 @@ Wants=network-online.target
[Service] [Service]
Type=simple Type=simple
User=erik # Dedicated unprivileged user — kernel-level isolation from `erik`.
Group=erik # overlord-agent has NO access to /home/erik/.claude (mode 0700),
# Working directory MUST be the repo root so: # /home/erik/.ssh, /home/erik/.bash_history, /home/erik/.gitconfig, etc.
# - claude -p sessions land at ~/.claude/projects/-home-erik-MosswartOverlord/ # Its own claude state lives at /var/lib/overlord-agent/.claude/ and its
# - .mcp.json is auto-loaded # claude session JSONLs land there — completely separate from any
# interactive Claude Code use by the human user.
User=overlord-agent
Group=overlord-agent
# Working directory: the repo root (group-readable to overlord-agent).
# claude session JSONLs path-encode this cwd so it's important to keep
# stable across restarts.
WorkingDirectory=/home/erik/MosswartOverlord WorkingDirectory=/home/erik/MosswartOverlord
# Secrets moved OUT of /home/erik/ to /etc/overlord/agent.env so # HOME explicitly set so claude reads /var/lib/overlord-agent/.claude/*
# ProtectHome=read-only blocks their read entirely. The file is # instead of trying /home/erik/.claude/* (which is now 0700, locked out).
# root-owned, mode 0640, group=erik. Environment="HOME=/var/lib/overlord-agent"
# Secrets file (root:overlord-agent 0640).
EnvironmentFile=-/etc/overlord/agent.env EnvironmentFile=-/etc/overlord/agent.env
# Backwards-compat: also try the old location during transition.
EnvironmentFile=-/home/erik/MosswartOverlord/.env
# Run inside the venv populated by install.sh. # Run inside the venv populated by install.sh.
ExecStart=/home/erik/MosswartOverlord/agent/.venv/bin/python -m agent.service ExecStart=/home/erik/MosswartOverlord/agent/.venv/bin/python -m agent.service
Restart=on-failure Restart=on-failure
@ -37,12 +42,11 @@ ProtectHome=read-only
# Allow writing only to the explicit paths claude / our service need. # Allow writing only to the explicit paths claude / our service need.
# - ~/.claude — session JSONL files # - ~/.claude — session JSONL files
# - .venv pycache — minor pip cache writes # - .venv pycache — minor pip cache writes
ReadWritePaths=/home/erik/.claude ReadWritePaths=/var/lib/overlord-agent/.claude
ReadWritePaths=/home/erik/MosswartOverlord/agent/.venv ReadWritePaths=/home/erik/MosswartOverlord/agent/.venv
ReadWritePaths=/var/log/overlord-agent ReadWritePaths=/var/log/overlord-agent
# Keep $HOME visible to the venv python so it can find pip cache etc. # StateDirectory creates/owns /var/lib/overlord-agent automatically.
# (read-only via ProtectHome=read-only — this writable carve-out is StateDirectory=overlord-agent
# narrowly the .claude session dir above.)
LogsDirectory=overlord-agent LogsDirectory=overlord-agent
LogsDirectoryMode=0755 LogsDirectoryMode=0755
PrivateTmp=true PrivateTmp=true