diff --git a/reference/rest/headers.md b/reference/rest/headers.md index 818398eb..9ce21d81 100644 --- a/reference/rest/headers.md +++ b/reference/rest/headers.md @@ -95,3 +95,4 @@ This is not recommended for production use — prefer the `Accept` header for cl - [REST Overview](./overview.md) — HTTP methods and URL structure - [Content Types](./content-types.md) — Supported encoding formats - [Security Overview](../security/overview.md) — Authentication headers and mechanisms +- [Response Header Hardening](../security/response-headers.md) — Adding browser security headers and cache-control to Harper responses (from a custom Resource, or at your own edge) diff --git a/reference/security/configuration.md b/reference/security/configuration.md index c34ad1d6..c543b813 100644 --- a/reference/security/configuration.md +++ b/reference/security/configuration.md @@ -64,6 +64,7 @@ Password hashing algorithm used when storing user passwords. Replaced the previo ## Related +- [Response Header Hardening](./response-headers.md) — Setting browser security headers (`X-Content-Type-Options`, CSP, etc.) and cache-control on Harper responses. - [JWT Authentication](./jwt-authentication.md) - [Basic Authentication](./basic-authentication.md) - [Users & Roles / Configuration](../users-and-roles/configuration.md) diff --git a/reference/security/response-headers.md b/reference/security/response-headers.md new file mode 100644 index 00000000..c755d3ca --- /dev/null +++ b/reference/security/response-headers.md @@ -0,0 +1,75 @@ +--- +id: response-headers +title: Response Header Hardening +--- + +Harper is an origin/API server, and by default it does not emit the browser security-hardening response headers that a public-facing web application typically wants — `X-Content-Type-Options`, `X-Frame-Options`, `Content-Security-Policy`, `Referrer-Policy`, `Strict-Transport-Security`, `Permissions-Policy`, or the `Cross-Origin-*` family (COOP, COEP, CORP). Nor does it emit `Cache-Control`. For browser-facing deployments you generally want to add these. + +Where you add them depends on your deployment. On Harper-hosted deployments (Fabric) there is no customer-controlled reverse proxy, CDN, or edge to configure — so the reliable place to set these headers is from your application code, in a custom Resource. If you run your own edge, an edge configuration is also an option (see the fallback note below). + +## Setting headers from a custom Resource + +A custom [Resource](../resources/resource-api.md) handler can set response headers through the request context. When a Resource method is invoked over HTTP, `getContext()` returns the `Request` object, which carries a `responseHeaders` map with a `set(name, value)` method: + +```javascript +export class Product extends tables.Product { + get(query) { + const { responseHeaders } = this.getContext(); + // Highest-value, lowest-effort hardening header: + responseHeaders.set('X-Content-Type-Options', 'nosniff'); + // Frame-embedding and content-source policies go here as well. + // Keep these conservative and application-specific — e.g. only + // relax X-Frame-Options if your app is legitimately embedded: + responseHeaders.set('X-Frame-Options', 'SAMEORIGIN'); + // responseHeaders.set('Content-Security-Policy', "default-src 'self'"); + return super.get(query); + } +} +``` + +`X-Content-Type-Options: nosniff` is the highest-value, lowest-effort header to add: it stops browsers from MIME-sniffing a response away from its declared `Content-Type`, closing a class of content-injection attacks with essentially no cost. + +This applies to responses that flow through the custom Resource handler that sets them. It does not retroactively harden other endpoints — see the gap below. + +## The gap: auto-generated endpoints and global defaults + +There is currently no built-in or configuration-based way to apply these headers to Harper's auto-generated HTTP surfaces — the REST endpoints Harper exposes for a table, and the GraphQL endpoint — and no global response-header default that applies across all surfaces. The `responseHeaders` mechanism above only covers responses served by a custom Resource handler you have written. + +For now, security headers can be added from custom Resources; global and auto-endpoint coverage is tracked in [HarperFast/harper#1567](https://github.com/HarperFast/harper/issues/1567) ("Provide a built-in way to set security/cache response headers across all HTTP surfaces"). + +These defaults may tighten in a future release. Treat the guidance here as current behavior, not a permanent guarantee — check the release notes if you are relying on the absence of a header. + +## Caching of authenticated responses + +Harper does not emit `Cache-Control` on its responses. If a response is cached by anything shared between users — a CDN, a proxy cache, or a browser cache on a shared machine — a response that depends on the caller's identity must not be stored and replayed for a different caller. This includes authenticated reads and `allowRead`-gated rows, or any response that varies by `Authorization`. + +Until a built-in control lands (also tracked in [#1567](https://github.com/HarperFast/harper/issues/1567)), you can set `Cache-Control` from a custom Resource the same way as the hardening headers above: + +```javascript +get(query) { + const { responseHeaders } = this.getContext(); + // Prevent shared caches from storing an identity-dependent response: + responseHeaders.set('Cache-Control', 'no-store'); + return super.get(query); +} +``` + +If you do want identity-scoped responses cached, key the cache on identity (e.g. vary on `Authorization` or the session cookie) rather than storing a single shared copy. Getting this wrong can return one user's private data to another, so default to `no-store` for anything that depends on who is asking. + +## Fallback: setting headers at your own edge + +If you self-host Harper behind your own reverse proxy or CDN (not applicable to Harper-hosted/Fabric deployments), you can add the same headers there instead. This is the fallback rather than the primary answer, since the hosted deployment model has no such edge to configure. A minimal Nginx example: + +```nginx +add_header X-Content-Type-Options "nosniff" always; +add_header X-Frame-Options "SAMEORIGIN" always; +add_header Referrer-Policy "strict-origin-when-cross-origin" always; +``` + +CSP and `Strict-Transport-Security` values are application-specific; set them based on the origins your application legitimately loads from and your HTTPS posture. + +## Related + +- [Resource API](../resources/resource-api.md) — Custom Resource handlers and the request context +- [REST Headers](../rest/headers.md) — Standard request/response headers Harper does emit +- [Security Overview](./overview.md) diff --git a/sidebarsReference.ts b/sidebarsReference.ts index 08ecfdc5..a7189e55 100644 --- a/sidebarsReference.ts +++ b/sidebarsReference.ts @@ -285,6 +285,11 @@ const sidebars: SidebarsConfig = { id: 'security/configuration', label: 'Configuration', }, + { + type: 'doc', + id: 'security/response-headers', + label: 'Response Header Hardening', + }, { type: 'doc', id: 'security/basic-authentication',