Install
$ agentstack add skill-magnus919-agent-skills-tailnet-policy ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
tailnet-policy
Overview
Headscale uses Tailscale-compatible policy files written in huJSON (Human JSON — standard JSON with trailing commas and // comments). Policy files control:
- ACLs (deprecated legacy syntax) —
{action, users, ports}rules - Grants (modern syntax) —
{src, dst, ip, proto, via}rules - Tags — Node identity tags (
tag:dev,tag:prod) - TagOwners — Which users/groups can apply which tags
- AutoApprovers — Auto-approval for subnet routers and exit nodes
- Tailscale SSH — SSH access rules via
ssh.usersandssh.action
ACLs vs Grants
| Feature | ACLs (legacy) | Grants (modern) | |---------|--------------|-----------------| | Format | {action: "accept", users: [...], ports: [...]} | {src: [...], dst: [...], ip: [...], proto: "tcp"} | | Port filtering | Embedded in ports: ["*:*"] | Separate ip field for ports | | Protocol filtering | Not supported | proto field (tcp, udp, icmp) | | Destination routing | Not supported | via field for relay/exit nodes | | Status | Deprecated by Tailscale | Current recommended syntax |
Use Grants wherever possible. The migrate-acls-to-grants.py script can convert legacy ACL files automatically.
Policy Location
The policy file path is configured in Headscale's config.yaml:
policy:
path: /etc/headscale/policy.hujson
After modifying the policy file, reload it on the Headscale server:
# Reload via SIGHUP
kill -HUP $(pgrep headscale)
# Or use the convenience script
./skills/tailnet-policy/reload-headscale-policy.sh
Writing Policy
Allow-All (default-open)
{
// Grants that allow all traffic
"grants": [
{
"src": ["autogroup:member"],
"dst": ["autogroup:member"],
"ip": ["*:*"]
}
],
// Tag ownership
"tagOwners": {
"tag:dev": ["autogroup:admin"],
"tag:prod": ["autogroup:admin"]
}
}
Deny-All (default-closed)
{
"grants": [
// Only allow ICMP (ping) between all members
{
"src": ["autogroup:member"],
"dst": ["autogroup:member"],
"ip": ["*"],
"proto": "icmp"
}
],
// Specific grants added per-service
"tagOwners": {
"tag:monitor": ["autogroup:admin"]
}
}
Segmented (environments)
{
"grants": [
// Dev can reach dev
{
"src": ["tag:dev"],
"dst": ["tag:dev"],
"ip": ["*:*"]
},
// Prod can reach prod
{
"src": ["tag:prod"],
"dst": ["tag:prod"],
"ip": ["*:*"]
},
// Admin access to all
{
"src": ["autogroup:admin"],
"dst": ["tag:dev", "tag:prod"],
"ip": ["*:*"]
}
],
"tagOwners": {
"tag:dev": ["autogroup:admin"],
"tag:prod": ["autogroup:admin"]
}
}
Tag-Based Patterns
Tags are node-level identifiers set via tailscale up --advertise-tags=tag:dev. They decouple policy from user identity.
{
"tagOwners": {
"tag:ci-runner": ["autogroup:admin"],
"tag:database": ["autogroup:admin"],
"tag:webserver": ["autogroup:admin"],
"tag:monitoring": ["autogroup:admin"]
},
"grants": [
{
"src": ["tag:monitoring"],
"dst": ["tag:webserver", "tag:database"],
"ip": ["*:*"]
},
{
"src": ["tag:webserver"],
"dst": ["tag:database"],
"ip": ["tcp:5432"]
}
]
}
Grants Syntax
Grants are the modern policy primitive:
{
"grants": [
{
"src": ["tag:source", "user@example.com"],
"dst": ["tag:destination", "100.64.0.1"],
"ip": ["*:*"], // proto:port — "*:*" means all
"proto": "tcp", // optional protocol filter
"via": ["tag:exit-node"] // optional via/routing
}
]
}
Fields:
src— Source entities (tags, users, autogroups, IPs)dst— Destination entitiesip— Protocol and port filter (e.g.tcp:80,udp:53,*:*,*)proto— Protocol constraint (tcp,udp,icmp)via— Route through a specific exit node or relay
Auto Approvers
Auto-approvers let specific users approve subnet routes and exit nodes without manual intervention:
{
"autoApprovers": {
"routes": {
"10.0.0.0/8": ["autogroup:admin"],
"172.16.0.0/12": ["alice@example.com"]
},
"exitNode": ["autogroup:admin"]
}
}
routes: Maps CIDR ranges to lists of users who can auto-approve those routesexitNode: Lists users who can advertise exit nodes
Autogroups
Autogroups are dynamic groups resolved by Headscale/Tailscale at runtime:
| Autogroup | Description | |-----------|-------------| | autogroup:member | All tailnet members | | autogroup:admin | Tailnet admins | | autogroup:tagged | All tagged nodes (any node with at least one tag) | | autogroup:internet | The public internet (used for exit node routing) |
Tailscale SSH Configuration
Tailscale SSH rules are configured via the ssh section:
{
"ssh": [
{
"action": "accept", // "accept" or "check"
"src": ["autogroup:admin"],
"dst": ["tag:webserver"],
"users": ["root", "ubuntu"]
},
{
"action": "check", // "check" requires node-level SSH authorization
"src": ["autogroup:member"],
"dst": ["tag:dev"],
"users": ["*"]
}
]
}
action:"accept"(allow directly) or"check"(require node-level auth)src: Source users/groupsdst: Destination tags/usersusers: Which OS users can be SSH'd into
Testing Policies
Policy files include test definitions that are validated when loaded:
{
"grants": [...],
"tests": [
{
"src": "alice@example.com",
"dst": "tag:webserver",
"ip": ["tcp:443"],
"action": "accept" // expected result
},
{
"src": "bob@example.com",
"dst": "tag:database",
"ip": ["tcp:22"],
"action": "drop" // expected result
}
]
}
Validate tests with:
./skills/tailnet-policy/validate-policy.py --policy policy.hujson
Gotchas
- Headscale does NOT support device posture — rules like
devicePostureordevice:managedare Tailscale-only - Headscale does NOT support IP sets —
ipSetsandipprotocolare not supported - Headscale does NOT support OIDC groups in ACLs — groups from OIDC claims cannot be used in policy; use
autogroup:adminandautogroup:memberinstead - Tag names must start with
tag:and contain only lowercase letters, numbers, and hyphens - Policy reload via SIGHUP does not return errors on failure — always validate before reloading
- ACL
usersfield and Grantsrcfield are NOT interchangeable — grants usesrc/dst, legacy ACLs useusers/ports - Tests are not enforced at runtime — they only validate during parsing; a passing test does not guarantee runtime behavior
Trigger Conditions
This skill is automatically loaded when the user's message contains any of these keywords:
- tailnet policy
- headscale acl
- hujson policy
- tailscale grant
- tagowners
- autoapprovers
- tailscale ssh policy
- policy validation
- migrate acls
- /etc/headscale/policy
- policy.hujson
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: magnus919
- Source: magnus919/agent-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.