Skip to content

Commit 6d18cd0

Browse files
Elior Hamamyclaude
andcommitted
Forward only the known dev/prod X-Data-Env values
Defense-in-depth: gate the propagated header on the closed dev/prod set instead of any truthy string, matching the backend contract and avoiding relaying an arbitrary caller-supplied header value onward. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 125c36c commit 6d18cd0

2 files changed

Lines changed: 30 additions & 1 deletion

File tree

src/client.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,9 @@ export function createClientFromRequest(request: Request): Base44Client {
445445
// matters for the user-scoped client: unlike the service token, the user JWT
446446
// carries no data-env, so without forwarding this header the callbacks fall
447447
// back to production data even when the app runs in test-data mode.
448-
if (dataEnvHeader) {
448+
// Forward only the known closed set (matches the backend contract) rather
449+
// than relaying an arbitrary attacker-supplied header value onward.
450+
if (dataEnvHeader === "dev" || dataEnvHeader === "prod") {
449451
additionalHeaders["X-Data-Env"] = dataEnvHeader;
450452
}
451453

tests/unit/client.test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,33 @@ describe('Service Role Authorization Headers', () => {
569569
expect(scope.isDone()).toBe(true);
570570
});
571571

572+
test('should not forward an X-Data-Env value outside the dev/prod set', async () => {
573+
const mockRequest = {
574+
headers: {
575+
get: (name) => {
576+
const headers = {
577+
'Authorization': 'Bearer user-token-123',
578+
'Base44-App-Id': appId,
579+
'Base44-Api-Url': serverUrl,
580+
'X-Data-Env': 'evil'
581+
};
582+
return headers[name] || null;
583+
}
584+
}
585+
};
586+
587+
const client = createClientFromRequest(mockRequest);
588+
589+
scope.get(`/api/apps/${appId}/entities/Todo`)
590+
.matchHeader('X-Data-Env', (val) => !val) // arbitrary value must not be relayed
591+
.matchHeader('Authorization', 'Bearer user-token-123')
592+
.reply(200, { items: [], total: 0 });
593+
594+
await client.entities.Todo.list();
595+
596+
expect(scope.isDone()).toBe(true);
597+
});
598+
572599
test('should not include X-Data-Env header when not present in original request', async () => {
573600
const mockRequest = {
574601
headers: {

0 commit comments

Comments
 (0)