astro 5.16.2 → 5.16.3

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.
@@ -6,19 +6,20 @@ export * from './shared.js';
6
6
  export type ActionAccept = 'form' | 'json';
7
7
  export type ActionHandler<TInputSchema, TOutput> = TInputSchema extends z.ZodType ? (input: z.infer<TInputSchema>, context: ActionAPIContext) => MaybePromise<TOutput> : (input: any, context: ActionAPIContext) => MaybePromise<TOutput>;
8
8
  export type ActionReturnType<T extends ActionHandler<any, any>> = Awaited<ReturnType<T>>;
9
- declare const inferSymbol: unique symbol;
9
+ export type InferKey = '__internalInfer';
10
10
  /**
11
11
  * Infers the type of an action's input based on its Zod schema
12
12
  *
13
13
  * @see https://docs.astro.build/en/reference/modules/astro-actions/#actioninputschema
14
14
  */
15
15
  export type ActionInputSchema<T extends ActionClient<any, any, any>> = T extends {
16
- [inferSymbol]: any;
17
- } ? T[typeof inferSymbol] : never;
16
+ [key in InferKey]: any;
17
+ } ? T[InferKey] : never;
18
18
  export type ActionClient<TOutput, TAccept extends ActionAccept | undefined, TInputSchema extends z.ZodType | undefined> = TInputSchema extends z.ZodType ? ((input: TAccept extends 'form' ? FormData : z.input<TInputSchema>) => Promise<SafeResult<z.input<TInputSchema> extends ErrorInferenceObject ? z.input<TInputSchema> : ErrorInferenceObject, Awaited<TOutput>>>) & {
19
19
  queryString: string;
20
20
  orThrow: (input: TAccept extends 'form' ? FormData : z.input<TInputSchema>) => Promise<Awaited<TOutput>>;
21
- [inferSymbol]: TInputSchema;
21
+ } & {
22
+ [key in InferKey]: TInputSchema;
22
23
  } : ((input?: any) => Promise<SafeResult<never, Awaited<TOutput>>>) & {
23
24
  orThrow: (input?: any) => Promise<Awaited<TOutput>>;
24
25
  };
@@ -20,7 +20,6 @@ import {
20
20
  isActionAPIContext
21
21
  } from "./utils.js";
22
22
  export * from "./shared.js";
