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
19 changes: 19 additions & 0 deletions .changeset/young-cougars-mix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
'@astrojs/node': patch
'astro': patch
---

Adds a new `security.actionBodySizeLimit` option to configure the maximum size of Astro Actions request bodies.

This lets you increase the default 1 MB limit when your actions need to accept larger payloads. For example, actions that handle file uploads or large JSON payloads can now opt in to a higher limit.

If you do not set this option, Astro continues to enforce the 1 MB default to help prevent abuse.

```js
// astro.config.mjs
export default defineConfig({
security: {
actionBodySizeLimit: 10 * 1024 * 1024 // set to 10 MB
}
})
```
15 changes: 7 additions & 8 deletions packages/astro/src/actions/runtime/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,10 @@ export function getActionContext(context: APIContext): AstroActionContext {
throw error;
}

const bodySizeLimit = pipeline.manifest.actionBodySizeLimit;
let input;
try {
input = await parseRequestBody(context.request);
input = await parseRequestBody(context.request, bodySizeLimit);
} catch (e) {
if (e instanceof ActionError) {
return { data: undefined, error: e };
Expand Down Expand Up @@ -253,24 +254,22 @@ function getCallerInfo(ctx: APIContext) {
return undefined;
}

const DEFAULT_ACTION_BODY_SIZE_LIMIT = 1024 * 1024;

async function parseRequestBody(request: Request) {
async function parseRequestBody(request: Request, bodySizeLimit: number) {
const contentType = request.headers.get('content-type');
const contentLengthHeader = request.headers.get('content-length');
const contentLength = contentLengthHeader ? Number.parseInt(contentLengthHeader, 10) : undefined;
const hasContentLength = typeof contentLength === 'number' && Number.isFinite(contentLength);

if (!contentType) return undefined;
if (hasContentLength && contentLength > DEFAULT_ACTION_BODY_SIZE_LIMIT) {
if (hasContentLength && contentLength > bodySizeLimit) {
throw new ActionError({
code: 'CONTENT_TOO_LARGE',
message: `Request body exceeds ${DEFAULT_ACTION_BODY_SIZE_LIMIT} bytes`,
message: `Request body exceeds ${bodySizeLimit} bytes`,
});
}
if (hasContentType(contentType, formContentTypes)) {
if (!hasContentLength) {
const body = await readRequestBodyWithLimit(request.clone(), DEFAULT_ACTION_BODY_SIZE_LIMIT);
const body = await readRequestBodyWithLimit(request.clone(), bodySizeLimit);
const formRequest = new Request(request.url, {
method: request.method,
headers: request.headers,
Expand All @@ -283,7 +282,7 @@ async function parseRequestBody(request: Request) {
if (hasContentType(contentType, ['application/json'])) {
if (contentLength === 0) return undefined;
if (!hasContentLength) {
const body = await readRequestBodyWithLimit(request.clone(), DEFAULT_ACTION_BODY_SIZE_LIMIT);
const body = await readRequestBodyWithLimit(request.clone(), bodySizeLimit);
if (body.byteLength === 0) return undefined;
return JSON.parse(new TextDecoder().decode(body));
}
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/container/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ function createManifest(
i18n: manifest?.i18n,
checkOrigin: false,
allowedDomains: manifest?.allowedDomains ?? [],
actionBodySizeLimit: 1024 * 1024,
middleware: manifest?.middleware ?? middlewareInstance,
key: createKey(),
csp: manifest?.csp,
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/core/app/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export type SSRManifest = {
sessionDriver?: () => Promise<{ default: SessionDriverFactory | null }>;
checkOrigin: boolean;
allowedDomains?: Partial<RemotePattern>[];
actionBodySizeLimit: number;
sessionConfig?: SSRManifestSession;
cacheDir: URL;
srcDir: URL;
Expand Down
4 changes: 4 additions & 0 deletions packages/astro/src/core/build/plugins/plugin-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,10 @@ async function buildManifest(
buildFormat: settings.config.build.format,
checkOrigin:
(settings.config.security?.checkOrigin && settings.buildOutput === 'server') ?? false,
actionBodySizeLimit:
settings.config.security?.actionBodySizeLimit && settings.buildOutput === 'server'
? settings.config.security.actionBodySizeLimit
: 1024 * 1024,
allowedDomains: settings.config.security?.allowedDomains,
key: encodedKey,
sessionConfig: sessionConfigToManifest(settings.config.session),
Expand Down
5 changes: 5 additions & 0 deletions packages/astro/src/core/config/schemas/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export const ASTRO_CONFIG_DEFAULTS = {
checkOrigin: true,
allowedDomains: [],
csp: false,
actionBodySizeLimit: 1024 * 1024,
},
env: {
schema: {},
Expand Down Expand Up @@ -439,6 +440,10 @@ export const AstroConfigSchema = z.object({
)
.optional()
.default(ASTRO_CONFIG_DEFAULTS.security.allowedDomains),
actionBodySizeLimit: z
.number()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.security.actionBodySizeLimit),
csp: z
.union([
z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.security.csp),
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/manifest/serialized.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ async function createSerializedManifest(settings: AstroSettings): Promise<Serial
i18n: i18nManifest,
checkOrigin:
(settings.config.security?.checkOrigin && settings.buildOutput === 'server') ?? false,
actionBodySizeLimit:
(settings.config.security?.actionBodySizeLimit) ? settings.config.security.actionBodySizeLimit : 1024 * 1024, // 1mb default
key: await encodeKey(hasEnvironmentKey() ? await getEnvironmentKey() : await createKey()),
sessionConfig: sessionConfigToManifest(settings.config.session),
csp,
Expand Down
25 changes: 25 additions & 0 deletions packages/astro/src/types/public/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,31 @@ export interface AstroUserConfig<
*/
allowedDomains?: Partial<RemotePattern>[];

/**
* @docs
* @name security.actionBodySizeLimit
* @kind h4
* @type {number}
* @default `1048576` (1 MB)
* @version 5.18.0
* @description
*
* Sets the maximum size in bytes allowed for action request bodies.
*
* By default, action request bodies are limited to 1 MB (1048576 bytes) to prevent abuse.
* You can increase this limit if your actions need to accept larger payloads, for example when handling file uploads.
*
* ```js
* // astro.config.mjs
* export default defineConfig({
* security: {
* actionBodySizeLimit: 10 * 1024 * 1024 // 10 MB
* }
* })
* ```
*/
actionBodySizeLimit?: number;

/**
* @docs
* @name security.csp
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/vite-plugin-astro-server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ export async function createDevelopmentManifest(settings: AstroSettings): Promis
i18n: i18nManifest,
checkOrigin:
(settings.config.security?.checkOrigin && settings.buildOutput === 'server') ?? false,
actionBodySizeLimit:
(settings.config.security?.actionBodySizeLimit) ? settings.config.security.actionBodySizeLimit : 1024 * 1024, // 1mb default
key: hasEnvironmentKey() ? getEnvironmentKey() : createKey(),
middleware() {
return {
Expand Down