mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-16 17:11:44 +00:00
* fix(macos): reject revoked forwarded exec approvals * fix(macos): split approval plan validation * chore(i18n): refresh native source lines
205 lines
7.2 KiB
Swift
205 lines
7.2 KiB
Swift
import Foundation
|
|
import OpenClawKit
|
|
import Testing
|
|
@testable import OpenClaw
|
|
|
|
struct ExecApprovalsSocketAuthTests {
|
|
@Test
|
|
func `timing safe hex compare matches equal strings`() {
|
|
#expect(timingSafeHexStringEquals(String(repeating: "a", count: 64), String(repeating: "a", count: 64)))
|
|
}
|
|
|
|
@Test
|
|
func `timing safe hex compare rejects mismatched strings`() {
|
|
let expected = String(repeating: "a", count: 63) + "b"
|
|
let provided = String(repeating: "a", count: 63) + "c"
|
|
#expect(!timingSafeHexStringEquals(expected, provided))
|
|
}
|
|
|
|
@Test
|
|
func `timing safe hex compare rejects different length strings`() {
|
|
#expect(!timingSafeHexStringEquals(String(repeating: "a", count: 64), "deadbeef"))
|
|
}
|
|
|
|
@Test
|
|
func `minimum timestamp is rejected before authentication without overflow`() async {
|
|
#expect(!execHostTimestampIsFresh(nowMs: 1_700_000_000_000, requestMs: Int.min))
|
|
#expect(await ExecApprovalsPromptServer._testExecHostTimestampFailureReason(Int.min) == "ttl")
|
|
}
|
|
|
|
@Test
|
|
func `exec host limiter preserves small output`() {
|
|
#expect(ExecHostOutputLimiter.truncate("hello") == "hello")
|
|
}
|
|
|
|
@Test
|
|
func `exec host limiter preserves a valid utf8 tail`() {
|
|
let input = String(repeating: "x", count: 2 * 1024 * 1024) + "✅"
|
|
let limited = ExecHostOutputLimiter.truncate(input)
|
|
|
|
#expect(limited.hasPrefix("... (truncated) "))
|
|
#expect(limited.hasSuffix("✅"))
|
|
#expect(limited.utf8.count <= ExecHostOutputLimiter.maxOutputFieldBytes)
|
|
}
|
|
|
|
@Test
|
|
func `exec host limiter keeps escaped output below the jsonl cap`() throws {
|
|
let escaped = String(repeating: "\u{0}", count: 2 * 1024 * 1024)
|
|
let limited = ExecHostOutputLimiter.truncate(escaped)
|
|
let response = EncodedExecHostResponse(
|
|
type: "exec-res",
|
|
id: "test",
|
|
ok: true,
|
|
payload: EncodedExecHostRunResult(
|
|
exitCode: 0,
|
|
timedOut: false,
|
|
success: true,
|
|
stdout: limited,
|
|
stderr: limited,
|
|
error: nil),
|
|
error: nil)
|
|
|
|
#expect(try JSONEncoder().encode(response).count < ExecHostOutputLimiter.maxJsonlResponseBytes)
|
|
}
|
|
|
|
@Test
|
|
func `exec host limiter bounds real command output`() async {
|
|
let result = await ShellExecutor.runDetailed(
|
|
command: [
|
|
"/usr/bin/perl",
|
|
"-e",
|
|
"print 'x' x (2 * 1024 * 1024); print STDERR 'y' x (2 * 1024 * 1024);",
|
|
],
|
|
cwd: nil,
|
|
env: nil,
|
|
timeout: 10)
|
|
|
|
#expect(ExecHostOutputLimiter.truncate(result.stdout).utf8.count <= ExecHostOutputLimiter.maxOutputFieldBytes)
|
|
#expect(ExecHostOutputLimiter.truncate(result.stderr).utf8.count <= ExecHostOutputLimiter.maxOutputFieldBytes)
|
|
#expect(result.exitCode == 0)
|
|
}
|
|
|
|
@Test
|
|
func `socket decoded argv reaches executor without token normalization`() async throws {
|
|
let command = ["/usr/bin/printf", "<%s>|<%s>", " padded ", "-n"]
|
|
let request = ExecHostRequest(
|
|
command: command,
|
|
rawCommand: nil,
|
|
cwd: nil,
|
|
env: nil,
|
|
timeoutMs: nil,
|
|
needsScreenRecording: nil,
|
|
agentId: nil,
|
|
sessionKey: nil,
|
|
approvalDecision: .allowOnce,
|
|
policySnapshot: Self.policySnapshot)
|
|
let requestJSON = try JSONEncoder().encode(request)
|
|
let socketDecodedRequest = try JSONDecoder().decode(ExecHostRequest.self, from: requestJSON)
|
|
|
|
let validated: ExecHostValidatedRequest
|
|
switch ExecHostRequestEvaluator.validateRequest(socketDecodedRequest) {
|
|
case let .success(request):
|
|
validated = request
|
|
case let .failure(error):
|
|
Issue.record("unexpected invalid request: \(error.message)")
|
|
return
|
|
}
|
|
let result = await ShellExecutor.runDetailed(
|
|
command: validated.command,
|
|
cwd: nil,
|
|
env: nil,
|
|
timeout: 2)
|
|
|
|
#expect(validated.command == command)
|
|
#expect(validated.displayCommand == ExecCommandFormatter.displayString(for: command))
|
|
#expect(result.stdout == "< padded >|<-n>")
|
|
#expect(result.exitCode == 0)
|
|
}
|
|
|
|
@Test
|
|
func `socket serialization preserves timeout fallback provenance`() throws {
|
|
let request = ExecHostRequest(
|
|
command: ["/usr/bin/printf", "ok"],
|
|
rawCommand: nil,
|
|
cwd: nil,
|
|
env: nil,
|
|
timeoutMs: nil,
|
|
needsScreenRecording: nil,
|
|
agentId: "main",
|
|
sessionKey: "agent:main:main",
|
|
approvalDecision: nil,
|
|
approvalSource: "ask-fallback")
|
|
|
|
let decoded = try JSONDecoder().decode(
|
|
ExecHostRequest.self,
|
|
from: JSONEncoder().encode(request))
|
|
#expect(decoded.approvalSource == "ask-fallback")
|
|
#expect(decoded.approvalDecision == nil)
|
|
}
|
|
|
|
@Test
|
|
func `socket decodes TypeScript auto review policy snapshot`() throws {
|
|
let requestJSON = Data(#"""
|
|
{
|
|
"command": ["/usr/bin/printf", "ok"],
|
|
"agentId": "main",
|
|
"sessionKey": "agent:main:main",
|
|
"approvalSource": "auto-review",
|
|
"policySnapshot": {
|
|
"security": "allowlist",
|
|
"ask": "on-miss",
|
|
"askFallback": "deny",
|
|
"autoAllowSkills": true,
|
|
"allowlistRules": [
|
|
{"pattern": "/ä"},
|
|
{"pattern": "/A", "source": "allow-always"},
|
|
{"pattern": "/"},
|
|
{"pattern": "/"},
|
|
{"pattern": "/A"}
|
|
]
|
|
}
|
|
}
|
|
"""#.utf8)
|
|
|
|
let decoded = try JSONDecoder().decode(ExecHostRequest.self, from: requestJSON)
|
|
#expect(decoded.approvalSource == "auto-review")
|
|
#expect(decoded.approvalDecision == nil)
|
|
#expect(decoded.policySnapshot?.allowlistRules == [
|
|
OpenClawSystemRunApprovalPolicySnapshot.Rule(pattern: "/"),
|
|
OpenClawSystemRunApprovalPolicySnapshot.Rule(pattern: "/A"),
|
|
OpenClawSystemRunApprovalPolicySnapshot.Rule(pattern: "/A", source: .allowAlways),
|
|
OpenClawSystemRunApprovalPolicySnapshot.Rule(pattern: "/ä"),
|
|
])
|
|
switch ExecHostRequestEvaluator.validateRequest(decoded) {
|
|
case let .success(validated):
|
|
#expect(validated.delayedPolicySnapshot?.portable == decoded.policySnapshot)
|
|
case let .failure(error):
|
|
Issue.record("unexpected invalid request: \(error.message)")
|
|
}
|
|
}
|
|
|
|
private static let policySnapshot = OpenClawSystemRunApprovalPolicySnapshot(
|
|
security: .full,
|
|
ask: .off,
|
|
askFallback: .deny,
|
|
autoAllowSkills: false,
|
|
allowlistRules: [])
|
|
|
|
private struct EncodedExecHostResponse: Codable {
|
|
var type: String
|
|
var id: String
|
|
var ok: Bool
|
|
var payload: EncodedExecHostRunResult?
|
|
var error: String?
|
|
}
|
|
|
|
private struct EncodedExecHostRunResult: Codable {
|
|
var exitCode: Int?
|
|
var timedOut: Bool
|
|
var success: Bool
|
|
var stdout: String
|
|
var stderr: String
|
|
var error: String?
|
|
}
|
|
}
|