23
- const inferSymbol = Symbol("#infer");
24
23
  function defineAction({
25
24
  accept,
26
25
  input: inputSchema,
@@ -1,6 +1,6 @@
1
1
  class BuildTimeAstroVersionProvider {
2
2
  // Injected during the build through esbuild define
3
- version = "5.16.2";
3
+ version = "5.16.3";
4
4
  }
5
5
  export {
6
6
  BuildTimeAstroVersionProvider
@@ -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.16.2") {
167
+ if (previousAstroVersion && previousAstroVersion !== "5.16.3") {
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.16.2") {
176
- await this.#store.metaStore().set("astro-version", "5.16.2");
175
+ if ("5.16.3") {
176
+ await this.#store.metaStore().set("astro-version", "5.16.3");
177
177
  }
178
178
  if (currentConfigDigest) {
179
179
  await this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -30,6 +30,7 @@ import { ensure404Route } from "../routing/astro-designed-error-pages.js";
30
30
  import { createDefaultRoutes } from "../routing/default.js";
31
31
  import { matchRoute } from "../routing/match.js";
32
32
  import { PERSIST_SYMBOL } from "../session.js";
33
+ import { validateAndDecodePathname } from "../util/pathname.js";
33
34
  import { AppPipeline } from "./pipeline.js";
34
35
  import { deserializeManifest } from "./common.js";
35
36
  class App {
@@ -197,7 +198,7 @@ class App {
197
198
  const url = new URL(request.url);
198
199
  const pathname = prependForwardSlash(this.removeBase(url.pathname));
199
200
  try {
200
- return decodeURI(pathname);
201
+ return validateAndDecodePathname(pathname);
201
202
  } catch (e) {
202
203
  this.getAdapterLogger().error(e.toString());
203
204
  return pathname;
@@ -218,7 +219,12 @@ class App {
218
219
  if (!pathname) {
219
220
  pathname = prependForwardSlash(this.removeBase(url.pathname));
220
221
  }
221
- let routeData = matchRoute(decodeURI(pathname), this.#manifestData);
222
+ try {
223
+ pathname = validateAndDecodePathname(pathname);
224
+ } catch {
225
+ return void 0;
226
+ }
227
+ let routeData = matchRoute(pathname, this.#manifestData);
222
228
  if (!routeData) return void 0;
223
229
  if (allowPrerenderedRoutes) {
224
230
  return routeData;
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "5.16.2";
1
+ const ASTRO_VERSION = "5.16.3";
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.16.2";
25
+ const currentVersion = "5.16.3";
26
26
  const isPrerelease = currentVersion.includes("-");
27
27
  if (!isPrerelease) {
28
28
  try {
@@ -38,7 +38,7 @@ function serverStart({
38
38
  host,
39
39
  base
40
40
  }) {
41
- const version = "5.16.2";
41
+ const version = "5.16.3";
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.16.2"}`
278
+ `v${"5.16.3"}`
279
279
  )} ${headline}`
280
280
  );
281
281
  }
@@ -30,6 +30,7 @@ import { getParams, getProps, Slots } from "./render/index.js";
30
30
  import { isRoute404or500, isRouteExternalRedirect, isRouteServerIsland } from "./routing/match.js";
31
31
  import { copyRequest, getOriginPathname, setOriginPathname } from "./routing/rewrite.js";
32
32
  import { AstroSession } from "./session.js";
33
+ import { validateAndDecodePathname } from "./util/pathname.js";
33
34
  const apiContextRoutesSymbol = Symbol.for("context.routes");
34
35
  class RenderContext {
35
36
  constructor(pipeline, locals, middleware, actions, pathname, request, routeData, status, clientAddress, cookies = new AstroCookies(request), params = getParams(routeData, pathname), url = RenderContext.#createNormalizedUrl(request.url), props = {}, partial = void 0, shouldInjectCspMetaTags = !!pipeline.manifest.csp, session = pipeline.manifest.sessionConfig ? new AstroSession(cookies, pipeline.manifest.sessionConfig, pipeline.runtimeMode) : void 0) {
@@ -53,10 +54,14 @@ class RenderContext {
53
54
  static #createNormalizedUrl(requestUrl) {
54
55
  const url = new URL(requestUrl);
55
56
  try {
56
- url.pathname = decodeURI(url.pathname);
57
- } finally {
58
- return url;
57
+ url.pathname = validateAndDecodePathname(url.pathname);
58
+ } catch {
59
+ try {
60
+ url.pathname = decodeURI(url.pathname);
61
+ } catch {
62
+ }
59
63
  }
64
+ return url;
60
65
  }
61
66
  /**
62
67
  * A flag that tells the render content if the rewriting was triggered
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Validates that a pathname is not multi-level encoded.
3
+ * Detects if a pathname contains encoding that was encoded again (e.g., %2561dmin where %25 decodes to %).
4
+ * This prevents double/triple encoding bypasses of security checks.
5
+ *
6
+ * @param pathname - The pathname to validate
7
+ * @returns The decoded pathname if valid
8
+ * @throws Error if multi-level encoding is detected
9
+ */
10
+ export declare function validateAndDecodePathname(pathname: string): string;
@@ -0,0 +1,17 @@
1
+ function validateAndDecodePathname(pathname) {
2
+ let decoded;
3
+ try {
4
+ decoded = decodeURI(pathname);
5
+ } catch (_e) {
6
+ throw new Error("Invalid URL encoding");
7
+ }
8
+ const hasDecoding = decoded !== pathname;
9
+ const decodedStillHasEncoding = /%[0-9a-fA-F]{2}/.test(decoded);
10
+ if (hasDecoding && decodedStillHasEncoding) {
11
+ throw new Error("Multi-level URL encoding is not allowed");
12
+ }
13
+ return decoded;
14
+ }
15
+ export {
16
+ validateAndDecodePathname
17
+ };
@@ -1,5 +1,6 @@
1
1
  import { hasFileExtension } from "@astrojs/internal-helpers/path";
2
2
  import { appendForwardSlash, removeTrailingForwardSlash } from "../core/path.js";
3
+ import { validateAndDecodePathname } from "../core/util/pathname.js";
3
4
  import { runWithErrorHandling } from "./controller.js";
4
5
  import { recordServerError } from "./error.js";
5
6
  import { handle500Response } from "./response.js";
@@ -18,9 +19,15 @@ async function handleRequest({
18
19
  if (config.trailingSlash === "never" && !incomingRequest.url) {
19
20
  pathname = "";
20
21
  } else {
21
- pathname = decodeURI(url.pathname);
22
+ try {
23
+ pathname = validateAndDecodePathname(url.pathname);
24
+ } catch {
25
+ incomingResponse.writeHead(404, { "Content-Type": "text/plain" });
26
+ incomingResponse.end("Not Found");
27
+ return;
28
+ }
22
29
  }
23
- url.pathname = removeTrailingForwardSlash(config.base) + decodeURI(url.pathname);
30
+ url.pathname = removeTrailingForwardSlash(config.base) + pathname;
24
31
  if (config.trailingSlash === "never") {
25
32
  url.pathname = removeTrailingForwardSlash(url.pathname);
26
33
  } else if (config.trailingSlash === "always" && !hasFileExtension(url.pathname)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "5.16.2",
3
+ "version": "5.16.3",
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",
@@ -155,8 +155,8 @@
155
155
  "zod-to-json-schema": "^3.25.0",
156
156
  "zod-to-ts": "^1.2.0",
157
157
  "@astrojs/markdown-remark": "6.3.9",
158
- "@astrojs/internal-helpers": "0.7.5",
159
- "@astrojs/telemetry": "3.3.0"
158
+ "@astrojs/telemetry": "3.3.0",
159
+ "@astrojs/internal-helpers": "0.7.5"
160
160
  },
161
161
  "optionalDependencies": {
162
162
  "sharp": "^0.34.0"
@@ -195,8 +195,8 @@
195
195
  "undici": "^6.22.0",
196
196
  "unified": "^11.0.5",
197
197
  "vitest": "^3.2.4",
198
- "@astrojs/check": "0.9.6",
199
- "astro-scripts": "0.0.14"
198
+ "astro-scripts": "0.0.14",
199
+ "@astrojs/check": "0.9.6"
200
200
  },
201
201
  "engines": {
202
202
  "node": "18.20.8 || ^20.3.0 || >=22.0.0",