Skip to content

fix(tailscale): align tool coverage and outputs with the Tailscale API#5366

Merged
waleedlatif1 merged 4 commits into
stagingfrom
worktree-tailscale-validate
Jul 2, 2026
Merged

fix(tailscale): align tool coverage and outputs with the Tailscale API#5366
waleedlatif1 merged 4 commits into
stagingfrom
worktree-tailscale-validate

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Fixed list_users reading profilePicURL instead of the API's actual profilePicUrl field — was always returning null
  • Added nodeId (API's preferred device identifier), keyExpiryDisabled, and expires to device outputs
  • Fixed the set_acl If-Match header to be quoted per the Tailscale API spec
  • Added 4 new tools to close real coverage gaps: set_acl (write policy back, not just read), expire_device_key, suspend_user, delete_user (proper offboarding — the existing flow only deauthorized devices, never touched the user account)
  • Added wandConfig to the dnsServers/searchPaths block fields per repo convention
  • Expanded TailscaleBlockMeta skills/templates for ACL management and key-expiry incident response

Validated every one of the 24 tools' endpoints, methods, required/optional params, and response field extraction against the live Tailscale OpenAPI spec and Go SDK source. Two independent passes confirmed full API alignment and that no backwards-incompatible field removals were made.

Type of Change

  • Bug fix
  • New feature (additive tool coverage)

Testing

Tested manually. Verified against live Tailscale OpenAPI spec + Go client source. bun run lint and bun run type-check clean.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 2, 2026 5:14pm

Request Review

@cursor

cursor Bot commented Jul 2, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Adds and fixes tailnet security controls (ACL replacement, user suspend/delete, device key expiry) where misconfigured workflows could cut access or weaken policy; magicDNS removal from list DNS nameservers may break workflows that read it from that operation only.

Overview
Aligns the Tailscale block and tools with the live API and closes gaps in offboarding, incident response, and policy-as-code.

New operations and tools: set_acl (with optional If-Match ETag), expire_device_key, suspend_user, and delete_user, wired through the block, registry, and new tool modules.

API correctness fixes: list_users now reads profilePicUrl; device list/get outputs add nodeId, keyExpiryDisabled, and expires; list_auth_keys uses ?all=true; list_dns_nameservers drops magicDNS from that response (MagicDNS remains on get/set DNS preferences); Set ACL sends a quoted If-Match header per spec.

UX/docs: wandConfig on DNS nameserver and search-path fields; block inputs/outputs for userId, ACL, and ETag; updated templates/skills for ACL drift remediation, full offboarding, ACL updates, and compromised-device lockdown.

Reviewed by Cursor Bugbot for commit 7f43c6e. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes API alignment bugs in the Tailscale integration (wrong profilePicURL casing, unquoted If-Match ETag) and extends coverage with four new tools (expire_device_key, suspend_user, delete_user, set_acl), additional device output fields (nodeId, keyExpiryDisabled, expires), wandConfig hints for DNS fields, and expanded TailscaleBlockMeta skill templates.

  • Bug fixes: list_users now reads profilePicUrl (lowercase) matching the actual API field; set_acl properly wraps ETags in quotes per spec; get_device/list_devices now surface nodeId, keyExpiryDisabled, and expires.
  • New tools: expire_device_key, suspend_user, delete_user, and set_acl close coverage gaps for incident response and offboarding workflows.
  • Behavior change in list_auth_keys: ?all=true was silently added, causing the endpoint to return all keys including revoked and expired ones rather than only active keys; existing workflows may need to filter by key state.

Confidence Score: 4/5

Safe to merge with one behavior change in list_auth_keys worth deciding on first.

The new tools and bug fixes are well-implemented and consistent with the existing codebase patterns. The one concern is list_auth_keys now always appending ?all=true, which changes what data existing callers receive — revoked and expired keys are silently included in results that previously contained only active keys. Workflows that iterate results to find a valid key or count active keys will behave differently without any code change on their part.

apps/sim/tools/tailscale/list_auth_keys.ts — the ?all=true query parameter addition is the only change in that file and alters behavior for all existing callers.

Important Files Changed

Filename Overview
apps/sim/tools/tailscale/list_auth_keys.ts Adding ?all=true silently expands the response to include revoked/expired keys, breaking existing workflows that assume only active keys are returned.
apps/sim/tools/tailscale/set_acl.ts New tool for writing ACL policy. ETag quoting, error handling, and response parsing all look correct and consistent with the existing get_acl tool.
apps/sim/tools/tailscale/expire_device_key.ts New tool using the correct POST /api/v2/device/{deviceId}/expire endpoint. Error handling and response shape are consistent with sibling tools.
apps/sim/tools/tailscale/suspend_user.ts New tool using the correct POST /api/v2/users/{userId}/suspend endpoint. Consistent with delete_user implementation.
apps/sim/tools/tailscale/delete_user.ts New tool using the correct POST /api/v2/users/{userId}/delete endpoint with proper error handling and output shape.
apps/sim/tools/tailscale/list_users.ts Bug fix: corrects profilePicURL field read from user.profilePicURL to the API's actual user.profilePicUrl (lowercase 'rl'), which was always returning null.
apps/sim/tools/tailscale/list_dns_nameservers.ts Correctly removes magicDNS from a tool that never received it from the API, but creates an asymmetry with set_dns_nameservers which still returns that field.
apps/sim/blocks/blocks/tailscale.ts Block config correctly wires the 4 new operations, adds wandConfig to DNS fields, maps new params, and updates TailscaleBlockMeta skills and templates.
apps/sim/tools/tailscale/types.ts Shared type updates (nodeId, keyExpiryDisabled, expires on TailscaleDeviceOutput; removal of magicDNS from TailscaleListDnsNameserversResponse) are consistent with tool changes.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Tailscale Block Operation] --> B{Operation type}
    B --> C[Device ops]
    B --> D[User ops]
    B --> E[ACL ops]
    B --> F[DNS ops]
    B --> G[Auth Key ops]

    C --> C1[list_devices / get_device / delete_device / authorize_device]
    C --> C2[get_device_routes / set_device_routes / update_device_key]
    C --> C3[NEW: expire_device_key - POST /device/id/expire]

    D --> D1[list_users - bug fix: profilePicUrl casing]
    D --> D2[NEW: suspend_user - POST /users/id/suspend]
    D --> D3[NEW: delete_user - POST /users/id/delete]

    E --> E1[get_acl - GET /tailnet/t/acl - returns ETag]
    E --> E2[NEW: set_acl - POST /tailnet/t/acl - If-Match quoted ETag]

    F --> F1[list_dns_nameservers - magicDNS removed]
    F --> F2[set_dns_nameservers / get_dns_preferences / set_dns_preferences]

    G --> G1[list_auth_keys - CHANGED: all=true includes revoked keys]
    G --> G2[create_auth_key / get_auth_key / delete_auth_key]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Tailscale Block Operation] --> B{Operation type}
    B --> C[Device ops]
    B --> D[User ops]
    B --> E[ACL ops]
    B --> F[DNS ops]
    B --> G[Auth Key ops]

    C --> C1[list_devices / get_device / delete_device / authorize_device]
    C --> C2[get_device_routes / set_device_routes / update_device_key]
    C --> C3[NEW: expire_device_key - POST /device/id/expire]

    D --> D1[list_users - bug fix: profilePicUrl casing]
    D --> D2[NEW: suspend_user - POST /users/id/suspend]
    D --> D3[NEW: delete_user - POST /users/id/delete]

    E --> E1[get_acl - GET /tailnet/t/acl - returns ETag]
    E --> E2[NEW: set_acl - POST /tailnet/t/acl - If-Match quoted ETag]

    F --> F1[list_dns_nameservers - magicDNS removed]
    F --> F2[set_dns_nameservers / get_dns_preferences / set_dns_preferences]

    G --> G1[list_auth_keys - CHANGED: all=true includes revoked keys]
    G --> G2[create_auth_key / get_auth_key / delete_auth_key]
