mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-16 04:06:21 +00:00
* fix(security): serialize exec approval mutations * fix(security): preserve additive approval writes * test(cli): expect normalized approval shape * fix(security): preserve exec approval compatibility * test(security): exercise locked approval initialization * test(security): mock serialized approval helpers * test(exec): derive enforced command path from plan * fix(gateway): always return approval CAS conflicts * fix(macos): serialize exec approvals writes * fix(security): repair approval build errors * fix(security): serialize exec approval mutations * fix(security): fail closed on approval persistence errors * test(security): cover detached approval persistence failures * fix(security): harden exec approval state * style(macos): format exec approval sources * fix(security): complete exec approval hardening Co-authored-by: Coy Geek <65363919+coygeek@users.noreply.github.com> * fix(macos): preserve approved login-shell semantics * fix(macos): keep login shell approvals one-shot * fix(security): linearize exec authorization Co-authored-by: Coy Geek <65363919+coygeek@users.noreply.github.com> * fix(security): preserve durable approval basis Co-authored-by: Coy Geek <65363919+coygeek@users.noreply.github.com> * fix(security): bind exec grants to current policy Co-authored-by: Coy Geek <65363919+coygeek@users.noreply.github.com> * test(security): fix exec revocation fixtures * test(security): align gateway approval fixtures * fix(macos): return approval decisions * chore(i18n): sync native approval strings * test(security): align approval hardening fixtures * test(node): authorize completed event fixture * test(security): fix approval decision fixtures * test(security): await durable approval visibility * fix(exec): preserve concurrent approval grants * fix(exec): address exact-head CI failures * fix(exec): preserve concurrent approval promotions * fix(exec): make Swift shutdown state explicit * test(macos): handle approval read failures * fix(macos): harden approval socket paths * fix(macos): preserve exact shell payload bytes * test(macos): make approval fixtures explicit * test(macos): fix approval suite compilation * fix(macos): bound approval socket JSONL reads * chore: move exec approval note to release process * chore: move exec approval note to release process --------- Co-authored-by: Coy Geek <65363919+coygeek@users.noreply.github.com>
180 lines
7.4 KiB
Swift
180 lines
7.4 KiB
Swift
import Foundation
|
|
import Testing
|
|
@testable import OpenClaw
|
|
|
|
private struct SystemRunCommandContractFixture: Decodable {
|
|
let cases: [SystemRunCommandContractCase]
|
|
}
|
|
|
|
private struct SystemRunCommandContractCase: Decodable {
|
|
let name: String
|
|
let command: [String]
|
|
let rawCommand: String?
|
|
let expected: SystemRunCommandContractExpected
|
|
}
|
|
|
|
private struct SystemRunCommandContractExpected: Decodable {
|
|
let valid: Bool
|
|
let displayCommand: String?
|
|
let errorContains: String?
|
|
}
|
|
|
|
struct ExecSystemRunCommandValidatorTests {
|
|
@Test func `matches shared system run command contract fixture`() throws {
|
|
for entry in try Self.loadContractCases() {
|
|
let result = ExecSystemRunCommandValidator.resolve(command: entry.command, rawCommand: entry.rawCommand)
|
|
|
|
if !entry.expected.valid {
|
|
switch result {
|
|
case let .ok(resolved):
|
|
Issue
|
|
.record("\(entry.name): expected invalid result, got displayCommand=\(resolved.displayCommand)")
|
|
case let .invalid(message):
|
|
if let expected = entry.expected.errorContains {
|
|
#expect(
|
|
message.contains(expected),
|
|
"\(entry.name): expected error containing \(expected), got \(message)")
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
|
|
switch result {
|
|
case let .ok(resolved):
|
|
#expect(
|
|
resolved.displayCommand == entry.expected.displayCommand,
|
|
"\(entry.name): unexpected display command")
|
|
case let .invalid(message):
|
|
Issue.record("\(entry.name): unexpected invalid result: \(message)")
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test func `validator keeps canonical wrapper text out of allowlist raw parsing`() {
|
|
let command = ["/bin/sh", "-lc", "/usr/bin/printf ok"]
|
|
let rawCommand = "/bin/sh -lc \"/usr/bin/printf ok\""
|
|
let result = ExecSystemRunCommandValidator.resolve(command: command, rawCommand: rawCommand)
|
|
|
|
switch result {
|
|
case let .ok(resolved):
|
|
#expect(resolved.displayCommand == rawCommand)
|
|
#expect(resolved.evaluationRawCommand == nil)
|
|
case let .invalid(message):
|
|
Issue.record("unexpected invalid result: \(message)")
|
|
}
|
|
}
|
|
|
|
@Test func `canonical display preserves padded argv bytes`() {
|
|
let command = ["/usr/bin/printf", "<%s>", " padded "]
|
|
let canonical = "/usr/bin/printf \"<%s>\" \" padded \""
|
|
|
|
switch ExecSystemRunCommandValidator.resolve(command: command, rawCommand: canonical) {
|
|
case let .ok(resolved):
|
|
#expect(resolved.displayCommand == canonical)
|
|
case let .invalid(message):
|
|
Issue.record("unexpected invalid result: \(message)")
|
|
}
|
|
}
|
|
|
|
@Test func `canonical display makes metacharacters and escapes unambiguous`() {
|
|
let command = ["printf", "<%s>", #"a\"b"#, "$(id)", "x;y", "a\nb"]
|
|
let canonical = #"printf "<%s>" "a\\\"b" "$(id)" "x;y" "a\nb""#
|
|
#expect(ExecCommandFormatter.displayString(for: command) == canonical)
|
|
|
|
switch ExecSystemRunCommandValidator.resolve(command: ["printf", "<%s>"], rawCommand: "printf <%s>") {
|
|
case let .ok(resolved):
|
|
#expect(resolved.displayCommand == #"printf "<%s>""#)
|
|
case let .invalid(message):
|
|
Issue.record("legacy display spelling was rejected: \(message)")
|
|
}
|
|
}
|
|
|
|
@Test func `env dash shell wrapper requires canonical raw command binding`() {
|
|
let command = ["/usr/bin/env", "-", "bash", "-lc", "echo hi"]
|
|
|
|
let legacy = ExecSystemRunCommandValidator.resolve(command: command, rawCommand: "echo hi")
|
|
switch legacy {
|
|
case .ok:
|
|
Issue.record("expected rawCommand mismatch for env dash prelude")
|
|
case let .invalid(message):
|
|
#expect(message.contains("rawCommand does not match command"))
|
|
}
|
|
|
|
let canonicalRaw = "/usr/bin/env - bash -lc \"echo hi\""
|
|
let canonical = ExecSystemRunCommandValidator.resolve(command: command, rawCommand: canonicalRaw)
|
|
switch canonical {
|
|
case let .ok(resolved):
|
|
#expect(resolved.displayCommand == canonicalRaw)
|
|
case let .invalid(message):
|
|
Issue.record("unexpected invalid result for canonical raw command: \(message)")
|
|
}
|
|
}
|
|
|
|
@Test func `fish attached c command requires canonical raw command binding`() {
|
|
let command = ["/usr/bin/fish", "-c/tmp/payload.fish", "/usr/bin/printf safe_marker"]
|
|
let result = ExecSystemRunCommandValidator.resolve(
|
|
command: command,
|
|
rawCommand: "/usr/bin/printf safe_marker")
|
|
|
|
switch result {
|
|
case .ok:
|
|
Issue.record("expected rawCommand mismatch for attached fish command payload")
|
|
case let .invalid(message):
|
|
#expect(message.contains("rawCommand does not match command"))
|
|
}
|
|
}
|
|
|
|
@Test func `startup shell wrappers require canonical raw command binding`() {
|
|
for command in [
|
|
["/bin/bash", "-lc", "/usr/bin/printf safe_marker"],
|
|
["/bin/bash", "--rcfile", "/tmp/payload.sh", "-i", "-c", "/usr/bin/printf safe_marker"],
|
|
["/bin/bash", "--login", "-c", "/usr/bin/printf safe_marker"],
|
|
["/usr/bin/fish", "--init-command=/tmp/payload.fish", "-c", "/usr/bin/printf safe_marker"],
|
|
] {
|
|
let legacy = ExecSystemRunCommandValidator.resolve(
|
|
command: command,
|
|
rawCommand: "/usr/bin/printf safe_marker")
|
|
switch legacy {
|
|
case .ok:
|
|
Issue.record("expected rawCommand mismatch for startup shell wrapper")
|
|
case let .invalid(message):
|
|
#expect(message.contains("rawCommand does not match command"))
|
|
}
|
|
|
|
let canonicalRaw = ExecCommandFormatter.displayString(for: command)
|
|
let canonical = ExecSystemRunCommandValidator.resolve(command: command, rawCommand: canonicalRaw)
|
|
switch canonical {
|
|
case let .ok(resolved):
|
|
#expect(resolved.displayCommand == canonicalRaw)
|
|
case let .invalid(message):
|
|
Issue.record("unexpected invalid result for canonical raw command: \(message)")
|
|
}
|
|
}
|
|
}
|
|
|
|
private static func loadContractCases() throws -> [SystemRunCommandContractCase] {
|
|
let fixtureURL = try self.findContractFixtureURL()
|
|
let data = try Data(contentsOf: fixtureURL)
|
|
let decoded = try JSONDecoder().decode(SystemRunCommandContractFixture.self, from: data)
|
|
return decoded.cases
|
|
}
|
|
|
|
private static func findContractFixtureURL() throws -> URL {
|
|
var cursor = URL(fileURLWithPath: #filePath).deletingLastPathComponent()
|
|
for _ in 0..<8 {
|
|
let candidate = cursor
|
|
.appendingPathComponent("test")
|
|
.appendingPathComponent("fixtures")
|
|
.appendingPathComponent("system-run-command-contract.json")
|
|
if FileManager.default.fileExists(atPath: candidate.path) {
|
|
return candidate
|
|
}
|
|
cursor.deleteLastPathComponent()
|
|
}
|
|
throw NSError(
|
|
domain: "ExecSystemRunCommandValidatorTests",
|
|
code: 1,
|
|
userInfo: [NSLocalizedDescriptionKey: "missing shared system-run command contract fixture"])
|
|
}
|
|
}
|