mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 00:02:29 +00:00
* fix(cli): name PowerShell when it strips quotes from JSON flags Windows PowerShell 5.1 does not escape inner quotes when building a native command line, so `--options '["a","b"]'` reaches orca.exe as `--options [a,b]`. The value is correct when printed and damaged by the time argv is parsed, so the resulting "invalid JSON" error blamed the user's input rather than the shell. #16743 recovered this for `--deps`, which is safe only because generated task IDs have a fixed 12-hex grammar. The same mangling hits `--options`, `--payload` and `--result`, and those are NOT safely recoverable: `["1","2"]` and `[1,2]` arrive at argv identically, so a general repair would silently turn strings into numbers. Detect instead. `getOptionalJsonFlag` rejects the damaged shape up front with an error that names the shell and shows the workaround. It fires only when the value is bracketed, quote-free, fails JSON.parse, AND consists entirely of bare tokens that quoting would rescue, so valid JSON is untouched. Also share the generated-id contract: `task-deps-flag` hardcoded /^task_[0-9a-f]{12}$/i, which silently diverges if `generateId`'s byte count changes. It now calls `isGeneratedId`, with a test pinning the two together. Verified on a Windows host. Measured argv, which the new test pins as a fixture: PS_VALUE=["task_b2a580db74d8","task_c3b691ec85e9"] ARGV=["--deps","[task_b2a580db74d8,task_c3b691ec85e9]"] Before: Invalid --options: must be a JSON array of strings After: --options arrived as [a,b], which is not valid JSON. Windows PowerShell 5.1 strips the inner quotes ... * fix(cli): scope JSON-flag detection to genuinely JSON flags Review found the detector wired to two flags that are not JSON: - `orchestration ask --options` is documented `<csv>` and the runtime splits it on commas, so `--options [a,b]` was a legitimate value being rejected. - `task-update --result` is stored verbatim and reused as dispatch failure text; existing tests pass free text, so a bracketed `[ok]` was being rejected. Both revert to `getOptionalStringFlag`. Only `gate-create --options` (`<json_array>`) and `send --payload` (`<json>`) are JSON-parsed and keep it. Three further review fixes: - Objects now require a `key:value` pair per entry. `{a,b}` and `{a:b,c}` were reported as quote-stripped although quoting them cannot produce valid JSON. - The raw value is no longer echoed. A `--payload` can carry secrets and this message reaches `--json` output; the flag name and guidance are enough. - The message hedges the shell attribution. Detection inspects only the value's shape, so it also fires when a macOS/Linux user forgets to quote, where PowerShell is not involved. Verified against a Windows host, all six cases: both JSON flags fire on the mangled shape and pass valid JSON through to the runtime; both non-JSON flags now reach the runtime again; and the secret in `{token:hunter2}` appears zero times in the error output.
54 lines
2.4 KiB
TypeScript
54 lines
2.4 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import { describeQuoteStrippedJsonFlag, looksQuoteStripped } from './quote-stripped-json-flag'
|
|
|
|
describe('quote-stripped JSON flag detection', () => {
|
|
it('flags the exact shape measured from Windows PowerShell 5.1', () => {
|
|
// Measured on a real Windows host: ConvertTo-Json emitted
|
|
// ["task_b2a580db74d8","task_c3b691ec85e9"] and argv received the value below.
|
|
expect(looksQuoteStripped('[task_b2a580db74d8,task_c3b691ec85e9]')).toBe(true)
|
|
expect(looksQuoteStripped('[a,b]')).toBe(true)
|
|
expect(looksQuoteStripped('{a:b}')).toBe(true)
|
|
})
|
|
|
|
it('leaves valid JSON alone', () => {
|
|
expect(looksQuoteStripped('["a","b"]')).toBe(false)
|
|
expect(looksQuoteStripped('{"a":"b"}')).toBe(false)
|
|
expect(looksQuoteStripped('[1,2]')).toBe(false)
|
|
expect(looksQuoteStripped('[]')).toBe(false)
|
|
expect(looksQuoteStripped('{}')).toBe(false)
|
|
})
|
|
|
|
it('does not claim mangling for values quoting would not rescue', () => {
|
|
expect(looksQuoteStripped('not json at all')).toBe(false)
|
|
expect(looksQuoteStripped('[a b, c]')).toBe(false)
|
|
expect(looksQuoteStripped('[a,,b]')).toBe(false)
|
|
})
|
|
|
|
it('requires a key:value pair per entry before calling an object stripped', () => {
|
|
// Quoting these cannot produce a valid object, so they are ordinary invalid JSON.
|
|
expect(looksQuoteStripped('{a,b}')).toBe(false)
|
|
expect(looksQuoteStripped('{a:b,c}')).toBe(false)
|
|
expect(looksQuoteStripped('{:b}')).toBe(false)
|
|
expect(looksQuoteStripped('{a:}')).toBe(false)
|
|
expect(looksQuoteStripped('{a:b}')).toBe(true)
|
|
expect(looksQuoteStripped('{a:b,c:d}')).toBe(true)
|
|
})
|
|
|
|
it('explains the likely cause without echoing the value', () => {
|
|
const message = describeQuoteStrippedJsonFlag('payload', '{secret:hunter2}')
|
|
expect(message).toContain('--payload is not valid JSON')
|
|
expect(message).toContain('PowerShell 5.1')
|
|
expect(message).toContain('$v')
|
|
// A payload can carry secrets, and this reaches --json output.
|
|
expect(message).not.toContain('hunter2')
|
|
expect(message).not.toContain('secret')
|
|
expect(describeQuoteStrippedJsonFlag('options', '["a","b"]')).toBeNull()
|
|
})
|
|
|
|
it('hedges the shell attribution, since it inspects only the value shape', () => {
|
|
// The same shape occurs when a macOS/Linux user simply forgets to quote.
|
|
expect(describeQuoteStrippedJsonFlag('options', '[a,b]')).toContain('If you ran this from')
|
|
})
|
|
})
|