Skip to content

[codex] Add governed external CLI connectors#102

Merged
Telli merged 4 commits into
mainfrom
codex/external-cli-connectors
May 9, 2026
Merged

[codex] Add governed external CLI connectors#102
Telli merged 4 commits into
mainfrom
codex/external-cli-connectors

Conversation

@Telli
Copy link
Copy Markdown
Contributor

@Telli Telli commented May 9, 2026

Summary

  • Adds disabled-by-default governed external CLI connectors for named, allowlisted commands.
  • Adds preview/fingerprint approval handling, bounded process execution, redaction, audit/runtime events, admin API endpoints, and CLI commands.
  • Adds ExternalCli tests and documentation for connector security, approvals, redaction, audit, and conservative presets.

Closes #100.

Validation

  • dotnet test src/OpenClaw.Tests/OpenClaw.Tests.csproj --filter ExternalCli
  • dotnet test OpenClaw.Net.slnx -c Release

@Telli Telli marked this pull request as ready for review May 9, 2026 17:43
Copilot AI review requested due to automatic review settings May 9, 2026 17:43
Comment thread src/OpenClaw.Core/ExternalCli/ExternalCliServices.cs Fixed
Comment thread src/OpenClaw.Agent/Tools/ExternalCliTool.cs Fixed
Comment thread src/OpenClaw.Core/ExternalCli/ExternalCliServices.cs Fixed
Comment thread src/OpenClaw.Core/ExternalCli/ExternalCliServices.cs Fixed
Comment thread src/OpenClaw.Core/ExternalCli/ExternalCliServices.cs Fixed
Comment thread src/OpenClaw.Core/ExternalCli/ExternalCliServices.cs Fixed
Comment thread src/OpenClaw.Core/ExternalCli/ExternalCliServices.cs Fixed
Comment thread src/OpenClaw.Core/ExternalCli/ExternalCliServices.cs Fixed
Comment thread src/OpenClaw.Core/ExternalCli/ExternalCliServices.cs Fixed
Comment thread src/OpenClaw.Core/ExternalCli/ExternalCliServices.cs Fixed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a governed “External CLI” connector framework to OpenClaw.NET, enabling named allowlisted command execution via official platform CLIs with preview/fingerprints, redaction, bounded execution, and audit/runtime event emission (disabled by default).

Changes:

  • Introduces ExternalCliOptions/connector/command models plus a registry+runner for safe template expansion, validation, redaction, fingerprinting, and structured output parsing.
  • Wires the feature into the agent tool surface (external_cli), gateway admin APIs, audit/runtime event sinks, and client/CLI commands.
  • Adds documentation and targeted tests for policy enforcement, parsing, truncation, and approval gating.

Reviewed changes

