httputil

package
v0.0.0-...-4b2f3aa Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 2, 2026 License: Apache-2.0 Imports: 39 Imported by: 7

Documentation

Index

Constants

View Source
const BlockedAddressLogKey = "blocked_by_ssrf_policy"

BlockedAddressLogKey is the field to alert on. It appears on exactly one log message, so filtering for it yields every outbound fetch this deployment refused on address grounds and nothing else.

View Source
const BodyMaxSize = 1024 * 1024 * 10
View Source
const InsecureFetchAddressAllowedFlag = "http.insecure_fetch_address_allowed"

InsecureFetchAddressAllowedFlag names the setting that lifts the restriction, so whoever reads the log does not have to go looking for it.

View Source
const MaxResponseBytes int64 = 2 * 1024 * 1024

MaxResponseBytes caps the response body of every fetch of a URL a project supplied. 2 MiB is orders of magnitude above any legitimate response on these paths -- a webhook result, an OIDC discovery document, a JWK set, an SMS gateway's reply -- and low enough that a hostile destination cannot exhaust memory.

Without it the reads on these paths are unbounded: a project admin could point a blocking hook at a host that streams indefinitely and take the server down, no address rule involved. The cap counts DECOMPRESSED bytes, because it is applied above the transport's transparent gzip handling; a small compressed body that expands without bound is refused too.

A fetch that needs a tighter bound still enforces its own: the CIMD document limit of 5120 bytes is set by the OAuth spec, not by this.

View Source
const RedactedQueryParamValue = "redacted"

Variables

View Source
var BlockedAddressLogger = slogutil.NewLogger("ssrf-address-policy")
View Source
var CSPNonceCookieDef = &CookieDef{
	NameSuffix: "csp_nonce",
	Path:       "/",
	SameSite:   http.SameSiteNoneMode,
}

CSPNonceCookieDef is a HTTP session cookie. The nonce has to be stable within a browsing session because Turbo uses XHR to load new pages. If nonce changes on every page load, the script in the new page cannot be run in the current page due to different nonce.

View Source
var CSPSchemeSourceHTTPS = CSPSchemeSource{Scheme: "https"}
View Source
var DefaultPermissionsPolicy = []PermissionsPolicyPolicy{
	{PermissionsPolicyDirectiveAccelerometer, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveAmbientLightSensor, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveAutoplay, PermissionsPolicyAllowlistAll},
	{PermissionsPolicyDirectiveBattery, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveBluetooth, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveBrowsingTopics, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveCamera, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveDisplayCapture, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveDocumentDomain, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveEncryptedMedia, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveExecutionWhileNotRendered, PermissionsPolicyAllowlistAll},
	{PermissionsPolicyDirectiveExecutionWhileOutOfViewport, PermissionsPolicyAllowlistAll},
	{PermissionsPolicyDirectiveFullscreen, PermissionsPolicyAllowlistAll},
	{PermissionsPolicyDirectiveGamepad, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveGeolocation, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveGyroscope, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveHid, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveIdentityCredentialsGet, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveIdleDetection, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveLocalFonts, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveMagnetometer, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveMicrophone, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveMidi, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveOtpCredentials, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectivePayment, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectivePictureInPicture, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectivePublickeyCredentialsCreate, PermissionsPolicyAllowlistSelf},
	{PermissionsPolicyDirectivePublickeyCredentialsGet, PermissionsPolicyAllowlistSelf},
	{PermissionsPolicyDirectiveScreenWakeLock, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveSerial, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveSpeakerSelection, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveStorageAccess, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveUsb, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveWebShare, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveWindowManagement, PermissionsPolicyAllowlistNone},
	{PermissionsPolicyDirectiveXrSpatialTracking, PermissionsPolicyAllowlistNone},
}

* Enabled features: * - autoplay=* * - execution-while-not-rendered=* * - execution-while-out-of-viewport=* * - fullscreen=* * - publickey-credentials-create=(self) (for WebAuthn) * - publickey-credentials-get=(self) (for WebAuthn)

View Source
var ErrBlockedAddress = errors.New("httputil: address is not publicly routable")

ErrBlockedAddress is returned when SafeDialer refuses to connect because the destination is not publicly routable.

