mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-21 16:18:38 +00:00
fix(macos): unify exec allowlist validation pipeline
This commit is contained in:
@@ -108,10 +108,9 @@ struct SystemRunSettingsView: View {
|
||||
TextField("Add allowlist path pattern (case-insensitive globs)", text: self.$newPattern)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
Button("Add") {
|
||||
let pattern = self.newPattern.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard self.model.isPathPattern(pattern) else { return }
|
||||
self.model.addEntry(pattern)
|
||||
self.newPattern = ""
|
||||
if self.model.addEntry(self.newPattern) == nil {
|
||||
self.newPattern = ""
|
||||
}
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.disabled(!self.model.isPathPattern(self.newPattern))
|
||||
@@ -120,6 +119,11 @@ struct SystemRunSettingsView: View {
|
||||
Text("Path patterns only. Basename entries like \"echo\" are ignored.")
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.secondary)
|
||||
if let validationMessage = self.model.allowlistValidationMessage {
|
||||
Text(validationMessage)
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.orange)
|
||||
}
|
||||
|
||||
if self.model.entries.isEmpty {
|
||||
Text("No allowlisted commands yet.")
|
||||
@@ -238,6 +242,7 @@ final class ExecApprovalsSettingsModel {
|
||||
var autoAllowSkills = false
|
||||
var entries: [ExecAllowlistEntry] = []
|
||||
var skillBins: [String] = []
|
||||
var allowlistValidationMessage: String?
|
||||
|
||||
var agentPickerIds: [String] {
|
||||
[Self.defaultsScopeId] + self.agentIds
|
||||
@@ -293,6 +298,7 @@ final class ExecApprovalsSettingsModel {
|
||||
|
||||
func selectAgent(_ id: String) {
|
||||
self.selectedAgentId = id
|
||||
self.allowlistValidationMessage = nil
|
||||
self.loadSettings(for: id)
|
||||
Task { await self.refreshSkillBins() }
|
||||
}
|
||||
@@ -305,6 +311,7 @@ final class ExecApprovalsSettingsModel {
|
||||
self.askFallback = defaults.askFallback
|
||||
self.autoAllowSkills = defaults.autoAllowSkills
|
||||
self.entries = []
|
||||
self.allowlistValidationMessage = nil
|
||||
return
|
||||
}
|
||||
let resolved = ExecApprovalsStore.resolve(agentId: agentId)
|
||||
@@ -314,6 +321,7 @@ final class ExecApprovalsSettingsModel {
|
||||
self.autoAllowSkills = resolved.agent.autoAllowSkills
|
||||
self.entries = resolved.allowlist
|
||||
.sorted { $0.pattern.localizedCaseInsensitiveCompare($1.pattern) == .orderedAscending }
|
||||
self.allowlistValidationMessage = nil
|
||||
}
|
||||
|
||||
func setSecurity(_ security: ExecSecurity) {
|
||||
@@ -371,30 +379,45 @@ final class ExecApprovalsSettingsModel {
|
||||
Task { await self.refreshSkillBins(force: enabled) }
|
||||
}
|
||||
|
||||
func addEntry(_ pattern: String) {
|
||||
guard !self.isDefaultsScope else { return }
|
||||
let trimmed = pattern.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard self.isPathPattern(trimmed) else { return }
|
||||
self.entries.append(ExecAllowlistEntry(pattern: trimmed, lastUsedAt: nil))
|
||||
ExecApprovalsStore.updateAllowlist(agentId: self.selectedAgentId, allowlist: self.entries)
|
||||
@discardableResult
|
||||
func addEntry(_ pattern: String) -> ExecAllowlistPatternValidationReason? {
|
||||
guard !self.isDefaultsScope else { return nil }
|
||||
switch ExecApprovalHelpers.validateAllowlistPattern(pattern) {
|
||||
case .valid(let normalizedPattern):
|
||||
self.entries.append(ExecAllowlistEntry(pattern: normalizedPattern, lastUsedAt: nil))
|
||||
let rejected = ExecApprovalsStore.updateAllowlist(agentId: self.selectedAgentId, allowlist: self.entries)
|
||||
self.allowlistValidationMessage = rejected.first?.reason.message
|
||||
return rejected.first?.reason
|
||||
case .invalid(let reason):
|
||||
self.allowlistValidationMessage = reason.message
|
||||
return reason
|
||||
}
|
||||
}
|
||||
|
||||
func updateEntry(_ entry: ExecAllowlistEntry, id: UUID) {
|
||||
guard !self.isDefaultsScope else { return }
|
||||
guard let index = self.entries.firstIndex(where: { $0.id == id }) else { return }
|
||||
@discardableResult
|
||||
func updateEntry(_ entry: ExecAllowlistEntry, id: UUID) -> ExecAllowlistPatternValidationReason? {
|
||||
guard !self.isDefaultsScope else { return nil }
|
||||
guard let index = self.entries.firstIndex(where: { $0.id == id }) else { return nil }
|
||||
var next = entry
|
||||
let trimmed = next.pattern.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard self.isPathPattern(trimmed) else { return }
|
||||
next.pattern = trimmed
|
||||
switch ExecApprovalHelpers.validateAllowlistPattern(next.pattern) {
|
||||
case .valid(let normalizedPattern):
|
||||
next.pattern = normalizedPattern
|
||||
case .invalid(let reason):
|
||||
self.allowlistValidationMessage = reason.message
|
||||
return reason
|
||||
}
|
||||
self.entries[index] = next
|
||||
ExecApprovalsStore.updateAllowlist(agentId: self.selectedAgentId, allowlist: self.entries)
|
||||
let rejected = ExecApprovalsStore.updateAllowlist(agentId: self.selectedAgentId, allowlist: self.entries)
|
||||
self.allowlistValidationMessage = rejected.first?.reason.message
|
||||
return rejected.first?.reason
|
||||
}
|
||||
|
||||
func removeEntry(id: UUID) {
|
||||
guard !self.isDefaultsScope else { return }
|
||||
guard let index = self.entries.firstIndex(where: { $0.id == id }) else { return }
|
||||
self.entries.remove(at: index)
|
||||
ExecApprovalsStore.updateAllowlist(agentId: self.selectedAgentId, allowlist: self.entries)
|
||||
let rejected = ExecApprovalsStore.updateAllowlist(agentId: self.selectedAgentId, allowlist: self.entries)
|
||||
self.allowlistValidationMessage = rejected.first?.reason.message
|
||||
}
|
||||
|
||||
func entry(for id: UUID) -> ExecAllowlistEntry? {
|
||||
@@ -402,9 +425,7 @@ final class ExecApprovalsSettingsModel {
|
||||
}
|
||||
|
||||
func isPathPattern(_ pattern: String) -> Bool {
|
||||
let trimmed = pattern.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmed.isEmpty else { return false }
|
||||
return trimmed.contains("/") || trimmed.contains("~") || trimmed.contains("\\")
|
||||
ExecApprovalHelpers.isPathPattern(pattern)
|
||||
}
|
||||
|
||||
func refreshSkillBins(force: Bool = false) async {
|
||||
|
||||
Reference in New Issue
Block a user