Configuration
jawbd's config.toml: the purchase gate, approvals, and local-network access.
jawbd reads one optional TOML file. It sets whether purchases need
approval, how purchases are detected, how approvals are collected, and a
default for local-network access.
Where the file is
<data_dir>/config.toml, next to daemon.token and your profiles:
| OS | Default path |
|---|---|
| Windows | %LOCALAPPDATA%\jawb\config.toml |
| Linux | $XDG_DATA_HOME/jawb/config.toml, else ~/.local/share/jawb/config.toml |
| macOS | ~/.local/share/jawb/config.toml unless XDG_DATA_HOME is set |
There is no separate override variable. A different --data-dir or
JAWB_DATA_DIR moves the config file with everything else, so a scratch
daemon never reads your real configuration.
The file is optional: without it, every key has its default. jawbd
creates it the first time you change a key (jawb config set, or the API
below), with a first line that links back to this page. It reads the
file when it starts and when you ask it to reload (below); it does not
watch the file, so a hand edit takes effect on the next reload or start.
A broken file never stops the daemon and never makes it weaker. An
unparseable file gives every key its default and the warning
W_CONFIG_INVALID (with line and column). An unknown key is ignored
with W_CONFIG_UNKNOWN_KEY. A known key with a bad value takes its
default, with W_CONFIG_INVALID. The warnings appear in
GET /v1/status. No flag or environment variable overrides a key.
# jawb configuration. Docs: https://jawb.app/docs/config
version = 1
[purchase]
gate = "approve" # deny | approve | allow
detection = "strict" # strict | standard
[approval]
assurance = "verified-if-available"
channels = ["desktop", "prompt", "terminal"]
timeout_s = 120
[egress]
allow_private_default = false
[profiles.shopping.purchase]
gate = "deny" # this profile never buys
Keys
| Key | Values | Default | Safety key |
|---|---|---|---|
version | 1 | 1 | no |
purchase.gate | deny, approve, allow | approve | yes |
purchase.detection | strict, standard | strict | yes |
approval.assurance | verified, verified-if-available, asserted | verified-if-available | yes |
approval.channels | an ordered list drawn from desktop, prompt, terminal, no duplicates; may be empty | ["desktop", "prompt", "terminal"] | no |
approval.timeout_s | integer, 30 to 600 | 120 | no |
egress.allow_private_default | true or false | false | no |
profiles.<name>.purchase.gate | as purchase.gate | unset: the profile uses the global value | yes |
profiles.<name>.purchase.detection | as purchase.detection | unset: the profile uses the global value | yes |
purchase.gateandpurchase.detection: see the purchase gate. A profile's value overrides the global one; incognito sessions use the global one.approval.assurance: whether approvals must be verified by the operating system. See assurance labels.approval.channels: the order in which approval channels are tried. A channel only requests verification;approval.assurancedecides whether verification is required. An empty list means nothing can approve, which has the same effect asdeny.approval.timeout_s: how long a held action or setting change waits for a human.egress.allow_private_default: whether a public page may reach local and private-network addresses (the--allow-privaterule) for a profile that has no setting of its own, and for incognito sessions. A profile's own setting (jawb profile settings) wins. The addresses that are always refused (link-local, cloud metadata,jawbd's own control port) stay refused whatever this says.
The floor rule
For each safety key, the floor is its built-in default.
- A file value equal to or stronger than the default applies immediately.
The order, strongest first, is
deny>approve>allowforpurchase.gate;strict>standardforpurchase.detection; andverified>verified-if-available>assertedforapproval.assurance. - A value weaker than the default is unconfirmed. The default stays in
force, and
jawbdholds one pending confirmation of classconfig. A human approves or denies it through the same channels as a purchase (see Policy and confirmation). Once approved, the weaker values apply until the daemon exits or the file is reloaded with different values. A deny, or the timeout, leaves the defaults in force. - This happens on every daemon start with a weaker-than-default file. That
is deliberate: nothing stored on disk is trusted to say "a human
agreed", so an agent that can edit
config.tomland restart the daemon still cannot switch the gate off without a person.
Known limit. The floor is the default, not your last choice. If you
set purchase.gate = "deny", a program running as you can edit it back to
approve without asking, because approve is not below the floor. You
would still be asked about every purchase. And on a host with no OS
verifier (macOS and Linux today), the confirmation of a weakening is only
asserted, so a program running as you can confirm it.
GET /v1/status reports the effective purchase.gate and
purchase.detection, lists unconfirmed keys, and warns
W_CONFIG_UNCONFIRMED while any are waiting and W_PURCHASE_UNGATED while
the effective gate is allow.
The /v1/config API
Every route needs the bearer token and refuses an Origin header, like all
/v1 routes (daemon API level 11 or later).
| Route | Body | Result |
|---|---|---|
GET /v1/config | none | {path, version, keys, pending, warnings}. Each key row has key, default, file, effective, safety and state (default, set or unconfirmed). pending is the waiting config confirmation or null. Profile overrides follow the global keys. |
PUT /v1/config | {"set": {<key>: <value>}, "unset": [<key>]}, both optional; unknown fields are refused | Writes the file and applies it. Returns the GET shape, or, when a change is weaker than its floor, E_CONFIRMATION_REQUIRED (HTTP 428) with details: {class: "config", pending, summary: {changes}, digest, expires_in_ms, hint}. The other changes in the same request are already applied. |
POST /v1/config/reload | {} | Re-reads the file, with the same reply as PUT. |
An unknown key is E_USAGE with reason: "unknown_key", a bad value
E_USAGE with reason: "invalid_value", and in both cases nothing is
written. Booleans also accept "true", "false", "on" and "off",
integers a numeric string, and approval.channels a comma-separated
string. unset returns a key to its default. A newer PUT or reload
replaces a waiting config confirmation, and the old one is reported as
denied with reason: "superseded".
$ curl -s -H "Authorization: Bearer $(cat "$JAWB_DATA_DIR/daemon.token")" \
http://127.0.0.1:4180/v1/config
The jawb config command
Available from jawb 0.1.1. Release 0.1.0 has no
jawb config; use the API above or edit the file.
| Command | What it does |
|---|---|
jawb config path | Prints <data_dir>/config.toml. Local; does not contact the daemon. |
jawb config show | Shows every key: the effective value, the default, whether it is a safety key, and a note with the file value when it differs. Unconfirmed keys are flagged with their pending ID. Profile overrides follow the global keys. -q prints key=value lines. |
jawb config get <key> | The effective value, plus the file value and pending ID when they differ. A profile override that is not set reports the global value it uses. -q prints the value only. |
jawb config set <key> <value> | Sets a key. Booleans accept true, false, on, off; integers a number; lists are comma-separated, for example desktop,terminal (an empty string is the empty list). Prints the key's new line. |
jawb config unset <key> | Returns a key to its default. Never needs approval. |
jawb config reload | Re-reads the file. |
Every command takes --json for the usual envelope. The --json result of
show is the GET /v1/config shape; get returns that key's row (plus
pending while it is unconfirmed); set and unset add changed (the
key) to the GET shape.
$ jawb config get purchase.gate
purchase.gate = approve (file: allow, unconfirmed, pending pc_7m2kq4x9d1ab)
When set or reload would weaken a safety setting, the file is saved
but the default stays in force, and jawb shows the changes. On an
interactive terminal it asks you to type weaken to approve, or deny:
$ jawb config set purchase.gate allow
jawb: this change WEAKENS a safety setting and needs a human (pc_7m2kq4x9d1ab):
purchase.gate: approve -> allow
the file is saved; until you approve, the default stays in force
approving applies it until jawbd exits; denying keeps the default
Type `weaken` to approve it, or `deny` to decline it: weaken
jawb: Verify with Windows Hello on this computer to finish the approval (up to 120s)...
approved pc_7m2kq4x9d1ab (confirmed via tty-verified: the operating system verified you)
purchase.gate = allow
After weaken, the approval goes through the normal confirmation flow:
where jawbd has an OS verifier (Windows Hello), it raises the prompt and
jawb waits for it, and the result shows the
assurance label. deny declines it
(exit 5; the default stays in force). Any other answer leaves it pending
until it expires (exit 5). --yes never answers this prompt. In --json
mode, without a terminal, or with --yes, jawb exits 5
(E_CONFIRMATION_REQUIRED) with the pending ID in details.pending, and
a human runs jawb confirm <pc_id> in their own terminal and types
weaken. The CLI refuses a jawbd older than the configuration API
(API level 11) with E_VERSION_MISMATCH (exit 3).