Call sites that fetch a URL supplied by a project's configuration match on this so the refusal can be reported as a policy decision rather than as just another connection failure -- an operator has to be able to tell "we blocked this" apart from "the host was down".

View Source
var ErrResponseTooLarge = errors.New("httputil: response exceeds the maximum size")

ErrResponseTooLarge is returned by a read against a response body that exceeds MaxResponseBytes. It surfaces wherever the caller reads the body -- io.ReadAll, a JSON decode, a schema validation -- so a caller that already handles a malformed response handles this too.

View Source
var FileServerIndexHTMLtemplateDataKey = FileServerIndexHTMLTemplateDataKeyType{}
View Source
var FlashMessageTypeCookieDef = &CookieDef{
	NameSuffix: "flash_message_type",
	Path:       "/",
	SameSite:   http.SameSiteNoneMode,
}

FlashMessageTypeCookieDef is a HTTP session cookie.

View Source
var JSONResponseWriterLogger = slogutil.NewLogger("json-response-writer")
View Source
var JSONTooLarge = apierrors.RequestEntityTooLarge.WithReason("JSONTooLarge")
View Source
var SensitiveQueryParams = []string{

	"id_token_hint",
	"login_hint",

	"code",
	"token",
}

SensitiveQueryParams lists query parameter names whose values must never appear verbatim in logs (e.g. request URLs), because they can carry identity tokens, PII, or one-time credentials.

Functions

func BindJSONBody

func BindJSONBody(r *http.Request, w http.ResponseWriter, v *validation.SchemaValidator, payload any, options ...JSONOption) error

func CSPNoncePerRequest

func CSPNoncePerRequest(r *http.Request) (nonce string, rWithNonce *http.Request)

func CSPNoncePerSession

func CSPNoncePerSession(cookieManager CSPNoncePerSessionCookieManager, w http.ResponseWriter, r *http.Request) (nonce string, rWithNonce *http.Request)

func CheckContentType

func CheckContentType(raws []string) func(next http.Handler) http.Handler

func ConstructInternalRedirectURI

func ConstructInternalRedirectURI(ctx context.Context, redirectURI string) string

ConstructInternalRedirectURI is to construct a redirect uri that is only for internal use, for example, redirecting to the auth ui from authorization endpoint.

func CookieDomainWithoutPort

func CookieDomainWithoutPort(host string) string

CookieDomainWithoutPort derives host from r. If host has port, the port is removed. If host-1 is longer than ETLD+1, host-1 is returned. If ETLD+1 cannot be derived, an empty string is returned. The return value never have port.

func GetCSPNonce

func GetCSPNonce(ctx context.Context) string

func GetHost

func GetHost(r *http.Request, trustProxy bool) string

func GetIP

func GetIP(r *http.Request, trustProxy bool) (ip string)

func GetProto

func GetProto(r *http.Request, trustProxy bool) string

func GetRedirectURI

func GetRedirectURI(r *http.Request, trustProxy bool) (out string, err error)

func GetWithContext

func GetWithContext(ctx context.Context, c *http.Client, url string) (resp *http.Response, err error)

GetWithContext is a compat method for http.Client.Get

func HeadWithContext

func HeadWithContext(ctx context.Context, c *http.Client, url string) (resp *http.Response, err error)

HeadWithContext is a compat method for http.Client.Head

func HealthCheckHandler

func HealthCheckHandler(w http.ResponseWriter, r *http.Request)

HealthCheckHandler is basic handler for server health check

func HostRelative

func HostRelative(u *url.URL) *url.URL

func ImageCSP

func ImageCSP(next http.Handler) http.Handler

ImageCSP sandboxes any response that a browser ends up treating as a document.

The images endpoint serves arbitrary uploaded bytes and is same origin with the Auth UI in the default deployment. Handlers are responsible for never declaring an active Content-Type, and this is the backstop if one slips through: as a document the response gets a unique origin and no script execution, and as an <img> subresource the directives do not apply.

func IsJSONContentType

func IsJSONContentType(contentType string) bool

func IsLoopbackHost

func IsLoopbackHost(host string) bool

