Configuration Reference
Every config primitive blacklight reads. Environment variables, on-disk config files, the state.json schema, and the /var/lib/bl/ layout. Authoritative against v0.6.0.
At a glance
| Layer | Path | Owner | Purpose |
|---|---|---|---|
| Secrets | $ANTHROPIC_API_KEY (env) | operator shell | Sole API credential. No file on disk. |
| Operator config | /etc/blacklight/blacklight.conf | operator | Allowlisted key=value runtime config. |
| Notification creds | /etc/blacklight/notify.d/* | operator | Slack/email/Telegram/Discord secrets, mode 0600. |
| Hook adapters | /etc/blacklight/hooks/ | installer | bl-lmd-hook shim called by maldet post_scan_hook. |
| Workspace state | /var/lib/bl/state/state.json | bl setup | Authoritative cache of agent/env/skill/file IDs. |
| Per-key fallbacks | /var/lib/bl/state/{agent-id,env-id,case.current,...} | bl setup (legacy) | Pre-M15 per-key files; retained as fallback reads. |
| Ledger | /var/lib/bl/ledger/<case>.jsonl | bl run / bl defend / bl clean | Append-only local audit, FD-200 flock. |
| Backups | /var/lib/bl/backups/ | bl clean | Pre-edit snapshots for --undo. |
| Quarantine | /var/lib/bl/quarantine/<case>/ | bl clean file | Files moved here, never unlinked. |
| FP baseline | /var/lib/bl/fp-corpus/ | operator-seeded | Benign samples the signature gate scans. |
| Outbox | /var/lib/bl/outbox/ | rate-limit queue | Wake / signal_upload / action_mirror records. |
/var/lib/bl/ is wipeable without data loss; authoritative state lives in the Anthropic workspace.
Environment variables
Required
| Variable | Default | Purpose |
|---|---|---|
ANTHROPIC_API_KEY | — | The only secret. Workspace scope = blast radius. Operators provision dedicated workspaces for production use. |
Path overrides
| Variable | Default | Purpose |
|---|---|---|
BL_VAR_DIR | /var/lib/bl | Local state root. Useful for non-root install. |
BL_STATE_DIR | $BL_VAR_DIR/state | Workspace state cache. |
BL_PREFIX | /usr/local | Install prefix used by install.sh. |
BL_LMD_CONF_PATH | /usr/local/maldetect/conf.maldet | LMD conf file edited by bl setup --install-hook lmd. |
BL_REPO_ROOT | (cwd if skills/ present) | Source-of-truth tree for bl setup --sync. |
BL_REPO_URL | https://github.com/rfxn/blacklight | Override for the shallow-clone fallback in --sync. |
Behaviour switches
| Variable | Default | Purpose |
|---|---|---|
BL_DISABLE_LLM | 0 | Set 1 to short-circuit every LLM call (curator + summary + FP gate). The full BATS suite exports this. |
BL_UNATTENDED | (auto: 1 when no TTY) | Force unattended mode. Tier-gated steps queue for the operator instead of prompting. |
BL_LIVE | 0 | Set 1 to enable tests/live/*.bats against a real Anthropic workspace. |
BL_EVAL_LIVE | 0 | Set 1 to enable the live skill-routing eval driven by bl setup --eval. |
BL_BRIEF_MIMES | text/markdown,text/html,application/pdf | Output formats bl case close requests; set text/markdown to skip the curator-side render. |
BL_OUTBOX_AGE_WARN_SECS | 3600 | Age threshold (seconds) before age-gated outbox drain runs in preflight. |
BL_DEFEND_FP_CORPUS | $BL_VAR_DIR/fp-corpus | FP-baseline directory the signature gate scans. |
BL_DEFEND_FW_ALLOW_BROAD_IP | 0 | Override for the CIDR /16 floor on bl defend firewall. Site-level setter is the conf file (item 15 on the roadmap). |
BL_PATH_ALLOWLIST | /var/www,/home,/etc/apache2,/etc/httpd | Comma-list of path roots bl observe and bl clean accept. |
BL_CURL_TRACE_LOG | (unset) | If set to a file path, every 2xx Anthropic response body is appended for cost / debug introspection. |
Test-only
| Variable | Default | Purpose |
|---|---|---|
BL_TEST_MODE | 0 | Internal flag; set by tests/helpers/curator-mock.bash. |
XDG_CACHE_HOME | ~/.cache | Used by bl setup for the BL_REPO_URL shallow-clone destination ($XDG_CACHE_HOME/blacklight/repo). |
/etc/blacklight/blacklight.conf
Loaded by _bl_load_blacklight_conf (30-preflight.sh) on every non-help, non-setup invocation. Allowlisted keys only — unknown keys log a warning and are dropped. Values are simple key="value" strings (shell-source-able; metacharacter rejection runs before export).
The allowlist grew from 7 → 22 keys in the v0.5.2 operator-config-tree expansion (the landed default config previously exposed only 7 of ~22 runtime-effective BL_* env knobs; an operator could not configure log verbosity, the LLM kill switch, defend's ASN/CDN/CIDR-floor levers, clean's TTL/grace, observe's journal cap, scanner sig dirs, or the alt skill repo without setting env vars per invocation). New keys ship commented-out at their source defaults so unconfigured hosts see no behaviour change.
# /etc/blacklight/blacklight.conf (excerpt — abbreviated)
# Substrate hook flow
unattended_mode="0"
# Logging
#log_level="info" # debug|info|warn|error
# LLM kill switch (cost-control / disconnected / CI without API key)
#disable_llm=""
# Notification channels (alert_lib substrate)
notify_channels_enabled="slack,syslog"
notify_severity_floor="info" # info|warn|critical
#notify_dir="/etc/blacklight/notify.d"
# LMD trigger
lmd_trigger_dedup_window_hours="24"
lmd_conf_path="/usr/local/maldetect/conf.maldet"
# cPanel lock-in
cpanel_lockin="auto" # auto|force|off
cpanel_lockin_timeout_seconds="60"
#cpanel_dir="/usr/local/cpanel"
# Defend tunables
#defend_extra_cdn_asns="" # space-separated ASN list
#defend_fw_allow_broad_ip="" # bypass /16 floor — audit before flipping
#defend_fp_corpus="/var/lib/bl/fp-corpus"
#defend_asn_cache="/var/cache/bl/asn"
# Clean tunables
#clean_dryrun_ttl_secs="300"
#clean_proc_grace_secs="5"
# Observe tunable
#obs_journal_max="10000" # journal-line cap per observe run
# Scanner sig dirs (only override for non-default scanner layouts)
#lmd_sig_dir="/usr/local/maldetect/sigs"
#clamav_sig_dir="/var/lib/clamav"
#yara_rules_dir="/etc/yara/rules"
# Alt skill source (air-gapped / fork-tracking)
#repo_url=""
Outbox tunables (watermarks, retry, drain batch / deadline) remain readonly constants in 27-outbox.sh — change at build time, not via conf.
Env-only knobs (cannot live in blacklight.conf due to bootstrap order or runtime semantics): BL_VAR_DIR, BL_BLACKLIGHT_DIR, BL_BLACKLIGHT_CONF, BL_HOST_LABEL, BL_INVOKED_BY, BL_UNATTENDED, BL_UNATTENDED_FLAG.
Allowlist enforcement
_bl_load_blacklight_conf rejects any key not in its allowlist with a warning, never a hard failure — a malformed conf does not lock the operator out of bl --help. Metacharacter rejection runs before any value is exported into the running shell.
/etc/blacklight/notify.d/
Per-channel notification credentials, one file per channel, mode 0600 (enforced by bl setup --import-from-lmd). Files are sourced by bl_notify (28-notify.sh). Layout under /etc/blacklight/notify.d/:
slack—SLACK_WEBHOOK_URL=...email—MAIL_TO=... MAIL_FROM=... SMTP_HOST=... SMTP_USER=...telegram—TELEGRAM_BOT_TOKEN=... TELEGRAM_CHAT_ID=...discord—DISCORD_WEBHOOK_URL=...
bl setup --import-from-lmd reads the equivalent keys from conf.maldet and writes them here verbatim, with metacharacter rejection on every imported value.
/etc/blacklight/hooks/
Hook-fired adapters. install.sh drops bl-lmd-hook here; bl setup --install-hook lmd wires it into conf.maldet's post_scan_hook= line.
bl-lmd-hook— exec'd by maldet on quarantine; callsbl trigger lmd --scanid <id>.
The shim is intentionally minimal — it exists so install.sh can be read-only against conf.maldet (operator opts in to the wiring via the explicit setup verb).
state.json schema
Authoritative cache of the workspace blacklight provisioned. Single JSON document at $BL_STATE_DIR/state.json, written atomically (mv from .tmp.$$) under flock -x 203 on state.json.lock. Pre-M15 per-key files are retained as fallback reads only.
{
"schema_version": 1,
"agent": {
"id": "agent_xxx",
"version": 7 // CAS field for POST /v1/agents/<id>
},
"env_id": "env_xxx",
// Routing Skills (Path C). Pinned at session creation.
"skills": {
"synthesizing-evidence": { "skill_id": "skl_xxx", "version": 3, "description_sha256": "...", "body_sha256": "..." },
"prescribing-defensive-payloads": { "skill_id": "skl_xxx", "version": 2, ... },
// 4 more
},
// Workspace reference Files mounted at /skills/<basename> on session create.
"files": {
"/skills/foundations.md": { "file_id": "file_xxx", "content_sha256": "..." },
"/skills/substrate-context-corpus.md": { "file_id": "file_xxx", "content_sha256": "..." }
// 6 more
},
// GC queue. Walked by bl setup --gc; deleted only when no live session refs.
"files_pending_deletion": [
{ "file_id": "file_old_xxx", "queued_at": "2026-04-26T03:14:00Z", "reason": "corpus-replaced" }
],
// Per-case memory store handles. _default seeds preflight's BL_MEMSTORE_CASE_ID.
"case_memstores": {
"_default": "memstore_bl_case_xxx",
"CASE-2026-0017": "memstore_bl_case_xxx"
},
// Per-case Files (evidence bundles + closed-case briefs). Hot-attached.
"case_files": {
"CASE-2026-0017": {
"evidence/bundle-host-2026-04-26.tgz": { "workspace_file_id": "file_xxx", "content_sha256": "..." }
}
},
"case_id_counter": { "2026": 17 },
"case_current": "CASE-2026-0017",
// One session per case. Resumable across the 30-day checkpoint window.
"session_ids": {
"CASE-2026-0017": "sess_xxx"
},
// M14 trigger registry.
"triggers": {
"lmd": { "installed": true, "installed_at": "2026-04-26T01:02:00Z" }
},
"last_sync": "2026-04-26T16:14:00Z"
}
Migration safety: first-run on a pre-M15 install reads each per-key file, writes the JSON, and stamps a recovery backup at state/migration-backup-<epoch>/.
/var/lib/bl/ layout
Six directories under /var/lib/bl/:
state/state.json— primary; schema abovestate.json.lock— FD 203,bl setup --syncserializationcase-id-counter— FD 201, flock'd YYYY/NNNN allocatorcase.current— current-case pointer for the shellagent-id— cached agent id (legacy fallback read)session-<case>— per-case session id (legacy mirror)
ledger/<case>.jsonl— FD 200, append-only audit, dual-write target
backups/<ISO-ts>.<hash>.<basename>— pre-edit snapshot forbl clean --undo
quarantine/<case>/<sha256>-<basename>—bl clean filelanding zone, never unlinked
fp-corpus/— operator-seeded benign-sample baseline, FP gate scansoutbox/— FD 202, rate-limit queue (wake / signal / mirror)
| File / dir | Permissions | Notes |
|---|---|---|
state/state.json | 0644 | Atomic write via tmp + rename. |
state/*.lock | 0644 | Lockfiles only; never read for content. |
ledger/<case>.jsonl | 0644 | Append-only; flock FD-200 protects concurrent writers. |
backups/* | 0644 | One file per pre-edit snapshot. |
quarantine/<case>/* | 0600 | Restricted; suspicious content. |
fp-corpus/* | 0644 | Operator-controlled. |
outbox/*.json | 0644 | One file per queued record. |
Configuration precedence
When the same setting is reachable through multiple surfaces, blacklight picks in this order (highest wins):
- Per-invocation flag on the verb (
--unsafe,--yes,--unattended,--source-conf, etc.). BL_*environment variable in the calling shell./etc/blacklight/blacklight.confallowlisted key.- Source-tree default (compiled into
bl).
This matters for unattended detection: --unattended on a single invocation overrides the conf-file unattended_mode=0; BL_UNATTENDED=1 exported in the shell overrides both ways.
For paths under BL_VAR_DIR, the env override is the only surface (no conf-file equivalent until roadmap item 15 ships).
See also
- Setup Flow — how
state.jsongets populated. - CLI Reference — every verb that reads or writes these surfaces.
- Security Model · Auth surface —
ANTHROPIC_API_KEYblast-radius detail. - Roadmap item 15 — pending conf-file keys.