|
|
@@ -1,20 +1,8 @@
|
|
|
/**
|
|
|
- * Gate: every user-facing value has one owner, and no shipped file smuggles a
|
|
|
- * second one in.
|
|
|
- *
|
|
|
- * Two rules, both about the same failure — a value reaching the harness
|
|
|
- * through a path nobody ranked:
|
|
|
- *
|
|
|
- * 1. Production package source does not read `process.env` directly. A
|
|
|
- * credential belongs to `ctx.credentials`, a user-configurable value to the
|
|
|
- * environment snapshot plus its owner's resolve step, and a real
|
|
|
- * process-launch fact to the app bootstrap. Each remaining read is listed
|
|
|
- * below with the reason it is one of those.
|
|
|
- * 2. Shipped Cordis configuration does not inline a credential or an endpoint
|
|
|
- * from the environment. Doing so re-creates the layer the snapshot exists
|
|
|
- * to rank: `apiKey: !!js process.env.X` and `baseURL: !!js process.env.X`
|
|
|
- * bypass both the credential seam and the endpoint ladder, and a project
|
|
|
- * file could then decide where a key is sent.
|
|
|
+ * Gate: shipped Cordis configuration does not use the ordinary inline form
|
|
|
+ * for a credential or endpoint from the environment. This narrow source-shape
|
|
|
+ * lint prevents checked-in composition from bypassing the credential seam and
|
|
|
+ * endpoint ladder; adapters remain responsible for actual value resolution.
|
|
|
* @module scripts/verify-config-source-ownership
|
|
|
*/
|
|
|
|
|
|
@@ -23,58 +11,6 @@ import { resolve, sep } from 'node:path'
|
|
|
|
|
|
const ROOT = resolve(import.meta.dirname, '..')
|
|
|
|
|
|
-/**
|
|
|
- * Production package sources allowed to read `process.env`, each with the
|
|
|
- * reason it is a process fact rather than a user-configurable value. Adding a
|
|
|
- * row is a deliberate act: state which of the three owners it belongs to and
|
|
|
- * why it cannot go there.
|
|
|
- */
|
|
|
-const ENV_READ_ALLOWLIST: Readonly<Record<string, string>> = {
|
|
|
- // The environment plane itself.
|
|
|
- 'packages/util/environment/src/index.ts': 'defines the snapshot; the inherited environment is its input',
|
|
|
- 'packages/ui/app-boot/src/index.ts': 'the app bootstrap that builds the snapshot and reads $DSH_SNAPSHOT',
|
|
|
- 'packages/util/paths/src/index.ts': 'resolves $DSH_HOME before any snapshot exists',
|
|
|
-
|
|
|
- // Process-launch facts owned by the boundary that spawns or is spawned.
|
|
|
- 'packages/subprocess/subprocess/src/index.ts': 'scrubs the parent environment for children',
|
|
|
- 'packages/workflow/workflow-workerthread/src/host.ts': 'passes the parent environment to a worker thread',
|
|
|
- 'packages/ui/tui/src/index.ts': 'reads $COLORTERM, a terminal capability of this process',
|
|
|
- 'packages/lsp/lsp-local/src/index.ts': 'passes the parent environment to a language server it spawns',
|
|
|
- 'packages/cordis/repository-plugin/src/index.ts': 'resolves an MCP manifest against the spawning environment',
|
|
|
- 'packages/host/directory-picker-native/src/win32-dialog-host.ts': 'builds the child environment for the dialog worker it spawns',
|
|
|
- 'packages/host/directory-picker-native/src/win32-dialog-worker.ts': 'the spawned worker reads the title its parent passed on the env channel',
|
|
|
- 'packages/bash/pwsh-local/src/resolve.ts': 'locates pwsh through $ProgramFiles and $SystemRoot, Windows install layout rather than user configuration',
|
|
|
-
|
|
|
- // Bootstrap-only DSH_* switches, which no discovered file may set.
|
|
|
- 'packages/skill/skill-local/src/index.ts': 'reads $DSH_AGENTS_HOME and $DSH_BUNDLED_SKILL_DIR, both bootstrap-only',
|
|
|
- 'packages/web/web/src/index.ts': 'reads $DSH_WEB_SEARCH_PROVIDER and $DSH_WEB_FETCH_PROVIDER, both bootstrap-only',
|
|
|
- 'packages/host/apiproxy/src/native-path-opener.ts': 'reads the WSL interop markers of this process to pick an opener',
|
|
|
- 'packages/host/directory-picker-auto/src/index.ts': 'reads launch facts (display, SSH) of this process',
|
|
|
- 'packages/host/directory-picker-auto/src/resolve.ts': 'reads launch facts (display, SSH) of this process',
|
|
|
-
|
|
|
- // Telemetry identity and consent, resolved once per process at bootstrap.
|
|
|
- 'packages/telemetry/session-telemetry-otel/src/user-id.ts': 'derives a machine identity from process facts',
|
|
|
- 'packages/sdk/telemetry/src/consent-resolver.ts': 'reads the SDK bootstrap consent switch',
|
|
|
- 'packages/sdk/telemetry/src/anonymous-id.ts': 'derives a machine identity from process facts',
|
|
|
-
|
|
|
- // SDK and example bins: their own app bootstrap, outside the product CLI.
|
|
|
- 'packages/sdk/sdk-client/src/client.ts': 'SDK host bootstrap',
|
|
|
- 'packages/sdk/helper/src/features/builtin/provider.ts': 'SDK scaffolding reads the developer environment',
|
|
|
- 'packages/sdk/helper/src/features/builtin/app.ts': 'SDK scaffolding reads the developer environment',
|
|
|
- 'packages/sdk/helper/src/package-managers/package-manager.ts': 'detects the invoking package manager',
|
|
|
- 'packages/sdk/create-sdk/src/create-wizard.ts': 'SDK scaffolding reads the developer environment',
|
|
|
- 'packages/examples/jsonrpc-demo/src/bin.ts': 'demo bin bootstrap',
|
|
|
- 'packages/examples/acp-demo/src/bin.ts': 'demo bin bootstrap',
|
|
|
-
|
|
|
- // Test and replay infrastructure.
|
|
|
- 'packages/support/loader-smoke/src/index.ts': 'test launcher composing a child environment',
|
|
|
- 'packages/support/llm-replay/src/index.ts': 'replay fixture switch',
|
|
|
- 'packages/support/acp-snapshot/src/launcher.ts': 'snapshot launcher composing a child environment',
|
|
|
-
|
|
|
- // Browser bundle: `process.env` is replaced at build time, never read at runtime.
|
|
|
- 'packages/client/runtime/src/client/contract/store.ts': 'build-time constant folded by the bundler',
|
|
|
-}
|
|
|
-
|
|
|
/** Shipped Cordis configuration these rules apply to. */
|
|
|
const SHIPPED_CONFIG_GLOBS = [
|
|
|
'apps/*/config/*.yml',
|
|
|
@@ -95,17 +31,6 @@ const INLINE_DENY = /^\s*(apiKey|baseURL|apiKeyEnv|authToken|headers)\s*:\s*!!js
|
|
|
|
|
|
const failures: string[] = []
|
|
|
|
|
|
-for (const file of globSync('packages/*/*/src/**/*.ts', { cwd: ROOT })) {
|
|
|
- const rel = file.split(sep).join('/')
|
|
|
- if (!readFileSync(resolve(ROOT, rel), 'utf8').includes('process.env')) continue
|
|
|
- if (rel in ENV_READ_ALLOWLIST) continue
|
|
|
- failures.push(
|
|
|
- `${rel}: reads process.env directly. A credential belongs to ctx.credentials, a user-configurable`
|
|
|
- + ' value to environmentOf(ctx) plus its owner\'s resolve step, and a process-launch fact to the app'
|
|
|
- + ' bootstrap. If it is genuinely one of those, add it to ENV_READ_ALLOWLIST with the reason.',
|
|
|
- )
|
|
|
-}
|
|
|
-
|
|
|
for (const glob of SHIPPED_CONFIG_GLOBS) {
|
|
|
for (const file of globSync(glob, { cwd: ROOT })) {
|
|
|
const rel = file.split(sep).join('/')
|
|
|
@@ -126,8 +51,7 @@ if (failures.length > 0) {
|
|
|
process.exit(1)
|
|
|
}
|
|
|
|
|
|
-const allowed = Object.keys(ENV_READ_ALLOWLIST).length
|
|
|
process.stdout.write(
|
|
|
- `verify-config-source-ownership: no unregistered process.env reads (${String(allowed)} allowlisted)`
|
|
|
- + ' and no credential or endpoint inlined in shipped configuration.\n',
|
|
|
+ 'verify-config-source-ownership: no credential or endpoint uses the ordinary inline environment form'
|
|
|
+ + ' in shipped configuration.\n',
|
|
|
)
|