IsLoopbackHost reports whether host -- as returned by url.URL.Hostname(), which strips the brackets from an IPv6 literal like "[::1]:3000" -- names a loopback interface: "localhost", or the IPv4/IPv6 loopback literals "127.0.0.1" and "::1". Per RFC 8252 §7.3, native/CLI OAuth clients use any of these interchangeably for their redirect_uri callback listener.

This checks the literal hostname string, not a resolved IP address: it is for validating a redirect_uri that Authgear itself never connects to (unlike the SSRF concern IsPubliclyRoutable addresses for URLs Authgear fetches), so there is no DNS resolution step to filter.

func IsPubliclyRoutable

func IsPubliclyRoutable(addr netip.Addr) bool

IsPubliclyRoutable reports whether addr is safe to connect to when fetching a URL supplied by a tenant/third party (the SSRF concern docs/specs/cimd.md § SSRF Protection describes, but the check itself is generic and not specific to CIMD): not loopback, not private, not link-local, not multicast, not unspecified, and not one of the additional special-use ranges above -- each of which either embeds an arbitrary (possibly private) IPv4 address, or is otherwise not a real routable destination.

func MatchHostPattern

func MatchHostPattern(patterns []string, host string) bool

MatchHostPattern reports whether host matches any of patterns.

Matching is on a hostname only -- never host:port -- and is case-insensitive. A leading "*." matches exactly ONE label, per the RFC 6125 / TLS certificate convention: "*.example.com" matches "a.example.com" but not "a.b.example.com" and not the apex "example.com". A single-label hostname such as "localhost" is a valid pattern.

An empty pattern list matches nothing. Callers whose empty case means "no restriction" check that themselves, so that the two readings of an empty list stay at the call site rather than being buried here.

func NewExternalClient

func NewExternalClient(timeout time.Duration) *http.Client

func NewExternalClientWithOptions

func NewExternalClientWithOptions(timeout time.Duration, opts ExternalClientOptions) *http.Client

NewExternalClientWithOptions returns a client for a destination this deployment configures: the Deno hook runner, object storage, an SMS or captcha vendor's API. Those legitimately address internal hosts, so no address restriction applies here.

For a destination taken from a project's configuration, use NewSSRFSafeExternalClient instead -- that is where the SSRF concern lives, and it cannot be handled here without breaking the call sites above.

func NewSSRFSafeExternalClient

func NewSSRFSafeExternalClient(timeout time.Duration, opts SSRFSafeExternalClientOptions) *http.Client

NewSSRFSafeExternalClient returns a client for fetching a URL whose destination comes from a project's configuration rather than from this deployment: event webhooks, the custom SMS provider, the account migration and phone verification hooks, and an OAuth provider's OIDC discovery document.

Whoever administers a project chooses those URLs, and on a deployment with open sign-up that is any user who created one, so the destination is not trusted to be outside the deployment's own network. SafeDialer is what enforces that; see its documentation for why the check cannot live in a dialer Control hook alone.

It also caps the response body at MaxResponseBytes. A hostile destination that streams without end is as effective against this server as one on an internal address, and neither is the caller's to remember.

Not for the clients whose destination this deployment configures -- the Deno hook runner, object storage, an SMS vendor's API. Those legitimately address internal hosts, and must keep using NewExternalClient.

func NoCache

func NoCache(next http.Handler) http.Handler

NoCache allows caches to store a response but requires them to revalidate it before reuse.

func NoStore

func NoStore(next http.Handler) http.Handler

NoStore makes the browser not to store the requests.

func ParseJSONBody

func ParseJSONBody(r *http.Request, w http.ResponseWriter, parse func(io.Reader, any) error, payload any, options ...JSONOption) error

func PermissionsPolicyHeader

func PermissionsPolicyHeader(next http.Handler) http.Handler

func PostFormWithContext

func PostFormWithContext(ctx context.Context, c *http.Client, url string, data url.Values) (resp *http.Response, err error)

PostFormWithContext is a compat method for http.Client.PostForm

func PostWithContext

func PostWithContext(ctx context.Context, c *http.Client, url string, contentType string, body io.Reader) (resp *http.Response, err error)

PostWithContext is a compat method for http.Client.Post

func RedactedRawQuery

