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

Make the body request limit a configurable
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 @@ -323,9 +323,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 @@ -381,24 +382,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 @@ -411,7 +410,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 @@ -162,6 +162,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 @@ -87,6 +87,7 @@ export type SSRManifest = {
actions?: () => Promise<SSRActions> | SSRActions;
checkOrigin: boolean;
allowedDomains?: Partial<RemotePattern>[];
actionBodySizeLimit: number;
sessionConfig?: ResolvedSessionConfig<any>;
cacheDir: string | URL;
srcDir: string | URL;
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,7 @@ async function createBuildManifest(
actions: () => actions,
checkOrigin:
(settings.config.security?.checkOrigin && settings.buildOutput === 'server') ?? false,
actionBodySizeLimit: settings.config.security.actionBodySizeLimit,
key,
csp,
};
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/core/build/plugins/plugin-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ async function buildManifest(
checkOrigin:
(settings.config.security?.checkOrigin && settings.buildOutput === 'server') ?? false,
allowedDomains: settings.config.security?.allowedDomains,
actionBodySizeLimit: settings.config.security.actionBodySizeLimit,
serverIslandNameMap: Array.from(settings.serverIslandNameMap),
key: encodedKey,
sessionConfig: 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 @@ -91,6 +91,7 @@ export const ASTRO_CONFIG_DEFAULTS = {
security: {
checkOrigin: true,
allowedDomains: [],
actionBodySizeLimit: 1024 * 1024,
},
env: {
schema: {},
Expand Down Expand Up @@ -440,6 +441,10 @@ export const AstroConfigSchema = z.object({
)
.optional()
.default(ASTRO_CONFIG_DEFAULTS.security.allowedDomains),
actionBodySizeLimit: z
.number()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.security.actionBodySizeLimit),
})
.optional()
.default(ASTRO_CONFIG_DEFAULTS.security),
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 @@ -633,6 +633,31 @@ export interface AstroUserConfig<
* When not configured, `X-Forwarded-Host` headers are not trusted and will be ignored.
*/
allowedDomains?: Partial<RemotePattern>[];

/**
* @docs
* @name security.actionBodySizeLimit
* @kind h4
* @type {number}
* @default `1048576` (1 MB)
* @version 5.x.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;
};

/**
Expand Down
3 changes: 3 additions & 0 deletions packages/astro/src/vite-plugin-astro-server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ export default function createVitePluginAstroServer({
warnMissingAdapter(logger, settings);
pipeline.manifest.checkOrigin =
settings.config.security.checkOrigin && settings.buildOutput === 'server';
pipeline.manifest.actionBodySizeLimit =
settings.config.security.actionBodySizeLimit;
pipeline.setManifestData(routesList);
}

Expand Down Expand Up @@ -311,6 +313,7 @@ export function createDevelopmentManifest(settings: AstroSettings): SSRManifest
i18n: i18nManifest,
checkOrigin:
(settings.config.security?.checkOrigin && settings.buildOutput === 'server') ?? false,
actionBodySizeLimit: settings.config.security.actionBodySizeLimit,
key: hasEnvironmentKey() ? getEnvironmentKey() : createKey(),
middleware() {
return {
Expand Down