ℹ

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.

On this page
  1. Prerequisites
  2. Install
  3. First login & admin account
  4. Configuration
  5. Add discovery sources
  6. Using the console
  7. Export and sign a CBOM
  8. Venafi integration
  9. Architecture
  10. Troubleshooting

1 Prerequisites

For the Docker Compose install:

To build from source instead:

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.

2 Install

Option A: Docker Compose (recommended)

git clone --branch v2.3.0 https://github.com/net4n6-dev/cipherflag.git
cd cipherflag
docker compose up -d

Compose starts two containers:

ServiceImageHost portRole
postgrespostgres:165433 → 5432Database; data is kept in the pg-data volume
cipherflagghcr.io/net4n6-dev/cipherflag-ce:2.3.08443 → 8443API 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.

Option B: Build from source

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.

3 First login & admin account

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.

Agent tokens for unattended ingest

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.

4 Configuration

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:

SectionWhat 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)

Turn on scoring

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.

Command-line reference

With Docker, run commands inside the running container: docker compose exec cipherflag ./cipherflag <command>.

CommandPurpose
serveStart the API server and console (applies migrations first)
migrateApply pending database migrations
versionPrint 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 | importPer-application data-retention metadata used by the harvest-now-decrypt-later analysis
ownership declare | import | backfillRecord 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, seedPrint a configuration banner; no-op (CE ships no sample dataset). CE has no interactive setup.

5 Add discovery sources

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.

osquery via Fleet

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.

Ingest API

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 scripts

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.

Trust-store scan

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

Endpoint connectors

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.

ConnectorSectionCollectsMain settings
Microsoft Defender for Endpoint[sources.defender]Crypto-library inventory through the Advanced Hunting APItenant_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 changesbase_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>"

Certificate Transparency

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.

SourceReads fromNotes
ct_crtshcrt.shHistorical and new certificates
ct_certspotterSSLMate CertSpotter APIOptional api_token; requests_per_hour caps the request rate (0 = default)
ct_staticStatic CT API (Sunlight) logsVerifies the log's signed checkpoint and each certificate's inclusion; watches forward only
ct_multiTwo or more of the above for one domainCombines 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.

6 Using the console

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

Dashboard

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.

Certificates

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.

PKI Constellation

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.

Analytics

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

Reports

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.

Live updates

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.

Settings

Users (admins only), Venafi (section 8), System status, and Profile, where you change your password.

7 Export and sign a CBOM

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.

EndpointReturns
GET /api/v1/export/cbom/estateEvery scored asset, as cipherflag-cbom-estate-<date>.cdx.json
GET /api/v1/export/cbomAssets 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'

Sign CBOMs

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 a signed CBOM

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 codeMeaning
0Signature valid (and, with --trusted-key, made by that key)
1Signature valid, but made by a different key than --trusted-key
2Invalid: the BOM changed after signing, or the signature block is missing or malformed
3Could 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.

Import a CBOM

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'

Scheduled push and sinks

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"
SinkSettings
filepath_template with {output_dir}, {scope} and {timestamp}
httpurl; auth = none, bearer or header; auth_ref names the environment variable holding the secret; auth_header_name for header auth
s3bucket, region, prefix, endpoint_url for S3-compatible stores, content_encoding = "gzip"; credentials come from the standard AWS credential chain
splunkHTTP Event Collector url, token_ref (environment variable holding the HEC token), index, source, sourcetype, batch_size, tls_insecure
syslogprotocol = 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.

8 Venafi integration

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.

Venafi Cloud

[export.venafi]
enabled = true
platform = "cloud"
api_key = "<Venafi Cloud API key>"
region = "us"                  # "us" or "eu"
push_interval_minutes = 60

Venafi TPP

[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

Change settings without a restart

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.

9 Architecture

CipherFlag CE runs as two containers:

Discovery sources osquery via Fleet · POST /api/v1/ingest · endpoint connectors Certificate Transparency · CBOM import · scan-truststore │ ▼ ┌─────────────────────────────────────┐ ┌───────────────────┐ │ cipherflag :8443 HTTP │ │ postgres │ │ │ │ │ │ Ingest host resolution, dedup │───────▶│ PostgreSQL 16 │ │ Scoring grades, PQC, compliance │◀───────│ pg-data volume │ │ Export CBOM, signing, sinks │ │ host port 5433 │ │ Venafi TPP / Cloud push │ └───────────────────┘ │ Console web UI, REST API, SSE │ └─────────────────────────────────────┘ │ ▼ Outputs CycloneDX 1.6 CBOMs → download, file, HTTP, S3, Splunk HEC, syslog Certificates → Venafi TPP / Cloud

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.

10 Troubleshooting

Port 8443 or 5433 is already in use

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.

The browser cannot connect, or reports an SSL error

Use http://. Port 8443 serves plain HTTP.

The cipherflag container exits

docker compose ps -a shows it as Exited. The last FTL line of docker compose logs cipherflag names the cause:

The compose file sets no restart policy, so start the container again with docker compose up -d after fixing the cause.

PostgreSQL is not healthy

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.

401 from the ingest endpoints

{"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>.

403 on import or other admin calls

{"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.

The console shows "API error: 401"

Your session is missing or has expired. Go to http://localhost:8443/login and sign in.

No grades, or the CBOM has no components

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.

Ingest returns 200 but no certificate appears

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.

Venafi push is not happening

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.

Need more help?

See the changelog and the repository on GitHub, or open an issue.