htb-hosts
Provides tools for managing /etc/hosts entries used in Hack The Box and other lab environments, allowing addition, removal, listing, and cleanup of hostname-to-IP mappings with tagging and backup features.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@htb-hostsadd 10.10.11.161 forest.htb"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
htb-hosts
Sudo-free, cleanable /etc/hosts management for Hack The Box (and any lab work).
Doing HTB, pentests, or CTFs means constantly adding IP β hostname lines to
/etc/hosts β then hunting them down to clean up later. htb-hosts keeps every
entry it manages inside one delimited block, grants you passwordless write access
via a POSIX ACL, and gives you one command to wipe a box (or everything). It ships
as both a CLI and a zero-dependency MCP server, so you and your AI coding
agents can manage hosts the same safe way.
$ htb-hosts add 10.10.11.161 forest.htb dc01.forest.htb
added: 10.10.11.161 forest.htb dc01.forest.htb [tag=forest]
$ htb-hosts show
HTB hosts β 5 managed entries in 3 boxes
forest (2)
10.10.11.161 forest.htb
10.10.11.161 dc01.forest.htb
machine (1)
10.129.45.2 machine.htb www.machine.htb
sequel (2)
10.10.11.202 sequel.htb dc01.sequel.htb
$ nmap -p- $(htb-hosts get forest) # resolve a box name to its IP, inline
Starting Nmap 7.95 ( https://nmap.org ) ...
$ htb-hosts clean --exclude forest # done with everything but forest
cleaned 3 entry/entries (all except ~forest)Features
π No sudo for daily use β a one-time ACL grant lets you edit
/etc/hostswithoutsudoever again.π§Ή One-shot cleanup β everything lives in a managed block.
cleanwipes it;clean --tag forestdrops one box;clean --exclude forestkeeps one and clears the rest.π Pipe-friendly lookups β
htb-hosts get forestprints just the IP, sonmap $(htb-hosts get forest)andffuf -u http://$(htb-hosts get forest --host)just work.π·οΈ Automatic per-box tagging β hostnames are grouped by box name (
dc01.forest.htbβforest), even across multiple IPs.π§° Safe by construction β strict input validation (no injection),
flocklocking for writers and readers, in-place writes that preserve the ACL, and automatic backups on every change (loud warning if a snapshot ever fails).βͺ Backup & restore β named snapshots (private:
0700/0600) and one-command rollback.β¨οΈ Tab completion β zsh and bash completions for commands, flags, and your live hostnames, IPs, tags, and snapshots.
π€ MCP server included β first-class tools for Claude Code / MCP agents.
πͺΆ Zero runtime dependencies β pure Python standard library.
Related MCP server: ssh-chat-mcp
Install
Requirements: Linux, Python 3.11+, and a filesystem with POSIX ACL support
(ext4/xfs/btrfs β the default almost everywhere) plus setfacl.
git clone https://github.com/sresarehumantoo/htb-hosts.git
cd htb-hosts
make installmake install copies the tool into /opt/htb-hosts and /usr/local/bin, grants
your user an ACL on /etc/hosts, installs the shell completions, and registers the
MCP server. It works whether
you run it directly or under sudo β the steps that need privilege call sudo
themselves, and the human user is auto-detected (SUDO_USER when under sudo, else
the current user) so the ACL always targets you, not root. Override with
HOSTS_USER=<name> if needed.
The one-time install needs privilege (it writes to
/optand/usr/local/binand sets the ACL). Everydayhtb-hostsuse afterward needs no sudo.
New shells pick the completions up automatically; in a shell that is already open,
exec zsh (or rm -f ~/.zcompdump && compinit) refreshes them.
Already have a pile of entries in /etc/hosts? Pull them into the managed block:
htb-hosts migrate # imports existing host lines, tagged by box nameTo uninstall: make remove (leaves your entries + ACL) or make purge (removes
everything, including the managed block and ACL).
Usage
htb-hosts add 10.10.11.161 forest.htb dc01.forest.htb # IP and hosts in any order
htb-hosts add 10.10.11.161 "forest.htb, dc01.forest.htb" # ...or one quoted string
htb-hosts add www.forest.htb 10.10.11.161 --tag forest # extra vhost, same box
htb-hosts show # box-grouped overview
htb-hosts show forest # ...narrowed to one box
htb-hosts show --flat # one line per entry
htb-hosts show --full # also show unmanaged/system lines
htb-hosts get forest # just the IP, for $(...)
htb-hosts get forest --host # just the hostnames
htb-hosts get forest --host -i 1 # just the first hostname
htb-hosts get forest --host -n # numbered, to pick an index
htb-hosts rm forest.htb # remove one hostname
htb-hosts clean --tag forest # remove a whole box
htb-hosts clean --match forest web # remove what `get` would show
htb-hosts clean --exclude forest # remove everything EXCEPT forest
htb-hosts clean # wipe all managed entries
htb-hosts doctor # check access / show the fixThe IP can go anywhere in the arguments β htb-hosts add forest.htb 10.10.11.161
works too, and any single argument may carry several names separated by commas or
spaces. Add --json to most commands for machine-readable output.
Command reference
Command | What it does |
| Add/update an entry (idempotent; merges by IP) |
| Remove a hostname or a whole IP from the block |
| Box-grouped view, narrowed to the pattern ( |
| Bare values for matching entries, for |
| Keep only the Nth value when a box has several |
| Remove all, one box, a fuzzy match, or all-but-matches |
| Re-derive every tag from its box name |
| Save an explicit snapshot |
| Roll back to a snapshot |
| Import existing |
| Check access + managed-block integrity; print the exact fix |
One rule for the two read commands:
showis for your eyes,getis for your pipes. Both take the same patterns; onlygetpromises bare, parseable output.
Selecting values for other tools
get prints nothing but the values of the entries matching a fuzzy pattern β
one per line, deduplicated β so they drop straight into another command:
nmap -p- $(htb-hosts get forest) # -> 10.10.11.161
ffuf -u "http://$(htb-hosts get forest --host)/FUZZ" -w words.txt
htb-hosts get # every managed IPThe pattern is matched case-insensitively against each entry's IP, tag, and
hostnames. --host switches the output from IPs to hostnames;
with --host, the output narrows to the hostnames that actually matched
(get jobs --host β jobs.example.htb), falling back to all of the entry's names
when the match came from its IP or tag.
Several patterns narrow, they don't add. Each one has to match, so patterns drill into a selection rather than growing it:
$ htb-hosts get forest --host # the whole box
forest.htb
dc01.forest.htb
www.forest.htb
$ htb-hosts get forest dc01 --host # drill in
dc01.forest.htb(clean --exclude is the opposite and deliberately so: it is a repeatable,
additive keep-list, where --exclude forest --exclude sequel keeps both.)
Nothing matched is an error: stdout stays empty and the exit code is 1, so a
typo can't silently hand an empty target list to the next command.
Picking one of several values
A box with a few vhosts has a few hostnames on one IP, and pasting all of them
into $(...) word-splits into several arguments β curl http://a.htb b.htb c.htb/.
-n/--numbered shows you what's there, and -i/--index keeps just the lines
you want (1-based):
$ htb-hosts get forest --host -n
1 forest.htb
2 dc01.forest.htb
3 www.forest.htb
$ curl "http://$(htb-hosts get forest --host -i 1)/" # -> forest.htb
$ htb-hosts get forest --host -i 2 # -> dc01.forest.htb
$ htb-hosts get forest --host -i -1 # last one
$ htb-hosts get forest --host -i 2-3 # a range
$ htb-hosts get forest --host -i 1,3 # a list, in that orderAn out-of-range index is an error (exit
2, empty stdout) rather than a silent drop, for the same reason an empty match
is: a pipeline should never quietly get fewer arguments than you asked for.
When you want every value but on one line, --sep joins them instead:
nmap $(htb-hosts get forest --sep ,) # -> nmap 10.10.11.161,10.10.11.162Tab completion
make install drops a zsh completion in $PREFIX/share/zsh/site-functions and a
bash one in $PREFIX/share/bash-completion/completions β both on the default
search paths, so no rc-file edit is needed. make completions installs just those.
Completion is live: candidates come from the managed block as it stands right now, not a static list.
You type | You get |
| subcommands, with descriptions |
| your managed hostnames and IPs |
| hostnames, IPs and tags (anything a pattern matches) |
| only the hostnames inside the forest box |
| your box tags |
| your snapshots, newest first |
| that subcommand's flags |
Completion is also scoped and field-aware: once you have typed a pattern, the
next one completes only from what that selection contains, and --host restricts
the menu to the hostnames that field can actually print. Accepting a suggestion
therefore always does what the menu implied β which is why patterns narrow.
The scripts get their data from htb-hosts complete <kind> [patternβ¦], a hidden
subcommand that prints one candidate per line. It is deliberately silent on failure β an
unreadable or malformed /etc/hosts degrades completion rather than spraying
errors into your prompt.
Tagging
Every entry carries a tag, defaulting to the box name derived from its first
hostname (dc01.forest.htb β forest; fileserver.corp.local β corp). That
groups a whole box β even across several IPs β under one tag, so clean --tag forest clears it in one go. Re-derive tags for existing entries any time:
htb-hosts retagRemoving a fuzzy match: clean --match
--match takes the same patterns as get, so what you selected is what goes:
$ htb-hosts get forest web --host # check first
web01.forest.htb
web02.forest.htb
$ htb-hosts clean --match forest web
will remove 2 entries:
10.10.11.161 web01.forest.htb [tag=forest]
10.10.11.161 web02.forest.htb [tag=forest]
proceed? [y/N] y
cleaned 2 entry/entries (~forest web)Because a fuzzy pattern is the one removal you cannot verify by reading the
command line, --match prints its plan and asks whenever it would remove more
than one entry. --yes skips the prompt, and --json implies it; with no
terminal to ask, it refuses rather than guessing. The exact modes (--tag,
--exclude, bare clean) are unchanged and still act immediately.
Keep-all-but: fuzzy --exclude
The inverse of --tag: remove everything except fuzzy matches. Patterns are
case-insensitive substrings tested against each entry's IP, tag, and hostnames,
and --exclude is repeatable:
htb-hosts clean --exclude forest # keep the forest box, clear the rest
htb-hosts clean --exclude forest --exclude vpnBackup & restore
Every write drops an automatic snapshot (last 5 kept). You can also take named snapshots that are never auto-pruned, and roll back:
htb-hosts backup --label before-pivot # explicit snapshot
htb-hosts restore --list # show snapshots (auto + saved)
htb-hosts restore # roll back to the most recent
htb-hosts restore before-pivot # roll back to a named/substring matchMCP integration
htb-hosts includes a stdio MCP server so MCP-aware agents (e.g. Claude Code) can
manage hosts natively. make install registers it; to register by hand:
claude mcp add -s user htb-hosts -- python3 /opt/htb-hosts/mcp_server.pyhtb_hosts_add, htb_hosts_remove, htb_hosts_show (accepts patterns),
htb_hosts_select, htb_hosts_clean (accepts match/exclude/dry_run),
htb_hosts_retag, htb_hosts_backup, htb_hosts_restore,
htb_hosts_list_backups.
How it works
Everything the tool manages lives between two markers:
# >>> htb-hosts >>> (managed by htb-hosts; run `htb-hosts clean` to remove)
10.10.11.161 forest.htb dc01.forest.htb # tag=forest added=2026-07-11
# <<< htb-hosts <<<Lines outside the markers (localhost, IPv6, anything you added by hand) are never touched.
Write access is granted once via a POSIX ACL:
setfacl -m u:youruser:rw /etc/hostsBecause a non-owner can't re-apply an ACL, the tool edits /etc/hosts in place
(open r+, rewrite, truncate) under an flock β it never renames a temp file over
it, which would drop the ACL and flip ownership. All input is strictly validated so
nothing can inject extra lines, and the prior contents are snapshotted before every
change. If the ACL is ever lost, htb-hosts doctor prints the exact command to
restore it (doctor also flags a mangled managed block, e.g. an orphaned marker).
Security notes
The ACL is broader than the tool.
setfacl -m u:you:rw /etc/hostslets any process running as your user rewrite the whole file and redirect name resolution system-wide β the managed block is a convention the tool honors, not something the kernel enforces. That's the deliberate trade-off for sudo-free use; make it on a personal lab machine, not a shared or production one.make purgeremoves the ACL again.Backups outlive
clean. Snapshots under~/.local/state/htb-hostskeep old box/engagement hostnames after you wipe the block. The directory is created0700(snapshots0600); delete snapshots by hand if that history matters.
Configuration
Variable | Default | Purpose |
|
| Target hosts file (handy for testing) |
|
| Where snapshots are stored |
| (empty) | Comma-separated domain substrings to keep outside the block on |
Development
make test # stdlib unittest suite (no root, /etc/hosts untouched)
make smoke # quick end-to-end CLI check against a throwaway file
make lint # black --check + pylint (gated at 10.00/10)
make help # list all targetsTests load the modules directly from src/, so they exercise the in-repo copy
regardless of what's installed under /opt, and each test runs against a throwaway
temp file β your real /etc/hosts is never touched. Coverage includes input
validation/injection rejection, add/merge/rehome/idempotency, clean/exclude,
migration + tagging, backup/restore (including snapshot permissions and the
warn-but-don't-block behavior on backup failure), doctor's marker-integrity
check, the in-place-edit (inode-preserving) invariant that protects the ACL,
flock concurrency, and the full CLI + MCP surfaces.
Layout
src/htb_hosts.py core library (parse/validate/lock/backup) β /opt/htb-hosts
src/mcp_server.py zero-dependency stdio JSON-RPC MCP server β /opt/htb-hosts
src/htb-hosts CLI entrypoint β /usr/local/bin
completions/ zsh + bash tab completion scripts β share/{zsh,bash-completion}
tests/ unittest suite (core, CLI, MCP)
Makefile install / remove / lint / test targets
pyproject.toml black + pylint config (line length 100)License
This server cannot be deployed
Maintenance
Related MCP Connectors
Syslog receiver and MCP server for homelab log intelligence.
Syslog receiver and MCP server for homelab log intelligence.
Nifty's MCP server β exposes tasks, projects, messages, and files as tools for AI agents.
Personal assistant MCP server with search, execute, packages, jobs, secrets, and integrations.
Related MCP Servers
- AlicenseBqualityAmaintenanceAll-in-one homelab management MCP server. Docker monitoring, volume backup/restore (with compose and env files), Wake-on-LAN, network scanning, and multi-server SSH management.44287MIT
- AlicenseAqualityDmaintenanceZero-config SSH/SFTP MCP server that lets an LLM client open temporary SSH/SFTP sessions to remote hosts, run commands, and upload/download files without holding any pre-baked credentials.178 npm2MIT
- AlicenseNot gradedqualityBmaintenanceHosts local MCP servers on Windows, enabling connection of local AI models (like Gemma) to external tools without cloud dependency.464 npmMIT
- AlicenseNot gradedqualityCmaintenanceA lightweight, cross-platform MCP server for managing background processes. Enables AI coding agents to spawn, monitor, and interact with long-lived processes.MIT