astro 5.17.2 → 5.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -159,10 +159,14 @@ function getActionContext(context) {
159
159
  }
160
160
  throw error;
161
161
  }
162
+ const bodySizeLimit = pipeline.manifest.actionBodySizeLimit;
162
163
  let input;
163
164
  try {
164
- input = await parseRequestBody(context.request);
165
+ input = await parseRequestBody(context.request, bodySizeLimit);
165
166
  } catch (e) {
167
+ if (e instanceof ActionError) {
168
+ return { data: void 0, error: e };
169
+ }
166
170
  if (e instanceof TypeError) {
167
171
  return { data: void 0, error: new ActionError({ code: "UNSUPPORTED_MEDIA_TYPE" }) };
168
172
  }
@@ -206,18 +210,73 @@ function getCallerInfo(ctx) {
206
210
  }
207
211
  return void 0;
208
212
  }
209
- async function parseRequestBody(request) {
213
+ async function parseRequestBody(request, bodySizeLimit) {
210
214
  const contentType = request.headers.get("content-type");
211
- const contentLength = request.headers.get("Content-Length");
215
+ const contentLengthHeader = request.headers.get("content-length");
216
+ const contentLength = contentLengthHeader ? Number.parseInt(contentLengthHeader, 10) : void 0;
217
+ const hasContentLength = typeof contentLength === "number" && Number.isFinite(contentLength);
212
218
  if (!contentType) return void 0;
219
+ if (hasContentLength && contentLength > bodySizeLimit) {
220
+ throw new ActionError({
221
+ code: "CONTENT_TOO_LARGE",
222
+ message: `Request body exceeds ${bodySizeLimit} bytes`
223
+ });
224
+ }
213
225
  if (hasContentType(contentType, formContentTypes)) {
226
+ if (!hasContentLength) {
227
+ const body = await readRequestBodyWithLimit(request.clone(), bodySizeLimit);
228
+ const formRequest = new Request(request.url, {
229
+ method: request.method,
230
+ headers: request.headers,
231
+ body: toArrayBuffer(body)
232
+ });
233
+ return await formRequest.formData();
234
+ }
214
235
  return await request.clone().formData();
215
236
  }
216
237
  if (hasContentType(contentType, ["application/json"])) {
217
- return contentLength === "0" ? void 0 : await request.clone().json();
238
+ if (contentLength === 0) return void 0;
239
+ if (!hasContentLength) {
240
+ const body = await readRequestBodyWithLimit(request.clone(), bodySizeLimit);
241
+ if (body.byteLength === 0) return void 0;
242
+ return JSON.parse(new TextDecoder().decode(body));
243
+ }
244
+ return await request.clone().json();
218
245
  }
219
246
  throw new TypeError("Unsupported content type");
220
247
  }
248
+ async function readRequestBodyWithLimit(request, limit) {
249
+ if (!request.body) return new Uint8Array();
250
+ const reader = request.body.getReader();
251
+ const chunks = [];
252
+ let received = 0;
253
+ while (true) {
254
+ const { done, value } = await reader.read();
255
+ if (done) break;
256
+ if (value) {
257
+ received += value.byteLength;
258
+ if (received > limit) {
259
+ throw new ActionError({
260
+ code: "CONTENT_TOO_LARGE",
261
+ message: `Request body exceeds ${limit} bytes`
262
+ });
263
+ }
264
+ chunks.push(value);
265
+ }
266
+ }
267
+ const buffer = new Uint8Array(received);
268
+ let offset = 0;
269
+ for (const chunk of chunks) {
270
+ buffer.set(chunk, offset);
271
+ offset += chunk.byteLength;
272
+ }
273
+ return buffer;
274
+ }
275
+ function toArrayBuffer(buffer) {
276
+ const copy = new Uint8Array(buffer.byteLength);
277
+ copy.set(buffer);
278
+ return copy.buffer;
279
+ }
221
280
  export {
222
281
  defineAction,
223
282
  formDataToObject,
@@ -1,7 +1,10 @@
1
1
  import CachePolicy from "http-cache-semantics";
2
2
  async function loadRemoteImage(src) {
3
3
  const req = new Request(src);
4
- const res = await fetch(req);
4
+ const res = await fetch(req, { redirect: "manual" });
5
+ if (res.status >= 300 && res.status < 400) {
6
+ throw new Error(`Failed to load remote image ${src}. The request was redirected.`);
7
+ }
5
8
  if (!res.ok) {
6
9
  throw new Error(
7
10
  `Failed to load remote image ${src}. The request did not return a 200 OK response. (received ${res.status}))`
@@ -22,7 +25,10 @@ async function revalidateRemoteImage(src, revalidationData) {
22
25
  ...revalidationData.lastModified && { "If-Modified-Since": revalidationData.lastModified }
23
26
  };
24
27
  const req = new Request(src, { headers, cache: "no-cache" });
25
- const res = await fetch(req);
28
+ const res = await fetch(req, { redirect: "manual" });
29
+ if (res.status >= 300 && res.status < 400) {
30
+ throw new Error(`Failed to revalidate cached remote image ${src}. The request was redirected.`);
31
+ }
26
32
  if (!res.ok && res.status !== 304) {
27
33
  throw new Error(
28
34
  `Failed to revalidate cached remote image ${src}. The request did not return a 200 OK / 304 NOT MODIFIED response. (received ${res.status} ${res.statusText})`
@@ -8,8 +8,12 @@ async function loadRemoteImage(src, headers) {
8
8
  try {
9
9
  const res = await fetch(src, {
10
10
  // Forward all headers from the original request
11
- headers
11
+ headers,
12
+ redirect: "manual"
12
13
  });
14
+ if (res.status >= 300 && res.status < 400) {
15
+ return void 0;
16
+ }
13
17
  if (!res.ok) {
14
18
  return void 0;
15
19
  }
@@ -6,7 +6,10 @@ import { getConfiguredImageService } from "../internal.js";
6
6
  import { etag } from "../utils/etag.js";
7
7
  async function loadRemoteImage(src) {
8
8
  try {
9
- const res = await fetch(src);
9
+ const res = await fetch(src, { redirect: "manual" });
10
+ if (res.status >= 300 && res.status < 400) {
11
+ return void 0;
12
+ }
10
13
  if (!res.ok) {
11
14
  return void 0;
12
15
  }
@@ -1,4 +1,5 @@
1
1
  import { isRemotePath } from "@astrojs/internal-helpers/path";
2
+ import { isRemoteAllowed } from "@astrojs/internal-helpers/remote";
2
3
  import { AstroError, AstroErrorData } from "../core/errors/index.js";
3
4
  import { DEFAULT_HASH_PROPS } from "./consts.js";
4
5
  import {
@@ -58,13 +59,21 @@ async function getImage(options, imageConfig) {
58
59
  };
59
60
  let originalWidth;
60
61
  let originalHeight;
61
- if (options.inferSize && isRemoteImage(resolvedOptions.src) && isRemotePath(resolvedOptions.src)) {
62
- const result = await inferRemoteSize(resolvedOptions.src);
63
- resolvedOptions.width ??= result.width;
64
- resolvedOptions.height ??= result.height;
65
- originalWidth = result.width;
66
- originalHeight = result.height;
62
+ if (options.inferSize) {
67
63
  delete resolvedOptions.inferSize;
64
+ if (isRemoteImage(resolvedOptions.src) && isRemotePath(resolvedOptions.src)) {
65
+ if (!isRemoteAllowed(resolvedOptions.src, imageConfig)) {
66
+ throw new AstroError({
67
+ ...AstroErrorData.RemoteImageNotAllowed,
68
+ message: AstroErrorData.RemoteImageNotAllowed.message(resolvedOptions.src)
69
+ });
70
+ }
71
+ const result = await inferRemoteSize(resolvedOptions.src, imageConfig);
72
+ resolvedOptions.width ??= result.width;
73
+ resolvedOptions.height ??= result.height;
74
+ originalWidth = result.width;
75
+ originalHeight = result.height;
76
+ }
68
77
  }
69
78
  const originalFilePath = isESMImportedImage(resolvedOptions.src) ? resolvedOptions.src.fsPath : void 0;
70
79
  const clonedSrc = isESMImportedImage(resolvedOptions.src) ? (
@@ -1,9 +1,13 @@
1
+ import type { AstroConfig } from '../../types/public/config.js';
1
2
  import type { ImageMetadata } from '../types.js';
3
+ type RemoteImageConfig = Pick<AstroConfig['image'], 'domains' | 'remotePatterns'>;
2
4
  /**
3
5
  * Infers the dimensions of a remote image by streaming its data and analyzing it progressively until sufficient metadata is available.
4
6
  *
5
7
  * @param {string} url - The URL of the remote image from which to infer size metadata.
8
+ * @param {RemoteImageConfig} [imageConfig] - Optional image config used to validate remote allowlists.
6
9
  * @return {Promise<Omit<ImageMetadata, 'src' | 'fsPath'>>} Returns a promise that resolves to an object containing the image dimensions metadata excluding `src` and `fsPath`.
7
10
  * @throws {AstroError} Thrown when the fetching fails or metadata cannot be extracted.
8
11
  */
9
- export declare function inferRemoteSize(url: string): Promise<Omit<ImageMetadata, 'src' | 'fsPath'>>;
12
+ export declare function inferRemoteSize(url: string, imageConfig?: RemoteImageConfig): Promise<Omit<ImageMetadata, 'src' | 'fsPath'>>;
13
+ export {};
@@ -1,7 +1,39 @@
1
+ import { isRemoteAllowed } from "@astrojs/internal-helpers/remote";
1
2
  import { AstroError, AstroErrorData } from "../../core/errors/index.js";
2
3
  import { imageMetadata } from "./metadata.js";
3
- async function inferRemoteSize(url) {
4
- const response = await fetch(url);
4
+ async function inferRemoteSize(url, imageConfig) {
5
+ if (!URL.canParse(url)) {
6
+ throw new AstroError({
7
+ ...AstroErrorData.FailedToFetchRemoteImageDimensions,
8
+ message: AstroErrorData.FailedToFetchRemoteImageDimensions.message(url)
9
+ });
10
+ }
11
+ const allowlistConfig = imageConfig ? {
12
+ domains: imageConfig.domains ?? [],
13
+ remotePatterns: imageConfig.remotePatterns ?? []
14
+ } : void 0;
15
+ if (!allowlistConfig) {
16
+ const parsedUrl = new URL(url);
17
+ if (!["http:", "https:"].includes(parsedUrl.protocol)) {
18
+ throw new AstroError({
19
+ ...AstroErrorData.FailedToFetchRemoteImageDimensions,
20
+ message: AstroErrorData.FailedToFetchRemoteImageDimensions.message(url)
21
+ });
22
+ }
23
+ }
24
+ if (allowlistConfig && !isRemoteAllowed(url, allowlistConfig)) {
25
+ throw new AstroError({
26
+ ...AstroErrorData.RemoteImageNotAllowed,
27
+ message: AstroErrorData.RemoteImageNotAllowed.message(url)
28
+ });
29
+ }
30
+ const response = await fetch(url, { redirect: "manual" });
31
+ if (response.status >= 300 && response.status < 400) {
32
+ throw new AstroError({
33
+ ...AstroErrorData.FailedToFetchRemoteImageDimensions,
34
+ message: AstroErrorData.FailedToFetchRemoteImageDimensions.message(url)
35
+ });
36
+ }
5
37
  if (!response.body || !response.ok) {
6
38
  throw new AstroError({
7
39
  ...AstroErrorData.FailedToFetchRemoteImageDimensions,
@@ -101,11 +101,11 @@ function assets({ fs, settings, sync, logger }) {
101
101
  if (id === resolvedVirtualModuleId) {
102
102
  return {
103
103
  code: `
104
- export { getConfiguredImageService, isLocalService } from "astro/assets";
105
- import { getImage as getImageInternal } from "astro/assets";
106
- export { default as Image } from "astro/components/${imageComponentPrefix}Image.astro";
107
- export { default as Picture } from "astro/components/${imageComponentPrefix}Picture.astro";
108
- export { inferRemoteSize } from "astro/assets/utils/inferRemoteSize.js";
104
+ export { getConfiguredImageService, isLocalService } from "astro/assets";
105
+ import { getImage as getImageInternal } from "astro/assets";
106
+ import { inferRemoteSize as inferRemoteSizeInternal } from "astro/assets/utils/inferRemoteSize.js";
107
+ export { default as Image } from "astro/components/${imageComponentPrefix}Image.astro";
108
+ export { default as Picture } from "astro/components/${imageComponentPrefix}Picture.astro";
109
109
 
110
110
  export { default as Font } from "astro/components/Font.astro";
111
111
  export * from "${RUNTIME_VIRTUAL_MODULE_ID}";
@@ -126,6 +126,9 @@ function assets({ fs, settings, sync, logger }) {
126
126
  enumerable: false,
127
127
  configurable: true,
128
128
  });
129
+ export const inferRemoteSize = async (url) => {
130
+ return inferRemoteSizeInternal(url, imageConfig)
131
+ }
129
132
  // This is used by the @astrojs/node integration to locate images.
130
133
  // It's unused on other platforms, but on some platforms like Netlify (and presumably also Vercel)
131
134
  // new URL("dist/...") is interpreted by the bundler as a signal to include that directory
@@ -1,6 +1,6 @@
1
1
  class BuildTimeAstroVersionProvider {
2
2
  // Injected during the build through esbuild define
3
- version = "5.17.2";
3
+ version = "5.18.0";
4
4
  }
5
5
  export {
6
6
  BuildTimeAstroVersionProvider
@@ -45,6 +45,7 @@ function createManifest(manifest, renderers, middleware) {
45
45
  i18n: manifest?.i18n,
46
46
  checkOrigin: false,
47
47
  allowedDomains: manifest?.allowedDomains ?? [],
48
+ actionBodySizeLimit: 1024 * 1024,
48
49
  middleware: manifest?.middleware ?? middlewareInstance,
49
50
  key: createKey(),
50
51
  csp: manifest?.csp
@@ -164,7 +164,7 @@ ${contentConfig.error.message}`);
164
164
  logger.info("Content config changed");
165
165
  shouldClear = true;
166
166
  }
167
- if (previousAstroVersion && previousAstroVersion !== "5.17.2") {
167
+ if (previousAstroVersion && previousAstroVersion !== "5.18.0") {
168
168
  logger.info("Astro version changed");
169
169
  shouldClear = true;
170
170
  }
@@ -172,8 +172,8 @@ ${contentConfig.error.message}`);
172
172
  logger.info("Clearing content store");
173
173
  this.#store.clearAll();
174
174
  }
175
- if ("5.17.2") {
176
- await this.#store.metaStore().set("astro-version", "5.17.2");
175
+ if ("5.18.0") {
176
+ await this.#store.metaStore().set("astro-version", "5.18.0");
177
177
  }
178
178
  if (currentConfigDigest) {
179
179
  await this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -71,6 +71,7 @@ export type SSRManifest = {
71
71
  actions?: () => Promise<SSRActions> | SSRActions;
72
72
  checkOrigin: boolean;
73
73
  allowedDomains?: Partial<RemotePattern>[];
74
+ actionBodySizeLimit: number;
74
75
  sessionConfig?: ResolvedSessionConfig<any>;
75
76
  cacheDir: string | URL;
76
77
  srcDir: string | URL;
@@ -39,7 +39,9 @@ function validateForwardedHeaders(forwardedProtocol, forwardedHost, forwardedPor
39
39
  if (hasProtocolPatterns) {
40
40
  try {
41
41
  const testUrl = new URL(`${forwardedProtocol}://example.com`);
42
- const isAllowed = allowedDomains.some((pattern) => matchPattern(testUrl, pattern));
42
+ const isAllowed = allowedDomains.some(
43
+ (pattern) => matchPattern(testUrl, { protocol: pattern.protocol })
44
+ );
43
45
  if (isAllowed) {
44
46
  result.protocol = forwardedProtocol;
45
47
  }
@@ -500,6 +500,7 @@ async function createBuildManifest(settings, internals, renderers, middleware, a
500
500
  },
501
501
  actions: () => actions,
502
502
  checkOrigin: (settings.config.security?.checkOrigin && settings.buildOutput === "server") ?? false,
503
+ actionBodySizeLimit: settings.config.security.actionBodySizeLimit,
503
504
  key,
504
505
  csp
505
506
  };
@@ -300,6 +300,7 @@ async function buildManifest(opts, internals, staticFiles, encodedKey) {
300
300
  buildFormat: settings.config.build.format,
301
301
  checkOrigin: (settings.config.security?.checkOrigin && settings.buildOutput === "server") ?? false,
302
302
  allowedDomains: settings.config.security?.allowedDomains,
303
+ actionBodySizeLimit: settings.config.security.actionBodySizeLimit,
303
304
  serverIslandNameMap: Array.from(settings.serverIslandNameMap),
304
305
  key: encodedKey,
305
306
  sessionConfig: settings.config.session,
@@ -64,6 +64,7 @@ export declare const ASTRO_CONFIG_DEFAULTS: {
64
64
  security: {
65
65
  checkOrigin: true;
66
66
  allowedDomains: never[];
67
+ actionBodySizeLimit: number;
67
68
  };
68
69
  env: {
69
70
  schema: {};
@@ -456,6 +457,7 @@ export declare const AstroConfigSchema: z.ZodObject<{
456
457
  protocol?: string | undefined;
457
458
  hostname?: string | undefined;
458
459
  }>, "many">>>;
460
+ actionBodySizeLimit: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
459
461
  }, "strip", z.ZodTypeAny, {
460
462
  checkOrigin: boolean;
461
463
  allowedDomains: {
@@ -463,6 +465,7 @@ export declare const AstroConfigSchema: z.ZodObject<{
463
465
  protocol?: string | undefined;
464
466
  hostname?: string | undefined;
465
467
  }[];
468
+ actionBodySizeLimit: number;
466
469
  }, {
467
470
  checkOrigin?: boolean | undefined;
468
471
  allowedDomains?: {
@@ -470,6 +473,7 @@ export declare const AstroConfigSchema: z.ZodObject<{
470
473
  protocol?: string | undefined;
471
474
  hostname?: string | undefined;
472
475
  }[] | undefined;
476
+ actionBodySizeLimit?: number | undefined;
473
477
  }>>>;
474
478
  env: z.ZodDefault<z.ZodOptional<z.ZodObject<{
475
479
  schema: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>, z.ZodIntersection<z.ZodEffects<z.ZodType<{
@@ -1097,6 +1101,7 @@ export declare const AstroConfigSchema: z.ZodObject<{
1097
1101
  protocol?: string | undefined;
1098
1102
  hostname?: string | undefined;
1099
1103
  }[];
1104
+ actionBodySizeLimit: number;
1100
1105
  };
1101
1106
  experimental: {
1102
1107
  clientPrerender: boolean;
@@ -1344,6 +1349,7 @@ export declare const AstroConfigSchema: z.ZodObject<{
1344
1349
  protocol?: string | undefined;
1345
1350
  hostname?: string | undefined;
1346
1351
  }[] | undefined;
1352
+ actionBodySizeLimit?: number | undefined;
1347
1353
  } | undefined;
1348
1354
  experimental?: {
1349
1355
  fonts?: {
@@ -46,7 +46,8 @@ const ASTRO_CONFIG_DEFAULTS = {
46
46
  redirects: {},
47
47
  security: {
48
48
  checkOrigin: true,
49
- allowedDomains: []
49
+ allowedDomains: [],
50
+ actionBodySizeLimit: 1024 * 1024
50
51
  },
51
52
  env: {
52
53
  schema: {},
@@ -252,7 +253,8 @@ const AstroConfigSchema = z.object({
252
253
  protocol: z.string().optional(),
253
254
  port: z.string().optional()
254
255
  })
255
- ).optional().default(ASTRO_CONFIG_DEFAULTS.security.allowedDomains)
256
+ ).optional().default(ASTRO_CONFIG_DEFAULTS.security.allowedDomains),
257
+ actionBodySizeLimit: z.number().optional().default(ASTRO_CONFIG_DEFAULTS.security.actionBodySizeLimit)
256
258
  }).optional().default(ASTRO_CONFIG_DEFAULTS.security),
257
259
  env: z.object({
258
260
  schema: EnvSchema.optional().default(ASTRO_CONFIG_DEFAULTS.env.schema),
@@ -301,6 +301,7 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
301
301
  protocol?: string | undefined;
302
302
  hostname?: string | undefined;
303
303
  }>, "many">>>;
304
+ actionBodySizeLimit: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
304
305
  }, "strip", z.ZodTypeAny, {
305
306
  checkOrigin: boolean;
306
307
  allowedDomains: {
@@ -308,6 +309,7 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
308
309
  protocol?: string | undefined;
309
310
  hostname?: string | undefined;
310
311
  }[];
312
+ actionBodySizeLimit: number;
311
313
  }, {
312
314
  checkOrigin?: boolean | undefined;
313
315
  allowedDomains?: {
@@ -315,6 +317,7 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
315
317
  protocol?: string | undefined;
316
318
  hostname?: string | undefined;
317
319
  }[] | undefined;
320
+ actionBodySizeLimit?: number | undefined;
318
321
  }>>>;
319
322
  env: z.ZodDefault<z.ZodOptional<z.ZodObject<{
320
323
  schema: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>, z.ZodIntersection<z.ZodEffects<z.ZodType<{
@@ -1020,6 +1023,7 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
1020
1023
  protocol?: string | undefined;
1021
1024
  hostname?: string | undefined;
1022
1025
  }[];
1026
+ actionBodySizeLimit: number;
1023
1027
  };
1024
1028
  experimental: {
1025
1029
  clientPrerender: boolean;
@@ -1267,6 +1271,7 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
1267
1271
  protocol?: string | undefined;
1268
1272
  hostname?: string | undefined;
1269
1273
  }[] | undefined;
1274
+ actionBodySizeLimit?: number | undefined;
1270
1275
  } | undefined;
1271
1276
  experimental?: {
1272
1277
  fonts?: {
@@ -1450,6 +1455,7 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
1450
1455
  protocol?: string | undefined;
1451
1456
  hostname?: string | undefined;
1452
1457
  }[];
1458
+ actionBodySizeLimit: number;
1453
1459
  };
1454
1460
  experimental: {
1455
1461
  clientPrerender: boolean;
@@ -1697,6 +1703,7 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
1697
1703
  protocol?: string | undefined;
1698
1704
  hostname?: string | undefined;
1699
1705
  }[] | undefined;
1706
+ actionBodySizeLimit?: number | undefined;
1700
1707
  } | undefined;
1701
1708
  experimental?: {
1702
1709
  fonts?: {
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "5.17.2";
1
+ const ASTRO_VERSION = "5.18.0";
2
2
  const REROUTE_DIRECTIVE_HEADER = "X-Astro-Reroute";
3
3
  const REWRITE_DIRECTIVE_HEADER_KEY = "X-Astro-Rewrite";
4
4
  const REWRITE_DIRECTIVE_HEADER_VALUE = "yes";
@@ -22,7 +22,7 @@ async function dev(inlineConfig) {
22
22
  await telemetry.record([]);
23
23
  const restart = await createContainerWithAutomaticRestart({ inlineConfig, fs });
24
24
  const logger = restart.container.logger;
25
- const currentVersion = "5.17.2";
25
+ const currentVersion = "5.18.0";
26
26
  const isPrerelease = currentVersion.includes("-");
27
27
  if (!isPrerelease) {
28
28
  try {
@@ -491,6 +491,19 @@ export declare const FailedToFetchRemoteImageDimensions: {
491
491
  message: (imageURL: string) => string;
492
492
  hint: string;
493
493
  };
494
+ /**
495
+ * @docs
496
+ * @message
497
+ * Remote image `IMAGE_URL` is not allowed by your image configuration.
498
+ * @description
499
+ * The remote image URL does not match your configured `image.domains` or `image.remotePatterns`.
500
+ */
501
+ export declare const RemoteImageNotAllowed: {
502
+ name: string;
503
+ title: string;
504
+ message: (imageURL: string) => string;
505
+ hint: string;
506
+ };
494
507
  /**
495
508
  * @docs
496
509
  * @description
@@ -175,6 +175,12 @@ const FailedToFetchRemoteImageDimensions = {
175
175
  message: (imageURL) => `Failed to get the dimensions for ${imageURL}.`,
176
176
  hint: "Verify your remote image URL is accurate, and that you are not using `inferSize` with a file located in your `public/` folder."
177
177
  };
178
+ const RemoteImageNotAllowed = {
179
+ name: "RemoteImageNotAllowed",
180
+ title: "Remote image is not allowed",
181
+ message: (imageURL) => `Remote image ${imageURL} is not allowed by your image configuration.`,
182
+ hint: "Update `image.domains` or `image.remotePatterns`, or remove `inferSize` for this image."
183
+ };
178
184
  const UnsupportedImageFormat = {
179
185
  name: "UnsupportedImageFormat",
180
186
  title: "Unsupported image format",
@@ -832,6 +838,7 @@ export {
832
838
  PrerenderDynamicEndpointPathCollide,
833
839
  PrerenderRouteConflict,
834
840
  RedirectWithNoLocation,
841
+ RemoteImageNotAllowed,
835
842
  RenderUndefinedEntryError,
836
843
  ReservedSlotName,
837
844
  ResponseSentError,
@@ -38,7 +38,7 @@ function serverStart({
38
38
  host,
39
39
  base
40
40
  }) {
41
- const version = "5.17.2";
41
+ const version = "5.18.0";
42
42
  const localPrefix = `${dim("\u2503")} Local `;
43
43
  const networkPrefix = `${dim("\u2503")} Network `;
44
44
  const emptyPrefix = " ".repeat(11);
@@ -275,7 +275,7 @@ function printHelp({
275
275
  message.push(
276
276
  linebreak(),
277
277
  ` ${bgGreen(black(` ${commandName} `))} ${green(
278
- `v${"5.17.2"}`
278
+ `v${"5.18.0"}`
279
279
  )} ${headline}`
280
280
  );
281
281
  }
@@ -563,6 +563,30 @@ export interface AstroUserConfig<TLocales extends Locales = never, TSession exte
563
563
  * When not configured, `X-Forwarded-Host` headers are not trusted and will be ignored.
564
564
  */
565
565
  allowedDomains?: Partial<RemotePattern>[];
566
+ /**
567
+ * @docs
568
+ * @name security.actionBodySizeLimit
569
+ * @kind h4
570
+ * @type {number}
571
+ * @default `1048576` (1 MB)
572
+ * @version 5.18.0
573
+ * @description
574
+ *
575
+ * Sets the maximum size in bytes allowed for action request bodies.
576
+ *
577
+ * By default, action request bodies are limited to 1 MB (1048576 bytes) to prevent abuse.
578
+ * You can increase this limit if your actions need to accept larger payloads, for example when handling file uploads.
579
+ *
580
+ * ```js
581
+ * // astro.config.mjs
582
+ * export default defineConfig({
583
+ * security: {
584
+ * actionBodySizeLimit: 10 * 1024 * 1024 // 10 MB
585
+ * }
586
+ * })
587
+ * ```
588
+ */
589
+ actionBodySizeLimit?: number;
566
590
  };
567
591
  /**
568
592
  * @docs
@@ -106,6 +106,7 @@ function createVitePluginAstroServer({
106
106
  }
107
107
  warnMissingAdapter(logger, settings);
108
108
  pipeline.manifest.checkOrigin = settings.config.security.checkOrigin && settings.buildOutput === "server";
109
+ pipeline.manifest.actionBodySizeLimit = settings.config.security.actionBodySizeLimit;
109
110
  pipeline.setManifestData(routesList);
110
111
  }
111
112
  viteServer.watcher.on("add", rebuildManifest.bind(null, null));
@@ -251,6 +252,7 @@ function createDevelopmentManifest(settings) {
251
252
  inlinedScripts: /* @__PURE__ */ new Map(),
252
253
  i18n: i18nManifest,
253
254
  checkOrigin: (settings.config.security?.checkOrigin && settings.buildOutput === "server") ?? false,
255
+ actionBodySizeLimit: settings.config.security.actionBodySizeLimit,
254
256
  key: hasEnvironmentKey() ? getEnvironmentKey() : createKey(),
255
257
  middleware() {
256
258
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "5.17.2",
3
+ "version": "5.18.0",
4
4
  "description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
5
5
  "type": "module",
6
6
  "author": "withastro",
@@ -113,7 +113,7 @@
113
113
  "dlv": "^1.1.3",
114
114
  "dset": "^3.1.4",
115
115
  "es-module-lexer": "^1.7.0",
116
- "esbuild": "^0.27.0",
116
+ "esbuild": "^0.27.3",
117
117
  "estree-walker": "^3.0.3",
118
118
  "flattie": "^1.1.1",
119
119
  "fontace": "~0.4.0",
@@ -193,8 +193,8 @@
193
193
  "undici": "^6.23.0",
194
194
  "unified": "^11.0.5",
195
195
  "vitest": "^3.2.4",
196
- "@astrojs/check": "0.9.6",
197
- "astro-scripts": "0.0.14"
196
+ "astro-scripts": "0.0.14",
197
+ "@astrojs/check": "0.9.6"
198
198
  },
199
199
  "engines": {
200
200
  "node": "18.20.8 || ^20.3.0 || >=22.0.0",