Skip to content

Commit ec8663b

Browse files
antonisclaude
andcommitted
fix(playground): Only auto-open sentry.io URLs, log others to console
For non-sentry.io hosts, the URL is printed to the Metro console instead of being opened automatically, so users can decide whether to trust it. This prevents the middleware from being used to open arbitrary URLs while still supporting all *.sentry.io subdomains. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f13370c commit ec8663b

File tree

2 files changed

+31
-4
lines changed

2 files changed

+31
-4
lines changed

packages/core/src/js/metro/openUrlMiddleware.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ export async function openURLMiddleware(req: IncomingMessage, res: ServerRespons
5454
return;
5555
}
5656

57+
if (!isTrustedSentryHost(url)) {
58+
// oxlint-disable-next-line no-console
59+
console.log(`${S} Untrusted host, not opening automatically. Open manually if you trust this URL: ${url}`);
60+
res.writeHead(200);
61+
res.end();
62+
return;
63+
}
64+
5765
if (!open) {
5866
// oxlint-disable-next-line no-console
5967
console.log(`${S} Could not open URL automatically. Open manually: ${url}`);
@@ -77,3 +85,12 @@ export async function openURLMiddleware(req: IncomingMessage, res: ServerRespons
7785
res.writeHead(200);
7886
res.end();
7987
}
88+
89+
function isTrustedSentryHost(url: string): boolean {
90+
try {
91+
const { hostname } = new URL(url);
92+
return hostname === 'sentry.io' || hostname.endsWith('.sentry.io');
93+
} catch (e) {
94+
return false;
95+
}
96+
}

packages/core/test/tools/openUrlMiddleware.test.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ describe('openURLMiddleware', () => {
7777
expect(res.end).toHaveBeenCalledWith(expect.stringContaining('Invalid URL scheme'));
7878
});
7979

80-
it('should open https URLs', async () => {
80+
it('should open sentry.io URLs', async () => {
8181
const req = createRequest('POST', JSON.stringify({ url: 'https://sentry.io/issues/' }));
8282
const res = createResponse();
8383

@@ -87,13 +87,23 @@ describe('openURLMiddleware', () => {
8787
expect(res.writeHead).toHaveBeenCalledWith(200);
8888
});
8989

90-
it('should open http URLs', async () => {
91-
const req = createRequest('POST', JSON.stringify({ url: 'http://localhost:3000' }));
90+
it('should open subdomain.sentry.io URLs', async () => {
91+
const req = createRequest('POST', JSON.stringify({ url: 'https://my-org.sentry.io/issues/?project=123' }));
9292
const res = createResponse();
9393

9494
await openURLMiddleware(req, res);
9595

96-
expect(mockOpen).toHaveBeenCalledWith('http://localhost:3000');
96+
expect(mockOpen).toHaveBeenCalledWith('https://my-org.sentry.io/issues/?project=123');
97+
expect(res.writeHead).toHaveBeenCalledWith(200);
98+
});
99+
100+
it('should not auto-open non-sentry.io URLs and log to console instead', async () => {
101+
const req = createRequest('POST', JSON.stringify({ url: 'https://example.com/malicious' }));
102+
const res = createResponse();
103+
104+
await openURLMiddleware(req, res);
105+
106+
expect(mockOpen).not.toHaveBeenCalled();
97107
expect(res.writeHead).toHaveBeenCalledWith(200);
98108
});
99109

0 commit comments

Comments
 (0)