Tailsnitch turns a tailnet review into 57 named checks
A security auditor for Tailscale configurations. Scans your tailnet for misconfigurations, overly permissive access controls, and security best practice violations.
At a glance
- What is it?
- Tailsnitch is a Go command line auditor for Tailscale that reads a tailnet through the Tailscale API, names every finding with a check id, and can apply a short list of fixes or export SOC 2 evidence. Its limits are visible: scope gaps, two checks it cannot confirm, and fixes that delete real devices and keys.
- Who is it for?
- Adopt Tailsnitch if you run a Tailscale tailnet and want a repeatable record of who can reach what, especially when an auditor asks for per device evidence. Do not adopt it as a replacement for the admin console, because Tailnet Lock state and several settings still need a human or the local tailscale CLI, and the fix mode can delete auth keys, tags and devices.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 50 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A tailnet audit where every finding carries a check id
Tailsnitch exists because a Tailscale tailnet is assembled from small settings that each look harmless on their own, and the combination is what exposes a network. The tool describes itself as a security auditor for Tailscale configurations and scans a tailnet for 57 misconfigurations, overly permissive access controls and security best practice violations. The useful design choice is that every finding is a named rule with a stable identifier: DEV-001, ACL-011, AUTH-001, LOG-012, NET-004. Those identifiers are not cosmetic. They show up in the --checks flag, in the .tailsnitch-ignore file, in the scope table that maps API permissions to rules, and in the SOC 2 export, so the same finding can be referenced in a ticket, a suppression file and an auditor spreadsheet without rewording.
The audience is a tailnet administrator, and the reading level is fairly technical. The command assumes you know what an ACL, a tag, a posture integration and a webhook destination are, because the rule names assume that knowledge. A team that runs Tailscale as a managed zero trust fabric for employee devices and servers is the intended user; a team with a handful of personal nodes will find more here than it needs.
Missing OAuth scopes produce not evaluated, not a pass
The credential model is the part to understand before trusting any output. Tailsnitch supports two authentication methods, and OAuth is preferred when both happen to be configured. An OAuth client is created at the Tailscale admin settings page for OAuth clients and is described as scoped, auditable access that does not expire when employees leave. An API key, created in the admin settings page for keys, operates as the user who created it and inherits that user's permissions, which is a materially different risk profile.
The project publishes a scope table tying each OAuth scope to the checks it unlocks: policy_file:read covers the tailnet policy file and the ACL, NET and SSH rules, devices:core:read covers the device list, dns:read covers DNS configuration, auth_keys:read covers machine auth keys, and further scopes cover tailnet settings, network flow logging, HTTPS certificates, log stream destinations, webhooks, OAuth clients, user roles, the security contact and posture integrations. The behaviour when a scope is missing is the detail worth knowing: a check that cannot read its setting reports that it could not read it rather than passing. That is the correct design, and it also means a partly scoped credential produces a report full of gaps, so the scope list has to be read before the results are.
Two auth checks are unconfirmed against a live tailnet
Two rules, AUTH-005 and AUTH-006, read the tailnet's federated identities, which the admin console calls trust credentials. They arrive from the same keys listing as auth keys, so the project expects auth_keys:read to cover them. The documentation is explicit that this has not been confirmed against a live tailnet, and it says why the uncertainty matters: if the keys listing cannot be read, both checks report not evaluated rather than passing, but if the listing comes back with the trust credentials silently filtered out instead of returning an error, then AUTH-005 would state that no trust credentials exist and AUTH-006 would find nothing to check. That is a false all clear rather than a reported gap, and it is the single most important caveat in the README.
A second gap sits in the device rules. DEV-010 and DEV-012 report on Tailnet Lock, which the Tailscale API does not expose as a tailnet setting. Locked out devices are visible through the API, but establishing whether the feature is enabled requires the local tailscale CLI, which reads the daemon on the machine running tailsnitch. When you audit a different tailnet with --tailnet, that part of the result reflects the machine you are standing on, not the tailnet you asked about. The project says so and offers --tailscale-path for a non standard binary location, but the check remains a local read presented inside a remote report.
Fix mode deletes real resources, and the dry run is the way in
Tailsnitch is not read only. The --fix flag remediates findings through the Tailscale API, and the README's own description of it is blunt, calling it interactive fix mode with a yolo aside. The extra OAuth scopes needed are devices:core for deleting devices and modifying tags, and auth_keys for deleting auth keys, which is a sign of how much power this mode holds.
Only a short list of checks can be fixed automatically. AUTH-001, AUTH-002 and AUTH-003 delete auth keys, DEV-002 removes tags from user devices, DEV-004 deletes stale devices and DEV-005 authorizes pending devices. Everything else gets a direct link to the admin console for manual intervention, which is a sensible division between what an API call can do safely and what a person should do. The related flags are --dry-run to preview what would be fixed, --auto to auto select safe fixes while still requiring confirmation, and --no-audit-log to disable the audit logging of fix actions. The dry run is the obvious first command to use here, and --no-audit-log is worth noting for anyone who wants the tool to leave no trace of its own changes.
The SOC 2 export turns passing checks into dated rows
The export path is what separates Tailsnitch from a linter that prints to a terminal. With --soc2 json or --soc2 csv, the tool produces an evidence report with Common Criteria control mappings for audit work. The report includes per resource test results, where each device, key and ACL rule is tested individually rather than summarised, CC code mappings such as CC6.1, CC6.2, CC6.3, CC6.6, CC7.1 and CC7.2, a Pass, Fail or N/A status for each control test, and a timestamp for the audit trail.
The CSV header the project shows makes the structure concrete, with columns for resource_type, resource_id, resource_name, check_id, check_title, cc_codes, status, details and tested_at. A row can say a tagged server device passed DEV-001 because key expiry is enabled on tag:server, while an auth key failed AUTH-001 because a reusable key expires in 45 days. That granularity is the point: an auditor generally wants a row per resource, and this is the one feature here that a spreadsheet cannot be produced from by hand at the same fidelity.
The machine readable --json output of a normal run serves the same purpose for internal pipelines, and --verbose adds passing checks to the terminal view. For regular use without export, --severity high narrows the output to critical and high findings, and --category access, auth, device, network, ssh or log restricts it by area. A single rule can be run with --checks, either by id or by name such as stale-devices or tailnet-lock-not-enabled, and --list-checks prints the full catalogue.
Suppressions are per check, per item, or per tagged object
Accepted risk is handled through a .tailsnitch-ignore file in the working directory, which gives the tool a per project suppression list rather than a global one. Entries are check ids, each with an optional comment explaining the decision, so a line can record that a plan requirement makes a check inapplicable or that the check is muted deliberately.
The finer control is on the right hand side of the colon. The syntax ACL-011:tag:monitoring suppresses one tagged object inside a check rather than muting the whole rule, and AUTH-001:tskey-a suppresses a single key. For a rule that flags a broad pattern, that difference matters: a team can accept the tag named monitoring and keep seeing the same rule fire for every other tag. This is the more precise mechanism of the two, and the one to prefer when a finding is a property of a single object rather than of the tailnet.
The project also ships HARDENING_TAILSCALE.md at the top level, alongside docs/, cmd/, pkg/ and main.go, which is the manual counterpart to the tool. That pairing is a fair alternative path for a small tailnet: read the guidance, fix the settings in the admin console by hand, and skip the credential setup entirely. Tailsnitch earns its keep when the tailnet is large enough that nobody can hold all 57 rules in their head, or when an auditor wants dated per resource rows, and it is overkill when there are three devices and one owner.
Install and first audit in three commands
Tailsnitch is a Go program, and the README offers a pre built binary, a go install path and a source build. The quickest start is the binary from the repository releases page, and macOS users are told to clear the quarantine attribute after downloading. Go users can install it directly from the module path, and the source build clones the repository and compiles a tailsnitch binary in the root directory. The build requires Go 1.24.0 with toolchain go1.24.1, as declared in go.mod, and the module pulls in the Tailscale API client, the hujson parser for policy files, cobra for the command line and the bubbletea, lipgloss and bubbles set for the terminal interface.
Install with Go:
go install github.com/Adversis/tailsnitch@latestSet the credential, then run the audit:
export TS_API_KEY="tskey-api-..."
tailsnitchThe OAuth path uses two variables instead, TS_OAUTH_CLIENT_ID and TS_OAUTH_CLIENT_SECRET, and the OAuth client is preferred when both credential types are present. Narrow the output to what matters:
tailsnitch --severity highYou should see findings grouped by category, each tagged with its check id, plus a summary of the rules that could not be evaluated because of a missing scope. If you want the full catalogue before running anything, tailsnitch --list-checks prints the 57 rule names and ids.
The project is MIT licensed, and the repository is not archived with the last push on 2026-09-03 and releases v1.5, v1.6 and v1.7 recorded on that day and in March 2026. The Makefile injects a version, build id and build date into the binary at link time, so a downloaded release and a locally built copy can be told apart. Upgrading is a matter of replacing the binary, and there is no server side component, no config file beyond .tailsnitch-ignore, and no state to migrate.
Editorial conclusion
Adopt Tailsnitch if you run a Tailscale tailnet and want a repeatable record of who can reach what, especially when an auditor asks for per device evidence. Do not adopt it as a replacement for the admin console, because Tailnet Lock state and several settings still need a human or the local tailscale CLI, and the fix mode can delete auth keys, tags and devices. Verify first that the credential you use has the read scopes for the checks you care about, because a missing scope produces a not evaluated result rather than a pass, and run tailsnitch --fix --dry-run before allowing it to change anything.
Frequently asked questions
What credentials does Tailsnitch need to audit a tailnet?
Either an API key in TS_API_KEY, which acts as the user who created it, or an OAuth client in TS_OAUTH_CLIENT_ID and TS_OAUTH_CLIENT_SECRET, which the project prefers when both are set. OAuth is described as scoped, auditable access that does not expire when employees leave.
What happens if Tailsnitch is missing an OAuth scope?
The checks that need the missing scope report that they could not read the setting instead of passing, and checks whose data is unaffected still run. The README also flags an unconfirmed case for AUTH-005 and AUTH-006, where a silently filtered keys listing would make them report that no trust credentials exist.
Can Tailsnitch fix the problems it finds?
Yes, with --fix, which needs the extra devices:core and auth_keys scopes. It can delete auth keys, remove tags from user devices, delete stale devices and authorize pending devices, and it prints admin console links for everything else. Use --fix --dry-run to preview first.
How does Tailsnitch handle Tailnet Lock checks?
The Tailscale API does not expose Tailnet Lock as a tailnet setting, so DEV-010 and DEV-012 depend on the local tailscale CLI reading the daemon on the machine running tailsnitch. Use --tailscale-path if that binary is in a non standard location, and treat those results with care when auditing another tailnet with --tailnet.
Can Tailsnitch produce evidence for a SOC 2 audit?
Yes, through --soc2 json and --soc2 csv, which export per resource test results with Common Criteria mappings such as CC6.1 and CC7.2, a Pass, Fail or N/A status for each control test, and a timestamped audit trail.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/adversis-tailsnitch)