# netwalk: read-only network surveys with the refusals written in code

> Six Claude Code skills that crawl a network from one device you can log into, read the health of what they find, draw the topology and produce a report, with three promises enforced by tests rather than by prompt wording. The interesting part is what it refuses to do.

**ripmilla/netwalk** — Read-only network survey toolkit for AI coding agents: crawl a site from one device, diagnose it, draw it, and hand over a report — without ever changing a device or seeing a credential.

- Repository: https://github.com/ripmilla/netwalk
- Stars: 86 · Forks: 30
- Language: Python
- License: MIT
- Published: 2026-08-23 · Updated: 2026-08-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/ripmilla-netwalk

## Six skills, and only one of them ever touches a password

netwalk is aimed at coding agents, and it is packaged as six slash commands rather than one binary. You point it at a single device you can log into, and it crawls the network hop by hop, reads the health of everything it finds, draws the topology and produces a report you can hand to the site owner. The skills can be used separately or run as a single end to end workflow, and they work on Windows, macOS and Linux.

```
/netwalk              the whole survey, end to end
/netwalk-login        collect credentials through a local browser form, never through the chat
/netwalk-scan         crawl the topology from one entry device
/netwalk-diag         export config read-only, read health, work out what is wrong
/netwalk-map          draw the diagram
/netwalk-fullreport   produce the deliverable HTML report
```

The division of labour is the point. Scanning, diagnosis, drawing and reporting are separate steps that can be re-run individually, while login is isolated in its own skill precisely because it is the only one that has to deal with a secret.

## The allowlist refuses writes in code, not in the prompt

The first of three promises is that netwalk never changes anything, and the enforcement is deliberately not a request to the model. Every command is checked against a per-vendor read-only allowlist before it is sent, so config writes, clear counters, dmesg -C, systemctl restart, reload, configure terminal and shell metacharacters are all refused by the tool itself. Configuration is exported for reading and nothing is ever imported.

```
$ netwalk_exec.py check --vendor mikrotik --cmd '/interface print detail'
ALLOW [mikrotik] read-only

$ netwalk_exec.py check --vendor mikrotik --cmd '/interface print; /system reboot'
DENY  [mikrotik] contains command separator or redirection ';' - run one plain command at a time
```

The allowlist itself lives in scripts/netwalk_policy.py and is covered by tests/test_policy.py with 397 cases, including every command in every shipped pack. The instruction that follows from that is blunt: if you widen the allowlist, run the tests. A separator like a semicolon is refused on its own, which means a compound command cannot smuggle a second action past a check that approved the first. The check is per vendor too, so each command is judged against that vendor's pack rather than one global list, and every response names the vendor it decided about.

## Credentials go into a browser form bound to loopback

The second promise is that credentials never touch the conversation, and the mechanism is a one-shot page served on your own machine. You type the password into your browser, it is written straight to a private file on disk, 0600 on POSIX and an owner-only ACL through icacls on Windows, and the listener exits. The assistant is told the path, never the value, and the report renderer refuses to build a document out of a record that contains credential material. The listener binds to 127.0.0.1 rather than 0.0.0.0, so the kernel never accepts a packet for it from another host.

```
$ netwalk_cred.py serve --site acme-hq ...
   http://127.0.0.1:62742/Mnoc4Wmy1w80_h6RBlJgXXMPBs7VLkCk

$ lsof -nP -iTCP:62742 -sTCP:LISTEN          # on the machine serving it
   Python  98900  TCP 127.0.0.1:62742 (LISTEN)      <- not *:62742
```

The example is worth reading closely: the listening socket is shown on the loopback address rather than on a wildcard, and the same port from another host is refused at the connection level. The port in it, 62742, and the token in the path after it are both produced for that run, which is why the curl in the README has nothing to guess.

## Four more barriers, and the one attack they do not stop

Loopback binding is only one layer, and the project is explicit about the rest. The port is chosen at random per run and never fixed. The URL carries a random token, so without the path the server answers nothing. Cross-origin POSTs are rejected, and that is named as the attack loopback binding alone does not stop: a page open in your browser from anywhere else cannot post to the form. And the server speaks JSON only and exits after the single submit it was waiting for, in request mode. Underneath those sits the honest caveat. Another process running as you, on your own machine, can reach loopback, which is the same trust boundary as the 0600 credential file itself. If your account is compromised, the form is not what saves you. That sentence is in the README, which is rarer than it should be in a tool that asks for a device password.

## Scope refusals cover supernets, public space and anything over a /16

The third promise is that netwalk never sweeps a range nobody authorised. Crawling outward from a device you name is fine, and sweeping an address range is allowed only when that range is written into the site's scope with the name of the person who authorised it, which then appears in the report.

```
$ netwalk_sweep.py hosts --site acme-hq --range 198.51.100.0/24
DENY  198.51.100.0/24 is outside the authorised scope for this site
      (authorised: 192.0.2.0/24). Ask the owner and authorise it - there is no override flag
```

The refusals are drawn tightly, and each one closes a specific loophole. A range outside the scope is refused. So is a supernet of an authorised range, since authorising a /24 does not authorise the /23 that contains it. So is public address space without a second explicit flag, because owning one address in a hosting provider's block does not make the rest of it yours to probe. So is anything larger than a /16, and so is a projected probe count over the cap. There is no override flag, and tests/test_sweep.py holds 81 cases, with the note that the ones that matter are the refusals. The scope is per site rather than global, which the refusal message makes visible by naming the site and printing the range that is authorised for it, 192.0.2.0/24, so whoever hits the denial knows exactly which entry to change and which owner to ask.

