fix: harden repo verification scripts

This commit is contained in:
Saravana Achu Mac 2026-04-04 14:35:01 -07:00
parent 5cbe90e540
commit d01ed51bff
4 changed files with 56 additions and 69 deletions

View File

@ -6,28 +6,28 @@
"main": "index.js",
"scripts": {
"test": "npm run check:websocket-contract && npm run check:session-rule-normalization",
"dev": "tsx src/index.ts",
"dev": "node --import tsx src/index.ts",
"build": "tsc",
"typecheck": "tsc --noEmit",
"start": "node dist/index.js",
"check:schema-contract": "tsx verifySchemaContract.ts",
"check:rls-policies": "tsx verifyRlsPolicies.ts",
"check:secret-hygiene": "tsx verifySecretHygiene.ts",
"check:security-guards": "tsx verifySecurityGuards.ts",
"check:tenant-isolation": "tsx verifyTenantIsolation.ts",
"check:trade-executor-lifecycle": "tsx testTradeExecutorLifecycle.ts",
"check:lifecycle-regressions": "tsx testLifecycleRegressions.ts",
"check:order-sync-regressions": "tsx testOrderStatusSyncRegressions.ts",
"check:supabase-order-persistence-regressions": "tsx testSupabaseOrderPersistenceRegressions.ts",
"check:failure-injection": "tsx testFailureInjection.ts",
"check:alpaca-subtag": "tsx testAlpacaSubTag.ts",
"check:strict-capital-guard": "tsx testStrictCapitalGuard.ts",
"check:reconciliation-parity-heartbeat": "tsx testReconciliationParityHeartbeat.ts",
"check:reconciliation-watchdog-auto-resume": "tsx testReconciliationWatchdogAutoResume.ts",
"check:reconciliation-exit-backfill-evidence-guard": "tsx testReconciliationExitBackfillEvidenceGuard.ts",
"check:backtest-isolation": "tsx testBacktestIsolation.ts",
"check:session-rule-normalization": "tsx testSessionRuleNormalization.ts",
"check:websocket-contract": "tsx src/scripts/verifyWebsocketContract.ts",
"check:schema-contract": "node --import tsx verifySchemaContract.ts",
"check:rls-policies": "node --import tsx verifyRlsPolicies.ts",
"check:secret-hygiene": "node --import tsx verifySecretHygiene.ts",
"check:security-guards": "node --import tsx verifySecurityGuards.ts",
"check:tenant-isolation": "node --import tsx verifyTenantIsolation.ts",
"check:trade-executor-lifecycle": "node --import tsx testTradeExecutorLifecycle.ts",
"check:lifecycle-regressions": "node --import tsx testLifecycleRegressions.ts",
"check:order-sync-regressions": "node --import tsx testOrderStatusSyncRegressions.ts",
"check:supabase-order-persistence-regressions": "node --import tsx testSupabaseOrderPersistenceRegressions.ts",
"check:failure-injection": "node --import tsx testFailureInjection.ts",
"check:alpaca-subtag": "node --import tsx testAlpacaSubTag.ts",
"check:strict-capital-guard": "node --import tsx testStrictCapitalGuard.ts",
"check:reconciliation-parity-heartbeat": "node --import tsx testReconciliationParityHeartbeat.ts",
"check:reconciliation-watchdog-auto-resume": "node --import tsx testReconciliationWatchdogAutoResume.ts",
"check:reconciliation-exit-backfill-evidence-guard": "node --import tsx testReconciliationExitBackfillEvidenceGuard.ts",
"check:backtest-isolation": "node --import tsx testBacktestIsolation.ts",
"check:session-rule-normalization": "node --import tsx testSessionRuleNormalization.ts",
"check:websocket-contract": "node --import tsx src/scripts/verifyWebsocketContract.ts",
"coverage:run": "node --loader ts-node/esm runCoverageSuite.ts",
"coverage:full": "npm run coverage:integration",
"coverage:integration": "c8 --all --include=src/**/*.ts --exclude=src/index.ts --exclude=src/scripts/** --reporter=text-summary --reporter=json-summary --reporter=lcov node --loader ts-node/esm runCoverageSuite.ts",
@ -43,8 +43,8 @@
"format": "npm run check:trade-executor-lifecycle && npm run check:lifecycle-regressions && npm run check:order-sync-regressions && npm run check:supabase-order-persistence-regressions && npm run check:failure-injection && npm run check:alpaca-subtag && npm run check:strict-capital-guard && npm run check:reconciliation-parity-heartbeat && npm run check:reconciliation-watchdog-auto-resume && npm run check:reconciliation-exit-backfill-evidence-guard && npm run check:backtest-isolation && npm run check:session-rule-normalization && npm run check:websocket-contract",
"check": "npm run build && npm run lint && npm run format",
"pre-deploy": "npm run check",
"cleanup-stale-orders": "tsx src/scripts/cleanupStaleOrders.ts",
"revert-expired-orders": "tsx src/scripts/revertExpiredOrders.ts"
"cleanup-stale-orders": "node --import tsx src/scripts/cleanupStaleOrders.ts",
"revert-expired-orders": "node --import tsx src/scripts/revertExpiredOrders.ts"
},
"keywords": [],
"author": "",

View File