Copilot reviewed 29 out of 29 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/OpenClaw.Tests/ExternalCliTests.cs Adds tests covering preview policy checks, template expansion, JSON parsing, truncation, and approval gating.
src/OpenClaw.Gateway/ExternalCliStores.cs Adds gateway-backed audit (jsonl) and runtime event sinks for external CLI activity.
src/OpenClaw.Gateway/Endpoints/EndpointHelpers.cs Extends role mapping to recognize admin.external-cli.execute scope.
src/OpenClaw.Gateway/Endpoints/AdminEndpoints.Support.cs Extends admin endpoint service container to include external CLI registry/runner/audit/events.
src/OpenClaw.Gateway/Endpoints/AdminEndpoints.ExternalCli.cs Adds admin endpoints for listing connectors/commands and for preview/execute flows with audit/events.
src/OpenClaw.Gateway/Endpoints/AdminEndpoints.cs Wires external CLI services into admin endpoint initialization and maps the new endpoints.
src/OpenClaw.Gateway/Composition/SecurityServicesExtensions.cs Registers IExternalCliAuditSink and IExternalCliEventSink implementations.
src/OpenClaw.Gateway/Composition/RuntimeInitializationExtensions.RuntimeFactories.cs Registers the ExternalCliTool when ExternalCli.Enabled is true.
src/OpenClaw.Gateway/Composition/RuntimeInitializationExtensions.CompositionStages.cs Adds external CLI services to resolved runtime services.
src/OpenClaw.Gateway/Composition/CoreServicesExtensions.cs Registers IExternalCliConnectorRegistry and IExternalCliRunner in DI.
src/OpenClaw.Gateway/appsettings.json Adds an OpenClaw:ExternalCli section with conservative, disabled-by-default defaults.
src/OpenClaw.Core/Validation/ConfigValidator.cs Adds config validation for key external CLI option constraints.
src/OpenClaw.Core/Pipeline/ToolActionPolicyResolver.cs Adds external_cli action-aware resolution and flags it as action-aware approval-capable.
src/OpenClaw.Core/Models/ToolingPolicyModels.cs Extends ToolActionDescriptor to include approval/fingerprint/risk/readonly metadata.
src/OpenClaw.Core/Models/Session.cs Adds JSON source-gen registrations for external CLI request/response/audit/event models.
src/OpenClaw.Core/Models/GatewayConfig.cs Adds ExternalCli to the gateway config model.
src/OpenClaw.Core/Models/ExternalCliModels.cs Introduces external CLI config + API request/response + audit/event models.
src/OpenClaw.Core/ExternalCli/ExternalCliServices.cs Implements registry/runner, template expansion, parameter validation, redaction, fingerprinting, and output parsing.
src/OpenClaw.Core/Abstractions/IToolActionDescriptorProvider.cs Introduces a tool interface for action-aware descriptors used by the executor.
src/OpenClaw.Client/OpenClawHttpClient.cs Adds client methods/URIs for external CLI admin endpoints.
src/OpenClaw.Cli/Program.cs Adds openclaw external ... command routing.
src/OpenClaw.Cli/OpenClawHttpClient.cs Exposes external CLI client calls through the CLI wrapper client.
src/OpenClaw.Cli/ExternalCliCommands.cs Implements openclaw external list/status/commands/preview/execute commands.
src/OpenClaw.Cli/CliArgs.cs Adds --dry-run to recognized CLI flags.
src/OpenClaw.Agent/Tools/ExternalCliTool.cs Adds external_cli tool implementation with preview/execute and audit/events.
src/OpenClaw.Agent/OpenClawToolExecutor.cs Uses tool-provided action descriptors and adds post-approval fingerprint stability checks.
docs/SITE_MAP.md Adds External CLI Connectors guide entry.
docs/README.md Links the new External CLI Connectors documentation.
docs/EXTERNAL_CLI_CONNECTORS.md Adds documentation for security model, config, approvals, audit/events, parsing, admin API, and CLI.

scope.StartsWith("admin.channels.auth.mutate", StringComparison.Ordinal) ||
scope.StartsWith("admin.channels.auth.restart", StringComparison.Ordinal) ||
scope.StartsWith("admin.models.evaluate", StringComparison.Ordinal) ||
scope.StartsWith("admin.external-cli.execute", StringComparison.Ordinal) ||
Comment on lines +111 to +120
catch (InvalidOperationException ex)
{
events.Record(new ExternalCliRuntimeEvent
{
Action = "command_blocked_by_policy",
Severity = "warning",
Summary = ex.Message
});
return Results.BadRequest(new OperationStatusResponse { Success = false, Error = ex.Message });
}
Comment on lines +179 to +188
catch (InvalidOperationException ex)
{
events.Record(new ExternalCliRuntimeEvent
{
Action = "command_blocked_by_policy",
Severity = "warning",
Summary = ex.Message
});
return Results.BadRequest(new OperationStatusResponse { Success = false, Error = ex.Message });
}
Comment on lines +171 to +181
var fingerprint = ComputeFingerprint(
connectorName,
commandName,
connector.Executable,
args,
workingDirectory,
timeout,
outputFormat,
dryRun,
command);
var parametersHash = ComputeParametersHash(request.Parameters);
Comment on lines +106 to +114
Preview returns:

