mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-17 04:12:17 +03:00
fix(api): key-first sweep scope, audit at warn, mixed-scope guard for completed batches
Round-2 findings of the omni-code-review battery on the previous commit: - a presented API key always scopes the sweep to that key, even when the request also carries a dashboard session cookie (parity with GET /v1/batches; a leaked or over-shared key can never widen a destructive sweep); only a session without a key sweeps the whole instance - both sweep modes log at warn so the audit trail survives APP_LOG_LEVEL=warn; the failure log carries the error stack - a scope carrying both apiKeyId and allTenants is rejected instead of widening - changelog fragment links the PR
This commit is contained in:
@@ -139,6 +139,15 @@ describe("deleteCompletedBatches — ownership boundary (GHSA-wvxc-jp3v-5mg5)",
|
||||
}),
|
||||
/apiKeyId required unless allTenants/
|
||||
);
|
||||
assert.throws(
|
||||
// A mixed scope must be rejected, never silently widened to the instance.
|
||||
() =>
|
||||
(deleteCompletedBatches as unknown as (s: unknown) => unknown)({
|
||||
apiKeyId: "key_survivor_wvxc",
|
||||
allTenants: true,
|
||||
}),
|
||||
/mutually exclusive/
|
||||
);
|
||||
|
||||
assert.ok(getBatch(survivor.batch.id), "a rejected call must not delete anything");
|
||||
assert.strictEqual(
|
||||
|
||||
@@ -112,16 +112,12 @@ describe("DELETE /api/v1/batches/delete-completed — caller scope (GHSA-wvxc-jp
|
||||
assert.ok(getBatch(victim.batch.id), "key B's batch still survives");
|
||||
});
|
||||
|
||||
it("a dashboard session sweeps the whole instance — even when the request also carries an API key", async () => {
|
||||
const keyA = await createApiKey("wvxc-route-session-a", "machine-wvxc-sa", []);
|
||||
it("a dashboard session WITHOUT a key sweeps the whole instance", async () => {
|
||||
const keyB = await createApiKey("wvxc-route-session-b", "machine-wvxc-sb", []);
|
||||
const other = seedCompletedBatch(keyB.id, "wvxc-route-session-other");
|
||||
const unowned = seedCompletedBatch(null, "wvxc-route-session-unowned");
|
||||
|
||||
const { res, body } = await callDelete({
|
||||
Authorization: `Bearer ${keyA.key}`,
|
||||
cookie: await sessionCookie(),
|
||||
});
|
||||
const { res, body } = await callDelete({ cookie: await sessionCookie() });
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.ok(
|
||||
@@ -133,6 +129,33 @@ describe("DELETE /api/v1/batches/delete-completed — caller scope (GHSA-wvxc-jp
|
||||
assert.strictEqual(getFile(other.file.id), null, "session sweep soft-deletes the files too");
|
||||
});
|
||||
|
||||
it("a request carrying BOTH a session cookie and an API key is scoped to the key (the key wins, like GET /v1/batches)", async () => {
|
||||
const keyA = await createApiKey("wvxc-route-both-a", "machine-wvxc-ba", []);
|
||||
const keyB = await createApiKey("wvxc-route-both-b", "machine-wvxc-bb", []);
|
||||
const own = seedCompletedBatch(keyA.id, "wvxc-route-both-own");
|
||||
const other = seedCompletedBatch(keyB.id, "wvxc-route-both-other");
|
||||
const unowned = seedCompletedBatch(null, "wvxc-route-both-unowned");
|
||||
|
||||
const { res, body } = await callDelete({
|
||||
Authorization: `Bearer ${keyA.key}`,
|
||||
cookie: await sessionCookie(),
|
||||
});
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(body.deletedBatches, 1, "only key A's own completed batch is swept");
|
||||
assert.strictEqual(getBatch(own.batch.id), null, "key A's own batch is swept");
|
||||
assert.ok(
|
||||
getBatch(other.batch.id),
|
||||
"key B's batch survives — a presented key never widens the sweep"
|
||||
);
|
||||
assert.ok(getBatch(unowned.batch.id), "the unowned batch survives a key-scoped sweep");
|
||||
assert.strictEqual(
|
||||
getFileContent(other.file.id)?.toString(),
|
||||
"wvxc-route-both-other",
|
||||
"key B's file content is intact"
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects an unauthenticated request with 401 and deletes nothing", async () => {
|
||||
const keyB = await createApiKey("wvxc-route-401-b", "machine-wvxc-401", []);
|
||||
const seeded = seedCompletedBatch(keyB.id, "wvxc-route-401");
|
||||
|
||||
Reference in New Issue
Block a user