Skip to content

Commit a3951d7

Browse files
authored
fix(astro): harden astro-island export resolution (#16422)
1 parent 0c0ae11 commit a3951d7

2 files changed

Lines changed: 21 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Hardens `astro-island` export resolution and hydration error handling for malformed component metadata

packages/astro/src/runtime/server/astro-island.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
type directiveAstroKeys = 'load' | 'idle' | 'visible' | 'media' | 'only';
66

7+
const FORBIDDEN_COMPONENT_EXPORT_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
8+
79
declare const Astro: {
810
[k in directiveAstroKeys]?: (
911
fn: () => Promise<() => void>,
@@ -20,6 +22,8 @@ declare const Astro: {
2022
const propTypes: PropTypeSelector = {
2123
0: (value) => reviveObject(value),
2224
1: (value) => reviveArray(value),
25+
// nosemgrep: javascript.lang.security.audit.detect-non-literal-regexp.detect-non-literal-regexp
26+
// Regex props are serialized by Astro and revived here on the client.
2327
2: (value) => new RegExp(value),
2428
3: (value) => new Date(value),
2529
4: (value) => new Map(reviveArray(value)),
@@ -113,10 +117,21 @@ declare const Astro: {
113117
]);
114118
const componentExport = this.getAttribute('component-export') || 'default';
115119
if (!componentExport.includes('.')) {
120+
if (FORBIDDEN_COMPONENT_EXPORT_KEYS.has(componentExport)) {
121+
throw new Error(`Invalid component export path: ${componentExport}`);
122+
}
116123
this.Component = componentModule[componentExport];
117124
} else {
118125
this.Component = componentModule;
119126
for (const part of componentExport.split('.')) {
127+
if (
128+
FORBIDDEN_COMPONENT_EXPORT_KEYS.has(part) ||
129+
!this.Component ||
130+
(typeof this.Component !== 'object' && typeof this.Component !== 'function') ||
131+
!Object.hasOwn(this.Component, part)
132+
) {
133+
throw new Error(`Invalid component export path: ${componentExport}`);
134+
}
120135
this.Component = this.Component[part];
121136
}
122137
}
@@ -127,7 +142,7 @@ declare const Astro: {
127142
this,
128143
);
129144
} catch (e) {
130-
console.error(`[astro-island] Error hydrating ${this.getAttribute('component-url')}`, e);
145+
console.error('[astro-island] Error hydrating %s', this.getAttribute('component-url'), e);
131146
}
132147
}
133148

0 commit comments

Comments
 (0)