- resolved executable and argument list
- redacted command-line preview
- risk level
- read-only or mutating classification
- whether approval is required
- output format
- required identity or scopes, when configured
Comment on lines +690 to +710
foreach (var (commandName, command) in connector.Commands)
{
if (string.IsNullOrWhiteSpace(commandName))
errors.Add($"ExternalCli.Connectors.{connectorName}.Commands contains an empty command name.");
if (command.ArgsTemplate.Length == 0)
errors.Add($"ExternalCli.Connectors.{connectorName}.Commands.{commandName}.ArgsTemplate must contain at least one argument.");
if (command.SupportsDryRun && command.DryRunArgsTemplate.Length == 0)
errors.Add($"ExternalCli.Connectors.{connectorName}.Commands.{commandName}.DryRunArgsTemplate must be set when SupportsDryRun=true.");
if (command.TimeoutSeconds is <= 0)
errors.Add($"ExternalCli.Connectors.{connectorName}.Commands.{commandName}.TimeoutSeconds must be >= 1 when set.");

var risk = ExternalCliRiskLevel.Normalize(command.RiskLevel);
if (!string.Equals(risk, command.RiskLevel, StringComparison.OrdinalIgnoreCase))
errors.Add($"ExternalCli.Connectors.{connectorName}.Commands.{commandName}.RiskLevel must be low, medium, or high.");

if (!string.IsNullOrWhiteSpace(command.StructuredOutput))
{
var format = ExternalCliOutputFormat.Normalize(command.StructuredOutput);
if (!string.Equals(format, command.StructuredOutput, StringComparison.OrdinalIgnoreCase))
errors.Add($"ExternalCli.Connectors.{connectorName}.Commands.{commandName}.StructuredOutput must be one of: json, ndjson, csv, text, table.");
}
Comment on lines +238 to +248
openclaw external list [--json]
openclaw external status <connector> [--json]
openclaw external commands <connector> [--json]
openclaw external preview <connector> <command> [--param key=value]... [--dry-run] [--json]
openclaw external execute <connector> <command> [--param key=value]... [--yes] [--json]

Notes:
- Commands talk to the gateway admin API.
- Mutating or high-risk commands require --yes after preview review.
- External CLI connectors are disabled unless OpenClaw:ExternalCli:Enabled=true.
""");
Comment on lines +232 to +238
string? version = null;
if (resolvedExecutable is not null && connector.VersionCommand is { Args.Length: > 0 } versionCommand)
version = await RunStatusCommandAsync(resolvedExecutable, versionCommand, connector, ct);

string authStatus = "unknown";
bool? authenticated = null;
if (resolvedExecutable is not null && connector.StatusCommand is { Args.Length: > 0 } statusCommand)
Comment on lines +96 to +108
var request = JsonSerializer.Deserialize(
string.IsNullOrWhiteSpace(argumentsJson) ? "{}" : argumentsJson,
CoreJsonContext.Default.ExternalCliToolRequest) ?? new ExternalCliToolRequest();
if (!string.Equals(request.Action, "execute", StringComparison.OrdinalIgnoreCase))
{
return new ToolActionDescriptor
{
Action = request.Action,
IsMutation = false,
RequiresApproval = false,
Summary = BuildSummary(request)
};
}
@augmentcode
Copy link
Copy Markdown

augmentcode Bot commented May 9, 2026

🤖 Augment PR Summary

Summary: Adds a disabled-by-default, governed “external_cli” tool for invoking allowlisted platform CLIs via named connectors/commands.

Changes:

  • Introduces External CLI config + models (connectors, command templates, risk/read-only flags, dry-run templates, output formats, redaction rules).
  • Implements connector registry + runner with argument-template expansion, parameter validation, output caps, redaction, timeouts, and optional structured JSON/NDJSON parsing.
  • Adds preview + execution flows with stable fingerprints intended for approval and change-detection.
  • Extends tool execution to consume tool-provided action descriptors and to block execution when the approval fingerprint changes mid-approval.
  • Adds admin API endpoints under /admin/external-cli/* plus audit/runtime event plumbing and JSONL audit storage.
  • Adds new openclaw external ... CLI commands to list/status/preview/execute external connectors.
  • Adds focused unit tests for parameter governance, approval classification, stdout truncation, and JSON parsing.

Technical Notes: Uses ProcessStartInfo.ArgumentList (no shell), rejects unknown parameters by default, and emphasizes conservative defaults (disabled feature + approval required for mutating/high-risk commands).

🤖 Was this summary useful? React with 👍 or 👎

Copy link
Copy Markdown

@augmentcode augmentcode Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 4 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

"connector":{"type":"string","description":"Configured connector name, such as gh, az, kubectl, stripe, or lark."},
"command":{"type":"string","description":"Allowlisted command name inside the connector."},
"parameters":{"type":"object","additionalProperties":true,"description":"Named command parameters. Unknown parameters are rejected unless the command explicitly allows them."},
"execute_dry_run":{"type":"boolean","default":false,"description":"For preview only: execute the explicit dry-run template when the command supports it."},
Copy link
Copy Markdown

@augmentcode augmentcode Bot May 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ExternalCliTool.ParameterSchema documents snake_case fields like execute_dry_run/approved_fingerprint/approval_reason, but ExternalCliToolRequest is deserialized with the core JSON context’s camelCase policy, so these values likely won’t bind (e.g., request.ExecuteDryRun will stay false even if the caller sends execute_dry_run).

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

ArgsHash = ExternalCliConnectorRegistry.ComputeArgsHash(result.Preview.Arguments.ToArray()),
RedactedArgsPreview = result.Preview.RedactedCommandLine,
ParametersHash = result.Preview.ParametersHash,
ApprovalFingerprint = approvalFingerprint,
Copy link
Copy Markdown

@augmentcode augmentcode Bot May 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ExternalCliAuditEntry.ApprovalFingerprint is recorded from the request-provided approval fingerprint, so executions that don’t populate it (e.g., dry-run previews, or agent tool calls that rely on the tool-approval flow) won’t store the actual result.Preview.Fingerprint even though it’s available for auditing.

Severity: medium

Other Locations
  • src/OpenClaw.Agent/Tools/ExternalCliTool.cs:178

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.


foreach (var required in placeholderNames)
{
if (!parameters.ContainsKey(required))
Copy link
Copy Markdown

@augmentcode augmentcode Bot May 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parameter placeholder/allowed-name sets are computed case-insensitively, but lookups use parameters.ContainsKey/TryGetValue which will be case-sensitive for typical request dictionaries; this can lead to confusing failures where a key like Repo passes the allowlist check but is then treated as “missing” for {{repo}}.

Severity: medium

Other Locations
  • src/OpenClaw.Core/ExternalCli/ExternalCliServices.cs:367

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

new OperationStatusResponse
{
Success = false,
Error = "External CLI command requires approval. Preview the command and resubmit with the matching approved_fingerprint."
Copy link
Copy Markdown

@augmentcode augmentcode Bot May 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error message tells clients to resubmit approved_fingerprint, but the JSON request model/serialization here is camelCase (e.g., approvedFingerprint); this mismatch may cause unnecessary client confusion when handling 409 responses.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread src/OpenClaw.Core/ExternalCli/ExternalCliServices.cs Fixed
Comment thread src/OpenClaw.Core/ExternalCli/ExternalCliServices.cs Fixed
Comment thread src/OpenClaw.Gateway/ExternalCliStores.cs Fixed
Comment thread src/OpenClaw.Tests/ExternalCliTests.cs Fixed
Comment thread src/OpenClaw.Tests/ExternalCliTests.cs Fixed
Comment thread src/OpenClaw.Tests/ExternalCliTests.cs Fixed
Comment thread src/OpenClaw.Core/ExternalCli/ExternalCliServices.cs Fixed
Comment on lines +321 to +325
foreach (var required in placeholderNames)
{
if (!parameters.ContainsKey(required))
throw new InvalidOperationException($"External CLI parameter '{required}' is required.");
}
Comment on lines +335 to +339
foreach (var key in parameters.Keys)
{
if (!allowedNames.Contains(key))
throw new InvalidOperationException($"External CLI parameter '{key}' is not allowed for this command.");
}
Comment thread src/OpenClaw.Core/ExternalCli/ExternalCliServices.cs Fixed
Comment thread src/OpenClaw.Core/ExternalCli/ExternalCliServices.cs Fixed
Comment thread src/OpenClaw.Core/ExternalCli/ExternalCliServices.cs Fixed
Comment thread src/OpenClaw.Tests/ExternalCliTests.cs Fixed
Comment thread src/OpenClaw.Tests/ExternalCliTests.cs Fixed
Comment thread src/OpenClaw.Tests/ExternalCliTests.cs Fixed
Comment on lines +509 to +514
foreach (var candidate in candidates)
{
var fullPath = Path.Combine(dir, candidate);
if (File.Exists(fullPath))
return fullPath;
}
Comment on lines +441 to +460
foreach (var pattern in redactionRules)
{
if (string.IsNullOrWhiteSpace(pattern))
continue;

try
{
current = Regex.Replace(
current,
pattern,
"[REDACTED:external-cli]",
RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(250));
}
catch (ArgumentException)
{
// Invalid config is reported by startup validation; keep redaction best-effort.
continue;
}
}
Comment on lines +747 to +765
foreach (var pattern in redactionRules)
{
if (string.IsNullOrWhiteSpace(pattern))
continue;
try
{
current = Regex.Replace(
current,
pattern,
"[REDACTED:external-cli]",
RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(250));
}
catch (ArgumentException)
{
// Invalid config is reported by startup validation; keep redaction best-effort.
continue;
}
}
@Telli Telli merged commit bde72ea into main May 9, 2026
17 checks passed
@Telli Telli deleted the codex/external-cli-connectors branch May 9, 2026 20:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Generic External CLI Connector framework

2 participants