func RedactedRawQuery(rawQuery string) string

RedactedRawQuery returns rawQuery with the values of SensitiveQueryParams replaced, so it is safe to write to logs. It returns rawQuery unchanged if none of SensitiveQueryParams are present.

func Redirect

func Redirect(ctx context.Context, w http.ResponseWriter, r *http.Request, redirectURI string, statusCode int)

func ShouldSendSameSiteNone

func ShouldSendSameSiteNone(useragent string, secure bool) bool

func UpdateCookie

func UpdateCookie(w http.ResponseWriter, cookie *http.Cookie)

func WithCSPNonce

func WithCSPNonce(ctx context.Context, nonce string) context.Context

func WriteJSONResponse

func WriteJSONResponse(ctx context.Context, w http.ResponseWriter, resp *api.Response)

func XContentTypeOptionsNosniff

func XContentTypeOptionsNosniff(next http.Handler) http.Handler

func XFrameOptionsDeny

func XFrameOptionsDeny(next http.Handler) http.Handler

func XRobotsTag

func XRobotsTag(next http.Handler) http.Handler

Types

type BodyDefaulter

type BodyDefaulter interface {
	SetDefaults()
}

type CSPDirective

type CSPDirective struct {
	Name  CSPDirectiveName
	Value CSPSources
}

func (CSPDirective) String

func (d CSPDirective) String() string

type CSPDirectiveName

type CSPDirectiveName string
const (

	// connect-src is not needed when there is no default-src.
	// CSPDirectiveNameConnectSrc CSPDirectiveName = "connect-src"
	// font-src is not needed when there is no default-src.
	// CSPDirectiveNameFontSrc    CSPDirectiveName = "font-src"
	// frame-src is not needed when there is no default-src.
	// CSPDirectiveNameFrameSrc   CSPDirectiveName = "frame-src"
	// img-src is not needed when there is no default-src.
	// CSPDirectiveNameImgSrc     CSPDirectiveName = "img-src"
	CSPDirectiveNameObjectSrc CSPDirectiveName = "object-src"
	CSPDirectiveNameScriptSrc CSPDirectiveName = "script-src"

	CSPDirectiveNameBaseURI CSPDirectiveName = "base-uri"
	// CSPDirectiveNameBlockAllMixedContent is deprecated.
	// See https://www.w3.org/TR/mixed-content/#strict-checking
	// CSPDirectiveNameBlockAllMixedContent CSPDirectiveName = "block-all-mixed-content"
	CSPDirectiveNameFrameAncestors CSPDirectiveName = "frame-ancestors"
)

type CSPDirectives

type CSPDirectives []CSPDirective

func (CSPDirectives) String

func (d CSPDirectives) String() string

type CSPHashSource

type CSPHashSource struct {
	Hash string
}

func (CSPHashSource) CSPLevel

func (s CSPHashSource) CSPLevel() int

func (CSPHashSource) String

func (s CSPHashSource) String() string

type CSPHostSource

type CSPHostSource struct {
	Scheme string
	Host   string
}

func (CSPHostSource) CSPLevel

func (s CSPHostSource) CSPLevel() int

func (CSPHostSource) String

func (s CSPHostSource) String() string

type CSPKeywordSourceLevel1

type CSPKeywordSourceLevel1 string
const (
	CSPSourceNone CSPKeywordSourceLevel1 = "'none'"
	CSPSourceSelf CSPKeywordSourceLevel1 = "'self'"
)

func (CSPKeywordSourceLevel1) CSPLevel

func (_ CSPKeywordSourceLevel1) CSPLevel() int

func (CSPKeywordSourceLevel1) String

func (s CSPKeywordSourceLevel1) String() string

type CSPKeywordSourceLevel3

type CSPKeywordSourceLevel3 string
const (
	// 'unsafe-hashes' is not needed when we no longer specify style-src.
	// If you want it to allow inline event handler, you should migrate from inline event handler instead.
	// CSPSourceUnsafeHashes  CSPKeywordSourceLevel3 = "'unsafe-hashes'"
	CSPSourceStrictDynamic CSPKeywordSourceLevel3 = "'strict-dynamic'"
)

func (CSPKeywordSourceLevel3) CSPLevel

