Skip to content

Commit 055410f

Browse files
committed
improvement(audits): flag deployment-shape env-flags imports in client settings surfaces
check:client-boundary now fails when a 'use client' module under the workspace, organization, or standalone settings surfaces imports isHosted, isBillingEnabled, isChatEnabled, or an enterprise feature flag from env-flags instead of reading the seeded deployment shape.
1 parent 951f895 commit 055410f

1 file changed

Lines changed: 67 additions & 0 deletions

File tree

‎scripts/check-client-boundary-imports.ts‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@
4646
* Escape hatch: `// client-boundary-allow: <reason>` on the line directly above
4747
* the import (reason required). Use only for a genuinely browser-only code path.
4848
*
49+
* ## Deployment-shape flags in client surfaces
50+
*
51+
* A `'use client'` module under the workspace, organization, or standalone settings
52+
* surfaces must not import `isHosted`, `isBillingEnabled`, `isChatEnabled`, or the
53+
* enterprise feature flags from `env-flags`: those freeze at module init from the root
54+
* layout's `NEXT_PUBLIC_*` transport, which a recovered 404 or `global-error` tab never
55+
* ran, so Sim Cloud renders as self-hosted. Read them through `useDeploymentShape()` /
56+
* `getDeploymentShape()` from `@/lib/core/config/deployment-shape` (CLAUDE.md).
57+
*
4958
* Usage:
5059
* bun run scripts/check-client-boundary-imports.ts # report
5160
* bun run scripts/check-client-boundary-imports.ts --check # CI gate (fail on any)
@@ -67,6 +76,32 @@ function isServerSurface(rel: string): boolean {
6776
return false
6877
}
6978

79+
/** `env-flags` exports that `@/lib/core/config/deployment-shape` re-serves to the browser. */
80+
const DEPLOYMENT_SHAPE_FLAGS = new Set([
81+
'isHosted',
82+
'isBillingEnabled',
83+
'isChatEnabled',
84+
'isAzureConfigured',
85+
'isCohereConfigured',
86+
'isAccessControlEnabled',
87+
'isAuditLogsEnabled',
88+
'isCustomBlocksEnabled',
89+
'isDataDrainsEnabled',
90+
'isDataRetentionEnabled',
91+
'isInboxEnabled',
92+
'isSandboxesEnabled',
93+
'isScimEnabled',
94+
'isSessionPoliciesEnabled',
95+
'isSsoEnabled',
96+
'isUsageMonitoringEnabled',
97+
'isWhitelabelingEnabled',
98+
])
99+
100+
/** Surfaces whose shell seeds the server-resolved deployment shape (paths relative to apps/sim). */
101+
function isDeploymentShapeSurface(rel: string): boolean {
102+
return /^(?:app\/(?:workspace|o|account|selfhost\/settings)|components\/settings|ee)\//.test(rel)
103+
}
104+
70105
const SOURCE_EXTENSIONS = ['.ts', '.tsx']
71106
const ALLOW_DIRECTIVE = 'client-boundary-allow'
72107
const sourceCache = new Map<string, string>()
@@ -310,6 +345,38 @@ async function main() {
310345
}
311346
}
312347

348+
const shapeViolations: Array<Violation & { flags: string[] }> = []
349+
for (const absFile of allFiles) {
350+
if (!absFile.startsWith(`${APP_DIR}${path.sep}`)) continue
351+
const rel = path.relative(APP_DIR, absFile)
352+
if (!isDeploymentShapeSurface(rel) || !(await isUseClientModule(absFile))) continue
353+
const content = await readSource(absFile)
354+
for (const imp of parseImports(content)) {
355+
if (imp.specifier !== '@/lib/core/config/env-flags' || !importsAValue(imp.clause)) continue
356+
const braced = imp.clause.slice(imp.clause.indexOf('{') + 1, imp.clause.lastIndexOf('}'))
357+
const flags = braced
358+
.split(',')
359+
.map((member) => member.trim().split(/\s+as\s+/)[0])
360+
.filter((name) => DEPLOYMENT_SHAPE_FLAGS.has(name))
361+
if (flags.length === 0 || hasAllowDirective(content, imp.line)) continue
362+
shapeViolations.push({ file: rel, line: imp.line, specifier: imp.specifier, flags })
363+
}
364+
}
365+
366+
if (shapeViolations.length === 0) {
367+
console.log('✓ No client settings surface reads deployment-shape flags from env-flags.')
368+
} else {
369+
failed = true
370+
console.error(
371+
`\n✗ ${shapeViolations.length} 'use client' module(s) in a workspace/organization/settings surface import deployment-shape flags from env-flags.\n` +
372+
` Those constants freeze from the root layout's NEXT_PUBLIC_* transport, so a recovered 404/global-error tab renders Sim Cloud as self-hosted.\n` +
373+
` Read them via useDeploymentShape() (components) or getDeploymentShape() (helpers) from @/lib/core/config/deployment-shape.\n`
374+
)
375+
for (const v of shapeViolations) {
376+
console.error(` ${v.file}:${v.line} imports ${v.flags.join(', ')} from '${v.specifier}'`)
377+
}
378+
}
379+
313380
if (failed && checkMode) process.exit(1)
314381
}
315382

0 commit comments

Comments
 (0)