Documentation
¶
Overview ¶
Package helpers provides common utility functions used across the mcp-oauth library.
This package contains helper functions for string manipulation, formatting, IP classification, and other shared operations that don't fit into domain-specific packages. These utilities are used internally by multiple packages to avoid code duplication and maintain consistent behavior across the codebase.
Key utilities:
- SafeTruncate: Safely truncates strings for logging sensitive data
- ClassifyIP: Classifies IP addresses for SSRF protection (public, private, loopback, etc.)
- IsLinkLocal: Checks if an IP is link-local (cloud metadata SSRF protection)
- IsLoopbackHostname: Checks if a hostname represents a loopback address
- ValidateMetadataPath: Validates paths for security concerns (path traversal, etc.)
- ValidateClientName: Validates client_name for XSS and log injection prevention
Package helpers provides common utility functions used across the mcp-oauth library.
Package helpers provides common utility functions used across the mcp-oauth library. These utilities handle string manipulation, formatting, and other shared operations that don't fit into domain-specific packages.
Index ¶
- Constants
- func FindMatchingAudience(tokenAudiences, trustedAudiences []string) string
- func IsLinkLocal(ip net.IP) bool
- func IsLoopbackHostname(hostname string) bool
- func IsPrivateOrInternal(ip net.IP) bool
- func MatchAudienceSecure(tokenAudience string, trustedAudiences []string) string
- func NormalizeURL(rawURL string) string
- func PathMatchesPrefix(resourcePath, prefix string) bool
- func SafeTruncate(s string, maxLen int) string
- func ValidateClientName(name string) error
- func ValidateMetadataPath(path string) error
- type IPClassification
- type PathValidationError
Constants ¶
const MaxClientNameLength = 256
MaxClientNameLength is the maximum allowed length for client_name in runes.
const MaxMetadataPathLength = 256
MaxMetadataPathLength is the maximum allowed length for custom metadata paths. This prevents DoS attacks through excessively long path registration.
const MaxPathSegments = 10
MaxPathSegments is the maximum number of path segments (slashes) allowed. This prevents DoS attacks through deeply nested paths.
Variables ¶
This section is empty.
Functions ¶
func FindMatchingAudience ¶ added in v0.2.39
FindMatchingAudience checks if any of the token's audiences match any trusted audience. Returns the first matched token audience value, or empty string if no match. This is the multi-audience version of MatchAudienceSecure for JWT tokens that may have multiple audience claims.
func IsLinkLocal ¶
IsLinkLocal checks if an IP address is link-local (unicast or multicast). This includes:
- IPv4 link-local: 169.254.0.0/16 (also catches cloud metadata 169.254.169.254)
- IPv6 link-local unicast: fe80::/10
- IPv6 link-local multicast: ff02::/16
Link-local addresses are a significant security concern in cloud environments as they can access instance metadata services (AWS, GCP, Azure).
func IsLoopbackHostname ¶
IsLoopbackHostname checks if a hostname represents a loopback address. This includes the entire 127.0.0.0/8 range (RFC 1122) and IPv6 ::1. Expects hostname without port (as returned by url.URL.Hostname()).
Note: This function does NOT consider 0.0.0.0 as loopback (it's "unspecified").
func IsPrivateOrInternal ¶
IsPrivateOrInternal checks if an IP is private, loopback, link-local, or unspecified. This is a convenience function for SSRF protection that returns true for any non-public IP address.
Used by client ID metadata document fetching for comprehensive SSRF protection.
func MatchAudienceSecure ¶ added in v0.2.39
MatchAudienceSecure checks if a token audience matches any of the trusted audiences. Uses URL normalization for consistent comparison and constant-time comparison for security (defense-in-depth against timing attacks).
Returns the matched trusted audience value, or empty string if no match found. This is used for SSO token forwarding where tokens from trusted upstream services are validated.
Examples:
MatchAudienceSecure("https://example.com", []string{"https://EXAMPLE.COM"}) // Returns "https://EXAMPLE.COM"
MatchAudienceSecure("https://example.com:443/", []string{"https://example.com"}) // Returns "https://example.com"
MatchAudienceSecure("https://other.com", []string{"https://example.com"}) // Returns ""
func NormalizeURL ¶
NormalizeURL normalizes a URL for comparison. This is used for RFC 8707 resource identifier and audience comparison, where semantically equivalent URLs should be considered equal.
Normalization includes:
- Lowercase scheme and host (case-insensitive per RFC 3986)
- Remove default ports (:443 for https, :80 for http)
- Remove trailing slashes from path
- Preserve path case (paths are case-sensitive)
If the input is not a valid URL, it returns the lowercase trimmed input for backwards compatibility with non-URL audience values.
Example:
NormalizeURL("https://EXAMPLE.COM/") // Returns: "https://example.com"
NormalizeURL("https://example.com:443/") // Returns: "https://example.com"
NormalizeURL("https://example.com") // Returns: "https://example.com"
NormalizeURL("https://example.com///") // Returns: "https://example.com"
NormalizeURL("HTTPS://Example.COM/Path") // Returns: "https://example.com/Path"
NormalizeURL("client-id") // Returns: "client-id" (non-URL)
func PathMatchesPrefix ¶
PathMatchesPrefix checks if resourcePath matches or starts with prefix. Handles path boundaries correctly: /mcp/files matches /mcp but not /mc.
This is a pure function used for longest-prefix matching in path configuration lookups. It ensures that path matching respects segment boundaries.
Returns false if either resourcePath or prefix is empty (empty prefix should not match anything in the context of path configuration).
Examples:
PathMatchesPrefix("/mcp/files", "/mcp") // true - valid prefix match
PathMatchesPrefix("/mcp", "/mcp") // true - exact match
PathMatchesPrefix("/mcpx", "/mcp") // false - not a segment boundary
PathMatchesPrefix("/other/mcp", "/mcp") // false - not a prefix
PathMatchesPrefix("/a", "") // false - empty prefix
func SafeTruncate ¶
SafeTruncate safely truncates a string to maxLen characters without panicking. Returns the original string if it's shorter than maxLen, otherwise returns the first maxLen characters. This prevents index out of bounds errors when logging sensitive data like tokens, where only a prefix should be shown.
If maxLen is negative, it's treated as 0 and returns an empty string.
Example:
SafeTruncate("very-long-token-abc123", 8) // Returns: "very-lon"
SafeTruncate("short", 10) // Returns: "short"
SafeTruncate("test", -1) // Returns: ""
func ValidateClientName ¶ added in v0.2.47
ValidateClientName validates the client_name field to prevent potential stored XSS, script injection, and log injection attacks. This is a defense-in-depth measure - while client_name is typically only used in JSON responses (which escape HTML), validation prevents issues if the value is ever displayed in various contexts (admin dashboards, log viewers, audit reports, markdown renderers).
Validation rules:
- Must not contain HTML-like characters (< or >)
- Must not contain quote characters (' " `) that enable script/template injection
- Must not exceed 256 characters (runes, not bytes)
- Must contain only printable characters (no control characters)
- Must not contain newlines (prevents log line splitting attacks)
Returns nil if the name is valid, or an error describing the validation failure.
func ValidateMetadataPath ¶
ValidateMetadataPath validates a metadata path for security concerns. This is used by both the HTTP handler (for runtime requests) and config validation (for startup configuration).
Security checks performed:
- Path traversal sequences (..)
- Null bytes (can cause issues in some HTTP implementations)
- Excessive path length (DoS prevention)
- Excessive path segments (DoS prevention)
Returns nil if the path is valid, otherwise returns an error describing the issue.
Types ¶
type IPClassification ¶
type IPClassification int
IPClassification represents the security classification of an IP address. This is used for SSRF protection in redirect URI validation and client ID metadata fetching.
const ( // IPClassificationPublic indicates a publicly routable IP address. IPClassificationPublic IPClassification = iota // IPClassificationLoopback indicates a loopback address (127.0.0.0/8, ::1). IPClassificationLoopback // IPClassificationPrivate indicates a private/internal address (RFC 1918, ULA). IPClassificationPrivate // IPClassificationLinkLocal indicates a link-local address (169.254.x.x, fe80::/10). IPClassificationLinkLocal // IPClassificationUnspecified indicates an unspecified address (0.0.0.0, ::). IPClassificationUnspecified )
func ClassifyIP ¶
func ClassifyIP(ip net.IP) IPClassification
ClassifyIP returns the security classification of an IP address. This is the single source of truth for IP classification used across the library for SSRF protection in redirect URI validation and client metadata fetching.
Classifications:
- Unspecified: 0.0.0.0, :: (always dangerous, undefined behavior)
- Loopback: 127.0.0.0/8, ::1 (allowed for native apps per RFC 8252)
- LinkLocal: 169.254.0.0/16, fe80::/10 (cloud metadata SSRF risk)
- Private: RFC 1918 (10/8, 172.16/12, 192.168/16), fc00::/7 (SSRF to internal networks)
- Public: All other addresses (generally safe)
func (IPClassification) String ¶
func (c IPClassification) String() string
String returns a human-readable name for the IP classification.
type PathValidationError ¶
PathValidationError represents a path validation failure.
func (*PathValidationError) Error ¶
func (e *PathValidationError) Error() string
Error implements the error interface.