func (_ CSPKeywordSourceLevel3) CSPLevel() int

func (CSPKeywordSourceLevel3) String

func (s CSPKeywordSourceLevel3) String() string

type CSPNoncePerSessionCookieManager

type CSPNoncePerSessionCookieManager interface {
	GetCookie(r *http.Request, def *CookieDef) (*http.Cookie, error)
	ValueCookie(def *CookieDef, value string) *http.Cookie
}

type CSPNonceSource

type CSPNonceSource struct {
	Nonce string
}

func (CSPNonceSource) CSPLevel

func (s CSPNonceSource) CSPLevel() int

func (CSPNonceSource) String

func (s CSPNonceSource) String() string

type CSPSchemeSource

type CSPSchemeSource struct {
	Scheme string
}

func (CSPSchemeSource) CSPLevel

func (s CSPSchemeSource) CSPLevel() int

func (CSPSchemeSource) String

func (s CSPSchemeSource) String() string

type CSPSource

type CSPSource interface {
	CSPLevel() int
	String() string
}

type CSPSources

type CSPSources []CSPSource

func (CSPSources) Len

func (s CSPSources) Len() int

func (CSPSources) Less

func (s CSPSources) Less(i, j int) bool

func (CSPSources) String

func (s CSPSources) String() string

func (CSPSources) Swap

func (s CSPSources) Swap(i, j int)

type CookieDef

type CookieDef struct {
	// NameSuffix means the cookie could have prefix.
	NameSuffix string
	Path       string
	// Domain is omitted because it is controlled somewhere else.
	// Domain            string
	AllowScriptAccess bool
	SameSite          http.SameSite
	MaxAge            *int

	// This flag is the inverse of http cookie host-only-flag (RFC6265 section5.3.6), default false
	IsNonHostOnly bool
}

CookieDef defines a cookie that is written to the response. All cookies in our server expects to be created with this definition.

func (*CookieDef) HostOnly

func (cd *CookieDef) HostOnly() bool

type CookieManager

type CookieManager struct {
	Request      *http.Request
	TrustProxy   bool
	CookiePrefix string
	CookieDomain string
}

func (*CookieManager) ClearCookie

func (f *CookieManager) ClearCookie(def *CookieDef) *http.Cookie

ClearCookie generates a cookie that when set, the cookie is clear.

func (*CookieManager) CookieName

func (f *CookieManager) CookieName(def *CookieDef) string

CookieName returns the full name, that is, CookiePrefix followed by NameSuffix.

func (*CookieManager) GetCookie

func (f *CookieManager) GetCookie(r *http.Request, def *CookieDef) (*http.Cookie, error)

GetCookie is wrapper around http.Request.Cookie, taking care of cookie name.

func (*CookieManager) ValueCookie

func (f *CookieManager) ValueCookie(def *CookieDef, value string) *http.Cookie

ValueCookie generates a cookie that when set, the cookie is set to the specified value.

type CrossOriginProtection

type CrossOriginProtection struct{}

func NewCrossOriginProtection

func NewCrossOriginProtection() *CrossOriginProtection

func (*CrossOriginProtection) Check

func (m *CrossOriginProtection) Check(r *http.Request) error

type ExternalClientOptions

type ExternalClientOptions struct {
	FollowRedirect bool
	Transport      http.RoundTripper
}

type FileServer

type FileServer struct {
	FileSystem          http.FileSystem
	AssetsDir           string
	FallbackToIndexHTML bool
	// SourceMap configures how source map (*.map) files are served.
	// The zero value serves no source map file.
	SourceMap SourceMapConfig
}

FileServer is a specialized version of http.FileServer that assumes files rooted at FileSystem are name-hashed. Cache-control are written specifically for index.html and name-hashed files. When serving index.html, index.html is assumed to be a Go template. FileServer will use the context value FileServerIndexHTMLTemplateDataKey to render.

func (*FileServer) ServeHTTP

func (s *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request)

type FileServerIndexHTMLTemplateDataKeyType

type FileServerIndexHTMLTemplateDataKeyType struct{}

type FilesystemCache

type FilesystemCache struct {
	// contains filtered or unexported fields
}

