Configuration
Layered config, fail mode, policy-as-code, sensitive paths, the package-manager nudge, auto-quarantine, and the local corpus.
Beekeeper is configured through a layered JSON config, declarative policy files,
and a small set of beekeeper config set keys. This page covers each layer, the
fail-closed posture, sensitive-path enforcement, and the package-manager nudge.
Config file location
| OS | Config file |
|---|---|
| Linux / macOS | ~/.beekeeper/config.json |
| Windows | %APPDATA%\beekeeper\config.json |
Layered configuration
Config is merged from four layers, each overriding the one before it:
- System: machine-wide defaults
- User:
~/.beekeeper/config.json(or%APPDATA%\beekeeper\config.json) - Project: a
.beekeeper/config.jsondiscovered by walking up from the working directory - Environment:
BEEKEEPER_*environment variables
The project layer is the lowest-trust file layer: it is exactly the
agent-cloned repository Beekeeper exists to police. It is still honored for most
settings, with one hardening exception: a project-layer attempt to disable the
nudge ({"nudge":{"enabled":false}}) is refused, so an untrusted repo cannot
silently turn off supply-chain enforcement. See the caveat below for the fail-mode
risk that is not refused.
Fail mode
Beekeeper's default posture is fail-closed (fail_closed): any crash,
timeout, oversized input, or missing/corrupt index in beekeeper check or the
gateway results in a block, not an allow. Opting out (fail_mode: open,
historically written fail_open) is explicit and reduces security:
{
"fail_mode": "open"
}Security-relevant caveat. Because the project layer is merged above user config, a project-local
.beekeeper/config.json(or a dependencypostinstallthat writes one) containing{"fail_mode":"open"}converts every fail-closed safety net into fail-open for that working tree. Treat the project.beekeeper/config.jsonas security-relevant, and do not run agents in untrusted repositories with project-config discovery enabled if you rely on a fail-closed posture (see Security).
Background catalog sync
Threat-intel freshness can run on its own. Alongside the manual
beekeeper catalogs sync, an unprivileged per-user daemon syncs on an interval.
The default config block:
{
"catalog_sync": {
"enabled": true,
"interval": "2h"
}
}enabled(defaulttrue) controls whether the installed schedule runs.interval(default2h) is clamped to a 2h to 24h range; an out-of-range or unparseable value is clamped fail-safe rather than disabling sync.
Install the schedule with beekeeper catalogs daemon install (a systemd user
timer, a macOS LaunchAgent, or a Windows current-user scheduled task, all
unprivileged). Sync uses conditional ETag requests, so an unchanged feed is nearly
free.
Like the nudge, catalog_sync is self-defended against the project layer: an
untrusted repository's .beekeeper/config.json cannot disable it or loosen the
interval. Only tightening the cadence from a project layer is honored, so an agent
cannot slow down or switch off threat-intel freshness from inside a repo.
Auto-quarantine (first-responder loop)
When the background sync produces a catalog delta, Beekeeper runs a read-only
cross-reference of installed packages against the updated index. The
auto_quarantine block controls what happens when a scan hit reaches the
corroboration threshold:
{
"auto_quarantine": {
"enabled": false,
"dry_run": true,
"threshold": 2
}
}enabled(defaultfalse) is the opt-in gate. A fresh install never quarantines anything automatically.dry_run(defaulttrue) controls whether a threshold hit produces a real quarantine move or only an audit record. Set bothenabled: trueanddry_run: falseto activate live moves.threshold(default2, clamped to[1, 3]) is the minimum number of independent catalog sources required to trigger a quarantine move. A zero or absent value resolves to the default2, not the floor1. An out-of-range value (for example5) is rejected at load time fail-closed, not silently clamped, so a misconfigured threshold never weakens the posture.
There is no beekeeper config set command for auto_quarantine keys. Edit
config.json directly, then confirm the value loads cleanly with
beekeeper diag.
What auto-quarantine does (and does not do)
When enabled and not in dry-run mode, and a scan hit reaches the threshold with a known on-disk path, Beekeeper moves the artifact to the quarantine directory as a reversible directory rename plus a restore manifest. It then writes an audit record and surfaces a TUI incident.
If the installed path cannot be resolved, a pending-quarantine record is
written rather than guessing the path. No partial deletes happen on failure.
The destructive purge is never automatic. The TUI incident surfaces [P] purge (permanent) and [R] restore options. The CLI purge command requires a y/N confirmation. See Security for the full first-responder design, including the catalog-to-Sentry targeted trace that runs alongside the quarantine path.
Cross-reference with catalog_sync
The cross-reference only runs when a sync produces a delta. The catalog_sync
interval (default 2h) is therefore the outer cadence. Tightening the sync
interval tightens how quickly a newly-flagged installed package is noticed.
Corpus (local incident record)
The corpus block controls the local, append-only record of confirmed incidents
that drives the first-responder feedback loop (see Security).
It writes nothing off the machine.
{
"corpus": {
"enabled": false,
"path": "",
"downstream_clean_days": 30,
"scope": "org_only"
}
}enabled(defaultfalse) turns the corpus store on. While off, no corpus file is written.path(default empty) overrides the corpus file location. When empty, Beekeeper uses<state-dir>/corpus/beekeeper-corpus.ndjson, owner-only (0600).downstream_clean_days(default30) is the rolling window the adjudication engine waits before labelling an allowed package benign when no follow-on incident with the same cluster appears. It is the weakest benign signal and never reachesenforceweight.scope(defaultorg_only) is the record scope.community_shareableis reserved for a future milestone and has no effect in this release; promotion to it always returns an error.
Like auto_quarantine, there is no beekeeper config set command for corpus
keys, so edit config.json directly. The corroboration threshold that gates the
corpus-driven Sentry elevation is the same two-source bar used elsewhere; it is
not a separate config key.
Policy-as-code
Declarative policy files live in ~/.beekeeper/policies/*.json (owner-only,
0600). Validate, dry-run, and list them:
beekeeper policy validate ~/.beekeeper/policies/my-policy.jsonbeekeeper policy test ~/.beekeeper/policies/my-policy.json --tool-call ./call.jsonbeekeeper policy listA package_allowlist rule with "action":"allow" is an escape hatch: it can
override a catalog-corroborated block for the exact listed package, useful for an
internal package a fresh catalog source misflags. Every allowlist-override
decision is recorded in the audit Reason field, so it is forensically visible.
Treat ~/.beekeeper/policies/ as part of your security-relevant configuration.
Policy files may declare release_age (minimum package age) and
lifecycle_script_allowlist rules, but these two rule types are not enforced
by the policy overlay in this release; they require package-publication-age and
lifecycle-script metadata that is not present in a pure tool call. They are
accepted for documentation and beekeeper policy test dry-runs only, and must
not be relied upon for live enforcement. The engine's built-in catalog-side
release-age handling remains the enforcement path.
Sensitive paths
Beekeeper blocks agent reads (and shell-redirect writes) of credential and
secret paths outside the project working directory. The default blocklist
(DefaultSensitivePaths) covers:
~/.ssh,~/.aws,~/.cargo/credentials.envand.env.*globs- Editor MCP config directories (Cursor, Windsurf)
with normalization for Windows alternate data streams and trailing-dot tricks.
Policy files can add sensitive_path rules; a sensitive-path block is merged
most-restrictive-wins and can never be downgraded by a package_allowlist
allow.
Package-manager nudge
The nudge steers agents away from npm / yarn toward pnpm (≥ 11) or bun
(≥ 1.3), which ship structural supply-chain defenses npm does not. The default
config block:
{
"nudge": {
"enabled": true,
"mode": "soft",
"require_hardened": false,
"preferred": "pnpm",
"check_socket_scanner": true,
"major_drift_check": { "enabled": true, "interval": "168h" },
"version_floors": { "pnpm": "11.0.0", "bun": "1.3.0", "node": "22.0.0" }
}
}Modes
nudge.mode accepts three values (any other value is rejected fail-closed by the
config validator):
| Mode | Behavior |
|---|---|
soft | Default. Emit an advisory recommending pnpm/bun, then allow the original npm install to proceed (exit 0). |
hard | Rewrite the command to its pnpm/bun equivalent and surface the rewrite to the agent (advisory: Beekeeper does not execute it). |
block | Deny npm / yarn installs when a hardened PM is available, telling the agent to use pnpm/bun instead. Detection-independent supply-chain enforcement. |
When you run beekeeper hooks install, Beekeeper enables mode: block on first
install (installing the hook is an explicit "protect this machine" action). To
opt down to advisory-only:
beekeeper config set nudge.mode softSettable keys
beekeeper config set is scoped to five nudge keys (each validated fail-closed,
each change written to the audit log as a config_change record):
beekeeper config set nudge.enabled truebeekeeper config set nudge.mode hardbeekeeper config set nudge.require_hardened truebeekeeper config set nudge.preferred bunbeekeeper config set nudge.check_socket_scanner falserequire_hardened: true is a separate blocking trigger from mode: block: it
denies an npm install when no hardened package manager is installed at all
(whereas mode: block denies npm/yarn when pnpm/bun is available and steers to
it). Both default off in the shipped library; mode: block is turned on by the
hook installer.
Package-manager detection is fail-open by design: a slow or missing
pnpm/bun/nodebinary is treated as "not installed" so an advisory never blocks a legitimate install.mode: blockenforcement, by contrast, is detection-independent and does not fail open.
See the CLI Reference for beekeeper nudge status /
check / audit.