fix(security-review): Handle null shell output (#231)
Normalize shell command stdout and stderr before the prompt-shell path and shared tool-result mappers use string operations. This prevents /security-review from crashing when a shell tool returns null output fields and adds regression coverage for both direct mapper calls and prompt generation. Fixes #165 Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
77
src/utils/promptShellExecution.test.ts
Normal file
77
src/utils/promptShellExecution.test.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { afterEach, expect, test } from 'bun:test'
|
||||
import { getEmptyToolPermissionContext } from '../Tool.js'
|
||||
import { BashTool } from '../tools/BashTool/BashTool.js'
|
||||
import { executeShellCommandsInPrompt } from './promptShellExecution.js'
|
||||
|
||||
const originalCall = BashTool.call
|
||||
const originalMapToolResultToToolResultBlockParam =
|
||||
BashTool.mapToolResultToToolResultBlockParam
|
||||
|
||||
afterEach(() => {
|
||||
BashTool.call = originalCall
|
||||
BashTool.mapToolResultToToolResultBlockParam =
|
||||
originalMapToolResultToToolResultBlockParam
|
||||
})
|
||||
|
||||
test('executeShellCommandsInPrompt normalizes null shell output', async () => {
|
||||
let normalizedResult:
|
||||
| { stdout: string; stderr: string; interrupted: boolean }
|
||||
| undefined
|
||||
|
||||
BashTool.call = (async () => ({
|
||||
data: {
|
||||
stdout: null,
|
||||
stderr: null,
|
||||
interrupted: false,
|
||||
},
|
||||
})) as unknown as typeof BashTool.call
|
||||
|
||||
BashTool.mapToolResultToToolResultBlockParam = (result, toolUseID) => {
|
||||
normalizedResult = result as {
|
||||
stdout: string
|
||||
stderr: string
|
||||
interrupted: boolean
|
||||
}
|
||||
return originalMapToolResultToToolResultBlockParam(result, toolUseID)
|
||||
}
|
||||
|
||||
await executeShellCommandsInPrompt(
|
||||
'```!\ngit status\n```',
|
||||
{
|
||||
abortController: new AbortController(),
|
||||
options: {
|
||||
commands: [],
|
||||
debug: false,
|
||||
mainLoopModel: 'sonnet',
|
||||
tools: new Map(),
|
||||
verbose: false,
|
||||
thinkingConfig: { type: 'disabled' },
|
||||
mcpClients: [],
|
||||
mcpResources: {},
|
||||
isNonInteractiveSession: false,
|
||||
agentDefinitions: {
|
||||
systemDefinitions: [],
|
||||
projectDefinitions: [],
|
||||
userDefinitions: [],
|
||||
},
|
||||
},
|
||||
readFileState: new Map(),
|
||||
getAppState() {
|
||||
return {
|
||||
toolPermissionContext: {
|
||||
...getEmptyToolPermissionContext(),
|
||||
alwaysAllowRules: { command: ['Bash(*)'] },
|
||||
},
|
||||
}
|
||||
},
|
||||
setAppState() {},
|
||||
} as never,
|
||||
'security-review',
|
||||
)
|
||||
|
||||
expect(normalizedResult).toEqual({
|
||||
stdout: '',
|
||||
stderr: '',
|
||||
interrupted: false,
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user