13 Commands
Robin edited this page 2026-08-22 23:15:50 +00:00

Commands

Two binaries. shieldlist-agent is the daemon and its one-shot modes; shield is what you type at it.

shieldlist-agent

shieldlist-agent [-config /etc/shieldlist] [-state /var/lib/shieldlist/agent.db]
Flag
-config <dir> Configuration directory. Default /etc/shieldlist.
-state <file> The durable store. Default /var/lib/shieldlist/agent.db.
-t Validate the configuration and exit. Changes nothing.
-dry-run <duration> Read the logs' past and report what the rules make of it. Enforces nothing. 0 reads everything.
-rule <name> With -dry-run, judge only this rule.
-version Version, commit, build date, Go version and platform.

-t reports every problem at once, each with its file and line — run it after every edit.

-dry-run prints a summary and then every crossing with the lines that convicted it. See Replay.

shield

shield [-state /var/lib/shieldlist/agent.db] <command>

Reading commands open the store read-only and work whether or not the agent is running. Acting commands write a request the agent answers on its next round — see below.

status

shield status                 # everything
shield status bans rules      # just those sections
shield status new_today       # one key: its value alone (277526) — for a script, a monitor

One key prints its value and nothing else (exit 1 if the key is not there); new_today is the sanctions since midnight in the machine's zone, new_today_utc the same in UTC, active_bans what is in force. Sections: bans, agent, kernel, store, rules, reports. The reports section only appears when asked for by name — its counts are a scan of the store, which the default status must not pay for on a big one. With a live agent, every figure is read from its published snapshot — status answers in milliseconds whatever the store's size, because the agent has already paid for the counting on its own cadence (counts_age and counts_cost in the store section say when and how much). Without a live agent the CLI counts for itself, which on a multi-million-row store takes seconds.

status (fields)

The aggregate summary. Safe on a store holding millions of bans: it counts, it never lists.

shield status
active_bans	5099267
active_addresses	5099267
active_ranges	0
new_1h	7048
new_24h	7048
new_7d	7048
new_today	4211
new_today_utc	4009
bans_loadavg	124 280 469
unban_loadavg	235 254 166
lines_read	5995115
read_lag	0B
lines_loadavg	101k 337k 400k
hits_loadavg	103k 343k 408k
memory_rss	650.3MB
memory_heap	454.2MB
eval_groups	9
eval_events	12
goroutines	6
blocked_packets	1840233
blocked_bytes	212.4MB
state_file	/var/lib/shieldlist/agent.db
state_size	3.8GB
history_rows	5104299
counts_age	1m37s
counts_cost	19s
rule_ssh-password-bf	10

Output is key<TAB>value with a real tab, so cut -f2 and awk both work and a terminal still aligns.

The four loadavgs are rates per minute over 1, 5 and 15 minutes, the same shape as /proc/loadavg: bans issued against bans expired tells you whether the active set is growing; lines and hits tell you how fast it is reading and how much of that it recognises.

counts_age and counts_cost appear because the aggregate figures are computed by the agent on its own cadence and read from its health snapshot — scanning millions of rows on every invocation would cost more than the agent does. They say how old the figures are and what the scan took.

Some lines only appear when they are not zero — shed, unenforced, nft_table MISSING — because zero is the only good answer for them.

stats

The running agent's health, in more detail: uptime, throughput, read lag, counters for hits, bans, absorbed triggers and errors, memory, the size of the evaluator's working set, the index of active sanctions and its misses, and the state file.

shield stats

agent stale with a last_beat means nothing has written a health snapshot recently — the agent is stopped or wedged.

stats also prints what each parser and each rule costs (parser_cpu_*, rule_cpu_*, dearest first, sampled): which pattern the cycles go to, and which rule never fires — a rule at 0 hits is naming itself dead. The agent warns in its log when a parser or rule runs past its budget; it never switches one off on its own.

bans

shield bans [-rule NAME] [-n 50]

Active bans, soonest to expire first, bounded by an explicit limit because a fleet under attack holds more than anyone wants printed.

ranges

shield ranges [-n 20]

The networks (/24 for IPv4, /64 for IPv6) holding the most active bans — the question behind "should this be a range ban". Every ban is stamped with its network as it is written; bans from before the stamp existed carry none and stay out of the grouping.

why

shield why 203.0.113.9
shield why 198.51.100.0/24

Everything on record against an address or a range — active and past sanctions, with the log lines that convicted each one, and for an address, any range ban that covers it without naming it. Detect-only records appear too, marked detected only, never enforced. This is the answer to "why is my customer blocked".

tail

shield tail              # every decision, live
shield tail -rule ssh-bf

Follow the agent's decisions as they are made, each with the log lines that convicted it — the real-time view of "what is this thing actually doing". Reads the store concurrently, costs the agent nothing, works in test (and says so). Long evidence is cut at ten lines; shield why <address> has the rest. ^C to stop.