@ -24,28 +24,40 @@ async function verifyStaticGuards(): Promise<void> {
}
async function verifyRuntimeGuards(): Promise<void> {
const port = 5900 + Math.floor(Math.random() * 300);
const server = new ApiServer(port);
const originalStartServer = (ApiServer.prototype as any).startServer;
(ApiServer.prototype as any).startServer = () => undefined;
try {
await new Promise((resolve) => setTimeout(resolve, 250));
const response = await fetch(`http://127.0.0.1:${port}/api/trade`, {
const server = new ApiServer(0);
const req = {
headers: {},
method: 'POST',
headers: {
'Content-Type': 'application/json'
path: '/api/trade'
} as any;
let statusCode = 200;
let responseBody: any = null;
let nextCalled = false;
const res = {
status(code: number) {
statusCode = code;
return this;
},
body: JSON.stringify({
symbol: 'BTC/USD',
side: 'buy',
qty: 0.01,
type: 'market'
})
json(payload: unknown) {
responseBody = payload;
return this;
}
} as any;
await (server as any).requireAuth(req, res, () => {
nextCalled = true;
});
assert.equal(response.status, 401, `Expected 401 for unauthorized /api/trade, got ${response.status}`);
assert.equal(statusCode, 401, `Expected 401 for unauthorized /api/trade, got ${statusCode}`);
assert.equal(nextCalled, false, 'Unauthorized /api/trade should not call next()');
assert.equal(responseBody?.error, 'Unauthorized: missing bearer token');
} finally {
await server.stop();
(ApiServer.prototype as any).startServer = originalStartServer;
}
}

View File

@ -19,21 +19,14 @@ async function verifyStaticIsolationGuards(): Promise<void> {
}
async function verifyRuntimeIsolationGuards(): Promise<void> {
const originalVerify = supabaseService.verifyAccessToken.bind(supabaseService);
const mockedVerify = async (token: string): Promise<{ userId: string | null; error?: string }> => {
if (token === 'token-user-a') return { userId: 'user-a' };
if (token === 'token-user-b') return { userId: 'user-b' };
return { userId: null, error: 'invalid token' };
};
(supabaseService as any).verifyAccessToken = mockedVerify;
const originalStartServer = (ApiServer.prototype as any).startServer;
(ApiServer.prototype as any).startServer = () => undefined;
const originalLoadSnapshot = supabaseService.loadLatestBotStateSnapshot.bind(supabaseService);
(supabaseService as any).loadLatestBotStateSnapshot = async () => null;
const port = 6200 + Math.floor(Math.random() * 400);
const server = new ApiServer(port);
const server = new ApiServer(0);
try {
await new Promise((resolve) => setTimeout(resolve, 250));
const runtimeState = server.getState();
runtimeState.symbols = {};
runtimeState.positions = [];
@ -194,17 +187,8 @@ async function verifyRuntimeIsolationGuards(): Promise<void> {
},
rules: {}
});
const readStatus = async (token: string): Promise<any> => {
const response = await fetch(`http://127.0.0.1:${port}/api/state`, {
headers: { Authorization: `Bearer ${token}` }
});
assert.equal(response.status, 200, `Expected /api/state 200 for token ${token}`);
return await response.json();
};
const userAState = await readStatus('token-user-a');
const userBState = await readStatus('token-user-b');
const userAState = (server as any).getScopedState('user-a', false);
const userBState = (server as any).getScopedState('user-b', false);
assert.equal(userAState.positions.length, 1, 'User A should only receive one owned position.');
assert.equal(userAState.positions[0].profileId, 'profile-a', 'User A received foreign position.');
@ -228,22 +212,13 @@ async function verifyRuntimeIsolationGuards(): Promise<void> {
assert.deepEqual(Object.keys(userBState.symbols['BTC/USD'].profileSignals || {}), ['profile-b'], 'User B received foreign profile signal.');
assert.equal(userBState.symbols['BTC/USD'].activePosition?.profileId, 'profile-b', 'User B should receive owned active symbol position.');
const symbolResponseA = await fetch(`http://127.0.0.1:${port}/api/symbol/BTC%2FUSD`, {
headers: { Authorization: 'Bearer token-user-a' }
});
assert.equal(symbolResponseA.status, 200, 'User A symbol endpoint should be reachable.');
const symbolStateA = await symbolResponseA.json();
const symbolStateA = (server as any).getScopedSymbolState(runtimeState.symbols['BTC/USD'], 'user-a');
assert.deepEqual(Object.keys(symbolStateA.profileSignals || {}), ['profile-a'], 'Symbol endpoint leaked foreign profile signal to User A.');
const symbolResponseB = await fetch(`http://127.0.0.1:${port}/api/symbol/BTC%2FUSD`, {
headers: { Authorization: 'Bearer token-user-b' }
});
assert.equal(symbolResponseB.status, 200, 'User B symbol endpoint should be reachable.');
const symbolStateB = await symbolResponseB.json();
const symbolStateB = (server as any).getScopedSymbolState(runtimeState.symbols['BTC/USD'], 'user-b');
assert.deepEqual(Object.keys(symbolStateB.profileSignals || {}), ['profile-b'], 'Symbol endpoint leaked foreign profile signal to User B.');
} finally {
await server.stop();
(supabaseService as any).verifyAccessToken = originalVerify;
(ApiServer.prototype as any).startServer = originalStartServer;
(supabaseService as any).loadLatestBotStateSnapshot = originalLoadSnapshot;
}
}

0
scripts/verify.sh Normal file → Executable file
View File