Configure sensitive data scanning Enterprise Grid
Sensitive data scanning is powered by Argus, a separate service that ships with Plane Commercial Edition v3.2.0 and later. Argus reads workspace content from the Plane database, detects PII, credentials, financial identifiers and government IDs, and serves the findings to Workspace settings → Sensitive data scanning. It is off by default on every deployment method.
For what the feature does from a workspace admin's point of view, see Sensitive data scanning.
How Argus fits into Plane
- The web app calls Argus at
/argus/*on your Plane domain. Your proxy or ingress routes that path to Argus on port8100. - Argus forwards the user's Plane session cookie to the API (
API_INTERNAL_URL) to verify it. There are no separate credentials for users. - Argus uses the same Postgres database as Plane. It creates and owns an
argusschema there and only reads Plane's own tables. It never writes them. - Argus never stores, logs or returns a matched value. Findings hold a location and an HMAC fingerprint derived from
ARGUS_FINGERPRINT_SECRET. - Nothing leaves your deployment. Argus makes no outbound calls apart from the Plane API, the database and, if you configure it, AWS Secrets Manager.
Each deployment has two pieces:
| Component | What it does |
|---|---|
argus | Long-running service. Serves the API under /argus and runs scans. Always exactly one replica. |
argus migrate | One-shot job that creates or upgrades the argus schema. argus start doesn't migrate on its own. |
Before you begin
Plane Commercial Edition v3.2.0 or later. On Kubernetes,
plane-enterpriseHelm chart 3.7.0 or later.The workspace's plan must include sensitive data scanning. Without it, the settings page shows Sensitive data scanning isn't enabled even when Argus is running.
A fingerprint secret, generated once per deployment:
bashopenssl rand -hex 32
About the fingerprint secret
ARGUS_FINGERPRINT_SECRET is the master key for every finding fingerprint. Treat it like a database password.
- Unique per deployment. Argus refuses to start on known placeholders or on anything shorter than 32 characters. A shared value would let anyone with read access to the
argus.findingtable brute-force low-entropy values such as emails and phone numbers. - Keep it stable. Rotating it invalidates every stored fingerprint. The next scan reports every value as new, old findings never go stale, and existing triage decisions and fingerprint-based allow-list entries stop matching. If you must rotate, delete the old findings, run a full scan and triage again.
Kubernetes
The plane-enterprise chart renders an Argus Deployment and Service, a migration Job, a ConfigMap and Secret, and an /argus/ route on the chart's ingress (nginx, Traefik or OpenShift).
Enable Argus
Add to your
values.yaml:yamlservices: argus: enabled: true env: argus_envs: fingerprint_secret: "<output of openssl rand -hex 32>"The chart refuses to render with
services.argus.enabled=trueand no fingerprint secret, unless you supply one through an existing Secret (below).You don't need to set a database URL. The chart builds
ARGUS_DATABASE_URLfromservices.postgresorenv.pgdb_*. Ifservices.postgres.read_replicais enabled, Argus uses the replica for content scans.Upgrade the release:
bashhelm upgrade --install plane-app plane/plane-enterprise \ --namespace plane \ -f values.yaml \ --timeout 10m \ --wait \ --wait-for-jobsCheck the rollout:
bashkubectl get deploy,job,svc -n plane | grep argusYou should see
plane-app-argus-wlat1/1, a completedplane-app-argus-migrate-<timestamp>job and aplane-app-argusservice on port8100. The migration job has no Helm hook, so the Argus pod may restart a few times on first install until the schema exists. That is expected.
Keep secrets out of values.yaml
To manage the Argus secret yourself, for example with External Secrets Operator, create a Secret and point the chart at it:
external_secrets:
argus_env_existingSecret: plane-argus-envThe Secret must contain:
| Key | Required | Value |
|---|---|---|
ARGUS_DATABASE_URL | Yes | Plane database DSN, for example postgresql://plane:<password>@plane-pgdb.plane.svc.cluster.local/plane |
ARGUS_FINGERPRINT_SECRET | Yes | Output of openssl rand -hex 32 |
ARGUS_CONTENT_DATABASE_URL | No | Read replica for content scans. Empty falls back to ARGUS_DATABASE_URL |
ARGUS_INTERNAL_SECRET | No | Reserved for service-to-service routes. Unused today. |
An ExternalSecret that composes the DSN from a database secret holding only a username and password looks like this:
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: plane-argus-env
namespace: plane
spec:
refreshInterval: 1h
secretStoreRef:
kind: SecretStore
name: aws-secrets-manager
target:
name: plane-argus-env
template:
data:
ARGUS_DATABASE_URL: "postgresql://{{ .DATABASE_USERNAME }}:{{ .DATABASE_PASSWORD }}@<db-host>:5432/plane"
ARGUS_FINGERPRINT_SECRET: "{{ .ARGUS_FINGERPRINT_SECRET }}"
data:
- secretKey: DATABASE_USERNAME
remoteRef: { key: plane/database, property: DATABASE_USERNAME }
- secretKey: DATABASE_PASSWORD
remoteRef: { key: plane/database, property: DATABASE_PASSWORD }
- secretKey: ARGUS_FINGERPRINT_SECRET
remoteRef: { key: plane/argus, property: ARGUS_FINGERPRINT_SECRET }See External secrets for setting up the SecretStore.
Helm values
| Value | Default | Description |
|---|---|---|
services.argus.enabled | false | Install Argus. The chart pins it to one replica with strategy: Recreate. There is no replicas value. |
services.argus.image | makeplane/argus-commercial | Use makeplane/argus-commercial-fips for FIPS builds, together with env.argus_envs.data_dir: /app/data. |
services.argus.memoryLimit / cpuLimit | 1000Mi / 500m | Resource limits. |
services.argus.memoryRequest / cpuRequest | 250Mi / 50m | Resource requests. |
services.argus.termination_grace_period_seconds | 60 | Argus drains running scan workers before shutting down. The Kubernetes default of 30s can cut that short. |
services.argus.nodeSelector / tolerations / affinity | {} / [] / {} | Scheduling. |
env.argus_envs.fingerprint_secret | "" | ARGUS_FINGERPRINT_SECRET. Required unless the existing Secret provides it. |
env.argus_envs.database_url | "" | ARGUS_DATABASE_URL. Empty derives it from the chart's Postgres settings. |
env.argus_envs.content_database_url | "" | ARGUS_CONTENT_DATABASE_URL. Read replica for scans. |
env.argus_envs.base_path | /argus | ARGUS_BASE_PATH and the ingress path. Must stay /argus to match the web app. |
env.argus_envs.schema | argus | ARGUS_SCHEMA. Leave as argus. |
env.argus_envs.data_dir | /home/nonroot/data | Where export CSVs are written. The chart mounts an emptyDir here. |
env.argus_envs.trusted_proxies | "" | ARGUS_TRUSTED_PROXIES. CIDRs your ingress connects from, so audit rows record the user's IP. |
env.argus_envs.cors_allowed_origins | "" | CORS_ALLOWED_ORIGINS. Leave empty when Argus is served on the Plane domain. |
env.argus_envs.metrics_addr | 127.0.0.1:9100 | Prometheus listener. Set :9100 so a cluster scraper can reach it, or "" to turn metrics off. |
env.argus_envs.worker_pool | "" | Scan workers. Empty uses min(8, NumCPU). |
env.argus_envs.default_rate_rps / max_rate_rps | 500 / 10000 | Default and maximum rows per second a scan reads. |
The full list is in the plane-enterprise chart README.
Your own ingress
If you don't use the chart's ingress, add this route next to the other Plane routes:
| Host | Path | Service |
|---|---|---|
plane.example.com | /argus/* | http://plane-app-argus.<namespace>:8100 |
Don't strip the /argus prefix. Argus serves every route under it.
Docker Compose
This covers installs made with Prime CLI or the Commercial Docker Compose files. The compose file already defines argus and argus-migrator services and the /argus/* proxy route, both scaled to zero.
Edit
/opt/plane/plane.env:bashARGUS_REPLICAS=1 ARGUS_MIGRATOR_REPLICAS=1 ARGUS_FINGERPRINT_SECRET=<output of openssl rand -hex 32>Never set
ARGUS_REPLICASabove1. Export files live on the container's own filesystem, and a second replica would fail the first one's running scans and exports on startup.Restart Plane:
bashprime-cli restartor, if you manage the containers directly:
bashdocker compose --env-file plane.env up -dCheck that the migrator exited cleanly and Argus is up:
bashdocker compose ps -a | grep argus docker compose logs argus --tail 20The
argus-migratorcontainer should showExited (0)and thearguslog should includehttp server listeningwithbase_path/argus.
Argus reads the same DATABASE_URL as the rest of Plane, so there is no database to configure. For a FIPS install, docker-compose-fips.yml uses makeplane/argus-commercial-fips and the same variables. See FIPS deployment.
Optional variables
| Variable | Default | Description |
|---|---|---|
ARGUS_TRUSTED_PROXIES | empty | CIDRs of the proxy in front of Argus, so audit rows record the user's IP instead of the proxy's |
ARGUS_CORS_ALLOWED_ORIGINS | empty | Browser origins allowed to call Argus cross-origin. Leave empty. * is rejected |
ARGUS_METRICS_ADDR | 127.0.0.1:9100 | Prometheus listener. :9100 exposes it on the compose network |
ARGUS_WORKER_POOL | empty | Scan workers. Empty uses min(8, NumCPU) |
ARGUS_DEFAULT_RATE_RPS | 500 | Default rows per second a scan reads |
ARGUS_MAX_RATE_RPS | 10000 | Highest rate an admin can choose for a scan |
Docker AIO
The all-in-one image bundles Argus but doesn't start it. Pass two more variables to docker run:
docker run --name plane-aio --rm -it \
-p 80:80 \
-e DOMAIN_NAME=your-domain.com \
-e DATABASE_URL=postgresql://user:pass@host:port/database \
...
-e ENABLE_ARGUS=1 \
-e ARGUS_FINGERPRINT_SECRET=<output of openssl rand -hex 32> \
makeplane/plane-aio-commercial:stableThe container runs the schema migration and then starts Argus. It refuses to start if ENABLE_ARGUS=1 and the secret is missing or shorter than 32 characters. ARGUS_TRUSTED_PROXIES is also accepted.
Podman Quadlets
The Podman installer generates ARGUS_FINGERPRINT_SECRET in /opt/plane/plane.env and installs argus.container and argus-migrator.container. Start them after the API and monitor services:
systemctl start --user argus-migrator.service
systemctl start --user argus.service
systemctl status --user {argus-migrator,argus}.serviceIf you upgraded from a version without Argus, add ARGUS_FINGERPRINT_SECRET to plane.env yourself first.
Coolify and Portainer
Both compose templates include argus and argus-migrator scaled to zero.
- Coolify generates the fingerprint secret for you (
SERVICE_PASSWORD_64_ARGUSFINGERPRINT). SetARGUS_REPLICAS=1andARGUS_MIGRATOR_REPLICAS=1in the service's environment variables and redeploy. - Portainer: set
ARGUS_FINGERPRINT_SECRET,ARGUS_REPLICAS=1andARGUS_MIGRATOR_REPLICAS=1in the stack's environment variables and update the stack.
Airgapped installs
The airgapped bundle includes argus-commercial-<version>.tar. Load it with the other images, then enable Argus with the Docker Compose or Kubernetes steps above. Argus makes no internet calls, so it needs nothing beyond what Plane itself needs. See Airgapped edition.
External reverse proxy
If you run your own proxy in front of Plane, see External reverse proxy, and add a route that sends /argus/* to the Argus container on port 8100 with the path unchanged. For example, in Caddy:
reverse_proxy /argus/* argus:8100The web app is built to call /argus on the Plane domain. ARGUS_BASE_PATH must stay /argus. Any other value makes every request 404.
Use AWS Secrets Manager for database credentials
On EKS, Argus can read its database credentials from AWS Secrets Manager through IRSA or EKS Pod Identity, and pick up rotated passwords without a restart. Set:
RDS_SECRET_ARN=arn:aws:secretsmanager:us-east-1:123456789012:secret:plane/rds
AWS_REGION=us-east-1
ARGUS_DATABASE_URL=postgresql://your-db-or-rds-proxy-endpoint:5432/plane?sslmode=requireThe secret owns the credentials and the DSN owns the endpoint. If your secret uses other key names, set RDS_DB_USERNAME_KEY and RDS_DB_PASSWORD_KEY (defaults username and password). The IAM role needs secretsmanager:GetSecretValue on the secret. See IRSA and EKS Pod Identity.
Verify the setup
Check that Argus answers through your Plane domain:
bashcurl https://plane.example.com/argus/healthzA response of
{"status":"ok"}means routing works. If you get Plane's HTML page instead, the/argus/*route is missing on your proxy or ingress.As a workspace admin, open Workspace settings → Sensitive data scanning and run a scan on one project.
Troubleshooting
The page says "Sensitive data scanning isn't set up". The web app probes /argus/healthz before it shows anything. This message means the probe failed. Check, in order, that the Argus container or pod is running, that /argus/* routes to it, and that ARGUS_BASE_PATH is /argus.
The page says "Sensitive data scanning isn't enabled". Argus is fine. The workspace's plan doesn't include the feature.
Argus exits with ARGUS_FINGERPRINT_SECRET: is a placeholder or a length error. Generate a real value with openssl rand -hex 32.
Argus logs relation does not exist. The schema migration hasn't run. On Docker Compose, check that ARGUS_MIGRATOR_REPLICAS=1. On Kubernetes, check the argus-migrate job logs.
argus migrate refuses to run on an existing schema. The database holds an argus schema from an older or newer build that this binary can't reconcile. The error message gives the recovery command. It drops the argus schema, which deletes all findings and scan history but leaves Plane's data untouched.
Every workspace call returns 403. Either the user isn't a workspace admin, or ARGUS_CONTENT_DATABASE_URL points at a database without Plane's tables, so Argus can't look up workspace membership.
Audit entries show the proxy's IP address. Set ARGUS_TRUSTED_PROXIES to the addresses your proxy or ingress connects from.
An export download is gone after a restart. Export files are temporary and the download link expires after 15 minutes anyway. Run the export again.

