Skip to main content
reference·Doc 02 of 13

Configuration Reference

Every config primitive bl reads. Environment variables (ANTHROPIC_API_KEY + BL_* overrides), /etc/blacklight/blacklight.conf allowlist, the state.json schema, /var/lib/bl/ layout, and the precedence rules between them.

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

LayerPathOwnerPurpose
Secrets$ANTHROPIC_API_KEY (env)operator shellSole API credential. No file on disk.
Operator config/etc/blacklight/blacklight.confoperatorAllowlisted key=value runtime config.
Notification creds/etc/blacklight/notify.d/*operatorSlack/email/Telegram/Discord secrets, mode 0600.
Hook adapters/etc/blacklight/hooks/installerbl-lmd-hook shim called by maldet post_scan_hook.
Workspace state/var/lib/bl/state/state.jsonbl setupAuthoritative 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>.jsonlbl run / bl defend / bl cleanAppend-only local audit, FD-200 flock.
Backups/var/lib/bl/backups/bl cleanPre-edit snapshots for --undo.
Quarantine/var/lib/bl/quarantine/<case>/bl clean fileFiles moved here, never unlinked.
FP baseline/var/lib/bl/fp-corpus/operator-seededBenign samples the signature gate scans.
Outbox/var/lib/bl/outbox/rate-limit queueWake / signal_upload / action_mirror records.

/var/lib/bl/ is wipeable without data loss; authoritative state lives in the Anthropic workspace.


Environment variables

Required

VariableDefaultPurpose
ANTHROPIC_API_KEYThe only secret. Workspace scope = blast radius. Operators provision dedicated workspaces for production use.

Path overrides

VariableDefaultPurpose
BL_VAR_DIR/var/lib/blLocal state root. Useful for non-root install.
BL_STATE_DIR$BL_VAR_DIR/stateWorkspace state cache.
BL_PREFIX/usr/localInstall prefix used by install.sh.
BL_LMD_CONF_PATH/usr/local/maldetect/conf.maldetLMD 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_URLhttps://github.com/rfxn/blacklightOverride for the shallow-clone fallback in --sync.

Behaviour switches

VariableDefaultPurpose
BL_DISABLE_LLM0Set 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_LIVE0Set 1 to enable tests/live/*.bats against a real Anthropic workspace.
BL_EVAL_LIVE0Set 1 to enable the live skill-routing eval driven by bl setup --eval.
BL_BRIEF_MIMEStext/markdown,text/html,application/pdfOutput formats bl case close requests; set text/markdown to skip the curator-side render.
BL_OUTBOX_AGE_WARN_SECS3600Age threshold (seconds) before age-gated outbox drain runs in preflight.
BL_DEFEND_FP_CORPUS$BL_VAR_DIR/fp-corpusFP-baseline directory the signature gate scans.
BL_DEFEND_FW_ALLOW_BROAD_IP0Override 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/httpdComma-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

VariableDefaultPurpose
BL_TEST_MODE0Internal flag; set by tests/helpers/curator-mock.bash.
XDG_CACHE_HOME~/.cacheUsed 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/:

  • slackSLACK_WEBHOOK_URL=...
  • emailMAIL_TO=... MAIL_FROM=... SMTP_HOST=... SMTP_USER=...
  • telegramTELEGRAM_BOT_TOKEN=... TELEGRAM_CHAT_ID=...
  • discordDISCORD_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; calls bl 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 above
    • state.json.lock — FD 203, bl setup --sync serialization
    • case-id-counter — FD 201, flock'd YYYY/NNNN allocator
    • case.current — current-case pointer for the shell
    • agent-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 for bl clean --undo
  • quarantine/
    • <case>/<sha256>-<basename>bl clean file landing zone, never unlinked
  • fp-corpus/ — operator-seeded benign-sample baseline, FP gate scans
  • outbox/ — FD 202, rate-limit queue (wake / signal / mirror)
File / dirPermissionsNotes
state/state.json0644Atomic write via tmp + rename.
state/*.lock0644Lockfiles only; never read for content.
ledger/<case>.jsonl0644Append-only; flock FD-200 protects concurrent writers.
backups/*0644One file per pre-edit snapshot.
quarantine/<case>/*0600Restricted; suspicious content.
fp-corpus/*0644Operator-controlled.
outbox/*.json0644One file per queued record.

Configuration precedence

When the same setting is reachable through multiple surfaces, blacklight picks in this order (highest wins):

Configuration precedence ladder, top to bottom: per-invocation flag, BL_* environment variable, /etc/blacklight/blacklight.conf allowlist key, source-tree default. Highest layer that defined the setting wins.

  1. Per-invocation flag on the verb (--unsafe, --yes, --unattended, --source-conf, etc.).
  2. BL_* environment variable in the calling shell.
  3. /etc/blacklight/blacklight.conf allowlisted key.
  4. 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