FilesystemCache is a helper to write the response into the tmp directory. The response is then served with http.FileServer, with the advantage of supporting range request and cache validation. If the file is not modified, the response is a 304. For even better performance, we need to add Cache-Control header to take advantage of the fact that the filename is hashed. However, http.FileServer does not support Cache-Control. Unconditionally adding Cache-Control for non-existent file is problematic.

func NewFilesystemCache

func NewFilesystemCache() *FilesystemCache

func (*FilesystemCache) Clear

func (c *FilesystemCache) Clear() error

func (*FilesystemCache) Serve

func (c *FilesystemCache) Serve(r *http.Request, make func() ([]byte, error)) (handler http.Handler)

type FlashMessage

type FlashMessage struct {
	Cookies FlashMessageCookieManager
}

func (*FlashMessage) Flash

func (f *FlashMessage) Flash(rw http.ResponseWriter, messageType string)

func (*FlashMessage) Pop

type FlashMessageCookieManager

type FlashMessageCookieManager interface {
	GetCookie(r *http.Request, def *CookieDef) (*http.Cookie, error)
	ValueCookie(def *CookieDef, value string) *http.Cookie
	ClearCookie(def *CookieDef) *http.Cookie
}

type HTTPHost

type HTTPHost string

type HTTPOrigin

type HTTPOrigin string

func MakeHTTPOrigin

func MakeHTTPOrigin(proto HTTPProto, host HTTPHost) HTTPOrigin

type HTTPPermissionsPolicy

type HTTPPermissionsPolicy []PermissionsPolicyPolicy

func (HTTPPermissionsPolicy) String

func (p HTTPPermissionsPolicy) String() string

type HTTPProto

type HTTPProto string

type HTTPReferer

type HTTPReferer string

func GetReferer

func GetReferer(r *http.Request) HTTPReferer

type HTTPRequestURL

type HTTPRequestURL string

func GetRequestURL

func GetRequestURL(r *http.Request, proto HTTPProto, host HTTPHost) HTTPRequestURL

type InternalRedirectResult

type InternalRedirectResult struct {
	Cookies []*http.Cookie
	URL     string
}

InternalRedirectResult is a redirect result that is only for internal use, for example, redirecting to the auth ui from authorization endpoint.

func (*InternalRedirectResult) IsInternalError

func (re *InternalRedirectResult) IsInternalError() bool

func (*InternalRedirectResult) WriteResponse

func (re *InternalRedirectResult) WriteResponse(rw http.ResponseWriter, r *http.Request)

type JSONOption

type JSONOption func(option *jsonOption)

func WithBodyMaxSize

func WithBodyMaxSize(size int64) JSONOption

type NetIPResolver

type NetIPResolver interface {
	LookupNetIP(ctx context.Context, network, host string) ([]netip.Addr, error)
}

NetIPResolver is the one *net.Resolver method SafeDialer needs, pulled out as an interface so tests can stub DNS resolution without spinning up a real DNS server. *net.Resolver satisfies it as-is.

type PermissionsPolicyAllowlist

type PermissionsPolicyAllowlist string

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy#allowlist

const (
	PermissionsPolicyAllowlistAll  PermissionsPolicyAllowlist = "*"
	PermissionsPolicyAllowlistNone PermissionsPolicyAllowlist = "()"
	PermissionsPolicyAllowlistSelf PermissionsPolicyAllowlist = "(self)"
	PermissionsPolicyAllowlistSrc  PermissionsPolicyAllowlist = "(src)"
)

type PermissionsPolicyDirective

type PermissionsPolicyDirective string

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy#directives

