- the ChatCLI server, installed with the
chatcliHelm chart or managed by the operator as anInstance; - the operator, with its REST API and web dashboard.
For the full AIOps install (operator, Instance, policies, observability), follow AIOps Production Setup. This page is about the security settings on top of it.
Prerequisites
- Helm 3.8 or later (Helm 4 included),
kubectl,openssl. - A namespace for the server (
chatclibelow) and rights to create Secrets in it. - For the certificate steps, cert-manager or your own CA.
The server’s security model
Read this before you choose settings. It explains what each control does and does not cover.Step 1: Choose and create the credential
- JWT HS256
- JWT RS256
- mTLS
Step 2: TLS certificates
The certificate must cover the name clients dial. An operator-managed Instance is dialed as<instance>.<namespace>.svc.cluster.local, which needs a private CA; see TLS certificate in the AIOps setup. For a server reached from outside the cluster under a public name:
tls.existingSecret mounts the Secret at /etc/chatcli/tls, and the certificate and key paths default to /etc/chatcli/tls/tls.crt and /etc/chatcli/tls/tls.key (set certFile / keyFile only for other file names). tls.enabled: true without a Secret or both paths fails the install rather than serving plaintext:
Step 3: Deploy the server with hardened values
Every key below exists in the chart’s schema, which rejects unknown keys. This example uses HS256 JWTs.RuntimeDefault seccomp profile, and meet the restricted Pod Security Standard. Probes use /healthz on the metrics port and a TCP check on the gRPC port, which work with TLS on.
The chart renders a PodDisruptionBudget only when
replicaCount is greater than 1. Several replicas on one ReadWriteOnce sessions volume fail to schedule across nodes. Use persistence.accessModes: [ReadWriteMany] with a storage class that supports it, or stay on one replica; ReadWriteOncePod with more than one replica fails the render. Without ReadWriteMany, the chart stops the old pod before starting the new one on each rollout (maxSurge: 0, maxUnavailable: 1), so an upgrade has a short gap instead of hanging on the volume; see Rollouts on the sessions volume.Step 4: Audit trail
WithCHATCLI_AUDIT_LOG_PATH (chart security.auditLogPath, Instance spec.server.security.auditLogPath), the server appends one JSON line per RPC to a hash-chained file. Each line records the time, action and method, the caller (user:<subject> or anonymous), the role, the client host, the result (success, error or denied) and the duration. The path must be absolute and on a writable, persistent volume, as in step 3. On an Instance the only writable paths are emptyDir volumes, lost when the pod restarts, unless you enable persistence.
To check that nobody edited the file, read it from the volume (for example in a helper pod that mounts the same PVC) and run, in the chatcli REPL:
AuditEvent resources, readable with kubectl get auditevents -A and exportable through GET /api/v1/audit/export. Enable the Kubernetes API server audit log to capture who changed ChatCLI resources.
Step 5: Encryption at rest
The server encrypts what it writes under~/.chatcli (sessions, history, memory and other local stores), and on Kubernetes this is on by default with a key in a Secret the server or the operator creates. For production, supply the key yourself so it lives in your secret manager: security.encryptionKeySecret in the chart, spec.features.encryptionKeyRef on an Instance, or a Secret as in step 3. Do not use the chart’s security.encryptionKey: it puts the key in the pod spec as a plain value. Back the key Secret up with the PVC data; see Encryption at Rest. Rotation is described in Security.
Step 6: Agent, pipeline and plugin controls
These matter only if the server runs agent work or loads plugins.-
Pipeline.
pipeline.enabled(Instancespec.pipeline.enabled) lets clients run the coder, agent and tools on the server. Those RPCs require the admin role. Leave it off unless you need it. -
Command allowlist.
strictis the default mode: only commands in the allowlist run, andCHATCLI_AGENT_ALLOWLISTadds more (separated by,or;). The built-in list includes shells and interpreters such asbash,python,nodeandeval, so it is not a sandbox. Contain the pod instead: restricted NetworkPolicy egress, no chart RBAC when the watcher is off (rbac.create: false), and nothing sensitive on the pod’s volumes. -
Plugins. At load, every tool plugin and provider plugin needs a detached signature (
<plugin>.sig) that verifies against a trusted public key — in~/.chatcli/trusted-keys/, or for a fleet in the managed/etc/chatcli/trusted-keys/that every user trusts. Unsigned plugins, and plugins on a machine with no trusted key, are skipped unlessCHATCLI_ALLOW_UNSIGNED_PLUGINS=true, which you must never set in production. A plugin whose signature does not verify is always skipped.In a pod,~/.chatcliis/home/chatcli/.chatcli. Mount the trusted public keys at/home/chatcli/.chatcli/trusted-keyswithextraVolumes/extraVolumeMounts. Keep the private key out of the cluster.
Step 7: Operator, REST API and dashboard
1
API keys
The REST API and dashboard authenticate only with the
X-API-Key header, against the Secret chatcli-operator-secrets (key api-keys) in the operator’s namespace. Create one key per team and role (viewer, operator, admin), and keep admin keys few. See Dashboard and REST API keys for the commands.Keys are re-read every 30 seconds. To revoke one, remove it from the Secret. Deleting both the Secret and the ConfigMap chatcli-operator-config revokes every key within about 30 seconds. An edit that leaves invalid YAML does not revoke anything: the last valid key set stays in force and the operator logs the error. Give each approver their own key and set its name: approvals are recorded against the key, and a quorum counts each key once.2
Never enable dev mode
security.devMode: true (CHATCLI_OPERATOR_DEV_MODE) makes the REST API accept every caller as admin while no keys are loaded. The variable is read with boolean semantics, case-insensitively: true, TRUE, 1 and t all turn it on, and the startup log reports the same mode the API applies. Keep it false.3
TLS for the REST API
Terminate TLS at your ingress, or serve HTTPS (TLS 1.3) from the operator itself:
4
CORS stays closed
Cross-origin calls are denied until you name an origin in
security.corsAllowedOrigins. The dashboard is served from the same origin as the API and needs none.5
NetworkPolicy
Set
networkPolicy.enabled: true on the operator chart, restrict apiIngressFrom and metricsIngressFrom, and keep egress: restricted. The values are in step 1 of the AIOps setup.6
RBAC on ChatCLI resources
The operator runs with a cluster-wide ClusterRole that can read and write Secrets in every namespace, change workloads, create pods (chaos stress pods), evict pods (
DrainNode) and update nodes. No admission webhook checks what users submit, so Kubernetes RBAC is the only gate on who can make the operator act. Grant create/update on remediationplans, runbooks, instances, chaosexperiments, approvalrequests and the policy kinds only to the people who should hold that power. The chart pre-provisions the ClusterRoles chatcli-role-viewer, chatcli-role-operator, chatcli-role-admin and chatcli-role-superadmin; bind them yourself, since nothing binds them automatically.7
Remediation guardrails
security.allowedDiagnosticCommandsadds commands to the read-onlyExecDiagnosticallowlist; everything else is refused.security.allowedResourceTypesadds kinds that theApplyManifestaction may create. It extends the built-in list and never narrows it.security.logScrubPatternsadds comma-separated regular expressions to the scrubbing applied to the logs and context the operator sends to the LLM. It does not scrub the operator’s own logs.- Gate risky actions with an ApprovalPolicy: see Approvals.
Step 8: Verify the images
Release images are signed with cosign (keyless) and carry SBOM and provenance attestations.ghcr.io/diillson/chatcli-operator:1.226.0. Pin image tags, or digests, in production instead of latest.
Step 9: Verify the hardening
1
TLS 1.3 only, with the right name
2
Health answers, without a credential
CHATCLI_TLS_CLIENT_CERT and CHATCLI_TLS_CLIENT_KEY in the pod.3
Calls without a credential are refused
ca.crt is the CA of the server certificate (not needed for a public CA). $JWT is a token from step 1.4
Reflection is off
5
Pod settings
6
Network isolation
7
Audit lines are written
After a few calls, the audit file on the volume grows by one line per RPC, and
/config security verify-audit reports the chain as intact.8
Operator REST API
What ChatCLI does not protect
These gaps exist in current releases. Cover each one with the mitigation next to it.Final checklist
Next steps
Security
Every security control in detail.
AIOps Production Setup
The complete operator and Instance runbook.
K8s Operator
Instance fields and what the operator creates.
Environment variables
Every variable the server and the operator read.