mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
Merge pull request #11526 from Kilo-Org/truth-athlete
fix(cli): avoid encoded PowerShell commands
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Run Windows PowerShell tool commands without `-EncodedCommand` to reduce antivirus false positives.
|
||||
@@ -1,16 +1,124 @@
|
||||
import { Buffer } from "node:buffer"
|
||||
|
||||
export function args(command: string) {
|
||||
return ["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", encoded(command)]
|
||||
return ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", script(command)]
|
||||
}
|
||||
|
||||
function encoded(command: string) {
|
||||
const payload = Buffer.from(command, "utf8").toString("base64")
|
||||
return Buffer.from(
|
||||
`[Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false);
|
||||
const setup = `[Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false);
|
||||
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false);
|
||||
$OutputEncoding = [Console]::OutputEncoding;
|
||||
& ([scriptblock]::Create([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${payload}'))))`,
|
||||
"utf16le",
|
||||
).toString("base64")
|
||||
`
|
||||
|
||||
function script(command: string) {
|
||||
const pos = prologue(command)
|
||||
const head = command.slice(0, pos)
|
||||
const body = command.slice(pos)
|
||||
const gap = head && !/[;\r\n]\s*$/.test(head) ? "\n" : ""
|
||||
return `${head}${gap}${setup}${body}`
|
||||
}
|
||||
|
||||
function prologue(command: string) {
|
||||
const pos = scan(command, 0)
|
||||
const attr = attrs(command, pos)
|
||||
const body = command.slice(attr)
|
||||
const match = /^param\s*\(/i.exec(body)
|
||||
if (!match) return pos
|
||||
|
||||
const start = attr + match[0].lastIndexOf("(")
|
||||
const end = block(command, start, "(", ")")
|
||||
if (end === undefined) return pos
|
||||
return end
|
||||
}
|
||||
|
||||
function attrs(command: string, start: number) {
|
||||
let pos = start
|
||||
while (pos < command.length) {
|
||||
const next = scan(command, pos)
|
||||
if (command[next] !== "[") return next
|
||||
const end = block(command, next, "[", "]")
|
||||
if (end === undefined) return start
|
||||
pos = end
|
||||
}
|
||||
return pos
|
||||
}
|
||||
|
||||
function scan(command: string, start: number) {
|
||||
let pos = start
|
||||
while (pos < command.length) {
|
||||
const next = trivia(command, pos)
|
||||
if (next !== pos) {
|
||||
pos = next
|
||||
continue
|
||||
}
|
||||
const end = line(command, pos)
|
||||
const value = command.slice(pos, end)
|
||||
if (/^using\s+(?:assembly|module|namespace|type)\b/i.test(value)) {
|
||||
pos = end
|
||||
continue
|
||||
}
|
||||
return pos
|
||||
}
|
||||
return pos
|
||||
}
|
||||
|
||||
function trivia(command: string, start: number) {
|
||||
let pos = start
|
||||
while (pos < command.length) {
|
||||
while (/\s/.test(command[pos] ?? "")) pos++
|
||||
if (command[pos] === "#") {
|
||||
pos = line(command, pos)
|
||||
continue
|
||||
}
|
||||
if (command.startsWith("<#", pos)) {
|
||||
const end = command.indexOf("#>", pos + 2)
|
||||
if (end === -1) return command.length
|
||||
pos = end + 2
|
||||
continue
|
||||
}
|
||||
return pos
|
||||
}
|
||||
return pos
|
||||
}
|
||||
|
||||
function line(command: string, start: number) {
|
||||
const index = command.indexOf("\n", start)
|
||||
if (index === -1) return command.length
|
||||
return index + 1
|
||||
}
|
||||
|
||||
function block(command: string, start: number, open: string, close: string) {
|
||||
let depth = 0
|
||||
let quote: string | undefined
|
||||
for (let pos = start; pos < command.length; pos++) {
|
||||
const char = command[pos]
|
||||
if (quote) {
|
||||
if (quote === "'" && char === "'" && command[pos + 1] === "'") {
|
||||
pos++
|
||||
continue
|
||||
}
|
||||
if (quote === '"' && char === "`") {
|
||||
pos++
|
||||
continue
|
||||
}
|
||||
if (char === quote) quote = undefined
|
||||
continue
|
||||
}
|
||||
if (char === "'" || char === '"') {
|
||||
quote = char
|
||||
continue
|
||||
}
|
||||
if (command.startsWith("<#", pos)) {
|
||||
const end = command.indexOf("#>", pos + 2)
|
||||
if (end === -1) return
|
||||
pos = end + 1
|
||||
continue
|
||||
}
|
||||
if (char === "#") {
|
||||
pos = line(command, pos) - 1
|
||||
continue
|
||||
}
|
||||
if (char === open) depth++
|
||||
if (char === close) {
|
||||
depth--
|
||||
if (depth === 0) return pos + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import * as PowerShell from "@/kilocode/shell/shell" // kilocode_change - encoded PowerShell args
|
||||
import * as PowerShell from "@/kilocode/shell/shell" // kilocode_change - PowerShell args
|
||||
import { lazy } from "@/util/lazy"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { which } from "@/util/which"
|
||||
@@ -189,7 +189,7 @@ export function args(file: string, command: string, cwd: string) {
|
||||
]
|
||||
}
|
||||
if (n === "cmd") return ["/c", command]
|
||||
if (ps(file)) return PowerShell.args(command) // kilocode_change - encoded PowerShell args
|
||||
if (ps(file)) return PowerShell.args(command) // kilocode_change - PowerShell args
|
||||
return ["-c", command]
|
||||
}
|
||||
|
||||
|
||||
@@ -312,7 +312,7 @@ const ask = Effect.fn("ShellTool.ask")(function* (
|
||||
|
||||
function cmd(shell: string, command: string, cwd: string, env: NodeJS.ProcessEnv) {
|
||||
if (process.platform === "win32" && Shell.ps(shell)) {
|
||||
// kilocode_change start - encoded PowerShell args
|
||||
// kilocode_change start - PowerShell args
|
||||
return ChildProcess.make(shell, Shell.args(shell, command, cwd), {
|
||||
// kilocode_change end
|
||||
cwd,
|
||||
|
||||
@@ -1,31 +1,48 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Buffer } from "node:buffer"
|
||||
import * as PowerShell from "@/kilocode/shell/shell"
|
||||
import { Shell } from "@/shell/shell"
|
||||
|
||||
const command = `Write-Output "こんにちは 😀"; Write-Output '$value'; Write-Output \`tick\`
|
||||
Write-Output "done"`
|
||||
|
||||
function script(args: string[]) {
|
||||
return Buffer.from(args[4], "base64").toString("utf16le")
|
||||
}
|
||||
|
||||
describe("PowerShell arguments", () => {
|
||||
test("transports commands through UTF-8 inside EncodedCommand", () => {
|
||||
test("transports commands in plaintext with UTF-8 console setup", () => {
|
||||
const args = PowerShell.args(command)
|
||||
const value = script(args)
|
||||
const payload = value.match(/FromBase64String\('([^']+)'\)/)?.[1]
|
||||
const value = args[4]
|
||||
|
||||
expect(args.slice(0, 4)).toEqual(["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand"])
|
||||
expect(args.slice(0, 4)).toEqual(["-NoLogo", "-NoProfile", "-NonInteractive", "-Command"])
|
||||
expect(args).toHaveLength(5)
|
||||
expect(value).toContain("[Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false)")
|
||||
expect(value).toContain("[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)")
|
||||
expect(value).toContain("$OutputEncoding = [Console]::OutputEncoding")
|
||||
expect(payload).toBeDefined()
|
||||
expect(Buffer.from(payload!, "base64").toString("utf8")).toBe(command)
|
||||
expect(value).toContain(command)
|
||||
expect(args).not.toContain("-EncodedCommand")
|
||||
expect(value).not.toContain("FromBase64String")
|
||||
})
|
||||
|
||||
test.each(["powershell", "pwsh"])("routes %s through the Kilo argument builder", (shell) => {
|
||||
expect(Shell.args(shell, command, "/tmp")).toEqual(PowerShell.args(command))
|
||||
})
|
||||
|
||||
test("keeps script-level prologue before the UTF-8 console setup", () => {
|
||||
const input = `#requires -Version 5.1
|
||||
using namespace System.Text
|
||||
<#
|
||||
Block comment before attributes.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
[OutputType([string])]
|
||||
param(
|
||||
[string]$Name
|
||||
)
|
||||
Write-Output $Name`
|
||||
const value = PowerShell.args(input)[4]
|
||||
const setup = value.indexOf("[Console]::InputEncoding")
|
||||
|
||||
expect(value.startsWith("#requires -Version 5.1")).toBe(true)
|
||||
expect(value.slice(0, setup)).toContain("[CmdletBinding()]")
|
||||
expect(value.slice(0, setup)).toContain("[OutputType([string])]")
|
||||
expect(value.slice(0, setup)).toContain("param(")
|
||||
expect(setup).toBeLessThan(value.indexOf("Write-Output"))
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user