Files
deepseek-harness/scripts/verify-config-source-ownership.ts
T
Yichen Jiang 33e3871c77 Merge remote-tracking branch 'origin/master' into claude/unified-environment-credentials-c8841a
# Conflicts:
#	.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.zh.md
#	.agents/notes/implemented/architecture/2026-07-29-request-level-llm-config-credentials.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.i18n.yaml
#	apps/cli/package.json
#	apps/cli/src/web.ts
#	examples/mcp-memory/README.i18n.yaml
#	packages/cordis/repository-plugin/README.i18n.yaml
#	packages/credentials/credentials-local/README.i18n.yaml
#	packages/credentials/credentials-local/README.zh.md
#	packages/llm/llm-deepseek/README.i18n.yaml
#	packages/web/web-search-deepseek/README.i18n.yaml
2026-08-05 12:40:20 +08:00

133 lines
7.1 KiB
TypeScript

/**
* 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.
* @module scripts/verify-config-source-ownership
*/
import { globSync, readFileSync } from 'node:fs'
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/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',
'examples/*/*.cordis.yml',
'examples/*/cordis.yml',
// The Python runtime ships its own default composition inside the wheel.
'python/*/src/**/cordis.yml',
]
/**
* Config keys that must never be inlined from the environment. Line-anchored
* on purpose: this is a tripwire for the shape people actually write, not a
* YAML analysis. A folded scalar or a block-literal spelling would slip past
* it, which is acceptable because the rule it guards is also stated in the
* owning Agent Note and enforced by the adapters' own resolution.
*/
const INLINE_DENY = /^\s*(apiKey|baseURL|apiKeyEnv|authToken|headers)\s*:\s*!!js\b/
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('/')
readFileSync(resolve(ROOT, rel), 'utf8').split('\n').forEach((line, index) => {
if (!INLINE_DENY.test(line)) return
failures.push(
`${rel}:${String(index + 1)}: inlines a credential or endpoint from the environment.`
+ ' The adapter resolves apiKeyEnv through ctx.credentials and the endpoint through the'
+ ' environment snapshot; inlining here bypasses both ladders.',
)
})
}
}
if (failures.length > 0) {
process.stderr.write('verify-config-source-ownership: configuration source ownership violated:\n')
for (const failure of failures) process.stderr.write(` ${failure}\n`)
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',
)