- surf: APIDocsProvider (one plugin) supplies a YAML or JSON OpenAPI document,
converted once at boot to compact JSON in source key order; zero or several
providers or a spec that does not convert fail boot
- http.api_docs.enabled/path/title mount GET /api-docs and /api-docs.json
(summercms.apidocs, public, refusing to replace a plugin route)
- the page loads swagger-ui-dist 5.33.1 from jsDelivr with sha384 SRI and
crossorigin on all three assets, a per-request CSP nonce for its inline
script, nosniff and no-referrer
- docs: API documentation page section, README rows, ExampleAPIDocsProvider
- bouncer: Turnstile.Verify posts secret/response/remoteip to siteverify with
a 5 s bound and PHP's open and closed modes (503 unconfigured in production,
422 on a missing, rejected or overlong token, an error or a timeout); errors
never carry the secret or the token
- bouncer: Turnstile.Middleware reads the token from the query, a JSON body or
a form body, restores the body and writes refusals through a published
TurnstileWriter or neutral JSON
- surf: factory turnstile:open|closed from turnstile.secret/turnstile.field
(a published *bouncer.Turnstile wins), client IP with trusted proxies; any
other param fails boot
- docs: Turnstile section, middleware table, README rows
- surf: LoadCORSProfiles reads http.cors_profiles.<name> (http.cors keys
without paths, validated the same way); factory cors:<profile> sets the
profile's headers; an unknown profile fails boot naming plugin and route
- BuildRouter registers OPTIONS <path> (owner surf, recovery only) for every
path whose routes name cors:<profile>; two profiles on one path, an existing
OPTIONS route or overlap with http.cors.paths fail boot
- docs: CORS profiles section, middleware table and README rows
- bouncer: CookieConfig from auth.cookie.* (LoadCookieConfig validates domain,
same_site none without Secure, secure false in production), TokenCookie with
Max-Age = refresh window, ForgetTokenCookie, CookieRefreshHandler and
CookieLogoutHandler with an app-supplied writer
- surf: http.cors refuses credentials with a * origin and bad or PHP-delimited
patterns at boot, turns wildcard origins into anchored patterns, echoes the
exact origin and sends Vary: Origin on every dynamic CORS answer; the
non-credentialed * answer is unchanged
- smoke tests for the www -> api refresh and the refused wildcard config