fix(leases): project status lease row to lease columns so joined connection PII never escapes (#12115)

Corrige vazamento de colunas de conexão (email/nome/etc.) através do cast em `getExclusiveConnectionLeaseStatus` — a projeção agora fica restrita às colunas de lease, evitando que um futuro consumidor sirva PII sem querer via o tipo `ExclusiveConnectionLease`. Documentado como Finding 8 do seu próprio bug-audit (#12113). Teste próprio (103 linhas). Validado no worktree combinado. Obrigado!
This commit is contained in:
SHANMUGAPRIYAN
2026-08-30 19:41:03 +05:30
committed by GitHub
parent 55f6b9808b
commit 3d15294967
2 changed files with 122 additions and 1 deletions

View File

@@ -59,7 +59,25 @@ type LeaseUpdateResult<T extends string> =
const database = () => getDbInstance();
const ACTIVE_SQL = "SELECT * FROM exclusive_connection_leases WHERE state = 'ACTIVE' AND ";
const lease = (row: LeaseRow) => rowToCamel(row) as ExclusiveConnectionLease;
// Projection, not a cast: joined SELECTs (the status query) carry connection_*
// identity columns (email/display name) that must never escape on the lease object.
const lease = (row: LeaseRow): ExclusiveConnectionLease => {
const camel = rowToCamel(row) as ExclusiveConnectionLease;
return {
id: camel.id,
leaseOwnerHash: camel.leaseOwnerHash,
apiKeyId: camel.apiKeyId,
provider: camel.provider,
connectionId: camel.connectionId,
generation: camel.generation,
state: camel.state,
acquiredAt: camel.acquiredAt,
renewedAt: camel.renewedAt,
expiresAt: camel.expiresAt,
endedAt: camel.endedAt,
endReason: camel.endReason,
};
};
function timestamp(value?: string): string {
const parsed = Date.parse(value ?? new Date().toISOString());
if (!Number.isFinite(parsed)) throw new Error("now must be a valid ISO timestamp");

View File

@@ -0,0 +1,103 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-lease-status-projection-"));
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.DISABLE_SQLITE_AUTO_BACKUP = "true";
const core = await import("../../src/lib/db/core.ts");
const providersDb = await import("../../src/lib/db/providers.ts");
const leases = await import("../../src/lib/db/exclusiveConnectionLeases.ts");
const OWNER = "vlo_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
const EMAIL = "lease-connection-owner@example.com";
const DISPLAY_NAME = "Lease Connection Owner";
test.after(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
});
test("status lease object never carries the joined connection identity columns", async () => {
const connection = (await providersDb.createProviderConnection({
provider: "glm",
authType: "access_token",
accessToken: "lease-status-projection-token",
name: "Team GLM",
email: EMAIL,
displayName: DISPLAY_NAME,
isActive: true,
testStatus: "active",
})) as { id: string };
const acquired = leases.acquireExclusiveConnectionLease({
leaseOwnerId: OWNER,
apiKeyId: "key-status-projection",
provider: "glm",
connectionId: connection.id,
now: "2026-08-30T12:00:00.000Z",
});
assert.equal(acquired.kind, "ACQUIRED");
if (acquired.kind !== "ACQUIRED") return;
const status = leases.getExclusiveConnectionLeaseStatus({
leaseOwnerId: OWNER,
generation: acquired.lease.generation,
apiKeyId: "key-status-projection",
now: "2026-08-30T12:00:01.000Z",
});
assert.notEqual(status, null);
if (!status) return;
// Status-level fenced fields stay exactly as before: the configured (safe)
// name plus the joined connection's provider.
assert.equal(status.provider, "glm");
assert.equal(status.connectionName, "Team GLM");
// The lease object must be a projection of lease columns only — the joined
// connection_* identity values (owner email / display name) must never ride
// along on the runtime object, even though the current route consumer
// whitelists what it serializes.
for (const forbidden of [
"connectionEmail",
"connectionDisplayName",
"connectionName",
"connectionAuthType",
"connectionProvider",
]) {
assert.equal(forbidden in status.lease, false, `lease must not carry ${forbidden}`);
}
const serialized = JSON.stringify(status.lease);
assert.equal(serialized.includes(EMAIL), false);
assert.equal(serialized.includes(DISPLAY_NAME), false);
// Legitimate lease lifecycle fields are intact.
assert.equal(status.lease.state, "ACTIVE");
assert.equal(status.lease.generation, acquired.lease.generation);
assert.equal(status.lease.connectionId, connection.id);
assert.equal(status.lease.apiKeyId, "key-status-projection");
assert.equal(status.lease.provider, "glm");
assert.equal(status.lease.acquiredAt, "2026-08-30T12:00:00.000Z");
assert.equal(status.lease.renewedAt, "2026-08-30T12:00:00.000Z");
assert.equal(status.lease.expiresAt, "2026-08-30T12:02:00.000Z");
assert.equal(status.lease.leaseOwnerHash, leases.hashLeaseOwnerId(OWNER));
assert.equal(status.lease.endedAt, null);
assert.equal(status.lease.endReason, null);
assert.deepEqual(Object.keys(status.lease).sort(), [
"acquiredAt",
"apiKeyId",
"connectionId",
"endReason",
"endedAt",
"expiresAt",
"generation",
"id",
"leaseOwnerHash",
"provider",
"renewedAt",
"state",
]);
});