-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfromdocs.go
More file actions
81 lines (78 loc) · 3.31 KB
/
Copy pathfromdocs.go
File metadata and controls
81 lines (78 loc) · 3.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package stdocs
import (
"net/http"
"net/url"
"strings"
)
// FromDocs reports whether r appears to originate from the interactive
// docs UI served under docsPrefix — that is, from a "Try it out" /
// "Test Request" console on the docs page rather than from a regular
// API client. An empty (or all-slash) docsPrefix means the default
// "/docs".
//
// Detection is based on the Referer header: browsers attach the docs
// page's URL to the fetch calls the consoles make. The check matches
// on the URL path only, so it keeps working behind reverse proxies
// that add their own path prefix (a page at /api/docs/ still matches
// a docs prefix of "/docs").
//
// FromDocs is a convenience guardrail, NOT a security control. The
// Referer header is fully client-controlled: a caller can forge it
// (false positives), and there are false negatives too — privacy
// extensions or a strict Referrer-Policy can strip the header, and
// try-it requests sent to a DIFFERENT origin (an absolute WithServer
// URL on another host) carry an origin-only Referer under browsers'
// default policy, so FromDocs reports false for them (Scalar
// additionally routes cross-origin try-it calls through its own
// proxy). Detection is reliable only when the docs page and the API
// share an origin — the normal stdocs setup, where one mux serves
// both. Use FromDocs only to RESTRICT what docs-originated traffic
// may do — never to grant access, skip authentication, or relax
// validation. Endpoints that must not be mutated by strangers need
// real authentication regardless.
//
// The intended use is a small middleware in front of the mux:
//
// guard := func(next http.Handler) http.Handler {
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// if r.Method != http.MethodGet && stdocs.FromDocs(r, "/docs") {
// http.Error(w, "try-it requests cannot modify data", http.StatusForbidden)
// return
// }
// next.ServeHTTP(w, r)
// })
// }
// log.Fatal(http.ListenAndServe(":8080", guard(mux)))
//
// Teams that prefer other policies can branch on FromDocs however
// they like: route writes to a scratch datastore, add a dry-run flag,
// tag the request for observability, and so on.
func FromDocs(r *http.Request, docsPrefix string) bool {
referer := r.Referer()
if referer == "" {
return false
}
u, err := url.Parse(referer)
if err != nil {
return false
}
prefix := "/docs"
if trimmed := strings.Trim(docsPrefix, "/"); trimmed != "" {
prefix = "/" + trimmed
}
// The docs page lives at <prefix>/ (possibly below a proxy's own
// path prefix), so match the path segment anywhere in the referring
// URL's path. The leading "/" in prefix makes the match
// boundary-safe ("/mydocs/" does not match prefix "/docs"), and
// deliberately loose matching errs toward true (as does matching
// on the percent-decoded path): FromDocs gates restrictions, so a
// false positive is the safe direction.
return u.Path == prefix || strings.Contains(u.Path, prefix+"/")
}
// FromDocs reports whether r appears to originate from this mux's
// docs UI. It is FromDocs(r, prefix) with the mux's configured docs
// prefix; see the package-level [FromDocs] for the detection
// mechanics and the security caveats.
func (m *Mux) FromDocs(r *http.Request) bool {
return FromDocs(r, m.cfg.DocsPrefix)
}