Operations and Security
This page covers day-2 operations for a running AICR deployment: the admin dashboard and Prometheus metrics endpoint, admin authentication, the secret scrubber, the sandbox security model, the Podman/Docker socket threat model, backups, and upgrade / rollback.
Admin dashboard
Section titled “Admin dashboard”Set admin.username_env plus either admin.password_env or
admin.password_hash_env to enable the built-in dashboard. The dashboard is a
separate super-admin surface — it does not reuse webhook HMAC or trigger
API keys (see Authentication & secrets).
It pairs observability (statistics, live runs, events) with configuration
management (the Config tab).
| Route | Method | Purpose |
|---|---|---|
/dashboard and / |
GET | Serve the embedded SPA |
/api/admin/login |
POST | Returns a Bearer session token |
/api/admin/stats |
GET | All-time, today, this-week, this-month statistics, plus project / provider / recent-run data |
Admin auth
Section titled “Admin auth”Prefer password_hash_env (format sha256:<hex>) in production; a raw password
env is allowed for small internal deployments but is compared with a
constant-time digest check, rate-limited, and never logged.
Config secret sealing
Section titled “Config secret sealing”Literal credentials published to the database configuration source (the
api_key/token/webhook_url/… siblings of the *_env fields) are sealed
with AES-256-GCM before they are written — revisions, runtime snapshots and
their backups hold only ciphertext. Set AICR_CONFIG_SECRETS_KEY (32 bytes,
hex or base64) on every replica that publishes or activates such configuration;
publishing a literal without it fails closed (secrets_key_missing), and a
replica missing the key refuses to activate sealed configuration
(config_unavailable / 503 until fixed). Env-reference-only deployments need
no key. To rotate: generate a new key into AICR_CONFIG_SECRETS_KEY and move
the old one to AICR_CONFIG_SECRETS_KEY_PREVIOUS (comma-separated,
decrypt-only); new writes use the new key, historical revisions keep working.
Store backups are safe to copy off-host but still protect the unsealed
remainder; the sealing key itself never enters the store.
Generate a key:
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"Generate a sha256 password hash:
node -e "console.log('sha256:' + require('crypto').createHash('sha256').update(process.argv[1]).digest('hex'))" 'your-password'Prometheus metrics
Section titled “Prometheus metrics”The server exposes a Prometheus-compatible /metrics endpoint. Scrape it from
your existing Prometheus/Grafana stack alongside /healthz for liveness.
Store (SQLite / PostgreSQL)
Section titled “Store (SQLite / PostgreSQL)”The built-in store supports two backends, selected by
storage.database.kind. SQLite + Drizzle at storage.database.sqlite.path
(default /app/data/aicr.sqlite) is the default. PostgreSQL is wired end to
end — stats, webhook events, review deferrals, reflection memory, and the
model catalog all dispatch on the backend — so a single instance can share one
database across restarts without local state:
storage: database: kind: postgres postgres: url_env: AICR_DATABASE_URL # env var holding the PostgreSQL DSN migrate: auto # or: verifystorage.database.postgres.url_env names the environment variable that holds
the connection string (a plaintext storage.database.postgres.url fallback is
honored when url_env is absent). storage.database.migrate: auto applies
pending schema migrations at open; verify refuses to start when the
migration ledger is behind, drifted, or newer than this program. The redis
cache kind is wired in and used by the model catalog’s Redis backend. The
schema also reserves S3-compatible object storage under
storage.object.*, which is not wired yet. See
Storage.
Keep secrets in .env; config.yaml should contain env-var names only.
Secret scrubber
Section titled “Secret scrubber”AICR scrubs known secrets at four boundaries before persisting or emitting content:
| Boundary | What is scrubbed |
|---|---|
| Prompt | The instruction payload handed to the agent |
| Log | Anything written to the application log |
| Template | Rendered output-channel templates |
| Output | Final published reports (PR comments, Feishu cards, etc.) |
Sandbox security model
Section titled “Sandbox security model”Every agent run can be isolated in a sandbox container. The security model
holds across the native, docker, docker_socket, and podman backends:
- Command allowlist. The container command is checked against
ALLOWED_COMMANDSbefore any container engine is invoked. Unknown commands fail preflight. - Read-only
source/. Source is mounted read-only at/workspace/source. The agent cannot modify the code under review. - Isolated cwd.
agent/andtmp/are the only writable workspace mounts; the working directory is scoped per run. - Env file outside mounts. Temporary
--env-filefiles are created outside the mounted workspace directories and deleted after the run, so a compromised agent cannot read sibling run secrets from the workspace tree. - Timeout cleanup.
agent.timeout_secondstriggers a whole-process-tree kill — the agent binary and its worker subprocesses — so runs cannot overrun by leaving orphaned workers behind. See Docker deployment.
Podman / Docker socket risk
Section titled “Podman / Docker socket risk”The nested container sandbox (see Docker and Podman) requires mounting the host Podman or Docker socket into the AICR container. That socket is full, root-equivalent access to the host container engine — whoever can talk to it can start containers and run arbitrary code as the host user that owns it.
Only enable a nested container sandbox on a host you fully control, and prefer
the rootless user-level Podman socket (/run/user/$UID/podman/podman.sock)
over the system Docker socket when possible.
Data directory backup
Section titled “Data directory backup”Back up the three mounted data volumes. They contain everything AICR persists:
| Volume | Container path | What it holds |
|---|---|---|
aicr-data |
/app/data |
SQLite store, run history, queue state |
aicr-workspaces |
/app/workspaces |
Cloned repo / checkout caches |
aicr-logs |
/app/logs |
Application log files |
A minimal offline backup:
docker run --rm -v aicr-data:/data -v "$PWD/backup:/backup" alpine \ tar czf /backup/aicr-data-$(date +%F).tgz -C /data .config.yaml and .env live on the host (typically under example/) — back
those up separately. They are not part of the named volumes.
Upgrade and rollback
Section titled “Upgrade and rollback”Because config.yaml and .env are volume-mounted (not baked into the
image), there are two upgrade lanes:
| Change type | Procedure |
|---|---|
| Config-only (LLM model, triggers, outputs, workspace additions, label rules, etc.) | Edit config.yaml and/or .env, then restart the container. No rebuild needed. |
| Code change (new AICR release, Dockerfile / tool-list change) | docker build -t aicodereviewer -f deploy/Dockerfile . then restart the container. |
Restart for config-only changes:
docker restart aicrRollback works the same way in reverse: keep the previous image tag around, and
docker run the old image against the same volumes. Because state lives in the
volumes (not the image), an image rollback does not lose run history as long as
the SQLite schema has not migrated forward irreversibly — so before deploying a
new image that ships a schema migration, snapshot the aicr-data volume.