瀏覽代碼

fix(subprocess): share password scrub with direct spawners

ZiyaZhang 2 月之前
父節點
當前提交
d0cbf825a5

+ 2 - 2
packages/sdk/helper/src/package-managers/package-manager.ts

@@ -5,7 +5,7 @@
  */
 
 import { execFile, spawn } from 'node:child_process'
-import { scrubbedParentEnv } from '@deepseek-ai/dsh-subprocess'
+import { scrubbedParentEnv, SENSITIVE_ENV_PATTERN } from '@deepseek-ai/dsh-subprocess'
 import { promisify } from 'node:util'
 import type { PackageJsonFile } from '../documents/package-json-file.ts'
 import { PnpmWorkspaceFile } from '../documents/pnpm-workspace-file.ts'
@@ -60,7 +60,7 @@ export async function probePackageManagerVersion(name: PackageManagerName, cwd:
  */
 export function scrubEnvironment(environment?: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
   if (environment === undefined) return scrubbedParentEnv()
-  return Object.fromEntries(Object.entries(environment).filter(([name]) => !/(?:KEY|SECRET|TOKEN)/i.test(name)))
+  return Object.fromEntries(Object.entries(environment).filter(([name]) => !SENSITIVE_ENV_PATTERN.test(name)))
 }
 
 /** Node child-process command runner with inherited stdio and quiescent completion. */

+ 6 - 1
packages/sdk/helper/tests/documents.spec.ts

@@ -309,7 +309,12 @@ describe('package manager strategies', () => {
     await expect(npm.install('/tmp', failed)).rejects.toThrow('exited with code 2')
     const killed: CommandRunner = { run: async () => ({ exitCode: null, signal: 'SIGTERM' }) }
     await expect(npm.build('/tmp', killed)).rejects.toThrow('killed by SIGTERM')
-    expect(scrubEnvironment({ PATH: '/bin', API_KEY: 'secret', TOKEN_VALUE: 'secret' })).toEqual({ PATH: '/bin' })
+    expect(scrubEnvironment({
+      PATH: '/bin',
+      API_KEY: 'secret',
+      DB_PASSWORD: 'secret',
+      TOKEN_VALUE: 'secret',
+    })).toEqual({ PATH: '/bin' })
   })
 
   it('probes versions and runs real child-process boundaries', async () => {

+ 2 - 0
packages/ui/tui/package.json

@@ -45,6 +45,7 @@
     "@deepseek-ai/dsh-session-reference": "^0.0.1",
     "@deepseek-ai/dsh-session-title": "^0.0.1",
     "@deepseek-ai/dsh-skill": "^0.0.1",
+    "@deepseek-ai/dsh-subprocess": "^0.0.1",
     "@deepseek-ai/dsh-system-prompt": "^0.0.1",
     "@deepseek-ai/dsh-token-meter": "^0.0.1",
     "@deepseek-ai/dsh-tools": "^0.0.1",
@@ -82,6 +83,7 @@
     "@deepseek-ai/dsh-session-reference": "workspace:^",
     "@deepseek-ai/dsh-session-title": "workspace:^",
     "@deepseek-ai/dsh-skill": "workspace:^",
+    "@deepseek-ai/dsh-subprocess": "workspace:^",
     "@deepseek-ai/dsh-system-prompt": "workspace:^",
     "@deepseek-ai/dsh-token-meter": "workspace:^",
     "@deepseek-ai/dsh-tool-cordis": "workspace:^",

+ 2 - 4
packages/ui/tui/src/chat/helpers.ts

@@ -16,6 +16,7 @@ import {
   visibleWidth,
 } from '@earendil-works/pi-tui'
 import type { Session } from '@deepseek-ai/dsh-session'
+import { scrubbedParentEnv } from '@deepseek-ai/dsh-subprocess'
 
 /** Editor that shows a placeholder without making it editable content. */
 export class HintEditor extends Editor {
@@ -65,13 +66,10 @@ export function formatCwd(cwd: string | undefined): string {
  */
 export function gitBranch(cwd: string): string | undefined {
   try {
-    const env = Object.fromEntries(
-      Object.entries(process.env).filter(([name]) => !/(?:KEY|SECRET|TOKEN)/iu.test(name)),
-    )
     const branch = execFileSync('git', ['branch', '--show-current'], {
       cwd,
       encoding: 'utf8',
-      env,
+      env: scrubbedParentEnv(),
       stdio: ['ignore', 'pipe', 'ignore'],
       timeout: 1_000,
     }).trim()

+ 29 - 0
packages/ui/tui/tests/chat-helpers.spec.ts

@@ -0,0 +1,29 @@
+import { execFileSync } from 'node:child_process'
+import { afterEach, describe, expect, it, vi } from 'vitest'
+import { gitBranch } from '../src/chat/helpers.ts'
+
+vi.mock('node:child_process', () => ({
+  execFileSync: vi.fn(() => 'main\n'),
+}))
+
+afterEach(() => {
+  vi.unstubAllEnvs()
+  vi.clearAllMocks()
+})
+
+describe('chat helpers', () => {
+  it('scrubs ambient credentials and DSH names from the Git child', () => {
+    vi.stubEnv('TUI_TEST_PASSWORD', 'ambient-password')
+    vi.stubEnv('DSH_TUI_TEST_FLAG', 'ambient-harness-state')
+    expect(gitBranch('/workspace')).toBe('main')
+    const call = vi.mocked(execFileSync).mock.calls[0] as unknown as [
+      string,
+      string[],
+      { env: NodeJS.ProcessEnv },
+    ]
+    expect(call[0]).toBe('git')
+    expect(call[1]).toEqual(['branch', '--show-current'])
+    expect(call[2].env).not.toHaveProperty('TUI_TEST_PASSWORD')
+    expect(call[2].env).not.toHaveProperty('DSH_TUI_TEST_FLAG')
+  })
+})

+ 3 - 0
packages/ui/tui/tsconfig.json

@@ -56,6 +56,9 @@
     {
       "path": "../../skill/skill"
     },
+    {
+      "path": "../../subprocess/subprocess"
+    },
     {
       "path": "../user-interaction"
     },

+ 3 - 0
pnpm-lock.yaml

@@ -4611,6 +4611,9 @@ importers:
       '@deepseek-ai/dsh-skill':
         specifier: workspace:^
         version: link:../../skill/skill
+      '@deepseek-ai/dsh-subprocess':
+        specifier: workspace:^
+        version: link:../../subprocess/subprocess
       '@deepseek-ai/dsh-system-prompt':
         specifier: workspace:^
         version: link:../../core/system-prompt