Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion cli/mcp/dev-server-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ function localContext(): HandlerContext {
return {
projectDir: "/project",
securityConfig: null,
cspUserHeader: null,
isLocalProject: true,
} as HandlerContext;
}
Expand Down
14 changes: 7 additions & 7 deletions docs/api-reference/veryfront/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,23 +55,23 @@ applySecurityHeaders(response.headers, false, generateNonce(), null);
| `applyCORSHeaders` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/cors/headers.ts#L86) |
| `applyCORSHeadersSync` | Apply CORS synchronously. Promise-returning values still fail closed at runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/cors/headers.ts#L112) |
| `applyCsrfCookie` | Set CSRF cookie on GET/HEAD responses when not already present. Uses httpOnly: false so client JS can read the cookie for double-submit. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/csrf/helpers.ts#L150) |
| `applySecurityHeaders` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/response/security-handler.ts#L179) |
| `applySecurityHeaders` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/response/security-handler.ts#L262) |
| `buildCacheControl` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/response/cache-handler.ts#L86) |
| `cors` | Create CORS middleware. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/cors/middleware.ts#L10) |
| `corsSimple` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/cors/middleware.ts#L39) |
| `createResponseBuilder` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/response/builder.ts#L60) |
| `createResponseBuilder` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/response/builder.ts#L58) |
| `createSecureFs` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/secure-fs.ts#L1144) |
| `createValidatedHandler` | Create a validated API handler with bounded body/query validation. Bodies without a schema are preflighted through a clone, leaving the original request body available to the handler after its size is verified. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/input-validation/handler.ts#L163) |
| `createValidationError` | Create an input validation error. Convenience wrapper around INPUT_VALIDATION_FAILED.create(). | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/input-validation/errors.ts#L12) |
| `createValidator` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/path-validation/index.ts#L446) |
| `generateCsrfToken` | Generate a CSRF token and return value + Set-Cookie header string | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/csrf/helpers.ts#L70) |
| `generateNonce` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/response/security-handler.ts#L48) |
| `getSecurityHeader` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/response/security-handler.ts#L166) |
| `generateNonce` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/response/security-handler.ts#L49) |
| `getSecurityHeader` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/response/security-handler.ts#L249) |
| `handleCORSPreflight` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/cors/preflight.ts#L126) |
| `isPreflightRequest` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/cors/preflight.ts#L186) |
| `isRequestBodyTooLargeError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/input-validation/limits.ts#L100) |
| `isValidSecurityConfig` | Check a standalone security configuration against the canonical project configuration schema. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/config.ts#L214) |
| `loadSecurityConfig` | Load the project's schema-validated security configuration. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/config.ts#L234) |
| `isValidSecurityConfig` | Check a standalone security configuration against the canonical project configuration schema. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/config.ts#L213) |
| `loadSecurityConfig` | Load the project's schema-validated security configuration. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/config.ts#L233) |
| `parseFormData` | Parse and validate multipart or URL-encoded form data. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/input-validation/parsers.ts#L139) |
| `parseJsonBody` | Parse and validate a JSON request body. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/input-validation/parsers.ts#L68) |
| `parseQueryParams` | Parse and validate query parameters from a bounded request URL. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/input-validation/parsers.ts#L195) |
Expand Down Expand Up @@ -99,7 +99,7 @@ applySecurityHeaders(response.headers, false, generateNonce(), null);
| `CsrfHandler` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/csrf/csrf-handler.ts#L55) |
| `ResponseBuilder` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/response/builder.ts#L9) |
| `SecureFs` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/secure-fs.ts#L645) |
| `SecurityConfigLoader` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/config.ts#L274) |
| `SecurityConfigLoader` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/config.ts#L272) |

### Types

Expand Down
15 changes: 8 additions & 7 deletions docs/guides/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ details, see [API reference](../api-reference/index.md).

## Build routes

| Goal | Guide |
| ------------------------------------------- | ------------------------------------------- |
| Add pages, layouts, and dynamic routes | [Pages and routing](./pages-and-routing.md) |
| Load data for pages | [Data fetching](./data-fetching.md) |
| Add HTTP endpoints | [API routes](./api-routes.md) |
| Add CORS, auth checks, logging, or timeouts | [Middleware](./middleware.md) |
| Set page metadata and social previews | [Head and SEO](./head-and-seo.md) |
| Goal | Guide |
| ----------------------------------------------- | ------------------------------------------------- |
| Add pages, layouts, and dynamic routes | [Pages and routing](./pages-and-routing.md) |
| Load data for pages | [Data fetching](./data-fetching.md) |
| Add HTTP endpoints | [API routes](./api-routes.md) |
| Add CORS, auth checks, logging, or timeouts | [Middleware](./middleware.md) |
| Allow Google Fonts or other third-party origins | [Security headers and CSP](./security-headers.md) |
| Set page metadata and social previews | [Head and SEO](./head-and-seo.md) |

## Add AI behavior

Expand Down
130 changes: 130 additions & 0 deletions docs/guides/security-headers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
---
title: "Security headers and CSP"
description: "Veryfront applies a Content-Security-Policy by default. Use this guide to allow Google Fonts, analytics, and other third-party origins."
order: 11
---

Every hosted Veryfront project is served with a Content-Security-Policy and a set of hardening headers. You do not switch them on; they apply in production whether or not you configure anything. What you do configure is the extra origins your own site needs.

## The default policy

In production, Veryfront serves this policy:

```http
default-src 'self';
script-src 'self' 'nonce-<generated>' https://esm.sh;
style-src 'self' 'unsafe-inline';
style-src-attr 'unsafe-inline';
img-src 'self' https://images.veryfront.com https://cdn.veryfront.com data:;
font-src 'self' data:;
connect-src 'self' https://esm.sh;
media-src 'self' blob:;
worker-src 'self' blob:;
object-src 'none';
frame-src 'self';
frame-ancestors 'none';
base-uri 'self';
form-action 'self'
```

Alongside it: `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, `Referrer-Policy: strict-origin-when-cross-origin`, `Strict-Transport-Security`, and `Cross-Origin-Opener-Policy` / `Cross-Origin-Resource-Policy` set to `same-origin`.