## Install bakes your absolute path into six skill folders

Installation is a clone and one Python script.

```bash
git clone https://github.com/ripmilla/netwalk
cd netwalk
python3 install.py
```

That copies the runtime to ~/.claude/skills/netwalk/toolkit/ and writes the six skill folders with the real absolute paths baked in, so the commands inside them point at your clone rather than at a guess. After that, /netwalk in Claude Code starts the workflow. Four optional extras exist for the awkward moments: an environment report, a fetch of vendor logos for the diagram, the policy self-test, and an uninstall.

```bash
python3 install.py --check                          # environment report
python3 ~/.claude/skills/netwalk/toolkit/scripts/netwalk_logos.py fetch    # vendor logos
python3 ~/.claude/skills/netwalk/toolkit/tests/test_policy.py              # self-test
python3 install.py --uninstall
```

Running the policy test after an install is the cheapest way to find out whether the toolkit on your disk still behaves the way the repository says it should.

## Only the instruction layer is Claude-specific

netwalk is two layers, and knowing which is which decides how portable it is. The first is plain Python scripts that need no agent at all. The second is a set of instructions telling an agent how to drive them, and only that layer is Claude specific. AGENTS.md is the agent-agnostic form of exactly the same instructions, so porting is a one-liner that picks the file the target agent reads.

```bash
python3 /path/to/netwalk/install.py --agent cursor     # .cursor/rules/netwalk.mdc
python3 /path/to/netwalk/install.py --agent codex      # AGENTS.md
```

The mapping covers Cursor, which gets .cursor/rules/netwalk.mdc, the Codex CLI, OpenCode, Amp and Jules, which share AGENTS.md, Windsurf, which gets .windsurf/rules/netwalk.md, Cline and Roo Code, GitHub Copilot, the Gemini CLI with GEMINI.md, Continue and Aider with CONVENTIONS.md. Claude Code is the default and needs no flag, landing in ~/.claude/skills/netwalk-*/SKILL.md. Because the absolute path to your clone is substituted in, the installed instructions belong to the machine and the directory you installed them from, so moving the checkout afterwards means running the installer again rather than editing the skill files by hand.

## Two releases on the same day, and a Thai README alongside

The repository has a short history and says so in its tags. v0.1.0 is labelled the first public release and v0.2.0 is labelled sweep, hardening audit, close-out, and both were published on 23 August 2026, the same day as the last recorded push. The sweep promise in particular reads as the response to an audit rather than to a feature request. The tree is small and each directory has a job: install.py at the root, scripts/ for the toolkit runtime, skills/ for the six command folders, tests/ for the policy and sweep suites, tools/, schema/, and an examples directory whose example-scan.json shows what a scan record looks like. Documentation is bilingual, with README.th.md carrying a Thai translation, and CHANGELOG.md and LICENSE sit at the root under an MIT licence. The schema directory next to that example record is the other hint about how the toolkit is meant to grow: a new vendor pack has a defined shape, and the policy tests enumerate the commands in the packs that ship with the project.

## Conclusion

netwalk fits a network engineer or site owner who has been asked to survey one network from a single entry device and wants the result in a form they can hand over, and it fits anyone who cannot accept an agent that might reboot something by accident, because the refusal logic is in the toolkit and covered by tests. Two limits are worth stating plainly before you run it. It will sweep an address range only when that range is written into the site scope with the name of the person who authorised it, so somebody has to do that bookkeeping first. And the credential form protects against everyone else on the network, not against another process already running as you, since loopback is reachable from your own account. Both releases, v0.1.0 and v0.2.0, were published on 23 August 2026 with the last push the same day, the licence is MIT, and a Thai translation of the README sits beside the English one.

## FAQ

### What does netwalk do?

It is a read-only network survey toolkit for Claude Code. You point it at one device you can log into, and it crawls the network hop by hop, reads the health of everything it finds, draws the topology, and produces a report you can hand to the site owner.

### How does netwalk collect credentials?

The login skill serves a one-shot page on your own machine, bound to 127.0.0.1 rather than 0.0.0.0. You type the password into your browser and it is written to a private file, 0600 on POSIX and an owner-only ACL through icacls on Windows. The assistant is told the path, never the value.

### Can netwalk change anything on a device?

No. Every command is checked against a per-vendor read-only allowlist before it is sent, and config writes, clear counters, dmesg -C, systemctl restart, reload, configure terminal and shell metacharacters are refused by the tool. Configuration is exported for reading and nothing is ever imported.

### Does netwalk work with agents other than Claude Code?

Yes. Only the instruction layer is Claude-specific, and AGENTS.md is the agent-agnostic form of it. The installer takes an --agent flag and writes the file a given agent reads, covering Cursor, Codex, OpenCode, Windsurf, Cline, Copilot, Gemini, Continue and Aider.

### What stops netwalk from sweeping addresses it was not given?

A range must be written into the site scope together with the name of the person who authorised it, and that name appears in the report. Refusals cover ranges outside the scope, a supernet of an authorised range, public address space without a second explicit flag, anything larger than a /16, and a projected probe count over the cap. There is no override flag.

## Sources

- [Official README](https://github.com/ripmilla/netwalk#readme)
- [Project repository](https://github.com/ripmilla/netwalk)
- [Release notes](https://github.com/ripmilla/netwalk/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ripmilla-netwalk
