Skip to content

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 port 8100.
  • 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 argus schema 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:

ComponentWhat it does
argusLong-running service. Serves the API under /argus and runs scans. Always exactly one replica.
argus migrateOne-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-enterprise Helm 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:

    bash
    openssl 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.finding table 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 ​

  1. Add to your values.yaml:

    yaml
    services:
      argus:
        enabled: true
    
    env:
      argus_envs:
        fingerprint_secret: "<output of openssl rand -hex 32>"

    The chart refuses to render with services.argus.enabled=true and 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_URL from services.postgres or env.pgdb_*. If services.postgres.read_replica is enabled, Argus uses the replica for content scans.

  2. Upgrade the release:

    bash
    helm upgrade --install plane-app plane/plane-enterprise \
        --namespace plane \
        -f values.yaml \
        --timeout 10m \
        --wait \
        --wait-for-jobs
  3. Check the rollout:

    bash
    kubectl get deploy,job,svc -n plane | grep argus

    You should see plane-app-argus-wl at 1/1, a completed plane-app-argus-migrate-<timestamp> job and a plane-app-argus service on port 8100. 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:

yaml
external_secrets:
  argus_env_existingSecret: plane-argus-env

The Secret must contain:

KeyRequiredValue
ARGUS_DATABASE_URLYesPlane database DSN, for example postgresql://plane:<password>@plane-pgdb.plane.svc.cluster.local/plane
ARGUS_FINGERPRINT_SECRETYesOutput of openssl rand -hex 32
ARGUS_CONTENT_DATABASE_URLNoRead replica for content scans. Empty falls back to ARGUS_DATABASE_URL
ARGUS_INTERNAL_SECRETNoReserved 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:

yaml
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 ​

ValueDefaultDescription
services.argus.enabledfalseInstall Argus. The chart pins it to one replica with strategy: Recreate. There is no replicas value.
services.argus.imagemakeplane/argus-commercialUse makeplane/argus-commercial-fips for FIPS builds, together with env.argus_envs.data_dir: /app/data.
services.argus.memoryLimit / cpuLimit1000Mi / 500mResource limits.
services.argus.memoryRequest / cpuRequest250Mi / 50mResource requests.
services.argus.termination_grace_period_seconds60Argus 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/argusARGUS_BASE_PATH and the ingress path. Must stay /argus to match the web app.
env.argus_envs.schemaargusARGUS_SCHEMA. Leave as argus.
env.argus_envs.data_dir/home/nonroot/dataWhere 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_addr127.0.0.1:9100Prometheus 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_rps500 / 10000Default 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:

HostPathService
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.

  1. Edit /opt/plane/plane.env:

    bash
    ARGUS_REPLICAS=1
    ARGUS_MIGRATOR_REPLICAS=1
    ARGUS_FINGERPRINT_SECRET=<output of openssl rand -hex 32>

    Never set ARGUS_REPLICAS above 1. 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.

  2. Restart Plane:

    bash
    prime-cli restart

    or, if you manage the containers directly:

    bash
    docker compose --env-file plane.env up -d
  3. Check that the migrator exited cleanly and Argus is up:

    bash
    docker compose ps -a | grep argus
    docker compose logs argus --tail 20

    The argus-migrator container should show Exited (0) and the argus log should include http server listening with base_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 ​

VariableDefaultDescription
ARGUS_TRUSTED_PROXIESemptyCIDRs of the proxy in front of Argus, so audit rows record the user's IP instead of the proxy's
ARGUS_CORS_ALLOWED_ORIGINSemptyBrowser origins allowed to call Argus cross-origin. Leave empty. * is rejected
ARGUS_METRICS_ADDR127.0.0.1:9100Prometheus listener. :9100 exposes it on the compose network
ARGUS_WORKER_POOLemptyScan workers. Empty uses min(8, NumCPU)
ARGUS_DEFAULT_RATE_RPS500Default rows per second a scan reads
ARGUS_MAX_RATE_RPS10000Highest 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:

bash
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:stable

The 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:

bash
systemctl start --user argus-migrator.service
systemctl start --user argus.service
systemctl status --user {argus-migrator,argus}.service

If 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). Set ARGUS_REPLICAS=1 and ARGUS_MIGRATOR_REPLICAS=1 in the service's environment variables and redeploy.
  • Portainer: set ARGUS_FINGERPRINT_SECRET, ARGUS_REPLICAS=1 and ARGUS_MIGRATOR_REPLICAS=1 in 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:

text
reverse_proxy /argus/* argus:8100

The 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:

bash
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=require

The 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 ​

  1. Check that Argus answers through your Plane domain:

    bash
    curl https://plane.example.com/argus/healthz

    A 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.

  2. 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.