Development serves no CSP at all, so HMR and dev tooling are never blocked and a local allowance can never widen your production policy.

Two directives are worth understanding:

- **`script-src` includes `https://esm.sh`** because the renderer writes React imports from that CDN into every document. A fresh nonce is generated per response for the framework's own inline bootstrap.
- **`frame-ancestors`** is `'none'` on your own domain. On `*.veryfront.com` addresses it instead allows the Studio origins, so the Studio preview iframe works.

## Adding an origin

Set `security.csp` in `veryfront.config.ts`. Values are **added to** the defaults, so you never restate them:

```ts
export default {
security: {
csp: {
styleSrc: ["https://fonts.googleapis.com"],
fontSrc: ["https://fonts.gstatic.com"],
},
},
};
```

That is the complete Google Fonts setup: `fonts.googleapis.com` serves the stylesheet, `fonts.gstatic.com` serves the font files, and both directives keep everything they already had.

Directive names may be camelCase (`fontSrc`) or the CSP spelling (`font-src`). Both work; camelCase matches the rest of your config. You do not need to repeat `'self'`; it is already there.

A few more examples:

```ts
export default {
security: {
csp: {
// An analytics endpoint your client code posts to
connectSrc: ["https://analytics.example.com"],
// Embedding YouTube
frameSrc: ["https://www.youtube.com"],
// Images from your own CDN
imgSrc: ["https://cdn.example.com"],
},
},
};
```
Comment thread
kwakayama marked this conversation as resolved.

Misspelling a directive fails configuration loading rather than silently doing nothing. Browsers ignore unrecognized directive names, so `fontSource: [...]` would otherwise look configured and protect nothing.

## What you cannot remove

Some sources are structural: the renderer writes those URLs into the documents it serves, so a project that dropped them would break only its own site. `'self'`, the nonce, `https://esm.sh` in `script-src`, and the platform image origins are always present. No `security.csp` setting can remove them. (`VERYFRONT_CSP`, described below, is an operations-level exception.)

Everything else is a convenience you can drop.

## Tightening the policy

To remove the platform's optional sources for one directive, set it to `null`:

```ts
export default {
security: {
csp: {
// Serve no inline styles. Keeps 'self', drops 'unsafe-inline'.
styleSrc: null,
},
},
};
```

`null` removes the optional half of a directive and keeps the required half. It cannot lock you out of your own site.

Before doing this, check what your components actually need. `'unsafe-inline'` is in the default `style-src` because many React component libraries, including Veryfront's own, create styles at runtime. Removing it is safe only if you are certain yours do not.

## Replacing the policy entirely

Setting `VERYFRONT_CSP` in the environment replaces the whole policy, including the sources the renderer needs:

```bash
VERYFRONT_CSP="default-src 'self'; script-src 'self' 'nonce-{NONCE}'"
```

`{NONCE}` is substituted with the per-response nonce. Omitting `https://esm.sh` from `script-src` will stop your pages hydrating, so this is an operations-level escape hatch for policies you intend to own completely, not the way to add an origin. Use `security.csp` for that.

## Verify it worked

Read the policy your site is actually serving:

```bash
curl -sS -D - -o /dev/null https://your-site.example/ | grep -i content-security-policy
```

Ensure the origin you added appears in the directive you added it to, alongside the existing sources.

Then load the site with the browser console open. CSP violations name the directive that blocked the request, which maps directly onto the config key: a `style-src` violation is fixed with `styleSrc`, a `font-src` violation with `fontSrc`.

Preview deployments serve the same policy as production, so a CSP problem shows up on your preview URL before it reaches your live site.

## Related

- [Configuration](./configuration.md): the full `veryfront.config.ts` reference
- [Middleware](./middleware.md): CORS, rate limiting, and auth checks
- [Deploying](./deploying.md): preview and production environments
1 change: 0 additions & 1 deletion src/channels/control-plane.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ function createHandlerContext(): HandlerContext {
fs: {},
},
securityConfig: null,
cspUserHeader: null,
projectSlug: "demo-project",
projectId: "proj-1",
isLocalProject: false,
Expand Down
1 change: 0 additions & 1 deletion src/channels/invoke.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ function createHandlerContext(): HandlerContext {
fs: {},
},
securityConfig: null,
cspUserHeader: null,
projectSlug: "demo-project",
projectId: "proj-1",
isLocalProject: false,
Expand Down
Loading