fix(config): cover shipped bundle source ownership
This commit is contained in:
@@ -368,7 +368,6 @@
|
|||||||
name: '@deepseek-ai/dsh-web-search-deepseek'
|
name: '@deepseek-ai/dsh-web-search-deepseek'
|
||||||
config:
|
config:
|
||||||
apiKeyEnv: DEEPSEEK_API_KEY
|
apiKeyEnv: DEEPSEEK_API_KEY
|
||||||
baseURL: !!js process.env.DEEPSEEK_SEARCH_BASE_URL
|
|
||||||
|
|
||||||
- id: tool-web
|
- id: tool-web
|
||||||
name: '@deepseek-ai/dsh-tool-web'
|
name: '@deepseek-ai/dsh-tool-web'
|
||||||
|
|||||||
30
scripts/verify-config-source-ownership.spec.ts
Normal file
30
scripts/verify-config-source-ownership.spec.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||||
|
import { tmpdir } from 'node:os'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
import { afterEach, describe, expect, it } from 'vitest'
|
||||||
|
import { collectConfigSourceOwnershipViolations } from './verify-config-source-ownership.ts'
|
||||||
|
|
||||||
|
const roots: string[] = []
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('configuration source ownership gate', () => {
|
||||||
|
it('rejects inline endpoints in shipped bundle patches', () => {
|
||||||
|
const root = mkdtempSync(join(tmpdir(), 'dsh-config-source-ownership-'))
|
||||||
|
roots.push(root)
|
||||||
|
const directory = join(root, 'packages/bundle/base')
|
||||||
|
mkdirSync(directory, { recursive: true })
|
||||||
|
writeFileSync(
|
||||||
|
join(directory, 'cordis.patch.yml'),
|
||||||
|
'config:\n baseURL: !!js process.env.DEEPSEEK_SEARCH_BASE_URL\n',
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(collectConfigSourceOwnershipViolations(root)).toEqual([
|
||||||
|
'packages/bundle/base/cordis.patch.yml:2: 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.',
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -16,6 +16,7 @@ const SHIPPED_CONFIG_GLOBS = [
|
|||||||
'apps/*/config/*.yml',
|
'apps/*/config/*.yml',
|
||||||
'examples/*/*.cordis.yml',
|
'examples/*/*.cordis.yml',
|
||||||
'examples/*/cordis.yml',
|
'examples/*/cordis.yml',
|
||||||
|
'packages/bundle/*/cordis.patch.yml',
|
||||||
// The Python runtime ships its own default composition inside the wheel.
|
// The Python runtime ships its own default composition inside the wheel.
|
||||||
'python/*/src/**/cordis.yml',
|
'python/*/src/**/cordis.yml',
|
||||||
]
|
]
|
||||||
@@ -29,29 +30,35 @@ const SHIPPED_CONFIG_GLOBS = [
|
|||||||
*/
|
*/
|
||||||
const INLINE_DENY = /^\s*(apiKey|baseURL|apiKeyEnv|authToken|headers)\s*:\s*!!js\b/
|
const INLINE_DENY = /^\s*(apiKey|baseURL|apiKeyEnv|authToken|headers)\s*:\s*!!js\b/
|
||||||
|
|
||||||
const failures: string[] = []
|
/** Return every forbidden inline environment form in shipped configuration. */
|
||||||
|
export function collectConfigSourceOwnershipViolations(root: string): string[] {
|
||||||
for (const glob of SHIPPED_CONFIG_GLOBS) {
|
const failures: string[] = []
|
||||||
for (const file of globSync(glob, { cwd: ROOT })) {
|
for (const glob of SHIPPED_CONFIG_GLOBS) {
|
||||||
const rel = file.split(sep).join('/')
|
for (const file of globSync(glob, { cwd: root })) {
|
||||||
readFileSync(resolve(ROOT, rel), 'utf8').split('\n').forEach((line, index) => {
|
const rel = file.split(sep).join('/')
|
||||||
if (!INLINE_DENY.test(line)) return
|
readFileSync(resolve(root, rel), 'utf8').split('\n').forEach((line, index) => {
|
||||||
failures.push(
|
if (!INLINE_DENY.test(line)) return
|
||||||
`${rel}:${String(index + 1)}: inlines a credential or endpoint from the environment.`
|
failures.push(
|
||||||
+ ' The adapter resolves apiKeyEnv through ctx.credentials and the endpoint through the'
|
`${rel}:${String(index + 1)}: inlines a credential or endpoint from the environment.`
|
||||||
+ ' environment snapshot; inlining here bypasses both ladders.',
|
+ ' The adapter resolves apiKeyEnv through ctx.credentials and the endpoint through the'
|
||||||
)
|
+ ' environment snapshot; inlining here bypasses both ladders.',
|
||||||
})
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return failures
|
||||||
}
|
}
|
||||||
|
|
||||||
if (failures.length > 0) {
|
if (process.argv[1] && import.meta.filename === resolve(process.argv[1])) {
|
||||||
process.stderr.write('verify-config-source-ownership: configuration source ownership violated:\n')
|
const failures = collectConfigSourceOwnershipViolations(ROOT)
|
||||||
for (const failure of failures) process.stderr.write(` ${failure}\n`)
|
if (failures.length > 0) {
|
||||||
process.exit(1)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
process.stdout.write(
|
process.stdout.write(
|
||||||
'verify-config-source-ownership: no credential or endpoint uses the ordinary inline environment form'
|
'verify-config-source-ownership: no credential or endpoint uses the ordinary inline environment form'
|
||||||
+ ' in shipped configuration.\n',
|
+ ' in shipped configuration.\n',
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user