Skip to content

Commit 4c20ff6

Browse files
authored
improvement(audits): eliminate repeated parsing and scans (#7361)
* improvement(audits): eliminate repeated parsing and scans * fix(audits): resolve local bins under vitest * fix(audits): include drizzle subpath imports
1 parent 69d7266 commit 4c20ff6

22 files changed

Lines changed: 580 additions & 218 deletions

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414
"dev:sockets": "cd apps/realtime && bun run dev",
1515
"dev:full": "bunx concurrently -n \"App,Realtime\" -c \"cyan,magenta\" \"cd apps/sim && bun run dev\" \"cd apps/realtime && bun run dev\"",
1616
"dev:full:capped": "bunx concurrently -n \"App,Realtime\" -c \"cyan,magenta\" \"cd apps/sim && bun run dev:capped\" \"cd apps/realtime && bun run dev\"",
17-
"test": "bun run test:setup && bun run test:npm-package-versions && bun run test:icon-path-precision && bun run test:tool-registry-boundary && bun run test:tool-request-boundary && bun run test:actorless-executor-operations && bun run test:permission-group-enforcement && bun run test:capability-subject && bun run test:application-graph && bun run test:migrations-safety && bun run test:generators && turbo run test",
17+
"test": "bun run test:setup && bun run test:npm-package-versions && bun run test:audit-candidates && bun run test:icon-path-precision && bun run test:tool-registry-boundary && bun run test:tool-request-boundary && bun run test:actorless-executor-operations && bun run test:permission-group-enforcement && bun run test:capability-subject && bun run test:application-graph && bun run test:migrations-safety && bun run test:generators && turbo run test",
1818
"test:setup": "bun run --cwd packages/sim-setup test",
1919
"test:npm-package-versions": "bunx vitest run scripts/bump-npm-package-versions.test.ts",
20+
"test:audit-candidates": "bunx vitest run scripts/check-db-audit-candidates.test.ts scripts/check-egress-boundary.test.ts",
2021
"test:icon-path-precision": "bunx vitest run scripts/check-icon-path-precision.test.ts",
2122
"test:tool-registry-boundary": "bunx vitest run scripts/check-tool-registry-boundary.test.ts",
2223
"test:tool-request-boundary": "bunx vitest run scripts/check-tool-request-boundary.test.ts",

scripts/check-api-contract-routes.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ interface DeclaredContract {
4545
module: string
4646
}
4747

48+
const fileSourceCache = new Map<string, Promise<string | null>>()
49+
const routeSourceCache = new Map<string, Promise<string | null>>()
50+
4851
async function listContractModules(dir: string, results: string[] = []): Promise<string[]> {
4952
for (const entry of await readdir(dir, { withFileTypes: true })) {
5053
if (SKIP_DIRS.has(entry.name)) continue
@@ -68,12 +71,19 @@ function isRouteContract(value: unknown): value is { method: HttpMethod; path: s
6871
}
6972

7073
async function readIfFile(candidate: string): Promise<string | null> {
71-
try {
72-
if (!(await stat(candidate)).isFile()) return null
73-
return await readFile(candidate, 'utf8')
74-
} catch {
75-
return null
74+
let pending = fileSourceCache.get(candidate)
75+
if (!pending) {
76+
pending = (async () => {
77+
try {
78+
if (!(await stat(candidate)).isFile()) return null
79+
return await readFile(candidate, 'utf8')
80+
} catch {
81+
return null
82+
}
83+
})()
84+
fileSourceCache.set(candidate, pending)
7685
}
86+
return pending
7787
}
7888

7989
/**
@@ -84,6 +94,15 @@ async function readIfFile(candidate: string): Promise<string | null> {
8494
* own file — would look routeless and be silently exempted from the check.
8595
*/
8696
async function readRouteFile(routePath: string): Promise<string | null> {
97+
let pending = routeSourceCache.get(routePath)
98+
if (!pending) {
99+
pending = resolveRouteFile(routePath)
100+
routeSourceCache.set(routePath, pending)
101+
}
102+
return pending
103+
}
104+
105+
async function resolveRouteFile(routePath: string): Promise<string | null> {
87106
if (!routePath.startsWith('/api/')) return null
88107
const segments = routePath.slice('/api/'.length).split('/').filter(Boolean)
89108

scripts/check-api-validation-contracts.ts

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,16 @@ const SOURCE_SKIP_DIRS = new Set([
262262

263263
type AnnotationKind = 'raw-fetch' | 'double-cast' | 'raw-json' | 'untyped-response'
264264

265+
const sourceCache = new Map<string, string>()
266+
267+
async function readSource(filePath: string): Promise<string> {
268+
const cached = sourceCache.get(filePath)
269+
if (cached !== undefined) return cached
270+
const content = await readFile(filePath, 'utf8')
271+
sourceCache.set(filePath, content)
272+
return content
273+
}
274+
265275
interface AnnotationResult {
266276
allowed: boolean
267277
missingReason: boolean
@@ -1257,7 +1267,7 @@ async function auditQueryHooks(): Promise<QueryHookAudit[]> {
12571267
const audits: QueryHookAudit[] = []
12581268

12591269
for (const filePath of queryHookFiles) {
1260-
const content = await readFile(filePath, 'utf8')
1270+
const content = await readSource(filePath)
12611271
audits.push(auditQueryHook(filePath, content))
12621272
}
12631273

@@ -1274,7 +1284,7 @@ async function main() {
12741284
let rawJsonExemptions = 0
12751285

12761286
for (const filePath of routeFiles) {
1277-
const content = await readFile(filePath, 'utf8')
1287+
const content = await readSource(filePath)
12781288
audits.push(auditRoute(filePath, content))
12791289

12801290
const rawJson = findRawJsonFindings(filePath, content)
@@ -1293,12 +1303,14 @@ async function main() {
12931303
let doubleCastExemptions = 0
12941304

12951305
const appsSimRoot = path.join(ROOT, 'apps/sim')
1306+
const contractsRoot = path.join(CONTRACTS_DIR, path.sep)
12961307

12971308
for (const filePath of sourceFiles) {
1298-
const content = await readFile(filePath, 'utf8')
1309+
const content = sourceCache.get(filePath) ?? (await readFile(filePath, 'utf8'))
1310+
if (filePath.startsWith(contractsRoot)) sourceCache.set(filePath, content)
12991311
const normalized = filePath.replace(/\\/g, '/')
13001312

1301-
if (isClientHookFile(filePath)) {
1313+
if (isClientHookFile(filePath) && content.includes('fetch')) {
13021314
const rawFetch = findRawFetchFindings(filePath, content)
13031315
rawFetchFindings.push(...rawFetch.findings)
13041316
rawFetchExemptions += rawFetch.exemptions
@@ -1308,26 +1320,30 @@ async function main() {
13081320
if (
13091321
normalized.startsWith(`${appsSimRoot}/`) &&
13101322
!isApiRouteHandler(filePath) &&
1311-
filePath !== path.join(ROOT, 'scripts', 'check-api-validation-contracts.ts')
1323+
filePath !== path.join(ROOT, 'scripts', 'check-api-validation-contracts.ts') &&
1324+
content.includes('fetch') &&
1325+
content.includes('/api/')
13121326
) {
13131327
const sameOrigin = findSameOriginApiFetchFindings(filePath, content)
13141328
sameOriginApiFetchFindings.push(...sameOrigin.findings)
13151329
sameOriginApiFetchExemptions += sameOrigin.exemptions
13161330
annotationsMissingReason.push(...sameOrigin.missingReasons)
13171331
}
13181332

1319-
const doubleCast = findDoubleCastFindings(filePath, content)
1320-
doubleCastFindings.push(...doubleCast.findings)
1321-
doubleCastExemptions += doubleCast.exemptions
1322-
annotationsMissingReason.push(...doubleCast.missingReasons)
1333+
if (content.includes('as unknown as')) {
1334+
const doubleCast = findDoubleCastFindings(filePath, content)
1335+
doubleCastFindings.push(...doubleCast.findings)
1336+
doubleCastExemptions += doubleCast.exemptions
1337+
annotationsMissingReason.push(...doubleCast.missingReasons)
1338+
}
13231339
}
13241340

13251341
const contractFiles = await walk(CONTRACTS_DIR, (fileName) => /\.ts$/.test(fileName))
13261342
const untypedResponseFindings: UntypedResponseFinding[] = []
13271343
let untypedResponseExemptions = 0
13281344

13291345
for (const filePath of contractFiles) {
1330-
const content = await readFile(filePath, 'utf8')
1346+
const content = await readSource(filePath)
13311347
const untyped = findUntypedResponseFindings(filePath, content)
13321348
untypedResponseFindings.push(...untyped.findings)
13331349
untypedResponseExemptions += untyped.exemptions

scripts/check-client-boundary-imports.ts

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,15 @@ function isServerSurface(rel: string): boolean {
6969

7070
const SOURCE_EXTENSIONS = ['.ts', '.tsx']
7171
const ALLOW_DIRECTIVE = 'client-boundary-allow'
72+
const sourceCache = new Map<string, string>()
73+
74+
async function readSource(file: string): Promise<string> {
75+
const cached = sourceCache.get(file)
76+
if (cached !== undefined) return cached
77+
const source = await readFile(file, 'utf8')
78+
sourceCache.set(file, source)
79+
return source
80+
}
7281

7382
async function listFiles(dir: string): Promise<string[]> {
7483
const out: string[] = []
@@ -125,7 +134,7 @@ async function isUseClientModule(absFile: string): Promise<boolean> {
125134
if (cached !== undefined) return cached
126135
let isClient = false
127136
try {
128-
isClient = leadingDirective(await readFile(absFile, 'utf8')) === 'use client'
137+
isClient = leadingDirective(await readSource(absFile)) === 'use client'
129138
} catch {}
130139
useClientCache.set(absFile, isClient)
131140
return isClient
@@ -135,24 +144,26 @@ async function isUseClientModule(absFile: string): Promise<boolean> {
135144
* Locations declaring `'use server'` — module prologue or inline in a function
136145
* body. Either form registers Server Actions app-wide.
137146
*/
138-
async function findUseServerDirectives(): Promise<string[]> {
147+
async function findUseServerDirectives(files: readonly string[]): Promise<string[]> {
139148
const found: string[] = []
140-
for (const dir of DIRECTIVE_SCAN_DIRS) {
141-
for (const absFile of await listFiles(dir)) {
142-
const lines = (await readFile(absFile, 'utf8')).split('\n')
143-
for (let i = 0; i < lines.length; i++) {
144-
const match = DIRECTIVE_STATEMENT.exec(stripTrailingComment(lines[i].trim()))
145-
if (match?.[2] === 'use server') {
146-
found.push(`${path.relative(ROOT, absFile)}:${i + 1}`)
147-
}
149+
for (const absFile of files) {
150+
const lines = (await readSource(absFile)).split('\n')
151+
for (let i = 0; i < lines.length; i++) {
152+
const match = DIRECTIVE_STATEMENT.exec(stripTrailingComment(lines[i].trim()))
153+
if (match?.[2] === 'use server') {
154+
found.push(`${path.relative(ROOT, absFile)}:${i + 1}`)
148155
}
149156
}
150157
}
151158
return found
152159
}
153160

154161
/** Resolve an import specifier to an absolute source file, or null if external/unresolved. */
155-
async function resolveSpecifier(spec: string, fromFile: string): Promise<string | null> {
162+
function resolveSpecifier(
163+
spec: string,
164+
fromFile: string,
165+
sourceFiles: ReadonlySet<string>
166+
): string | null {
156167
let base: string
157168
if (spec.startsWith('@/')) {
158169
base = path.join(APP_DIR, spec.slice(2))
@@ -168,10 +179,7 @@ async function resolveSpecifier(spec: string, fromFile: string): Promise<string
168179
]
169180
for (const candidate of candidates) {
170181
if (!SOURCE_EXTENSIONS.includes(path.extname(candidate))) continue
171-
try {
172-
await readFile(candidate, 'utf8')
173-
return candidate
174-
} catch {}
182+
if (sourceFiles.has(candidate)) return candidate
175183
}
176184
return null
177185
}
@@ -246,7 +254,12 @@ async function main() {
246254
const checkMode = process.argv.includes('--check')
247255
let failed = false
248256

249-
const serverDirectives = await findUseServerDirectives()
257+
const allFiles: string[] = []
258+
for (const dir of DIRECTIVE_SCAN_DIRS) {
259+
allFiles.push(...(await listFiles(dir)))
260+
}
261+
const sourceFiles = new Set(allFiles)
262+
const serverDirectives = await findUseServerDirectives(allFiles)
250263
if (serverDirectives.length === 0) {
251264
console.log("✓ No 'use server' directives (Server Actions stay disabled).")
252265
} else {
@@ -260,19 +273,19 @@ async function main() {
260273
for (const location of serverDirectives) console.error(` ${location}`)
261274
}
262275

263-
const allFiles = await listFiles(APP_DIR)
264276
const violations: Violation[] = []
265277

266278
for (const absFile of allFiles) {
279+
if (!absFile.startsWith(`${APP_DIR}${path.sep}`)) continue
267280
const rel = path.relative(APP_DIR, absFile)
268281
if (!isServerSurface(rel)) continue
269282
// A server file that is itself `'use client'` is a client component — out of scope.
270283
if (await isUseClientModule(absFile)) continue
271284

272-
const content = await readFile(absFile, 'utf8')
285+
const content = await readSource(absFile)
273286
for (const imp of parseImports(content)) {
274287
if (!importsAValue(imp.clause)) continue
275-
const resolved = await resolveSpecifier(imp.specifier, absFile)
288+
const resolved = resolveSpecifier(imp.specifier, absFile, sourceFiles)
276289
if (!resolved) continue
277290
if (!(await isUseClientModule(resolved))) continue
278291
if (hasAllowDirective(content, imp.line)) continue
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { mayReferencePendingTable } from './check-pending-drop-tables'
3+
import { mayBindDrizzleSql } from './check-sql-date-binding'
4+
5+
describe('database audit candidate scans', () => {
6+
it('finds renamed pending-table imports', () => {
7+
expect(
8+
mayReferencePendingTable(
9+
"import { organization as org } from '@sim/db/schema'",
10+
new Set(['organization'])
11+
)
12+
).toBe(true)
13+
})
14+
15+
it('decodes escaped schema module literals', () => {
16+
expect(
17+
mayReferencePendingTable(
18+
String.raw`const organization = require('@sim/db/sch\u0065ma')`,
19+
new Set(['organization'])
20+
)
21+
).toBe(true)
22+
})
23+
24+
it('rejects unrelated uses of common table names', () => {
25+
expect(
26+
mayReferencePendingTable('const organization = getOrganization()', new Set(['organization']))
27+
).toBe(false)
28+
})
29+
30+
it('finds aliased drizzle sql imports', () => {
31+
expect(mayBindDrizzleSql("import { sql as query } from 'drizzle-orm'")).toBe(true)
32+
})
33+
34+
it('finds drizzle sql subpath imports', () => {
35+
expect(mayBindDrizzleSql("import { sql } from 'drizzle-orm/sql'")).toBe(true)
36+
})
37+
38+
it('decodes escaped drizzle module literals', () => {
39+
expect(mayBindDrizzleSql(String.raw`const { sql } = require('drizzle\x2dorm')`)).toBe(true)
40+
})
41+
42+
it('skips drizzle consumers that cannot bind sql', () => {
43+
expect(mayBindDrizzleSql("import { eq } from 'drizzle-orm'")).toBe(false)
44+
})
45+
})
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { mayLoadTransport } from './check-egress-boundary'
3+
4+
describe('egress transport candidate scan', () => {
5+
it('finds literal transport modules', () => {
6+
expect(mayLoadTransport("import { request } from 'node:https'")).toBe(true)
7+
})
8+
9+
it('decodes escaped transport module literals', () => {
10+
expect(mayLoadTransport(String.raw`const http = require('node:\x68ttp')`)).toBe(true)
11+
})
12+
13+
it('ignores transport names outside string tokens', () => {
14+
expect(mayLoadTransport('const https = createClient()')).toBe(false)
15+
})
16+
})

scripts/check-egress-boundary.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@
2727
import type { Dirent } from 'node:fs'
2828
import { readdirSync, readFileSync } from 'node:fs'
2929
import path from 'node:path'
30+
import { fileURLToPath } from 'node:url'
3031
import ts from '@typescript/typescript6'
3132

32-
const ROOT = path.resolve(import.meta.dir, '..')
33+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
3334

3435
const SCAN_DIRS = [
3536
'apps/sim/app',
@@ -141,7 +142,7 @@ function isElidedExport(node: ts.ExportDeclaration): boolean {
141142
* skipped: it has no runtime presence and cannot open anything.
142143
*/
143144
function findTransportLoads(file: string, source: string): Array<Omit<Violation, 'file'>> {
144-
const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true)
145+
const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, false)
145146
const found: Array<Omit<Violation, 'file'>> = []
146147

147148
const record = (node: ts.Node, specifier: string, kind: string) => {
@@ -177,6 +178,25 @@ function findTransportLoads(file: string, source: string): Array<Omit<Violation,
177178
return found
178179
}
179180

181+
export function mayLoadTransport(source: string): boolean {
182+
const scanner = ts.createScanner(
183+
ts.ScriptTarget.Latest,
184+
true,
185+
ts.LanguageVariant.Standard,
186+
source
187+
)
188+
for (let token = scanner.scan(); token !== ts.SyntaxKind.EndOfFileToken; token = scanner.scan()) {
189+
if (
190+
(token === ts.SyntaxKind.StringLiteral ||
191+
token === ts.SyntaxKind.NoSubstitutionTemplateLiteral) &&
192+
TRANSPORTS.has(scanner.getTokenValue())
193+
) {
194+
return true
195+
}
196+
}
197+
return false
198+
}
199+
180200
function main() {
181201
const violations: Violation[] = []
182202
let scanned = 0
@@ -186,7 +206,9 @@ function main() {
186206
const rel = path.relative(ROOT, file).split(path.sep).join('/')
187207
if (ALLOWED.has(rel)) continue
188208
scanned++
189-
for (const load of findTransportLoads(rel, readFileSync(file, 'utf8'))) {
209+
const source = readFileSync(file, 'utf8')
210+
if (!mayLoadTransport(source)) continue
211+
for (const load of findTransportLoads(rel, source)) {
190212
violations.push({ file: rel, ...load })
191213
}
192214
}
@@ -212,4 +234,4 @@ function main() {
212234
process.exit(1)
213235
}
214236

215-
main()
237+
if (import.meta.main) main()

0 commit comments

Comments
 (0)