mirror of
https://github.com/anomalyco/opencode.git
synced 2026-10-25 20:40:04 +00:00
470 lines
22 KiB
TypeScript
470 lines
22 KiB
TypeScript
import { describe, expect, test } from "bun:test"
|
||
import { ShellScan } from "../../src/shell/scan.js"
|
||
|
||
describe("PowerShell scanner safety", () => {
|
||
test("backticks in single quotes cannot hide subsequent executions", () => {
|
||
const result = ShellScan.scanPowerShell("Write-Output '`'; Remove-Item victim; Write-Output '`'")
|
||
expect(result.kind).toBe("scanned")
|
||
if (result.kind === "opaque") return
|
||
expect(result.commands.map((command) => command.words)).toEqual([
|
||
["Write-Output", "`"],
|
||
["Remove-Item", "victim"],
|
||
["Write-Output", "`"],
|
||
])
|
||
})
|
||
|
||
test.each([" ", "\t", "\v", "\f", "\u00a0", "\u2000", "\u2028", "\u2029", "\n", "\r", "\r\n"])(
|
||
"recognizes comments after escaped whitespace at a token boundary %j",
|
||
(space) => {
|
||
expect(ShellScan.scanPowerShell(`Write-Output \`${space}#'\nRemove-Item victim\n#'`)).toMatchObject({
|
||
kind: "scanned",
|
||
commands: [{ words: ["Write-Output"] }, { words: ["Remove-Item", "victim"] }],
|
||
})
|
||
for (const redirect of ["2>&1", "6>&1", "*>&1"]) {
|
||
expect(
|
||
ShellScan.scanPowerShell(`% { Write-Output ${redirect}\`${space}#} '\nRemove-Item victim\n} #'`),
|
||
).toMatchObject({
|
||
kind: "scanned",
|
||
commands: [
|
||
{ words: ["%", expect.any(String)] },
|
||
{ words: ["Write-Output"] },
|
||
{ words: ["Remove-Item", "victim"] },
|
||
],
|
||
})
|
||
}
|
||
},
|
||
)
|
||
|
||
test.each([" ", "\t", "\v", "\f", "\u00a0", "\u2000", "\u2028", "\u2029"])(
|
||
"keeps backtick whitespace inside strings literal: %j",
|
||
(space) => {
|
||
const result = ShellScan.scanPowerShell(`% { Write-Output '\`${space}#literal' "\`${space}#literal" }`)
|
||
expect(result.kind).toBe("scanned")
|
||
if (result.kind === "opaque") return
|
||
expect(result.commands[1]?.words).toEqual(["Write-Output", `\`${space}#literal`, `${space}#literal`])
|
||
},
|
||
)
|
||
|
||
test.each(["x2>&1", "x6>&1"])("keeps embedded greater-than text distinct from redirects: %s", (token) => {
|
||
expect(ShellScan.scanPowerShell(`Write-Output ${token}#'\nRemove-Item victim\n#'`)).toMatchObject({
|
||
kind: "scanned",
|
||
commands: [
|
||
{ resource: `Write-Output ${token.slice(0, 3)}`, words: ["Write-Output", token.slice(0, 3)] },
|
||
{ words: ["Remove-Item", "victim"] },
|
||
],
|
||
})
|
||
})
|
||
|
||
test.each([
|
||
"1",
|
||
",1",
|
||
"+1",
|
||
"-1",
|
||
".1",
|
||
"0x1",
|
||
"0b1",
|
||
"1L",
|
||
"1kb",
|
||
"1.0",
|
||
"1+1",
|
||
"1..2",
|
||
"-not 1",
|
||
"'x' -eq 1",
|
||
'"x" -eq 1',
|
||
"{} -eq 1",
|
||
])("recognizes expression-mode comments without inventing command heads: %s", (expression) => {
|
||
expect(ShellScan.scanPowerShell(`${expression}#'\nRemove-Item victim\n#'`)).toMatchObject({
|
||
kind: "scanned",
|
||
commands: [{ words: ["Remove-Item", "victim"] }],
|
||
})
|
||
expect(ShellScan.scanPowerShell(`% { ${expression}#} '\nRemove-Item victim\n} #'`)).toMatchObject({
|
||
kind: "scanned",
|
||
commands: [{ words: ["%", expect.any(String)] }, { words: ["Remove-Item", "victim"] }],
|
||
})
|
||
})
|
||
|
||
test.each(["\n", "\r", "\r\n"])("ends nested block comments at %j", (newline) => {
|
||
const result = ShellScan.scanPowerShell(`ForEach-Object { # } ignored${newline}Remove-Item victim }`)
|
||
expect(result.kind).toBe("scanned")
|
||
if (result.kind === "opaque") return
|
||
expect(result.commands.map((command) => command.words[0])).toEqual(["ForEach-Object", "Remove-Item"])
|
||
})
|
||
|
||
test.each([
|
||
'ForEach-Object { Write-Output "it\'s } literal"; Remove-Item victim }',
|
||
'ForEach-Object { Write-Output "a\'b{c}"; Remove-Item victim }',
|
||
"ForEach-Object { Write-Output '`'; Remove-Item victim }",
|
||
'ForEach-Object { Write-Output "a`\"}b"; Remove-Item victim }',
|
||
'ForEach-Object { Write-Output "a""}b"; Remove-Item victim }',
|
||
])("keeps block delimiters inside strings: %s", (input) => {
|
||
const result = ShellScan.scanPowerShell(input)
|
||
expect(result.kind).toBe("scanned")
|
||
if (result.kind === "opaque") return
|
||
expect(result.commands.map((command) => command.words[0])).toEqual([
|
||
"ForEach-Object",
|
||
"Write-Output",
|
||
"Remove-Item",
|
||
])
|
||
})
|
||
|
||
test("preserves doubled double quotes in argument values", () => {
|
||
const result = ShellScan.scanPowerShell('Write-Output "a""b"')
|
||
expect(result).toMatchObject({
|
||
kind: "scanned",
|
||
commands: [{ resource: 'Write-Output "a""b"', words: ["Write-Output", 'a"b'] }],
|
||
})
|
||
})
|
||
|
||
test.each(["'safe'", '"safe"', "{ Get-Item x }"])(
|
||
"recognizes comments after complete literal tokens: %s",
|
||
(argument) => {
|
||
const result = ShellScan.scanPowerShell(`Write-Output ${argument}# '\nRemove-Item victim\n# '`)
|
||
expect(result.kind).toBe("scanned")
|
||
if (result.kind === "opaque") return
|
||
expect(result.commands.some((command) => command.words[0] === "Remove-Item")).toBe(true)
|
||
expect(result.commands[0]?.resource).toBe(`Write-Output ${argument}`)
|
||
},
|
||
)
|
||
|
||
test("keeps generic-token quotes distinct from standalone strings", () => {
|
||
expect(ShellScan.scanPowerShell("Write-Output pre'safe'#literal; Get-Item x")).toMatchObject({
|
||
kind: "scanned",
|
||
commands: [
|
||
{ resource: "Write-Output pre'safe'#literal", words: ["Write-Output", "presafe#literal"] },
|
||
{ resource: "Get-Item x", words: ["Get-Item", "x"] },
|
||
],
|
||
})
|
||
expect(ShellScan.scanPowerShell("Write-Output 'safe'tail")).toMatchObject({
|
||
kind: "scanned",
|
||
commands: [{ resource: "Write-Output 'safe'tail", words: ["Write-Output", "safe", "tail"] }],
|
||
})
|
||
})
|
||
|
||
test("block matching agrees with tokenization after a merging redirect", () => {
|
||
const result = ShellScan.scanPowerShell("% { Get-Item x 2>&1# } '\nRemove-Item victim\n} # '")
|
||
expect(result.kind).toBe("scanned")
|
||
if (result.kind === "opaque") return
|
||
expect(result.commands.map((command) => command.words[0])).toEqual(["%", "Get-Item", "Remove-Item"])
|
||
})
|
||
|
||
test.each([
|
||
'cmd.exe \u0085--% "ignored\nRemove-Item victim\n# "',
|
||
"Write-Output \u2018a'; Remove-Item victim; Write-Output 'b\u2019",
|
||
'Write-Output \u201ca"; Remove-Item victim; Write-Output "b\u201d',
|
||
])("refuses unsupported lexical modes: %s", (input) => {
|
||
expect(ShellScan.scanPowerShell(input).kind).toBe("opaque")
|
||
})
|
||
|
||
test.each(["\n", "\r", "\r\n"])("ends stop-parsing at %j even adjacent to completed tokens", (newline) => {
|
||
for (const argument of ["'x'", '"x"', "{}"]) {
|
||
const result = ShellScan.scanPowerShell(`Write-Output ${argument}--% '${newline}Remove-Item victim${newline}# '`)
|
||
expect(result).toMatchObject({
|
||
kind: "scanned",
|
||
commands: [{ words: ["Write-Output", expect.any(String), "--%", "'"] }, { words: ["Remove-Item", "victim"] }],
|
||
})
|
||
const block = ShellScan.scanPowerShell(
|
||
`% { Write-Output ${argument}--%} '${newline}Remove-Item victim${newline}} # '`,
|
||
)
|
||
expect(block.kind).toBe("scanned")
|
||
if (block.kind === "scanned") expect(block.commands.at(-1)?.words).toEqual(["Remove-Item", "victim"])
|
||
}
|
||
})
|
||
|
||
test.each([
|
||
"& '' victim",
|
||
"Write-Output ok > > out",
|
||
"Write-Output ok > 2>&1",
|
||
"Write-Output ok 2>&",
|
||
"Write-Output ok 2>&2",
|
||
"Write-Output ok 1>&1",
|
||
"Write-Output ok >&1",
|
||
"Write-Output ok 7> out",
|
||
"Write-Output ok 2>>&1",
|
||
"Write-Output ok > # missing target\nRemove-Item victim",
|
||
"Write-Output ok | # missing pipeline\n",
|
||
"| Remove-Item victim",
|
||
"&& Remove-Item victim",
|
||
"& & Remove-Item victim",
|
||
"&",
|
||
".",
|
||
"Write-Output ok; &",
|
||
"Write-Output ok; .",
|
||
"Set-Location \u2013StackName old",
|
||
"Set-Location \u2014StackName old",
|
||
"Set-Location \u2015StackName old",
|
||
])("reports malformed or unsupported lexical syntax: %s", (input) => {
|
||
expect(ShellScan.scanPowerShell(input).kind).toBe("opaque")
|
||
})
|
||
|
||
test.each(["Set-Location", "SL", "cd", "chdir", "Push-Location", "pushd", "Microsoft.PowerShell.Management\\sl"])(
|
||
"preserves commands with directory variables for Core policy through %s",
|
||
(head) => {
|
||
expect(ShellScan.scanPowerShell(`${head} $target; Get-Item x`)).toMatchObject({
|
||
kind: "scanned",
|
||
commands: [
|
||
{ resource: `${head} $target`, words: [head, "$target"] },
|
||
{ resource: "Get-Item x", words: ["Get-Item", "x"] },
|
||
],
|
||
})
|
||
expect(ShellScan.scanPowerShell(`${head} $HOME/project; Get-Item x`).kind).toBe("scanned")
|
||
},
|
||
)
|
||
|
||
test.each([
|
||
"Pop-Location",
|
||
"popd",
|
||
"Microsoft.PowerShell.Management\\Pop-Location",
|
||
"Set-Location -",
|
||
"Set-Location +",
|
||
"Set-Location -StackName old",
|
||
"Set-Location -st old",
|
||
"Set-Location -Path:C:relative",
|
||
"Set-Location C:relative",
|
||
"Set-Location Registry::HKEY_CURRENT_USER",
|
||
"Set-Location $HOME/$target",
|
||
])("preserves directory command syntax without deciding directory policy: %s", (input) => {
|
||
expect(ShellScan.scanPowerShell(`${input}; Get-Item x`)).toMatchObject({
|
||
kind: "scanned",
|
||
commands: [
|
||
{ resource: input, words: input.split(" ") },
|
||
{ resource: "Get-Item x", words: ["Get-Item", "x"] },
|
||
],
|
||
})
|
||
})
|
||
|
||
test.each(["%", "?", "foreach", "where", "iex"])("keeps command aliases visible: %s", (head) => {
|
||
const result = ShellScan.scanPowerShell(`Get-Item x | ${head} { Remove-Item victim }`)
|
||
expect(result.kind).toBe("scanned")
|
||
if (result.kind === "opaque") return
|
||
expect(result.commands.map((command) => command.words[0])).toEqual(["Get-Item", head, "Remove-Item"])
|
||
})
|
||
|
||
test.each(["Get-*", "./g?t", "./[gr]it", ".\\script.ps1", "..\\scripts\\run.ps1", "\\\\host\\share\\run.ps1"])(
|
||
"retains command names and paths without resolving them: %s",
|
||
(head) => {
|
||
expect(ShellScan.scanPowerShell(`& ${head} victim`)).toMatchObject({
|
||
kind: "scanned",
|
||
commands: [{ resource: `& ${head} victim`, words: [head, "victim"], rawWords: [head, "victim"] }],
|
||
})
|
||
},
|
||
)
|
||
|
||
test("recursively extracts commands from nested script blocks", () => {
|
||
const result = ShellScan.scanPowerShell("Get-Item x | % { Get-Item y | ? { Remove-Item victim } }")
|
||
expect(result.kind).toBe("scanned")
|
||
if (result.kind === "opaque") return
|
||
expect(result.commands.map((command) => command.words[0])).toEqual([
|
||
"Get-Item",
|
||
"%",
|
||
"Get-Item",
|
||
"?",
|
||
"Remove-Item",
|
||
])
|
||
})
|
||
|
||
test("keeps hashes within words out of block-comment detection", () => {
|
||
const result = ShellScan.scanPowerShell("% { Write-Output a#b }; Remove-Item victim")
|
||
expect(result.kind).toBe("scanned")
|
||
if (result.kind === "opaque") return
|
||
expect(result.commands.map((command) => command.words[0])).toEqual(["%", "Write-Output", "Remove-Item"])
|
||
})
|
||
|
||
test.each([";", "&", "\n", "\r", "\r\n"])("never invents a command after a trailing %j", (separator) => {
|
||
expect(ShellScan.scanPowerShell(`Get-Item x${separator}`)).toMatchObject({
|
||
kind: "scanned",
|
||
commands: [{ resource: "Get-Item x", words: ["Get-Item", "x"] }],
|
||
})
|
||
})
|
||
|
||
test.each(["'victim'", '"victim"', "`victim", "{ Get-Item victim }"])(
|
||
"does not clear a dangling pipeline with a new empty statement: %s",
|
||
(tail) => {
|
||
expect(ShellScan.scanPowerShell(`Get-Item x | ; Write-Output ${tail}`).kind).toBe("opaque")
|
||
},
|
||
)
|
||
|
||
test("scans valid redirects without consuming command arguments", () => {
|
||
for (const redirect of [">", ">>", "1>", "2>>", "3>", "4>", "5>", "6>", "*>", "*>>"]) {
|
||
expect(ShellScan.scanPowerShell(`Get-Item x ${redirect} out.txt | Write-Output done`)).toMatchObject({
|
||
kind: "scanned",
|
||
commands: [
|
||
{ resource: `Get-Item x ${redirect} out.txt`, words: ["Get-Item", "x"] },
|
||
{ resource: "Write-Output done", words: ["Write-Output", "done"] },
|
||
],
|
||
})
|
||
}
|
||
for (const redirect of ["2>&1", "3>&1", "4>&1", "5>&1", "6>&1", "*>&1"]) {
|
||
expect(ShellScan.scanPowerShell(`Get-Item x ${redirect}`)).toMatchObject({
|
||
kind: "scanned",
|
||
commands: [{ resource: `Get-Item x ${redirect}`, words: ["Get-Item", "x"] }],
|
||
})
|
||
}
|
||
})
|
||
|
||
test("bounds script block nesting and input size", () => {
|
||
expect(ShellScan.scanPowerShell("% { ".repeat(33) + "Get-Item x" + " }".repeat(33)).kind).toBe("opaque")
|
||
expect(ShellScan.scanPowerShell(`Write-Output ${"x".repeat(64 * 1024)}`).kind).toBe("opaque")
|
||
})
|
||
|
||
test("scans compound subexpressions with expression-mode comments inside expandable strings", () => {
|
||
const result = ShellScan.scanPowerShell(
|
||
'$x = "$(switch ${x}#)\n{ default { Invoke-ProbeA; Set-Location /outside } })"',
|
||
)
|
||
expect(result).toMatchObject({
|
||
kind: "scanned",
|
||
commands: [
|
||
{ resource: "Invoke-ProbeA", words: ["Invoke-ProbeA"] },
|
||
{ resource: "Set-Location /outside", words: ["Set-Location", "/outside"] },
|
||
],
|
||
})
|
||
})
|
||
|
||
test.each([
|
||
"${x}<# comment #>; Invoke-ProbeA; Set-Location /outside",
|
||
"$(1)<# comment #>; Invoke-ProbeA; Set-Location /outside",
|
||
"@(1)<# comment #>; Invoke-ProbeA; Set-Location /outside",
|
||
"@{a=1}<# comment #>; Invoke-ProbeA; Set-Location /outside",
|
||
"[int]<# comment #>$x = 1; Invoke-ProbeA; Set-Location /outside",
|
||
"$x<# comment #>; Invoke-ProbeA; Set-Location /outside",
|
||
"1<# comment #>; Invoke-ProbeA; Set-Location /outside",
|
||
])("recognizes block comments immediately after expression tokens: %s", (source) => {
|
||
expect(ShellScan.scanPowerShell(source)).toMatchObject({
|
||
kind: "scanned",
|
||
commands: [
|
||
{ resource: "Invoke-ProbeA", words: ["Invoke-ProbeA"] },
|
||
{ resource: "Set-Location /outside", words: ["Set-Location", "/outside"] },
|
||
],
|
||
})
|
||
})
|
||
|
||
test("rejects mid-token here-string openers and preserves subsequent commands", () => {
|
||
expect(ShellScan.scanPowerShell("$x@'\n'; Invoke-ProbeA; Set-Location /outside; '\n'@").kind).toBe("opaque")
|
||
expect(
|
||
ShellScan.scanPowerShell("Write-Output $x@'\n'; Invoke-ProbeA; Set-Location /outside; #\n'@'"),
|
||
).toMatchObject({
|
||
kind: "scanned",
|
||
commands: [
|
||
{ words: ["Write-Output", "$x@\n"] },
|
||
{ resource: "Invoke-ProbeA", words: ["Invoke-ProbeA"] },
|
||
{ resource: "Set-Location /outside", words: ["Set-Location", "/outside"] },
|
||
],
|
||
})
|
||
})
|
||
|
||
test.each([
|
||
"Write-Output [a; Invoke-ProbeA; Set-Location /outside; Write-Output ]",
|
||
'Write-Output "${x"\'}"#"\nInvoke-ProbeA; Set-Location /outside\n# "\'',
|
||
"Write-Output ${a`}'}; Invoke-ProbeA; Set-Location /outside; #'",
|
||
".'Write-Output'# '\nInvoke-ProbeA; Set-Location /outside\n# '",
|
||
"$x > $null#'\n'; Invoke-ProbeA; Set-Location /outside; #'",
|
||
"$x>$null#'\n'; Invoke-ProbeA; Set-Location /outside; #'",
|
||
"Write-Output --% arg && Invoke-ProbeA; Set-Location /outside",
|
||
"Write-Output > --% out.txt; Invoke-ProbeA; Set-Location /outside",
|
||
"Write-Output `t#'\n'; Invoke-ProbeA; Set-Location /outside; #'",
|
||
"[CmdletBinding()] param($x) Invoke-ProbeA; Set-Location /outside",
|
||
"function f#a { Invoke-ProbeA; Set-Location /outside }; f#a",
|
||
'"$("a""; Invoke-ProbeA; Set-Location /outside; "b"")"',
|
||
'$x = "$("a""; Invoke-ProbeA; Set-Location /outside; "b"")"',
|
||
"${a`u{0041}'}; Invoke-ProbeA; Set-Location /outside; #'",
|
||
'"${a`u{0041}" # }; $(Invoke-ProbeA); $(Set-Location /outside)"',
|
||
'@"\n${a`u{0041}\n"@; \' # } $(Invoke-ProbeA); $(Set-Location /outside)\n"@#\'',
|
||
"$probe[-$(Invoke-ProbeA)]; $probe[1+$(Set-Location /outside)]",
|
||
"function else {}\nelse\nInvoke-ProbeA\nSet-Location /outside\n{}",
|
||
"function catch {}\ncatch\nInvoke-ProbeA\nSet-Location /outside\n{}",
|
||
"switch ('x') {\n a#'\n ' {} default { Invoke-ProbeA; Set-Location /outside } # '\n}",
|
||
"data -SupportedCommand a<#'#> ' {}; Invoke-ProbeA; Set-Location /outside # ' {}",
|
||
"if ($false) { switch -File $global:probe[0#'\n] { default {} } }; Invoke-ProbeA; Set-Location /outside # ' {} }",
|
||
"if ($false) { switch -File $.a[a<#'#>'] { default {} } }; Invoke-ProbeA; Set-Location /outside # '] {} }",
|
||
"Write-Output 'a'b[a<#'#>']; Invoke-ProbeA; Set-Location /outside; # ']",
|
||
"try { throw 1 } catch [Exception<#'#>] { Invoke-ProbeA; Set-Location /outside } # ' {}",
|
||
"Write-Output --`%('| Invoke-ProbeA; Set-Location /outside #')",
|
||
"if ($false) { Set-Location --`%('\n} if ($true) { Invoke-ProbeA; Set-Location /outside # ')\n}",
|
||
"if ($false) { $.a<#'#>' }; Invoke-ProbeA; Set-Location /outside; #' }",
|
||
"-$n#'\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
"--$n<#'#>; Invoke-ProbeA; Set-Location /outside; #'",
|
||
'Invoke-ProbeA "$( "a`"; Invoke-ProbeB; Set-Location /outside; `"b" )"',
|
||
'Invoke-ProbeA "$( `"\'"; Invoke-ProbeB; Set-Location /outside; "\'`" )"',
|
||
'Invoke-ProbeA "$( "a`""""; Invoke-ProbeB; Set-Location /outside; "c`"""" )"',
|
||
'Invoke-ProbeA "$( ""$( """"\'""""; Invoke-ProbeB; Set-Location /outside; """"\'"""" )"" )"',
|
||
'Invoke-ProbeA "$( Invoke-ProbeA @"\n""@; Invoke-ProbeB; Set-Location /outside; \'\n"@#\'\n)"',
|
||
'Invoke-ProbeA "$( \'(\' )"#" )"; Invoke-ProbeB; Set-Location /outside',
|
||
'Invoke-ProbeA "$( <# ( #> )"#" )"; Invoke-ProbeB; Set-Location /outside',
|
||
'Invoke-ProbeA "$( # (\n)"#" )"; Invoke-ProbeB; Set-Location /outside',
|
||
'Invoke-ProbeA "$( ${(} )"#" )"; Invoke-ProbeB; Set-Location /outside',
|
||
'Invoke-ProbeA "$( "(" )"#" )"; Invoke-ProbeB; Set-Location /outside',
|
||
"@\"\n$( '(' )\n\"@ ' )\n\"@\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
"Write-Output arg`\r\nInvoke-ProbeA\r\nSet-Location /outside",
|
||
"Write-Output arg`\r\n#'\r\nInvoke-ProbeA; Set-Location /outside\r\n#'",
|
||
'@"\n`\r\n"@; Invoke-ProbeA; Set-Location /outside; \'\r\n"@#\'',
|
||
"switch ('x') {\n $a#'\n ' {} default { Invoke-ProbeA; Set-Location /outside } # '\n}",
|
||
"switch ('x') {\n 1#'\n ' {} default { Invoke-ProbeA; Set-Location /outside } # '\n}",
|
||
"switch ('x') {\n ${a}b#'\n ' {} default { Invoke-ProbeA; Set-Location /outside } # '\n}",
|
||
"switch ('x') {\n $a<#'#>' {} default { Invoke-ProbeA; Set-Location /outside } # '\n}",
|
||
"if ($false) { switch -File @{} { a {} --% {} } }; Invoke-ProbeA; Set-Location /outside <#\n} } #>",
|
||
"- 1#'\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
"- $n#'\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
"-- $n#'\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
"-[int]$n#'\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
"--[int]$n#'\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
"$:a = Invoke-ProbeA; $:b = Set-Location /outside",
|
||
"$α = Invoke-ProbeA; $β = Set-Location /outside",
|
||
"foreach ($:a in Set-Location /outside) { Invoke-ProbeA }",
|
||
"foreach ($α in Set-Location /outside) { Invoke-ProbeA }",
|
||
"$:a#'\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
"$α#'\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
"$:a = @(1); Write-Output $:a[0]#'\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
"$α = @(1); Write-Output $α[0]#'\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
"Write-Output $?[a<#'#>']; Invoke-ProbeA; Set-Location /outside; # ']",
|
||
"Write-Output $?a[a<#'#>']; Invoke-ProbeA; Set-Location /outside; # ']",
|
||
"Write-Output $?.a[a<#'#>']; Invoke-ProbeA; Set-Location /outside; # ']",
|
||
"Write-Output ${a}::b[a<#'#>']; Invoke-ProbeA; Set-Location /outside; # ']",
|
||
"Write-Output $a..b[a<#'#>']; Invoke-ProbeA; Set-Location /outside; # ']",
|
||
"function f ([a#]$x) {}; Invoke-ProbeA; Set-Location /outside <#\n]) {} #>",
|
||
"if ($false) { [a#]$x }; Invoke-ProbeA; Set-Location /outside <#\n] } #>",
|
||
"if ($false) { $x.Foo[a#]() }; Invoke-ProbeA; Set-Location /outside <#\n] } #>",
|
||
"try { } catch [a#] { Invoke-ProbeA; Set-Location /outside } <#\n] {} #>",
|
||
"Write-Output a,#'\nb; Invoke-ProbeA; Set-Location /outside; #'",
|
||
"Write-Output a,<#'#>b; Invoke-ProbeA; Set-Location /outside; #'",
|
||
"Write-Output a,@'\n'\n'@; Invoke-ProbeA; Set-Location /outside; #'",
|
||
"Write-Output a,@{k=1#'\n}; Invoke-ProbeA; Set-Location /outside; #'}",
|
||
"Write-Output a,$probe[0]#'\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
'Invoke-ProbeA "$( "a``""; Invoke-ProbeB; Set-Location /outside; `"c``"" )"',
|
||
'Invoke-ProbeA "$( "a``""; Invoke-ProbeB; Set-Location /outside; ""c``"" )"',
|
||
'Invoke-ProbeA "$( `"a``""; Invoke-ProbeB; Set-Location /outside; `"c``"" )"',
|
||
'Invoke-ProbeA "$( Write-Output ``"a; Invoke-ProbeB; Set-Location /outside; Write-Output ``" )"',
|
||
'Invoke-ProbeA "$( Invoke-ProbeA --% ""`" && Set-Location /outside\n)"',
|
||
"Write-Output a, --% ; Invoke-ProbeA; Set-Location /outside",
|
||
"Write-Output a,\n--% ; Invoke-ProbeA; Set-Location /outside",
|
||
"Write-Output a, --`% ; Invoke-ProbeA; Set-Location /outside",
|
||
"Write-Output a,<##>--% ; Invoke-ProbeA; Set-Location /outside",
|
||
"Write-Output -Param: --% ; Invoke-ProbeA; Set-Location /outside",
|
||
"Write-Output -Param:<##>--% ; Invoke-ProbeA; Set-Location /outside",
|
||
"Write-Output a,`\r\n1# ; Invoke-ProbeA; Set-Location /outside",
|
||
"Write-Output a,`\r\n$x# ; Invoke-ProbeA; Set-Location /outside",
|
||
"Write-Output a,`\n\n1# ; Invoke-ProbeA; Set-Location /outside",
|
||
"Write-Output a,` #'\nb; Invoke-ProbeA; Set-Location /outside\n#'",
|
||
"Write-Output a,` <#'#>b; Invoke-ProbeA; Set-Location /outside; #'",
|
||
"Write-Output a,` @'\n'\n'@; Invoke-ProbeA; Set-Location /outside; #'",
|
||
"Write-Output a,` @{k=1#'\n}; Invoke-ProbeA; Set-Location /outside; #'}",
|
||
"Write-Output x,'a'[0]#'\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
"Write-Output x,\"a\"[0]#'\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
"Write-Output x,'a'#'\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
"Write-Output x,'a'<#'#>; Invoke-ProbeA; Set-Location /outside; #'",
|
||
"Write-Output $probe.Length#'\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
"Write-Output $probe.Length<#'#>; Invoke-ProbeA; Set-Location /outside; #'",
|
||
"Write-Output $probe.<#'#>Length; Invoke-ProbeA; Set-Location /outside; #'",
|
||
"Write-Output $probe.#'\nLength; Invoke-ProbeA; Set-Location /outside\n#'",
|
||
"Write-Output (1).ToString#'\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
"Write-Output (1)?.ToString#'\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
"Write-Output 'a'.Length#'\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
"Write-Output ${probe}.Length#'\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
"Write-Output x,$probe.Length#'\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
"Write-Output x,${probe}.Length#'\nInvoke-ProbeA; Set-Location /outside\n#'",
|
||
])("does not hide commands across lexical mode boundaries: %s", (source) => {
|
||
const result = ShellScan.scanPowerShell(source)
|
||
expect(result.kind).toBe("scanned")
|
||
if (result.kind === "opaque") return
|
||
expect(result.commands.map((command) => command.words[0])).toContain("Invoke-ProbeA")
|
||
expect(result.commands.map((command) => command.words)).toContainEqual(["Set-Location", "/outside"])
|
||
})
|
||
})
|