kidobo is a one-shot Linux firewall blocklist manager.
It builds IPv4/IPv6 blocklists from local and remote sources, subtracts
safelist entries, atomically replaces each managed ipset, and maintains
deterministic iptables/ip6tables wiring.
- Manages local and remote IPv4/IPv6 blocklists.
- Deduplicates, merges, and minimizes CIDR entries before enforcement.
- Carves operator-defined safe IP/CIDR ranges out of blocklists.
- Uses kernel
ipsetmatching with normalizediptables/ip6tablesrules. - Supports local IP/CIDR and ASN bans through the CLI or configuration files.
Install latest release:
curl -fsSL https://raw.githubusercontent.com/blwarren/kidobo/main/scripts/install.sh | sudo bashInstall a specific release:
curl -fsSL https://raw.githubusercontent.com/blwarren/kidobo/main/scripts/install.sh | sudo bash -s -- --version v0.14.1The command above pins the binary release while still using the installer from
the mutable main branch. To pin both, use the same release tag in the installer
URL and argument:
curl -fsSL https://raw.githubusercontent.com/blwarren/kidobo/vX.Y.Z/scripts/install.sh | sudo bash -s -- --version vX.Y.ZInstall and initialize in one step:
curl -fsSL https://raw.githubusercontent.com/blwarren/kidobo/main/scripts/install.sh | sudo bash -s -- --initUninstall:
curl -fsSL https://raw.githubusercontent.com/blwarren/kidobo/main/scripts/install.sh | sudo bash -s -- --uninstallSecurity note: piping a script to sudo bash is convenient, but for a stricter
install policy, download and review a tag-pinned installer before running it.
The installer verifies the requested checksum and binary version in a staged
file before atomically replacing an existing installation.
- Linux x86_64 for the published static musl binary. The release artifact is exercised on Debian 11 and Alpine 3.22; other architectures must build from source and are not currently tested.
- Bash,
curl,tar,sha256sum, and standard file-installation tools for the installer. GNUrealpathis additionally required for custom-root uninstall. sudo,ipset, andiptablesfor runtime checks and enforcement.ip6tableswhen IPv6 enforcement is enabled, which is the default.bgpq4for ASN resolution and for the completedoctorcheck.- systemd only when using the generated periodic sync service and timer.
Initialize the default files and generated systemd units:
sudo kidobo initConfigure your sources and safelist:
sudoedit /etc/kidobo/config.tomlCheck prerequisites and system wiring before changing source state:
sudo kidobo doctorAdd local entries (optional):
Use commands:
sudo kidobo ban 203.0.113.7
sudo kidobo unban 203.0.113.7
sudo kidobo ban --file targets.txt
sudo kidobo unban --file targets.txt --yes
sudo kidobo ban --asn 213412
sudo kidobo unban --asn AS213412ban --asn loads or resolves the ASN prefixes and caches them before updating
[asn].banned. A stale cache can be used when refresh fails. These commands
change source state only; they do not change live firewall enforcement.
Or edit the local blocklist file directly:
echo "203.0.113.0/24" | sudo tee -a /var/lib/kidobo/blocklist.txtApply blocklists to ipset and firewall rules after any source or configuration
change:
sudo kidobo syncCheck whether targets match (offline):
kidobo lookup 203.0.113.7
kidobo lookup --file targets.txt
kidobo lookup --file targets.txt --format tsvRemove kidobo firewall/ipset artifacts (optional):
sudo kidobo flush
sudo kidobo flush --cache-onlyflush attempts every cleanup step and exits with status 1 if any live
firewall, ipset, or cache artifact could not be removed. The installer preserves
the binary, configuration, data, cache, and generated units whenever the
config-aware flush fails, even if its direct default-name recovery cleanup
succeeds. Artifact removal starts only after a successful config-aware flush.
/etc/kidobo/config.toml:
[ipset]
set_name = "kidobo"
[safe]
ips = []
include_github_meta = true
github_meta_url = "https://api.github.com/meta"
[remote]
timeout_secs = 30
urls = []
[asn]
banned = []
cache_stale_after_secs = 86400Useful options:
ipset.set_name_v6: optional, defaults to<set_name>-v6ipset.enable_ipv6: defaulttrueipset.chain_action:DROP(default) orREJECTipset.maxelem: range[1, 500000]remote.timeout_secs: range[1, 3600]asn.banned: ASN bans loaded from cache or resolved to prefixes duringsyncasn.cache_stale_after_secs: ASN prefix cache refresh threshold (default86400, range[1, 604800])
Unknown configuration keys are rejected at every level so misspellings cannot silently select defaults. IPv4 and IPv6 set names must always be distinct.
- Config file:
/etc/kidobo/config.toml - Local blocklist:
/var/lib/kidobo/blocklist.txt - Cache dir:
/var/cache/kidobo - Systemd units:
/etc/systemd/system/kidobo-sync.service/etc/systemd/system/kidobo-sync.timer
kidobo init creates missing files and systemd units.
At default paths it also runs systemctl daemon-reload and enables
kidobo-sync.timer, and writes KIDOBO_LOG_FORMAT=journal into
kidobo-sync.service.
For default systemd units, init requires an installed kidobo binary at
/usr/local/bin/kidobo or /usr/bin/kidobo; it will not generate units from
an arbitrary build or cargo run path.
- IP/CIDR
banandunbancommands update the local blocklist. ASN bans load and cache prefixes before updating[asn].banned; ASN unbans remove the configuration entry and make a best-effort cache cleanup.--fileaccepts one strict IP/CIDR target per line. No ban or unban changes live enforcement beforesync. These commands require valid configuration; interactive unban validates it again after confirmation and before writing. lookupis offline-only and reports raw overlaps with the local blocklist, cached remote sources, configuredsafe.ips, compatible cached GitHub meta safelist data, and cached prefixes for currently configured ASN bans. It never fetches sources or invokesbgpq4.- Safelist lookup rows identify exemptions; lookup does not inspect live ipset state or calculate the final post-safelist firewall set. Missing or invalid config still permits lookup against the local blocklist and cached remote sources. Lookup warns on stderr when config-backed coverage or a configured GitHub/ASN cache is unavailable.
- Lookup prints a readable results table by default, including explicit match
status and summary counts for both single targets and files. Long source URLs
wrap without being truncated. Use
--format tsvfor the legacy tab-separated output intended for scripts; color is limited to interactive terminals and disabled whenNO_COLORis set. Target fields use the stable 1.x escaping\\,\t,\r,\n,\xNN, and\u{…}so control characters cannot create terminal sequences or additional TSV records. synccanonicalizes a valid local blocklist, preserving only the leading comment/header section before canonical entries. Invalid non-header local lines now failsync; they are not silently dropped or rewritten away.- Remote responses containing only whitespace or comments are treated as an intentional empty feed. A non-empty response with no valid CIDRs, or GitHub metadata missing a selected category, is treated as a soft fetch failure and does not replace the last usable cache.
- A remote or GitHub cache staging failure uses validated cached data and warns. If no cached data passes the applicable checks, sync fails before replacing either firewall set. Unchanged refreshes retain the previous generation; identical fresh data can repair a corrupted generation after admission.
- Remote HTTP bodies default to an 8 MiB limit. The
KIDOBO_MAX_HTTP_BODY_BYTESoverride is capped at 32 MiB, while GitHub metadata remains capped at 8 MiB. Feed line, unique-CIDR, and aggregate budgets scale withipset.maxelem; an aggregate-budget failure aborts before enforcement. - GitHub metadata safelists accept at most 4,096 distinct entries, reject IPv4
prefixes broader than
/8and IPv6 prefixes broader than/16, and limit each family's collapsed coverage to one-sixteenth. A batch that would erase a nonempty enabled family is rejected in favor of a compatible previous generation or the operator-controlled safelist. - Remote fetches follow at most ten redirects, and only when the destination keeps the configured URL's scheme, host, and effective port. A blocked redirect is a soft fetch failure and does not replace the last usable cache.
- Ipset replacement is atomic per set, not across both address families. IPv6 and IPv4 are capacity-checked before either set is replaced, but their swaps are separate operations.
- Ctrl-C exits with status 130. Prompts cancel without waiting for a response; other operations stop at safe boundaries after active I/O completes or reaches its configured timeout. Once sync begins replacing sets, it finishes enforcement and cleanup unless an operational error prevents completion. Full flush also finishes all scoped cleanup attempts once cleanup begins.
doctoris read-only by default. It checks whether the remote cache path is structurally plausible without creating directories or writing probe files; plausible permissions are reported asSKIPbecause effective access is not mutated to prove writability.KIDOBO_ROOTrelocates config, data, cache, and generated systemd paths under a custom root.initdoes not callsystemctlwhen this override is present, which also makes an unprivileged isolated setup possible when the root is writable. The installer rejects an explicitly empty value for--init, tries a writable custom root without sudo first, and preserves the exact value if elevation is required.
The supported 1.x compatibility promises are documented in docs/compatibility.md.
Development commands are defined in Justfile. See the
development-command reference for release and recovery
details, and the architecture guide for extension
boundaries and recipes.
cargo install --locked just --version 1.55.1
just _install-cooldown _install-deny _install-audit
just check
just ci
just release-notes-checkjust check is the fast development loop. Run just ci explicitly before every
push; GitHub does not run project CI for pushes or tags. The complete local gate
checks formatting, lints, dependency policy, audits, and tests. Coverage,
strict rustdoc, the isolated release-binary exercise, and static Debian/Alpine
compatibility run only during publication. The exercises use a temporary
KIDOBO_ROOT, a loopback HTTP feed, and fake privileged commands; they never
touch the development host's firewall or systemd. Dependabot update
PRs are the only GitHub-hosted automation retained. Run
just release-notes-check after every repository change.
Authenticate with gh auth login before publishing, then run
just publish-release X.Y.Z from a clean branch. The publisher validates and
packages the release locally, pushes the release commit and tag atomically,
verifies the draft's downloaded assets, and only then publishes it. See the
development-command reference for the complete workflow
and failure recovery.
Use just --list to see all available local and CI recipes.
MIT (see LICENSE).