const (
	PermissionsPolicyDirectiveAccelerometer               PermissionsPolicyDirective = "accelerometer"
	PermissionsPolicyDirectiveAmbientLightSensor          PermissionsPolicyDirective = "ambient-light-sensor"
	PermissionsPolicyDirectiveAutoplay                    PermissionsPolicyDirective = "autoplay"
	PermissionsPolicyDirectiveBattery                     PermissionsPolicyDirective = "battery"
	PermissionsPolicyDirectiveBluetooth                   PermissionsPolicyDirective = "bluetooth"
	PermissionsPolicyDirectiveBrowsingTopics              PermissionsPolicyDirective = "browsing-topics"
	PermissionsPolicyDirectiveCamera                      PermissionsPolicyDirective = "camera"
	PermissionsPolicyDirectiveDisplayCapture              PermissionsPolicyDirective = "display-capture"
	PermissionsPolicyDirectiveDocumentDomain              PermissionsPolicyDirective = "document-domain"
	PermissionsPolicyDirectiveEncryptedMedia              PermissionsPolicyDirective = "encrypted-media"
	PermissionsPolicyDirectiveExecutionWhileNotRendered   PermissionsPolicyDirective = "execution-while-not-rendered"
	PermissionsPolicyDirectiveExecutionWhileOutOfViewport PermissionsPolicyDirective = "execution-while-out-of-viewport"
	PermissionsPolicyDirectiveFullscreen                  PermissionsPolicyDirective = "fullscreen"
	PermissionsPolicyDirectiveGamepad                     PermissionsPolicyDirective = "gamepad"
	PermissionsPolicyDirectiveGeolocation                 PermissionsPolicyDirective = "geolocation"
	PermissionsPolicyDirectiveGyroscope                   PermissionsPolicyDirective = "gyroscope"
	PermissionsPolicyDirectiveHid                         PermissionsPolicyDirective = "hid"
	PermissionsPolicyDirectiveIdentityCredentialsGet      PermissionsPolicyDirective = "identity-credentials-get"
	PermissionsPolicyDirectiveIdleDetection               PermissionsPolicyDirective = "idle-detection"
	PermissionsPolicyDirectiveLocalFonts                  PermissionsPolicyDirective = "local-fonts"
	PermissionsPolicyDirectiveMagnetometer                PermissionsPolicyDirective = "magnetometer"
	PermissionsPolicyDirectiveMicrophone                  PermissionsPolicyDirective = "microphone"
	PermissionsPolicyDirectiveMidi                        PermissionsPolicyDirective = "midi"
	PermissionsPolicyDirectiveOtpCredentials              PermissionsPolicyDirective = "otp-credentials"
	PermissionsPolicyDirectivePayment                     PermissionsPolicyDirective = "payment"
	PermissionsPolicyDirectivePictureInPicture            PermissionsPolicyDirective = "picture-in-picture"
	PermissionsPolicyDirectivePublickeyCredentialsCreate  PermissionsPolicyDirective = "publickey-credentials-create"
	PermissionsPolicyDirectivePublickeyCredentialsGet     PermissionsPolicyDirective = "publickey-credentials-get"
	PermissionsPolicyDirectiveScreenWakeLock              PermissionsPolicyDirective = "screen-wake-lock"
	PermissionsPolicyDirectiveSerial                      PermissionsPolicyDirective = "serial"
	PermissionsPolicyDirectiveSpeakerSelection            PermissionsPolicyDirective = "speaker-selection"
	PermissionsPolicyDirectiveStorageAccess               PermissionsPolicyDirective = "storage-access"
	PermissionsPolicyDirectiveUsb                         PermissionsPolicyDirective = "usb"
	PermissionsPolicyDirectiveWebShare                    PermissionsPolicyDirective = "web-share"
	PermissionsPolicyDirectiveWindowManagement            PermissionsPolicyDirective = "window-management"
	PermissionsPolicyDirectiveXrSpatialTracking           PermissionsPolicyDirective = "xr-spatial-tracking"
)

type PermissionsPolicyPolicy

type PermissionsPolicyPolicy struct {
	Directive PermissionsPolicyDirective
	Allowlist PermissionsPolicyAllowlist
}

type RemoteIP

type RemoteIP string

type Result

type Result interface {
	WriteResponse(rw http.ResponseWriter, r *http.Request)
	IsInternalError() bool
}

type SSRFSafeExternalClientOptions

type SSRFSafeExternalClientOptions struct {
	// AllowNonPublicAddresses lifts the address restriction entirely. It
	// carries http.insecure_fetch_address_allowed.
	AllowNonPublicAddresses bool
	// AllowedHosts exempts named hosts from the restriction, carrying
	// http.insecure_fetch_address_allowed_hosts. Nothing else may widen the
	// policy.
	AllowedHosts []string
	// Sink names the configuration that chose the URL, e.g.
	// "hook.blocking_handlers". It appears in the log a refusal writes, so
	// that the message says which setting to go and fix.
	Sink string
	// RootCAs verifies the server certificate against this pool only,
	// instead of the system trust store. For a destination inside the
	// deployment, issued by a private certificate authority. nil keeps the
	// system trust store.
	RootCAs *x509.CertPool
}