Loading

Comments Outside Diff (1)

  1. apps/sim/tools/tailscale/list_auth_keys.ts, line 47 (link)

    P1 Silent behavior change: revoked/expired keys now included

    Adding ?all=true makes the Tailscale API return all keys regardless of state, including revoked and expired ones. Previously the endpoint returned only active (non-invalid) keys. Any existing workflow that calls list_auth_keys and iterates the results to find a usable key or to confirm active coverage will now also see revoked/expired entries, which could cause incorrect logic (e.g., attempting to delete or use an already-revoked key) or unexpected key counts. This change is not mentioned in the PR description and has no accompanying filter in transformResponse to offset it.

Reviews (3): Last reviewed commit: "fix(tailscale): list_auth_keys now retur..." | Re-trigger Greptile

Comment thread apps/sim/tools/tailscale/expire_device_key.ts Outdated
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Fixed — TailscaleExpireDeviceKeyResponse now extends ToolResponse to match delete_user/suspend_user. Pushed in fdf867f.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit fdf867f. Configure here.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 6fd8469. Configure here.

- fix list_users profilePicUrl field name (API returns lowercase, was always null)
- add nodeId, keyExpiryDisabled, expires to device outputs
- quote the If-Match header value on ACL updates per API spec
- add set_acl, expire_device_key, suspend_user, delete_user tools
- add wandConfig to dnsServers/searchPaths block fields
- expand BlockMeta skills/templates for ACL and key-expiry workflows
The GET /tailnet/{tailnet}/dns/nameservers response only returns
{dns: string[]} per the API spec — magicDNS is not part of this
endpoint's response and was always silently false.
Matches the pattern used by the other new tools in this PR
(delete_user, suspend_user).
GET /tailnet/{tailnet}/keys silently scopes to the caller's own
keys unless all=true is passed, contradicting the tool's stated
purpose of listing all auth keys in the tailnet.
@waleedlatif1 waleedlatif1 force-pushed the worktree-tailscale-validate branch from 6fd8469 to 7f43c6e Compare July 2, 2026 17:14
@waleedlatif1 waleedlatif1 merged commit f658e6d into staging Jul 2, 2026
11 checks passed
@waleedlatif1 waleedlatif1 deleted the worktree-tailscale-validate branch July 2, 2026 17:15
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant