astro 6.0.0-beta.10 → 6.0.0-beta.11
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.
- package/dist/assets/services/service.js +6 -5
- package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
- package/dist/content/content-layer.js +3 -3
- package/dist/content/mutable-data-store.js +14 -1
- package/dist/core/app/node.js +9 -7
- package/dist/core/app/{validate-forwarded-headers.d.ts → validate-headers.d.ts} +4 -3
- package/dist/core/app/{validate-forwarded-headers.js → validate-headers.js} +33 -13
- package/dist/core/build/static-build.js +14 -4
- package/dist/core/constants.js +1 -1
- package/dist/core/dev/dev.js +1 -1
- package/dist/core/messages.js +2 -2
- package/dist/types/public/integrations.d.ts +1 -0
- package/dist/vite-plugin-adapter-config/index.js +15 -0
- package/package.json +3 -3
|
@@ -64,7 +64,7 @@ function verifyOptions(options) {
|
|
|
64
64
|
if (options.widths && options.densities) {
|
|
65
65
|
throw new AstroError(AstroErrorData.IncompatibleDescriptorOptions);
|
|
66
66
|
}
|
|
67
|
-
if (options.src.format
|
|
67
|
+
if (options.src.format !== "svg" && options.format === "svg") {
|
|
68
68
|
throw new AstroError(AstroErrorData.UnsupportedImageConversion);
|
|
69
69
|
}
|
|
70
70
|
}
|
|
@@ -72,12 +72,13 @@ function verifyOptions(options) {
|
|
|
72
72
|
const baseService = {
|
|
73
73
|
propertiesToHash: DEFAULT_HASH_PROPS,
|
|
74
74
|
validateOptions(options) {
|
|
75
|
-
if (isESMImportedImage(options.src) && options.src.format === "svg") {
|
|
76
|
-
options.format = "svg";
|
|
77
|
-
}
|
|
78
75
|
verifyOptions(options);
|
|
79
76
|
if (!options.format) {
|
|
80
|
-
options.format
|
|
77
|
+
if (isESMImportedImage(options.src) && options.src.format === "svg") {
|
|
78
|
+
options.format = "svg";
|
|
79
|
+
} else {
|
|
80
|
+
options.format = DEFAULT_OUTPUT_FORMAT;
|
|
81
|
+
}
|
|
81
82
|
}
|
|
82
83
|
if (options.width) options.width = Math.round(options.width);
|
|
83
84
|
if (options.height) options.height = Math.round(options.height);
|
|
@@ -181,7 +181,7 @@ ${contentConfig.error.message}`
|
|
|
181
181
|
logger.info("Content config changed");
|
|
182
182
|
shouldClear = true;
|
|
183
183
|
}
|
|
184
|
-
if (previousAstroVersion && previousAstroVersion !== "6.0.0-beta.
|
|
184
|
+
if (previousAstroVersion && previousAstroVersion !== "6.0.0-beta.11") {
|
|
185
185
|
logger.info("Astro version changed");
|
|
186
186
|
shouldClear = true;
|
|
187
187
|
}
|
|
@@ -189,8 +189,8 @@ ${contentConfig.error.message}`
|
|
|
189
189
|
logger.info("Clearing content store");
|
|
190
190
|
this.#store.clearAll();
|
|
191
191
|
}
|
|
192
|
-
if ("6.0.0-beta.
|
|
193
|
-
await this.#store.metaStore().set("astro-version", "6.0.0-beta.
|
|
192
|
+
if ("6.0.0-beta.11") {
|
|
193
|
+
await this.#store.metaStore().set("astro-version", "6.0.0-beta.11");
|
|
194
194
|
}
|
|
195
195
|
if (currentConfigDigest) {
|
|
196
196
|
await this.#store.metaStore().set("content-config-digest", currentConfigDigest);
|
|
@@ -22,6 +22,8 @@ class MutableDataStore extends ImmutableDataStore {
|
|
|
22
22
|
#modulesDirty = false;
|
|
23
23
|
#assetImports = /* @__PURE__ */ new Set();
|
|
24
24
|
#moduleImports = /* @__PURE__ */ new Map();
|
|
25
|
+
#writeInProgress = false;
|
|
26
|
+
#writeQueued = false;
|
|
25
27
|
set(collectionName, key, value) {
|
|
26
28
|
const collection = this._collections.get(collectionName) ?? /* @__PURE__ */ new Map();
|
|
27
29
|
collection.set(String(key), value);
|
|
@@ -121,7 +123,7 @@ ${lines.join(",\n")}]);
|
|
|
121
123
|
this.#modulesDirty = false;
|
|
122
124
|
}
|
|
123
125
|
#maybeResolveSavePromise() {
|
|
124
|
-
if (!this.#saveTimeout && !this.#assetsSaveTimeout && !this.#modulesSaveTimeout && this.#savePromiseResolve) {
|
|
126
|
+
if (!this.#saveTimeout && !this.#assetsSaveTimeout && !this.#modulesSaveTimeout && !this.#writeQueued && !this.#writeInProgress && this.#savePromiseResolve) {
|
|
125
127
|
this.#savePromiseResolve();
|
|
126
128
|
this.#savePromiseResolve = void 0;
|
|
127
129
|
this.#savePromise = void 0;
|
|
@@ -317,11 +319,22 @@ ${lines.join(",\n")}]);
|
|
|
317
319
|
if (!this.#file) {
|
|
318
320
|
throw new AstroError(AstroErrorData.UnknownFilesystemError);
|
|
319
321
|
}
|
|
322
|
+
if (this.#writeInProgress) {
|
|
323
|
+
this.#writeQueued = true;
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
320
326
|
try {
|
|
321
327
|
this.#dirty = false;
|
|
328
|
+
this.#writeInProgress = true;
|
|
322
329
|
await this.#writeFileAtomic(this.#file, this.toString());
|
|
323
330
|
} catch (err) {
|
|
324
331
|
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
|
|
332
|
+
} finally {
|
|
333
|
+
this.#writeInProgress = false;
|
|
334
|
+
if (this.#writeQueued) {
|
|
335
|
+
this.#writeQueued = false;
|
|
336
|
+
await this.writeToDisk();
|
|
337
|
+
}
|
|
325
338
|
}
|
|
326
339
|
}
|
|
327
340
|
/**
|
package/dist/core/app/node.js
CHANGED
|
@@ -4,7 +4,7 @@ import { clientAddressSymbol, nodeRequestAbortControllerCleanupSymbol } from "..
|
|
|
4
4
|
import { deserializeManifest } from "./manifest.js";
|
|
5
5
|
import { createOutgoingHttpHeaders } from "./createOutgoingHttpHeaders.js";
|
|
6
6
|
import { App } from "./index.js";
|
|
7
|
-
import {
|
|
7
|
+
import { validateForwardedHeaders, validateHost } from "./validate-headers.js";
|
|
8
8
|
class NodeApp extends App {
|
|
9
9
|
headersMap = void 0;
|
|
10
10
|
setHeadersMap(headers) {
|
|
@@ -50,7 +50,7 @@ class NodeApp extends App {
|
|
|
50
50
|
return multiValueHeader?.toString()?.split(",").map((e) => e.trim())?.[0];
|
|
51
51
|
};
|
|
52
52
|
const providedProtocol = isEncrypted ? "https" : "http";
|
|
53
|
-
const
|
|
53
|
+
const untrustedHostname = req.headers.host ?? req.headers[":authority"];
|
|
54
54
|
const validated = validateForwardedHeaders(
|
|
55
55
|
getFirstForwardedValue(req.headers["x-forwarded-proto"]),
|
|
56
56
|
getFirstForwardedValue(req.headers["x-forwarded-host"]),
|
|
@@ -58,18 +58,20 @@ class NodeApp extends App {
|
|
|
58
58
|
allowedDomains
|
|
59
59
|
);
|
|
60
60
|
const protocol = validated.protocol ?? providedProtocol;
|
|
61
|
-
const
|
|
62
|
-
typeof
|
|
61
|
+
const validatedHostname = validateHost(
|
|
62
|
+
typeof untrustedHostname === "string" ? untrustedHostname : void 0,
|
|
63
|
+
protocol,
|
|
64
|
+
allowedDomains
|
|
63
65
|
);
|
|
64
|
-
const hostname = validated.host ??
|
|
66
|
+
const hostname = validated.host ?? validatedHostname ?? "localhost";
|
|
65
67
|
const port = validated.port;
|
|
66
68
|
let url;
|
|
67
69
|
try {
|
|
68
70
|
const hostnamePort = getHostnamePort(hostname, port);
|
|
69
71
|
url = new URL(`${protocol}://${hostnamePort}${req.url}`);
|
|
70
72
|
} catch {
|
|
71
|
-
const hostnamePort = getHostnamePort(
|
|
72
|
-
url = new URL(`${
|
|
73
|
+
const hostnamePort = getHostnamePort(hostname, port);
|
|
74
|
+
url = new URL(`${protocol}://${hostnamePort}`);
|
|
73
75
|
}
|
|
74
76
|
const options = {
|
|
75
77
|
method: req.method || "GET",
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type RemotePattern } from '@astrojs/internal-helpers/remote';
|
|
2
2
|
/**
|
|
3
|
-
* Validate a
|
|
4
|
-
*
|
|
3
|
+
* Validate a host against allowedDomains.
|
|
4
|
+
* Returns the host only if it matches an allowed pattern, otherwise undefined.
|
|
5
|
+
* This prevents SSRF attacks by ensuring the Host header is trusted.
|
|
5
6
|
*/
|
|
6
|
-
export declare function
|
|
7
|
+
export declare function validateHost(host: string | undefined, protocol: string, allowedDomains?: Partial<RemotePattern>[]): string | undefined;
|
|
7
8
|
/**
|
|
8
9
|
* Validate forwarded headers (proto, host, port) against allowedDomains.
|
|
9
10
|
* Returns validated values or undefined for rejected headers.
|
|
@@ -4,6 +4,33 @@ function sanitizeHost(hostname) {
|
|
|
4
4
|
if (/[/\\]/.test(hostname)) return void 0;
|
|
5
5
|
return hostname;
|
|
6
6
|
}
|
|
7
|
+
function parseHost(host) {
|
|
8
|
+
const parts = host.split(":");
|
|
9
|
+
return {
|
|
10
|
+
hostname: parts[0],
|
|
11
|
+
port: parts[1]
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function matchesAllowedDomains(hostname, protocol, port, allowedDomains) {
|
|
15
|
+
const hostWithPort = port ? `${hostname}:${port}` : hostname;
|
|
16
|
+
const urlString = `${protocol}://${hostWithPort}`;
|
|
17
|
+
if (!URL.canParse(urlString)) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const testUrl = new URL(urlString);
|
|
21
|
+
return allowedDomains.some((pattern) => matchPattern(testUrl, pattern));
|
|
22
|
+
}
|
|
23
|
+
function validateHost(host, protocol, allowedDomains) {
|
|
24
|
+
if (!host || host.length === 0) return void 0;
|
|
25
|
+
if (!allowedDomains || allowedDomains.length === 0) return void 0;
|
|
26
|
+
const sanitized = sanitizeHost(host);
|
|
27
|
+
if (!sanitized) return void 0;
|
|
28
|
+
const { hostname, port } = parseHost(sanitized);
|
|
29
|
+
if (matchesAllowedDomains(hostname, protocol, port, allowedDomains)) {
|
|
30
|
+
return sanitized;
|
|
31
|
+
}
|
|
32
|
+
return void 0;
|
|
33
|
+
}
|
|
7
34
|
function validateForwardedHeaders(forwardedProtocol, forwardedHost, forwardedPort, allowedDomains) {
|
|
8
35
|
const result = {};
|
|
9
36
|
if (forwardedProtocol) {
|
|
@@ -38,23 +65,16 @@ function validateForwardedHeaders(forwardedProtocol, forwardedHost, forwardedPor
|
|
|
38
65
|
const protoForValidation = result.protocol || "https";
|
|
39
66
|
const sanitized = sanitizeHost(forwardedHost);
|
|
40
67
|
if (sanitized) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const hostWithPort = portForValidation ? `${hostnameOnly}:${portForValidation}` : hostnameOnly;
|
|
46
|
-
const testUrl = new URL(`${protoForValidation}://${hostWithPort}`);
|
|
47
|
-
const isAllowed = allowedDomains.some((pattern) => matchPattern(testUrl, pattern));
|
|
48
|
-
if (isAllowed) {
|
|
49
|
-
result.host = sanitized;
|
|
50
|
-
}
|
|
51
|
-
} catch {
|
|
68
|
+
const { hostname, port: portFromHost } = parseHost(sanitized);
|
|
69
|
+
const portForValidation = result.port || portFromHost;
|
|
70
|
+
if (matchesAllowedDomains(hostname, protoForValidation, portForValidation, allowedDomains)) {
|
|
71
|
+
result.host = sanitized;
|
|
52
72
|
}
|
|
53
73
|
}
|
|
54
74
|
}
|
|
55
75
|
return result;
|
|
56
76
|
}
|
|
57
77
|
export {
|
|
58
|
-
|
|
59
|
-
|
|
78
|
+
validateForwardedHeaders,
|
|
79
|
+
validateHost
|
|
60
80
|
};
|
|
@@ -84,6 +84,19 @@ async function buildEnvironments(opts, internals) {
|
|
|
84
84
|
let currentRollupInput = void 0;
|
|
85
85
|
plugins.push({
|
|
86
86
|
name: "astro:resolve-input",
|
|
87
|
+
// When the rollup input is safe to update, we normalize it to always be an object
|
|
88
|
+
// so we can reliably identify which entrypoint corresponds to the adapter
|
|
89
|
+
enforce: "post",
|
|
90
|
+
config(config) {
|
|
91
|
+
if (typeof config.build?.rollupOptions?.input === "string") {
|
|
92
|
+
config.build.rollupOptions.input = { index: config.build.rollupOptions.input };
|
|
93
|
+
} else if (Array.isArray(config.build?.rollupOptions?.input)) {
|
|
94
|
+
config.build.rollupOptions.input = Object.fromEntries(
|
|
95
|
+
config.build.rollupOptions.input.map((v, i) => [`index_${i}`, v])
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
// We save the rollup input to be able to check later on
|
|
87
100
|
configResolved(config) {
|
|
88
101
|
currentRollupInput = config.build.rollupOptions.input;
|
|
89
102
|
}
|
|
@@ -113,10 +126,7 @@ async function buildEnvironments(opts, internals) {
|
|
|
113
126
|
}
|
|
114
127
|
});
|
|
115
128
|
function isRollupInput(moduleName) {
|
|
116
|
-
if (!currentRollupInput) {
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
if (!moduleName) {
|
|
129
|
+
if (!currentRollupInput || !moduleName) {
|
|
120
130
|
return false;
|
|
121
131
|
}
|
|
122
132
|
if (typeof currentRollupInput === "string") {
|
package/dist/core/constants.js
CHANGED
package/dist/core/dev/dev.js
CHANGED
|
@@ -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 = "6.0.0-beta.
|
|
25
|
+
const currentVersion = "6.0.0-beta.11";
|
|
26
26
|
const isPrerelease = currentVersion.includes("-");
|
|
27
27
|
if (!isPrerelease) {
|
|
28
28
|
try {
|
package/dist/core/messages.js
CHANGED
|
@@ -28,7 +28,7 @@ function serverStart({
|
|
|
28
28
|
host,
|
|
29
29
|
base
|
|
30
30
|
}) {
|
|
31
|
-
const version = "6.0.0-beta.
|
|
31
|
+
const version = "6.0.0-beta.11";
|
|
32
32
|
const localPrefix = `${dim("\u2503")} Local `;
|
|
33
33
|
const networkPrefix = `${dim("\u2503")} Network `;
|
|
34
34
|
const emptyPrefix = " ".repeat(11);
|
|
@@ -265,7 +265,7 @@ function printHelp({
|
|
|
265
265
|
message.push(
|
|
266
266
|
linebreak(),
|
|
267
267
|
` ${bgGreen(black(` ${commandName} `))} ${green(
|
|
268
|
-
`v${"6.0.0-beta.
|
|
268
|
+
`v${"6.0.0-beta.11"}`
|
|
269
269
|
)} ${headline}`
|
|
270
270
|
);
|
|
271
271
|
}
|
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import { isAstroServerEnvironment } from "../environments.js";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
2
3
|
const VIRTUAL_CLIENT_ID = "virtual:astro:adapter-config/client";
|
|
3
4
|
const RESOLVED_VIRTUAL_CLIENT_ID = "\0" + VIRTUAL_CLIENT_ID;
|
|
4
5
|
function vitePluginAdapterConfig(settings) {
|
|
5
6
|
return {
|
|
6
7
|
name: "astro:adapter-config",
|
|
8
|
+
config() {
|
|
9
|
+
const { adapter } = settings;
|
|
10
|
+
if (adapter && adapter.entryType === "self" && adapter.serverEntrypoint) {
|
|
11
|
+
return {
|
|
12
|
+
build: {
|
|
13
|
+
rollupOptions: {
|
|
14
|
+
input: {
|
|
15
|
+
index: typeof adapter.serverEntrypoint === "string" ? adapter.serverEntrypoint : fileURLToPath(adapter.serverEntrypoint)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
},
|
|
7
22
|
resolveId: {
|
|
8
23
|
filter: {
|
|
9
24
|
id: new RegExp(`^${VIRTUAL_CLIENT_ID}$`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro",
|
|
3
|
-
"version": "6.0.0-beta.
|
|
3
|
+
"version": "6.0.0-beta.11",
|
|
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",
|
|
@@ -156,8 +156,8 @@
|
|
|
156
156
|
"yocto-spinner": "^1.0.0",
|
|
157
157
|
"zod": "^4.3.6",
|
|
158
158
|
"@astrojs/internal-helpers": "0.8.0-beta.1",
|
|
159
|
-
"@astrojs/
|
|
160
|
-
"@astrojs/
|
|
159
|
+
"@astrojs/telemetry": "3.3.0",
|
|
160
|
+
"@astrojs/markdown-remark": "7.0.0-beta.6"
|
|
161
161
|
},
|
|
162
162
|
"optionalDependencies": {
|
|
163
163
|
"sharp": "^0.34.0"
|