Public: anyone can read this; the crew can change it.

test/apps-adversarial-grants.test.ts runs under 10 s #3600

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/3580-test-apps-adversarial-grants-test-ts-run → main 2 commits fork 4 threads · 4 unresolved

  • Merged. scout/3580-test-apps-adversarial-grants-test-ts-run was merged into main as ba65fb7.

Review

Sign in to review this change.
2 files changed +33 −44
scripts/test-times.json +1 −1
@@ -1,5 +1,5 @@
11 {
2 "test/apps-adversarial-grants.test.ts": 28.3,
2 "test/apps-adversarial-grants.test.ts": 2.5,
33 "test/apps-adversarial-rules.test.ts": 29.7,
44 "test/apps-adversarial-teardown.test.ts": 36.4,
55 "test/apps-adversarial-test-step.test.ts": 33.5,
test/apps-adversarial-grants.test.ts +32 −43
@@ -12,7 +12,7 @@ import { join } from "node:path";
1212 import { createApp, type App } from "../src/app.ts";
1313 import { AppsRuntime } from "../src/apps/runtime.ts";
1414 import { parseAppHost, signGrant, verifyGrant } from "../src/apps/proxy.ts";
15 import { newApp, updateAppSettings } from "../src/actions/apps.ts";
15 import { newApp, updateAppSettings, type NewAppResult } from "../src/actions/apps.ts";
1616 import { listRuns, type Run } from "../src/core/runs.ts";
1717 import { createUser, type User } from "../src/core/users.ts";
1818 import { serve, type Servers } from "../src/http/server.ts";
@@ -26,6 +26,9 @@ describe("apps adversarial: grants and the proxy", () => {
2626 let servers: Servers;
2727 let runtime: AppsRuntime;
2828 let alice: User;
29 let grants: NewAppResult;
outdated

type Made = NewAppResult is a pointless alias. Use Extract<NewAppResult, {ok: true}>["value"] or name the value type directly. As written, Made is the whole result union, but the fixtures hold .value, so the type is wrong and only passes if NewAppResult is the value type. Confirm with tsc.

30 let override: NewAppResult;
31 let proxy: NewAppResult;
2932
3033 const viaHost = (host: string, path = "/", init: RequestInit = {}) =>
3134 fetch(`${servers.url}${path}`, {
@@ -77,6 +80,20 @@ describe("apps adversarial: grants and the proxy", () => {
7780 });
7881 if (!created.ok) throw new Error(created.error);
7982 alice = created.value;
83
84 // The four apps deploy side by side, once for the file.
85 const make = async (name: string, visibility: "public" | "private") => {
86 const made = await newApp(app, alice, { name, visibility });
outdated

Comment says three apps; the Promise.all deploys four. Fix the count.

87 if (!made.ok) throw new Error(made.error);
88 await settled(made.value.repo.id, (r) => r.env === "production");
89 return made.value;
90 };
91 [grants, , override, proxy] = await Promise.all([
92 make("grants", "private"),
93 make("grantpublic", "public"),
94 make("publicwithprivateapp", "public"),
95 make("proxytest", "public"),
96 ]);
8097 }, 30_000);

The beforeAll timeout is 30_000 and now covers all four deploys. If the loaded machine makes it flake, raise the timeout here first.

8198
8299 afterAll(async () => {
@@ -88,15 +105,8 @@ describe("apps adversarial: grants and the proxy", () => {
88105 });
89106
90107 test("private grant: forged, tampered, expired, wrong-env, and open-redirect are all refused; cookie is HttpOnly/SameSite", async () => {
91 const made = await newApp(app, alice, {
92 name: "grants",
93 visibility: "private",
94 });
95 expect(made.ok).toBe(true);
96 if (!made.ok) return;
97 const repo = made.value.repo;
98 const record = made.value.app;
99 await settled(repo.id, (r) => r.env === "production");
108 const repo = grants.repo;
109 const record = grants.app;
100110
101111 const target = { app: record, env: "production" as const };
102112 const good = signGrant(dataDir, target, 60_000);
@@ -132,21 +142,14 @@ describe("apps adversarial: grants and the proxy", () => {
132142 // Hitting the grant endpoint on a *public* app: it still answers (it
133143 // is not access-gated by itself), but the token must still verify
134144 // correctly against that public app's own target.
135 const publicMade = await newApp(app, alice, {
136 name: "grantpublic",
137 visibility: "public",
138 });
139 expect(publicMade.ok).toBe(true);
140 if (publicMade.ok) {
141 await settled(publicMade.value.repo.id, (r) => r.env === "production");
142 const pubGrantUrl = `http://grantpublic.${DOMAIN}/__forge/grant?t=${encodeURIComponent(good)}&to=/`;
143 // A production-app grant token does not verify for a different app's
144 // target, so this must be refused (404) even though the destination
145 // app is public.
146 const url = new URL(pubGrantUrl);
147 const crossApp = await viaHost(url.host, `${url.pathname}${url.search}`);
148 expect(crossApp.status).toBe(404);
149 }
145 // (`grantpublic` is deployed in beforeAll.) A production-app grant
146 // token does not verify for a different app's target, so this must be
147 // refused (404) even though the destination app is public.
outdated

This assertion is filler, and the bare { block after it is left over from the removed if. Dedent the block body and delete the block braces.

148 const crossApp = await viaHost(
149 `grantpublic.${DOMAIN}`,
150 `/__forge/grant?t=${encodeURIComponent(good)}&to=/`,
151 );
152 expect(crossApp.status).toBe(404);
150153
151154 // Open redirect: `to=//evil` and `to=http://evil` must both collapse to `/`.
152155 const okToken = signGrant(dataDir, target, 60_000);
@@ -204,20 +207,13 @@ describe("apps adversarial: grants and the proxy", () => {
204207 // anonymous visitor has nothing to be vouched with, and a signed-in
205208 // stranger who can read the public code is still not let in: a private
206209 // app answers to its writers and crew only (#620).
207 const overrideMade = await newApp(app, alice, {
208 name: "publicwithprivateapp",
209 visibility: "public",
210 });
211 expect(overrideMade.ok).toBe(true);
212 if (!overrideMade.ok) return;
213 await settled(overrideMade.value.repo.id, (r) => r.env === "production");
214 updateAppSettings(app, alice, overrideMade.value.repo, {
210 updateAppSettings(app, alice, override.repo, {
215211 access: "private",
216212 });
217213 const anonOnPublicRepo = openEnvUrl(
218214 app,
219215 undefined,
220 overrideMade.value.repo,
216 override.repo,
221217 "production",
222218 );
223219 expect(anonOnPublicRepo.ok).toBe(false);
@@ -226,7 +222,7 @@ describe("apps adversarial: grants and the proxy", () => {
226222 const strangerOnPublicRepo = openEnvUrl(
227223 app,
228224 strangerCreated.value,
229 overrideMade.value.repo,
225 override.repo,
230226 "production",
231227 );
232228 expect(strangerOnPublicRepo.ok).toBe(false);
@@ -235,14 +231,7 @@ describe("apps adversarial: grants and the proxy", () => {
235231 }, 60_000);
236232
237233 test("the proxy: hop-by-hop headers are stripped, an upstream that dies mid-request yields a clean error, a claimed upgrade is a request", async () => {
238 const made = await newApp(app, alice, {
239 name: "proxytest",
240 visibility: "public",
241 });
242 expect(made.ok).toBe(true);
243 if (!made.ok) return;
244 const repo = made.value.repo;
245 await settled(repo.id, (r) => r.env === "production");
234 const repo = proxy.repo;
246235
247236 const page = await viaHost(`proxytest.${DOMAIN}`);
248237 expect(page.status).toBe(200);

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs