Install the Community Edition with Docker Compose, create the admin account, feed it discovery data, and export a signed CycloneDX 1.6 CBOM.
This guide covers CipherFlag Community Edition (CE), the open-source edition released under the Apache License 2.0. CipherFlag EE, the commercial edition, adds passive network discovery, cloud and container sources, risk prioritization and more. For EE, contact info@cipherflag.com.
For the Docker Compose install:
docker compose). The CipherFlag image is published for linux/amd64 and linux/arm64.git, to fetch the compose file and the configuration directory.8443 (console and API) and 5433 (PostgreSQL).To build from source instead:
go.mod requires 1.25.6).CipherFlag CE receives discovery data over HTTP and from the vendor APIs and Certificate Transparency logs it polls. It needs no special network access beyond reaching those services.
git clone --branch v2.3.0 https://github.com/net4n6-dev/cipherflag.git
cd cipherflag
docker compose up -d
Compose starts two containers:
| Service | Image | Host port | Role |
|---|---|---|---|
postgres | postgres:16 | 5433 → 5432 | Database; data is kept in the pg-data volume |
cipherflag | ghcr.io/net4n6-dev/cipherflag-ce:2.3.0 | 8443 → 8443 | API server with the web console built in |
The cipherflag container starts once PostgreSQL passes its health check, and applies database migrations on start. Check that both are up and the server answers:
docker compose ps
curl http://localhost:8443/healthz
# {"status":"ok"}
Compose mounts the repository's config/ directory into the container at /app/config and sets CIPHERFLAG_CONFIG=/app/config/cipherflag.toml. You configure CipherFlag by editing config/cipherflag.toml on the host and running docker compose restart cipherflag. The compose file also has a build section, so docker compose build builds the same image from the checked-out source instead of pulling it.
Port 8443 serves plain HTTP. Open http://localhost:8443, not https://. CipherFlag has no built-in TLS listener; put it behind a reverse proxy that terminates TLS before you expose it beyond your machine.
Change the default database password (changeme) before the first start. Set POSTGRES_PASSWORD (in your shell or in a .env file next to docker-compose.yml) and put the same password in [storage] postgres_url, because CipherFlag does not expand environment variables in its config file. PostgreSQL applies the password only when it creates the data volume, and Compose publishes it on host port 5433.
git clone --branch v2.3.0 https://github.com/net4n6-dev/cipherflag.git
cd cipherflag
# 1. Build the web console; the server binary embeds it
(cd frontend && npm ci && npm run build)
cp -R frontend/build/. internal/web/dist/
# 2. Build the server
go build -o cipherflag ./cmd/cipherflag
# 3. Point [storage] postgres_url in config/cipherflag.toml at PostgreSQL 16, then:
./cipherflag migrate
./cipherflag serve
If you skip step 1 the server still runs, but the console shows only a "frontend not built" placeholder. The server reads config/cipherflag.toml relative to the working directory, or the file named by CIPHERFLAG_CONFIG. The shipped file's postgres_url uses the host name postgres, which only resolves inside the Compose network; change it to your database, for example postgres://cipherflag:PASSWORD@localhost:5432/cipherflag?sslmode=disable. serve applies pending migrations itself, so migrate is only needed if you want to run them as a separate step.
Open http://localhost:8443. On a fresh install the console sends you to Create Admin Account: enter a display name, an email address and a password of at least 8 characters. Creating the account signs you in with the admin role.
Until the first account exists, CipherFlag does not require authentication for its console or API. Create the admin account as soon as the server is up, and before the port is reachable from other machines.
You can create the same account from a script. The endpoint only works while no account exists; after that it returns 403 users already exist.
curl -X POST http://localhost:8443/api/v1/auth/setup-admin \
-H 'Content-Type: application/json' \
-d '{"email":"admin@example.com","password":"choose-a-long-passphrase","display_name":"Admin"}'
To sign in later, go to http://localhost:8443/login. Sessions last 24 hours. Admins add more users under Settings → Users with the admin or viewer role, and every user can change their password under Settings → Profile.
Collectors and pipelines authenticate with an agent token, sent as Authorization: Bearer <token>. CE 2.3.0 has no console page for agent tokens, so an admin creates them through the API: sign in with curl to get a session cookie, then create the token.
curl -c cf-session.txt -X POST http://localhost:8443/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"admin@example.com","password":"choose-a-long-passphrase"}'
curl -b cf-session.txt -X POST http://localhost:8443/api/v1/auth/agent-tokens \
-H 'Content-Type: application/json' -d '{"name":"fleet-webhook"}'
# {"id":"…","name":"fleet-webhook","token":"<64 hex characters>","token_prefix":"…"}
export CF_TOKEN=<token from the response>
The token appears only in this response; CipherFlag stores a hash of it. List tokens with GET /api/v1/auth/agent-tokens and revoke one with DELETE /api/v1/auth/agent-tokens/{id}. An agent token can submit discovery data and read the inventory and CBOM exports. Admin-only calls (CBOM import, user and token management, Venafi settings) return 403 for it. Keep cf-session.txt private: it holds an admin session.
CipherFlag CE is configured in one TOML file, config/cipherflag.toml (with Docker, the host file mounted at /app/config/cipherflag.toml). Restart after editing it: docker compose restart cipherflag. These are the sections CE 2.3.0 reads:
| Section | What it controls |
|---|---|
[server] | listen: address and port, default 0.0.0.0:8443 (plain HTTP) |
[storage] | postgres_url: PostgreSQL connection string |
[analysis] | scorer_enabled: grading, post-quantum classification and compliance evaluation (default false); recheck_interval_hours: how often every asset is rescored (default 6); rule_sweep_batch_size (default 1000) |
[intake.dedup] | Optional in-memory cache that skips rewriting identical observations from collectors that resend them: enabled (default false), ttl_seconds, max_entries |
[scanners] | jvm_keystore_passwords: passwords scan-truststore tries on JVM keystores (default ["changeit"]) |
[sources.*] | Endpoint connectors and Certificate Transparency sources, all off by default (section 5) |
[cbom], [cbom.signing], [[cbom.scopes]] | CBOM signing, scheduled push and sinks (section 7) |
[export.venafi] | Venafi TPP and Cloud push (section 8) |
Do this before you look for grades or export a CBOM. With scorer_enabled off (the default, and the shipped file does not set it), CipherFlag stores what it discovers but does not grade it, classify its post-quantum status or evaluate compliance, and every CBOM export is empty. Add the line to the existing [analysis] section:
[analysis]
scorer_enabled = true
recheck_interval_hours = 6
On the next start the scorer grades everything already in the database, scores new assets as they arrive, and rescores the whole inventory every recheck_interval_hours. Compliance evaluation covers NIST SP 800-131A, NSA CNSA 2.0, FIPS 140-3 and EU NIS2.
Saving Venafi settings in the console, or with PUT /api/v1/venafi/config, writes the whole configuration back to config/cipherflag.toml: comments are removed and every setting is written out with its current value. Keep a copy if you rely on your annotations.
The Settings → Sources tab in 2.3.0 shows legacy settings that CE does not use. Configure sources in the file, as described in section 5.
With Docker, run commands inside the running container: docker compose exec cipherflag ./cipherflag <command>.
| Command | Purpose |
|---|---|
serve | Start the API server and console (applies migrations first) |
migrate | Apply pending database migrations |
version | Print the version |
scan-truststore --host-id <uuid> | One-shot trust-store scan of the local machine (below) |
declared-cas import --starter | --file <path> | Register the CAs you operate; CAs not on the list appear in GET /api/v1/inventory/shadow-cas |
application-metadata presets | declare | import | Per-application data-retention metadata used by the harvest-now-decrypt-later analysis |
ownership declare | import | backfill | Record which team, owner and service an asset belongs to |
generate-signing-key [--out <prefix>] | Create an Ed25519 key pair for CBOM signing |
sign-cbom --bom <file> --key <key> [--out <file>] | Sign an existing CBOM file |
verify-cbom --bom <file> [--trusted-key <pub>] | Verify a signed CBOM (exit codes) |
setup, seed | Print a configuration banner; no-op (CE ships no sample dataset). CE has no interactive setup. |
CE inventories certificates, SSH keys, crypto libraries and crypto configuration from the sources below. Every source feeds one ingester, which resolves each submission to a host and removes duplicates. The examples use the CF_TOKEN agent token from section 3.
discovery-packs/osquery/cipherflag-crypto.conf is an osquery pack with five snapshot queries: cipherflag_certificates, cipherflag_ssh_user_keys, cipherflag_authorized_keys, cipherflag_crypto_packages_deb and cipherflag_crypto_packages_rpm. Schedule them in Fleet and send the results to POST /api/v1/ingest/osquery with an agent token. The body is a JSON array of Fleet result entries; entries for other query names are skipped.
curl -X POST http://localhost:8443/api/v1/ingest/osquery \
-H "Authorization: Bearer $CF_TOKEN" \
-H 'Content-Type: application/json' \
-d @fleet-results.json
# {"processed":1,"skipped":0,"ownership_sightings_emitted":0}
// fleet-results.json
[
{
"host_identifier": "4C4C4544-0042-3510-8052-B7C04F4E3732",
"hostname": "web-01.example.com",
"platform": "ubuntu",
"name": "cipherflag_crypto_packages_deb",
"action": "snapshot",
"snapshot": [
{"name": "libssl3", "version": "3.0.2-0ubuntu1.15", "source": "openssl"}
]
}
]
If an entry also carries team (the Fleet team name), CipherFlag attributes the certificates, SSH keys and libraries in that entry to the team.
Any script or tool can post discovery results to POST /api/v1/ingest (bodies up to 10 MB). source is required; hostname, source_host_id, ip_addresses and os_family identify the host. Items in the certificates, ssh_keys, libraries, protocols and configs arrays use the field names shown here (RawPEM, FilePath, LibraryName, FingerprintSHA256 and so on), not snake_case: unrecognized item fields are ignored. A certificate needs RawPEM or FingerprintSHA256; given the PEM, CipherFlag parses the rest itself.
curl -X POST http://localhost:8443/api/v1/ingest \
-H "Authorization: Bearer $CF_TOKEN" \
-H 'Content-Type: application/json' \
-d @discovery.json
// discovery.json
{
"source": "inventory-script",
"hostname": "app-01.example.com",
"ip_addresses": ["10.0.0.21"],
"certificates": [
{ "RawPEM": "-----BEGIN CERTIFICATE-----\nMIID…\n-----END CERTIFICATE-----\n",
"FilePath": "/etc/nginx/tls/app.pem" }
],
"libraries": [
{ "LibraryName": "openssl", "Version": "1.1.1k", "PackageManager": "rpm" }
]
}
The response counts what was new or updated and returns the host_id the submission was resolved to.
discovery-packs/scripts/ holds four Bash scripts for Linux and macOS and four PowerShell scripts for Windows: discover-certs, discover-ssh-keys, discover-libraries and discover-configs. Each writes NDJSON to standard output. They are what the endpoint connectors run: upload them to SentinelOne Remote Script Orchestration or Absolute Reach and put their script IDs in the connector's settings, or serve their output from Tanium custom sensors named CipherFlag.Crypto.Certificates, CipherFlag.Crypto.SSHKeys, CipherFlag.Crypto.Libraries and CipherFlag.Crypto.Configs. CipherFlag collects and parses the output on each poll.
cipherflag scan-truststore --host-id <uuid> is a one-shot scan of the machine it runs on: OS CA bundles, JVM cacerts (opened with the passwords in [scanners] jvm_keystore_passwords), language-runtime CA stores, and trust bundles referenced from common server configurations. It writes straight to the database in [storage] postgres_url and attributes the results to a host that already exists, so run a source-built binary on the host you want to inventory. Take the host ID from an ingest response or from GET /api/v1/hosts.
./cipherflag scan-truststore --host-id 3ef05d93-25df-4249-9654-9e7bae03c62b
CE ships five connectors that poll documented vendor REST APIs. Each stays off until you set enabled = true in its section and restart. If a connector cannot start with its settings, the server stops at startup and logs the reason.
| Connector | Section | Collects | Main settings |
|---|---|---|---|
| Microsoft Defender for Endpoint | [sources.defender] | Crypto-library inventory through the Advanced Hunting API | tenant_id, client_id, client_secret, poll_interval_seconds |
| SentinelOne | [sources.sentinelone] | Installed applications (.app_inventory, hourly) and discovery-script output through Remote Script Orchestration (.rso, daily) | api_token, console_url, rso.cert_script_id, rso.ssh_keys_script_id, rso.libraries_script_id, rso.config_files_script_id |
| Tanium | [sources.tanium] | Installed Applications and the CipherFlag custom sensors, over the GraphQL API (hourly) | api_token, console_url, page_size |
| Absolute | [sources.absolute] | Installed applications (.inventory, hourly) and discovery-script output through Absolute Reach (.reach, daily) | token_id, secret_key, console_url, reach.*_script_id |
| Netwrix Auditor | [sources.netwrix] | AD CS certificate and certificate-template changes | base_url, username, password, insecure_skip_tls |
[sources.sentinelone]
enabled = true
api_token = "<SentinelOne API token>"
console_url = "https://<your-console>.sentinelone.net"
[sources.sentinelone.app_inventory]
enabled = true
[sources.sentinelone.rso]
enabled = true
cert_script_id = "<script ID of discover-certs>"
ssh_keys_script_id = "<script ID of discover-ssh-keys>"
libraries_script_id = "<script ID of discover-libraries>"
config_files_script_id = "<script ID of discover-configs>"
Four sources watch public CT logs for certificates issued for your domains. They are configured only in config/cipherflag.toml (there is no console page for them), poll hourly, and stay off until a domain entry is enabled. Domains must be lowercase. Every enabled entry is validated at startup, and an invalid one stops the server with a log line naming the source and domain.
| Source | Reads from | Notes |
|---|---|---|
ct_crtsh | crt.sh | Historical and new certificates |
ct_certspotter | SSLMate CertSpotter API | Optional api_token; requests_per_hour caps the request rate (0 = default) |
ct_static | Static CT API (Sunlight) logs | Verifies the log's signed checkpoint and each certificate's inclusion; watches forward only |
ct_multi | Two or more of the above for one domain | Combines their results and records which provider found each certificate |
[[sources.ct_crtsh.domains]]
enabled = true
domain = "example.com"
include_subdomains = true
[[sources.ct_certspotter.domains]]
enabled = true
domain = "example.com"
include_subdomains = true
api_token = "" # optional SSLMate API token
requests_per_hour = 0 # 0 = default rate
ct_static needs three values per log, all taken from the log's tiled_logs entry in Google's CT log list (log_list.json): log_url is the monitoring_url (HTTPS, ending in /); origin is the submission_url without https:// and without the trailing /, usually a different host from log_url; public_key_pem is the key value wrapped in a PUBLIC KEY PEM block.
[[sources.ct_static.domains]]
enabled = true
domain = "example.com"
log_url = "https://<monitoring_url host and path>/"
origin = "<submission_url without https:// and the trailing />"
public_key_pem = """
-----BEGIN PUBLIC KEY-----
<key value from log_list.json>
-----END PUBLIC KEY-----
"""
ct_static watches forward: when first enabled it starts at the log's current tree head and reports certificates logged after that point. Use ct_crtsh or ct_certspotter for history. Inside ct_multi, a static child keeps its position in memory only and starts again from the head after a restart.
[[sources.ct_multi.groups]]
enabled = true
domain = "example.org"
[[sources.ct_multi.groups.children]]
[sources.ct_multi.groups.children.crtsh]
[[sources.ct_multi.groups.children]]
[sources.ct_multi.groups.children.certspotter]
api_token = ""
A ct_multi group needs at least two children, each exactly one of crtsh, static or certspotter. A child that sets its own domain must use the group's domain.
The console is served by the same process at http://localhost:8443. The sidebar lists Dashboard, Certificates, PKI Constellation, Analytics, Reports and Statistics, with Settings at the bottom. The top bar holds the theme switch (dark, light, or follow the system) and the live-updates indicator. Grades, findings and compliance figures appear once scoring is on (section 4).
The Certificate Landscape: cards for expired certificates, certificates expiring within 30 and 90 days, grade F and certificates with findings, then a compliance scorecard, the grade distribution, the key and signature algorithm mix, the top priority actions, discovery sources, top issuers and a radial view of the PKI hierarchy.
A searchable inventory. Each certificate page shows its grade and health score, validity, a trust-chain graph, health findings with remediation guidance, and its subject alternative names.
A 3D view of your CAs and certificates, built on three.js, with search and grade filters. Select a node to open its details; for a CA, Blast Radius highlights the certificates beneath it. Browsers without WebGL get a 2D view instead. /pki opens the Constellation.
Seven tabs: Chain Flow (issuers to certificates), Ownership, Crypto Posture, Expiry Forecast, Source Lineage, Library Distribution (a treemap sized by host count, with libraries that have known CVEs in red) and SSH Key Analytics (weak, root-authorized, shared and unprotected keys).
Domain, CA, Crypto Compliance and Expiry Risk (30, 60 or 90 days) reports, opened from the charts on the Reports page. Each report can be downloaded as CSV or printed.
The dashboard and the Constellation refresh as assets are discovered and scored, streamed from GET /api/v1/events/stream. The dot in the top bar is green while the stream is connected.
Users (admins only), Venafi (section 8), System status, and Profile, where you change your password.
CipherFlag exports CycloneDX 1.6 CBOMs of scored assets, so turn on scorer_enabled first (section 4). Downloads accept a session or an agent token.
| Endpoint | Returns |
|---|---|
GET /api/v1/export/cbom/estate | Every scored asset, as cipherflag-cbom-estate-<date>.cdx.json |
GET /api/v1/export/cbom | Assets on selected hosts: hostname_pattern (* and ? wildcards), host_id, or scope (a scope defined in config). Narrow it with asset_type (certificate, ssh_key, crypto_library, crypto_protocol, crypto_config) or min_risk_score (0–100). Without a host selection the BOM is empty. |
curl -H "Authorization: Bearer $CF_TOKEN" -o estate.cdx.json \
http://localhost:8443/api/v1/export/cbom/estate
curl -H "Authorization: Bearer $CF_TOKEN" -o web.cdx.json \
'http://localhost:8443/api/v1/export/cbom?hostname_pattern=web-*&asset_type=certificate'
Signing is off by default. When it is on, every CBOM CipherFlag produces, downloads and scheduled pushes alike, carries an Ed25519 JSON Signature Format signature. Generate a key pair; with Docker, write it into the mounted config directory:
docker compose exec cipherflag ./cipherflag generate-signing-key --out /app/config/cbom-signing
# Wrote /app/config/cbom-signing.key (private, mode 0600)
# Wrote /app/config/cbom-signing.pub (public)
# Public key SHA-256 fingerprint: …
Record the fingerprint where the people verifying your CBOMs can check it. The files are standard PKCS#8 and SPKI PEM, readable by OpenSSL and by HSM and KMS tooling. Enable signing and restart:
[cbom.signing]
enabled = true
signer = "file"
path = "/app/config/cbom-signing.key"
# or: signer = "env" with env_var = "CIPHERFLAG_SIGNING_KEY"
At startup the server logs the public key's SHA-256. If the key cannot be loaded, the server stops with a single FTL line naming the key file or variable.
verify-cbom checks the signature and, with --trusted-key, that the BOM was signed by the key you expect. It needs no database, so you can run it from the image against files in the current directory:
docker compose run --rm --no-deps -v "$PWD:/work" cipherflag \
verify-cbom --bom /work/estate.cdx.json --trusted-key /work/config/cbom-signing.pub
# Signature valid. Embedded public key SHA-256: …
# Trust verified: embedded key matches --trusted-key.
| Exit code | Meaning |
|---|---|
0 | Signature valid (and, with --trusted-key, made by that key) |
1 | Signature valid, but made by a different key than --trusted-key |
2 | Invalid: the BOM changed after signing, or the signature block is missing or malformed |
3 | Could not verify: a usage error, an unreadable BOM or an unloadable trusted key. Treat it as a failure to run, never as a verdict. |
To sign a BOM produced elsewhere, use cipherflag sign-cbom --bom in.cdx.json --key cbom-signing.key --out signed.cdx.json. Without --out it signs the file in place.
POST /api/v1/import/cbom reads a CycloneDX BOM (up to 50 MB) into the inventory. It requires an admin session; agent tokens and viewers get 403. By default only certificates are imported. Add ?host_id=<uuid> to import SSH keys, libraries and crypto configs against that host as well; without it they are reported as skipped.
curl -b cf-session.txt -X POST -H 'Content-Type: application/json' \
--data-binary @vendor.cdx.json \
'http://localhost:8443/api/v1/import/cbom?host_id=3ef05d93-25df-4249-9654-9e7bae03c62b'
With [cbom] enabled = true, CipherFlag builds a CBOM for each [[cbom.scopes]] entry every push_interval (default "24h") and sends it to that scope's sinks. A scope selects hosts with host_patterns (wildcards) or host_ids, and can narrow them with asset_types and min_risk_score; host_patterns = ["*"] covers every host. With event_push_enabled = true, a scope is also re-sent when its assets are rescored, at most once per min_emit_interval (default "5m").
[cbom]
enabled = true
push_interval = "24h"
output_dir = "/app/config/cbom-out"
[[cbom.scopes]]
name = "all-hosts"
host_patterns = ["*"]
[[cbom.scopes.sinks]]
type = "file"
[cbom.scopes.sinks.file]
path_template = "{output_dir}/{scope}/{timestamp}.cdx.json"
[[cbom.scopes.sinks]]
type = "syslog"
[cbom.scopes.sinks.syslog]
protocol = "tls"
address = "siem.example.com:6514"
format = "rfc5424"
ca_file = "/app/config/siem-ca.pem"
| Sink | Settings |
|---|---|
file | path_template with {output_dir}, {scope} and {timestamp} |
http | url; auth = none, bearer or header; auth_ref names the environment variable holding the secret; auth_header_name for header auth |
s3 | bucket, region, prefix, endpoint_url for S3-compatible stores, content_encoding = "gzip"; credentials come from the standard AWS credential chain |
splunk | HTTP Event Collector url, token_ref (environment variable holding the HEC token), index, source, sourcetype, batch_size, tls_insecure |
syslog | protocol = udp, tcp or tls; address; format = rfc5424 or cef; facility; ca_file; cert_file with key_file for mutual TLS; tls_insecure |
By default file, http and s3 sinks receive the whole CBOM, while splunk and syslog receive one event per asset (granularity = "asset") or per finding (granularity = "finding"). Every sink accepts timeout (default 30s) and retries (default 3). Secrets named by auth_ref and token_ref are read from the server's environment; with Docker, add them to the cipherflag service's environment in a docker-compose.override.yml.
CipherFlag can push certificates to Venafi TLS Protect Cloud or to an on-premises Venafi Trust Protection Platform (TPP). It pushes the certificates it holds the PEM encoding for, such as those submitted with RawPEM through the ingest API, and sends new and changed ones in batches of 100 each push interval. A failed certificate is retried with exponential backoff; after five failures it is dead-lettered and no longer retried. Push is off by default.
[export.venafi]
enabled = true
platform = "cloud"
api_key = "<Venafi Cloud API key>"
region = "us" # "us" or "eu"
push_interval_minutes = 60
[export.venafi]
enabled = true
platform = "tpp"
base_url = "https://tpp.example.com"
client_id = "<OAuth client ID>"
refresh_token = "<OAuth refresh token>"
folder = "\\VED\\Policy\\Discovered\\CipherFlag"
push_interval_minutes = 60
Admins can change these settings under Settings → Venafi or with PUT /api/v1/venafi/config. The push scheduler applies them from its next cycle without a restart, and the new values are written to config/cipherflag.toml (see the note in section 4). The API changes only the fields you send; push_interval_minutes must be between 5 and 1440, and a TPP base_url must use HTTPS unless it points at localhost.
curl -b cf-session.txt -X PUT http://localhost:8443/api/v1/venafi/config \
-H 'Content-Type: application/json' \
-d '{"enabled":true,"platform":"cloud","region":"us","api_key":"<key>","push_interval_minutes":60}'
curl -b cf-session.txt -X POST http://localhost:8443/api/v1/venafi/test-connection
curl -b cf-session.txt http://localhost:8443/api/v1/venafi/status
# {"enabled":true,"last_push_at":…,"pending":…,"pushed":…,"failed":…,"dead_lettered":…,"next_push_at":…}
GET /api/v1/venafi/config returns the current settings, with has_api_key and has_refresh_token in place of the secrets.
CipherFlag CE runs as two containers:
The cipherflag container runs a single Go binary: the REST API, the web console (a SvelteKit app built into the binary), the ingester, the scorer, the CBOM exporter and the Venafi pusher. Every source feeds the same ingester, which resolves each submission to a host and deduplicates assets. With scoring on, each asset is graded against deterministic rules, classified for post-quantum readiness and evaluated against NIST SP 800-131A, CNSA 2.0, FIPS 140-3 and EU NIS2; CBOM exports are built from those results. PostgreSQL stores the inventory, and its notifications drive the console's live updates.
docker compose up fails with "port is already allocated" or "address already in use". Change the host (left-hand) side of the mapping in docker-compose.yml, for example "9443:8443", run docker compose up -d again and open http://localhost:9443. Leave the container side at 8443, which matches [server] listen.
Use http://. Port 8443 serves plain HTTP.
docker compose ps -a shows it as Exited. The last FTL line of docker compose logs cipherflag names the cause:
failed to connect to database: postgres_url is wrong. Inside Compose the host is postgres and the port 5432, and the password must match POSTGRES_PASSWORD.invalid ct_crtsh domain config (or ct_static, ct_certspotter, ct_multi): fix the named entry. Domains must be lowercase.[cbom.signing] is enabled but its key cannot be loaded: fix path or env_var, or set enabled = false.failed to load config: the TOML does not parse, for example because a section is declared twice.The compose file sets no restart policy, so start the container again with docker compose up -d after fixing the cause.
The cipherflag container waits for the postgres health check. Check docker compose ps and docker compose logs postgres. If you changed POSTGRES_PASSWORD after the first start, the existing pg-data volume still has the old password: set it back, or start over with docker compose down -v, which deletes all CipherFlag data.
{"error":"authentication required"} means the request had no credentials; {"error":"invalid or revoked agent token"} means the token is wrong or revoked. Send exactly Authorization: Bearer <token>.
{"error":"admin access required"}: CBOM import, user and agent-token management and Venafi settings need an admin session. Agent tokens and viewers are refused. Immediately after the first admin account is created, the server can take up to a minute to switch to authenticated mode, and admin-only calls return 403 during that window; wait a minute and retry.
Your session is missing or has expired. Go to http://localhost:8443/login and sign in.
Set [analysis] scorer_enabled = true and restart (section 4). For GET /api/v1/export/cbom, also choose hosts with hostname_pattern, host_id or scope, or use /api/v1/export/cbom/estate for everything.
Check the item field names: RawPEM and FingerprintSHA256, not raw_pem. The server logs cert has neither FingerprintSHA256 nor parseable RawPEM, skipping for each certificate it drops.
Check GET /api/v1/venafi/status for failed and dead-lettered certificates, and run Test Connection in Settings → Venafi (or POST /api/v1/venafi/test-connection). For TPP, make sure the refresh token has not been revoked. Only certificates CipherFlag holds the PEM for are pushed.
See the changelog and the repository on GitHub, or open an issue.