Skip to content

Commit 3c06178

Browse files
committed
fix(webapp): cap a public API key's query scope like a public access token
pk_ is browser-shipped and environment-bound. Nothing routes it to the query API today, so the cap costs no caller anything and the helper stops promising the wrong thing.
1 parent 2fd7969 commit 3c06178

2 files changed

Lines changed: 18 additions & 7 deletions

File tree

apps/webapp/app/v3/queryScope.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,14 @@ export function resolveQueryScope(args: {
3737
};
3838
}
3939

40-
/** A public access token is environment-bound; every other bearer credential isn't. */
40+
/**
41+
* A public credential is environment-bound; a secret key isn't. `PUBLIC` is the deprecated
42+
* `pk_*` key — same threat model as a public access token, so it caps the same way. It cannot
43+
* reach the query API today (the bearer resolver 401s `pk_*`), which is why capping it costs
44+
* no caller anything and why the helper must not promise it is uncapped.
45+
*/
4146
export function queryScopeCeilingFor(
4247
authenticationType: ApiAuthenticationResultSuccess["type"]
4348
): QueryScopeCeiling {
44-
return authenticationType === "PUBLIC_JWT" ? "environment" : "unbounded";
49+
return authenticationType === "PRIVATE" ? "unbounded" : "environment";
4550
}

apps/webapp/test/queryScope.test.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,22 @@ describe("the query scope ceiling", () => {
99
expect(queryScopeCeilingFor("PUBLIC_JWT")).toBe("environment");
1010
});
1111

12-
it("leaves every other bearer credential uncapped", () => {
12+
// `PUBLIC` is the deprecated `pk_*` key: browser-shipped and environment-bound, the same
13+
// shape of credential a public access token is. Nothing routes it here today, so this is
14+
// the helper refusing to hand it the organization if anything ever does.
15+
it("caps a public API key at its own environment", () => {
16+
expect(queryScopeCeilingFor("PUBLIC")).toBe("environment");
17+
});
18+
19+
it("leaves the secret key uncapped", () => {
1320
expect(queryScopeCeilingFor("PRIVATE")).toBe("unbounded");
14-
expect(queryScopeCeilingFor("PUBLIC")).toBe("unbounded");
1521
});
1622

17-
// Compile-time: the fallback is "uncapped", so a misspelled credential kind must not
18-
// be able to reach it.
23+
// Compile-time: the fallback is the cap, but a misspelled credential kind must still not
24+
// be able to name itself into the uncapped branch.
1925
it("takes only the credential kinds that exist", () => {
2026
// @ts-expect-error not an authentication type
21-
expect(() => queryScopeCeilingFor("public_jwt")).not.toThrow();
27+
expect(() => queryScopeCeilingFor("private")).not.toThrow();
2228
});
2329
});
2430

0 commit comments

Comments
 (0)