mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Implements docs/rfc/implemented/feature/2026-07-09-bash-backed-grep-glob- discovery.md: model-facing glob/grep in a new @deepseek-ai/dsh-tool-fs-search package, executing fixed ripgrep templates through ctx.bash.resolve/run — not ctx.fs provider methods — so filesystem backends stay free of a search contract and sandboxed/remote executors substitute cleanly. The tools never call ctx.bash.start(); the tool layer owns quoting (one singleQuote safety boundary), rg --json parsing, ItemRetainer/TextRetainer retention, and the first tool-owned ctx.spillFiles.saveText() handoff (item-level retention the generic post-execute spill policy cannot recover). RFC amendments on the way to implemented/: a shared src/search-core.ts (the SEARCH_* vocabulary + bash-run/raw-spill/spill plumbing was byte-identical across both tools — the missed-extraction smell), and a snapshot-gap note: wiring the acp-agent tree changes the assembled prompt, so goldens need a keyed re-record; the spill notice text is pinned by unit tests instead and only the coding-agent example ships the tools for now.
60 lines
2.0 KiB
TypeScript
60 lines
2.0 KiB
TypeScript
/**
|
|
* Unit tests for the shell-quoting safety boundary, plus a REAL round-trip:
|
|
* every adversarial value, quoted, must survive `bash -c "printf '%s' <quoted>"`
|
|
* byte-for-byte — proving the quoting is inert in an actual shell, not just
|
|
* against a mental model of one.
|
|
*/
|
|
|
|
import { describe, expect, it } from 'vitest'
|
|
import { spawnSync } from 'node:child_process'
|
|
import { singleQuote } from '@deepseek-ai/dsh-tool-fs-search'
|
|
|
|
/** Adversarial values a model could pass as pattern / path / include. */
|
|
const HOSTILE: readonly string[] = [
|
|
'plain',
|
|
'with spaces',
|
|
"it's got 'quotes'",
|
|
'"double quoted"',
|
|
'$(rm -rf /tmp/nope)',
|
|
'`touch /tmp/nope`',
|
|
'$HOME and ${PATH}',
|
|
'semi;colon && chain || pipe | bg &',
|
|
'newline\nin the middle',
|
|
'-leading-dash',
|
|
'--leading-double-dash',
|
|
'*?[a-z]{x,y}',
|
|
'!bang',
|
|
'\\backslash\\',
|
|
'~tilde',
|
|
'# not a comment',
|
|
'>redirect <input 2>&1',
|
|
]
|
|
|
|
describe('singleQuote', () => {
|
|
it('wraps a plain value in single quotes', () => {
|
|
expect(singleQuote('abc')).toBe("'abc'")
|
|
})
|
|
|
|
it("rewrites embedded single quotes as '\\''", () => {
|
|
expect(singleQuote("a'b")).toBe("'a'\\''b'")
|
|
expect(singleQuote("''")).toBe("''\\'''\\'''")
|
|
})
|
|
|
|
it.each(HOSTILE.map(value => [JSON.stringify(value), value] as const))(
|
|
'round-trips %s through a real bash -c unchanged',
|
|
(_label, value) => {
|
|
const result = spawnSync('bash', ['-c', `printf '%s' ${singleQuote(value)}`], { encoding: 'utf8' })
|
|
expect(result.status).toBe(0)
|
|
expect(result.stdout).toBe(value)
|
|
},
|
|
)
|
|
|
|
it('a quoted command substitution does not execute (the world stays untouched)', () => {
|
|
const canary = `/tmp/dsh-quote-canary-${process.pid}`
|
|
const result = spawnSync('bash', ['-c', `printf '%s' ${singleQuote(`$(touch ${canary})`)}`], { encoding: 'utf8' })
|
|
expect(result.stdout).toBe(`$(touch ${canary})`)
|
|
// The canary file must NOT exist — the substitution stayed literal.
|
|
expect(spawnSync('test', ['-e', canary]).status).not.toBe(0)
|
|
})
|
|
})
|