juanfont/headscale · error · ErrSSHActionInvalid
%q %w
Error message
%q %w
What it means
SSHAction.UnmarshalJSON accepts "accept", "check", or an empty string (surfaced later by per-rule Validate). Anything else returns ErrSSHActionInvalid with the value.
Source
Thrown at hscontrol/policy/v2/types.go:1642
// String returns the string representation of the [SSHAction].
func (a *SSHAction) String() string {
return string(*a)
}
// UnmarshalJSON trims surrounding whitespace before matching, lets the
// empty string through (per-rule Validate() surfaces it later), and
// rejects every other unknown value here.
func (a *SSHAction) UnmarshalJSON(b []byte) error {
str := strings.TrimSpace(strings.Trim(string(b), `"`))
switch str {
case "":
*a = SSHAction("")
case "accept":
*a = SSHActionAccept
case "check":
*a = SSHActionCheck
default:
return fmt.Errorf("%q %w", str, ErrSSHActionInvalid)
}
return nil
}
// MarshalJSON implements JSON marshaling for [SSHAction].
func (a *SSHAction) MarshalJSON() ([]byte, error) {
return json.Marshal(string(*a))
}
// Protocol represents a network protocol with its IANA number and descriptions.
type Protocol string
const (
ProtocolNameICMP Protocol = "icmp"
ProtocolNameIGMP Protocol = "igmp"
ProtocolNameIPv4 Protocol = "ipv4"
ProtocolNameIPInIP Protocol = "ip-in-ip"View on GitHub (pinned to 565fd254d0)
Solutions
- Use "accept" or "check" as the ssh action
- Delete rules intended as denies — absence of a rule already denies SSH
- Check spelling and quoting of the action string
Example fix
// before
{"action": "deny", "src": [...], "dst": [...], "users": [...]}
// after
{"action": "check", "src": [...], "dst": [...], "users": [...]} Defensive patterns
Strategy: validation
Validate before calling
func validSSHAction(s string) bool {
switch s { case "", "accept", "check": return true }
return false
} Prevention
- SSH rules only grant access (accept/check); omit rules to deny
- Use "check" for interactive approval flows instead of deny
When it happens
Trigger: An ssh rule with "action": "deny" or a typo like "accep" — SSH rules cannot deny, they only grant accept or check-session modes.
Common situations: Writing deny-style SSH rules; copy-paste from ACL actions assuming the same vocabulary; trailing whitespace variants other than simple trim cases.
Related errors
- tags in SSH source cannot access user-owned devices
- user destination requires source to contain only that same u
- autogroup:self destination requires source to contain only u
- tags in SSH source cannot access autogroup:member (user-owne
- wildcard (*) is not supported as SSH destination
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/05e87a9b07016bd4.
Report an issue: GitHub.