SSRFSafeExternalClientOptions configures NewSSRFSafeExternalClient.

type SafeDialer

type SafeDialer struct {
	Resolver                NetIPResolver // nil means net.DefaultResolver
	AllowNonPublicAddresses bool
	// AllowedHosts names the hosts exempt from the address rules, matched by
	// MatchHostPattern. It is the narrow alternative to AllowNonPublicAddresses:
	// an operator naming their own internal receiver, rather than opening every
	// non-public address.
	//
	// Exempting a host also exempts it from the rebinding protection, since
	// whatever it resolves to is accepted. That is inherent to naming a host you
	// trust, and is why the list must never contain a host someone else chooses.
	AllowedHosts []string
	// DialTimeout bounds the connect itself. Zero means no dial-specific
	// deadline, leaving whatever the context and http.Client.Timeout impose.
	DialTimeout time.Duration
	// Sink names the configuration that chose the destination, e.g.
	// "hook.blocking_handlers". It appears in the log a refusal writes; see
	// logIfBlockedAddress.
	Sink string
}

SafeDialer refuses to connect to an address that is not publicly routable, which is what stops a URL taken from configuration or from a third party being used to reach the deployment's own network.

It implements two rules together, because neither works without the other:

  • Resolve the hostname once per dial and connect only to an address validated in that same resolution -- a second, independent resolution at connect time is vulnerable to DNS rebinding.
  • Check every address a hostname resolves to, not just the first -- reject the whole hostname if any A/AAAA record is non-publicly-routable, rather than filtering to the routable subset. A mixed answer is an attack signature, not a misconfiguration to accommodate.

AllowNonPublicAddresses is a plain field rather than a config read, so the dialer stays a pure function of its inputs. Whoever constructs it decides the policy; there is no second mechanism that widens it.

func (*SafeDialer) DialContext

func (d *SafeDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error)

DialContext dials addr, and writes the refusal log if the policy rejects it.

The log is emitted here, not at the call sites that fetch a URL, because this is the only place that knows a refusal happened. A caller sees a transport error indistinguishable from an unreachable host, so leaving the log to callers means every future one has to remember to write it.

type SourceMapConfig

type SourceMapConfig struct {
	// Enabled sets whether source map files are served at all.
	// When it is false, source map files are served as if they did not exist.
	Enabled bool
	// SentryToken, when non-empty, protects source map files with HTTP basic authentication.
	// The token is the password, and the username is ignored.
	// Sentry fetches a publicly hosted source map with a configurable header,
	// so it can be configured to send `Authorization: Basic <base64 of ":<token>">`.
	// See https://docs.sentry.io/platforms/javascript/sourcemaps/uploading/hosting-publicly/
	SentryToken string
}

SourceMapConfig configures how FileServer serves source map (*.map) files.

func (SourceMapConfig) IsAuthorized

func (c SourceMapConfig) IsAuthorized(r *http.Request) bool

IsAuthorized tells whether r is allowed to fetch a source map file.

type TutorialCookie

type TutorialCookie struct {
	Cookies FlashMessageCookieManager
}

func (*TutorialCookie) Pop

func (*TutorialCookie) SetAll

func (t *TutorialCookie) SetAll(rw http.ResponseWriter)

type TutorialCookieManager

type TutorialCookieManager interface {
	GetCookie(r *http.Request, def *CookieDef) (*http.Cookie, error)
	ValueCookie(def *CookieDef, value string) *http.Cookie
	ClearCookie(def *CookieDef) *http.Cookie
}

type TutorialCookieName

type TutorialCookieName string
const (
	SignupLoginTutorialCookieName TutorialCookieName = "signup_login_tutorial"
	SettingsTutorialCookieName    TutorialCookieName = "settings_tutorial"
)

type UserAgentString

type UserAgentString string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL