Skip to content

feat(ai): Let the assistant edit files, run actions, and control deployments#185

Merged
nfebe merged 1 commit into
mainfrom
feat/ai-more-tools
Jul 20, 2026
Merged

feat(ai): Let the assistant edit files, run actions, and control deployments#185
nfebe merged 1 commit into
mainfrom
feat/ai-more-tools

Conversation

@nfebe

@nfebe nfebe commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

First set of #147. The assistant's built-in tools were read-only; this adds four, with the framework kept read-only-by-default and each state-changing tool authorizing write access itself:

  • write_deployment_file writes a file in a deployment's directory (path confined to it, honours protected mode's upload gate).
  • run_quick_action runs a configured quick action by id (honours the quick-action protected gate).
  • control_deployment starts, stops, or restarts a whole deployment.
  • get_security_events lists a deployment's recent security events to summarize (read-only, and returns a clear message when the security module is off).

The three mutating tools require deployment write access, verified by a test that a viewer is refused; file writes are confined to the deployment directory, verified by a traversal test. These become part of the shared tool set that #184 will also expose over MCP.

@sourceant

sourceant Bot commented Jul 19, 2026

Copy link
Copy Markdown

Code Review Summary

This PR adds several powerful tools to the AI assistant, enabling it to manage files, execute quick actions, control deployment states, and retrieve security events. The implementation correctly handles authorization and 'fail-closed' security checks.

🚀 Key Improvements

  • Added write_deployment_file, run_quick_action, control_deployment, and get_security_events tools.
  • Improved 'fail-closed' error handling for protected mode checks in exec_in_service and the new tools.
  • Added comprehensive tests for traversal protection and authorization.

💡 Minor Suggestions

  • Apply output redaction to the get_security_events tool to prevent secret leakage via log messages.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review complete. See the overview comment for a summary.

Comment thread internal/api/ai_tools.go Outdated
if !ok {
return "", fmt.Errorf("content is required")
}
if blocked, reason, perr := s.protectedDeploymentActionBlocked(name, protectedActionUploadFile); perr == nil && blocked {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The current logic 'fails open' if an error occurs while checking if the action is blocked (e.g., a database connection issue or configuration error). It is safer to fail closed by explicitly handling the error returned by protectedDeploymentActionBlocked to ensure the security check is enforced.

Suggested change
if blocked, reason, perr := s.protectedDeploymentActionBlocked(name, protectedActionUploadFile); perr == nil && blocked {
blocked, reason, perr := s.protectedDeploymentActionBlocked(name, protectedActionUploadFile)
if perr != nil {
return "", perr
}
if blocked {
return "", fmt.Errorf("%s", reason)
}

Comment thread internal/api/ai_tools.go Outdated
if actionID == "" {
return "", fmt.Errorf("action_id is required")
}
if blocked, reason, perr := s.protectedDeploymentActionBlocked(name, protectedActionQuickAction); perr == nil && blocked {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Similar to the file upload tool, the protection check here should handle potential errors from the check itself. If perr is non-nil, the code currently bypasses the block check.

Suggested change
if blocked, reason, perr := s.protectedDeploymentActionBlocked(name, protectedActionQuickAction); perr == nil && blocked {
blocked, reason, perr := s.protectedDeploymentActionBlocked(name, protectedActionQuickAction)
if perr != nil {
return "", perr
}
if blocked {
return "", fmt.Errorf("%s", reason)
}

@nfebe
nfebe force-pushed the feat/ai-more-tools branch from 822eb52 to 2146649 Compare July 19, 2026 19:40
…oyments

The assistant's tools were read-only. It can now write a file in a deployment's
directory, run a configured quick action, start, stop, or restart a deployment,
and list a deployment's recent security events to summarize them.

Every state-changing tool requires write access to the deployment and honours
protected mode, so the assistant can only do what the operator behind it is
allowed to do. File writes stay inside the deployment directory.
@nfebe
nfebe force-pushed the feat/ai-more-tools branch from 2146649 to 941a138 Compare July 19, 2026 19:42

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review complete. See the overview comment for a summary.

Comment thread internal/api/ai_tools.go
default:
return "", fmt.Errorf("action must be start, stop, or restart")
}
if err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Output from deployment control operations (like start, stop, or restart) often includes logs from the orchestration engine or the container's standard output. These logs can contain sensitive information such as environment variables, database connection strings, or internal system paths. Consistent with the run_quick_action tool, this output should be passed through the redactor to ensure no secrets are exposed to the AI model or the session logs.

Suggested change
if err != nil {
if err != nil {
return "", err
}
redactor := ai.NewRedactor(s.deploymentSecretValues(name))
redacted, _ := redactor.Redact(output)
return truncateToolOutput(fmt.Sprintf("Ran %s on deployment %s.\n%s", action, name, redacted)), nil

Comment thread internal/api/ai_tools.go
if s.securityManager == nil {
return "The security module is not enabled.", nil
}
limit := 50

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

While a default limit is provided, there is no upper bound enforced on the limit parameter. Allowing the AI to request an unlimited number of security events could lead to significant memory allocations or timeouts if a deployment has a large event history. It is safer to cap the maximum number of events returned (e.g., to 100).

Suggested change
limit := 50
limit := 50
if v, ok := args["limit"].(float64); ok && v > 0 {
limit = int(v)
if limit > 100 {
limit = 100
}
}

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review complete. See the overview comment for a summary.

Comment thread internal/api/ai_tools.go
fmt.Fprintf(&b, "- %s [%s] %s %s %d from %s: %s\n",
e.CreatedAt.Format(time.RFC3339), e.Severity, e.RequestMethod, e.RequestPath, e.StatusCode, e.SourceIP, e.Message)
}
return truncateToolOutput(b.String()), nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Security event logs can often contain sensitive data such as session tokens, API keys, or personal information within request paths or log messages. To prevent accidental exposure of these secrets to the AI model or session logs, the output should be processed through the redactor using the deployment's secret values, consistent with how the run_quick_action tool handles output.

Suggested change
return truncateToolOutput(b.String()), nil
redactor := ai.NewRedactor(s.deploymentSecretValues(name))
redacted, _ := redactor.Redact(b.String())
return truncateToolOutput(redacted), nil

@nfebe
nfebe merged commit de3b533 into main Jul 20, 2026
6 checks passed
@nfebe
nfebe deleted the feat/ai-more-tools branch July 20, 2026 07:25
@nfebe nfebe mentioned this pull request Jul 20, 2026
42 tasks
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.

1 participant