An offender RETURNING to a sanction that already answers it shows up too, as an again line with its running hit count — an address that keeps coming back must be seen coming back. (Under real enforcement these are rare by nature: the kernel drops a banned address's traffic, so returns belong to test and to the moment before a block lands.) The same count appears in shield bans (HITS and LAST columns) and shield why.

When reporting is armed, a report's fate follows the ban it belongs to: reported … score N, already reported by this account, or report refused with the provider's words. The same status is the REPORTED column of shield bans (sent / dup / owed / refused / - when the rule reports nothing) and a line in shield why.

12:33:31  srv00-mail  ban  213.176.26.159/32  ports all  25d
  | Aug 15 12:33:29 srv00 postfix/smtpd[…]: warning: unknown[213.176.26.159]: SASL LOGIN authentication failed…
12:33:52  srv00-mail  reported  213.176.26.159/32  score 100
12:41:07  srv00-mail  again  213.176.26.159/32  hits 14  detected, expires 24d23h
  | Aug 15 12:41:06 srv00 postfix/smtpd[…]: warning: unknown[213.176.26.159]: SASL LOGIN authentication failed…

last seen and the again timestamps are the LOG's clock — the newest evidence line — not the moment the verdict landed, which for a weighted rule can be minutes later (the lateness allowance).

unban

shield unban 203.0.113.9
shield unban 198.51.100.0/24

End a ban early. Unbanning something that was not banned is an answer, not an error — and if the address is covered by a range ban, the answer names the range, because the kernel would keep dropping it either way.

exempt

shield exempt 203.0.113.7
shield exempt 203.0.113.7 -for 2h -note "office, while I fix the client"

Flags go before or after the address, and durations read like the configuration's: 30s, 10m, 2h, 30d, 4w.

Never sanction this address. It takes effect immediately — the case that matters is someone who has just locked their own office out and cannot edit a file on a server they can no longer reach.

The durable record goes into exemptions-cli.yaml beside your own exemptions, plainly named so it can be found, read and removed with ordinary tools.

replay

shield replay            # everything the logs hold
shield replay -last 8h

Ask the running agent to read its logs' past and enforce what still applies. The reading runs beside the live watch — the agent keeps detecting while it judges the past — and is bounded by the agent's memory budget: if the past outgrows it, the newest evidence is judged and the answer says how much was dropped. See Replay for what "still applies" means.

report

shield report -last 6h          # count, estimate, then ask
shield report -last 2d -n 500 -y

Submit the abuse reports that could not leave at their moment — the live agent gives each offender one chance, at detection, and never drains a backlog by itself. You name the window (-last has no default), the count is shown before anything leaves, and the freshest go first. Unlike the other acting verbs this one does not go through the agent: it reads the key from /etc/shieldlist (-config points elsewhere) and submits directly, settling each outcome in the store. If it races the live sender over the same address, the provider answers "duplicate" to one of them, which is recorded and harmless. See Abuse reporting.

enrol

shield enrol <join-token>

Join the controller named in agent.yaml (mode: enrolled, controller: https://…). The token comes from the controller (shieldlist-controller token, or the console); the agent presents it, keeps the credential it gets back, pins the controller's certificate when the token carried its fingerprint, and from then on talks to the controller on its own. See Enrolment.

shield on the fleet

With -controller URL -token T — or SHIELD_CONTROLLER and SHIELD_TOKEN in the environment — shield is a client of the controller's API and its verbs act on the whole fleet:

shield agents                        # every enrolled machine: versions, seen, config, bans
shield agent srv00                   # one machine's declaration
shield set srv00 level=strict offer=no rules.ssh-bruteforce.thresholds=3/1m
shield bans -agent srv00 -n 20       # the fleet's bans, newest first
shield why 203.0.113.9               # banned where, by which rule, since when
shield unban 203.0.113.9 -reason "customer call"   # -withdraw-report: and withdraw what was reported (AbuseIPDB)
shield exempt 198.51.100.0/24 -note partner         # -lift: and lift its bans in force; -lift -withdraw-report
shield rules | shield rule ssh-bruteforce state=off
shield reload | shield validate | shield audit | shield token | shield sources | shield refresh all

The token is an operator's (shieldlist-controller api-token -name you mints one). Everything set, rule and exempt do is a write to the controller's configuration files, reloaded whole or reverted.

How acting commands work

unban, exempt and replay do not act directly. They write a request into the store; the agent picks it up on its next round, does the work, and writes the answer back, which shield prints.

That indirection is not ceremony. The agent owns the store and the kernel's sets: a second process writing either behind its back would leave the agent's memory wrong and the kernel disagreeing with the record. It also means a request made while the agent is stopped is not lost — it waits, and is carried out when one starts:

203.0.113.9	asked; no agent answered — it will be done when one starts