Skip to content

Commit 8d60032

Browse files
committed
fix: keep Vela workspace scope fail-closed
1 parent 44e95d2 commit 8d60032

2 files changed

Lines changed: 20 additions & 7 deletions

File tree

apps/daemon/src/routes/vela.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,10 +265,14 @@ function proxyAmrApiRequest(req: Request, res: Response): void {
265265
res.status(400).json({ error: 'invalid_workspace_id' });
266266
return;
267267
}
268+
const requestConnectionTokens = connectionHeaderTokens(req.headers.connection);
269+
if (workspaceId !== undefined && requestConnectionTokens.has('x-vela-workspace-id')) {
270+
res.status(400).json({ error: 'invalid_workspace_id' });
271+
return;
272+
}
268273
const body = velaProxyRequestBody(req);
269274
const streamBody = shouldStreamVelaProxyRequest(req, body);
270275
const headers: Record<string, string | string[]> = {};
271-
const requestConnectionTokens = connectionHeaderTokens(req.headers.connection);
272276
for (const [key, value] of Object.entries(req.headers)) {
273277
const lower = key.toLowerCase();
274278
if (lower === 'host' || isProxyHopByHopHeader(lower, requestConnectionTokens)) {

apps/daemon/tests/integrations/vela.routes.test.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2064,17 +2064,15 @@ describe('ALL /api/integrations/vela/api-proxy/*', () => {
20642064
return upstream;
20652065
}) as typeof https.request);
20662066
const daemonUrl = new URL(baseUrl);
2067-
const rawGet = (pathName: string, workspaceHeaders?: string[]) =>
2067+
const rawGet = (pathName: string, headers?: http.OutgoingHttpHeaders) =>
20682068
new Promise<{ status: number; body: unknown }>((resolve, reject) => {
20692069
const request = http.request(
20702070
{
20712071
hostname: daemonUrl.hostname,
20722072
port: daemonUrl.port,
20732073
method: 'GET',
20742074
path: pathName,
2075-
headers: workspaceHeaders === undefined
2076-
? undefined
2077-
: { 'x-vela-workspace-id': workspaceHeaders },
2075+
headers,
20782076
},
20792077
(response) => {
20802078
const chunks: Buffer[] = [];
@@ -2099,16 +2097,27 @@ describe('ALL /api/integrations/vela/api-proxy/*', () => {
20992097
);
21002098
const invalid = await rawGet(
21012099
'/api/integrations/vela/api-proxy/api/v1/wallet/balance',
2102-
['workspace/escape'],
2100+
{ 'x-vela-workspace-id': ['workspace/escape'] },
21032101
);
21042102
const duplicate = await rawGet(
21052103
'/api/integrations/vela/api-proxy/api/v1/wallet/balance',
2106-
['workspace-a', 'workspace-b'],
2104+
{ 'x-vela-workspace-id': ['workspace-a', 'workspace-b'] },
2105+
);
2106+
const connectionNominated = await rawGet(
2107+
'/api/integrations/vela/api-proxy/api/v1/wallet/balance',
2108+
{
2109+
connection: 'x-vela-workspace-id',
2110+
'x-vela-workspace-id': 'workspace-team',
2111+
},
21072112
);
21082113

21092114
expect(escaped).toEqual({ status: 404, body: { error: 'unknown_amr_api_proxy_path' } });
21102115
expect(invalid).toEqual({ status: 400, body: { error: 'invalid_workspace_id' } });
21112116
expect(duplicate).toEqual({ status: 400, body: { error: 'invalid_workspace_id' } });
2117+
expect(connectionNominated).toEqual({
2118+
status: 400,
2119+
body: { error: 'invalid_workspace_id' },
2120+
});
21122121
expect(upstreamRequestCount).toBe(0);
21132122
} finally {
21142123
requestSpy.mockRestore();

0 commit comments

Comments
 (0)