holmesgpt 0.12.6__py3-none-any.whl → 0.13.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of holmesgpt might be problematic. Click here for more details.
- holmes/__init__.py +1 -1
- holmes/clients/robusta_client.py +19 -1
- holmes/common/env_vars.py +17 -0
- holmes/config.py +69 -9
- holmes/core/conversations.py +11 -0
- holmes/core/investigation.py +16 -3
- holmes/core/investigation_structured_output.py +12 -0
- holmes/core/llm.py +13 -1
- holmes/core/models.py +9 -1
- holmes/core/openai_formatting.py +72 -12
- holmes/core/prompt.py +13 -0
- holmes/core/supabase_dal.py +3 -0
- holmes/core/todo_manager.py +88 -0
- holmes/core/tool_calling_llm.py +230 -157
- holmes/core/tools.py +10 -1
- holmes/core/tools_utils/tool_executor.py +7 -2
- holmes/core/tools_utils/toolset_utils.py +7 -2
- holmes/core/toolset_manager.py +1 -5
- holmes/core/tracing.py +4 -3
- holmes/interactive.py +1 -0
- holmes/main.py +9 -2
- holmes/plugins/prompts/__init__.py +7 -1
- holmes/plugins/prompts/_current_date_time.jinja2 +1 -0
- holmes/plugins/prompts/_default_log_prompt.jinja2 +4 -2
- holmes/plugins/prompts/_fetch_logs.jinja2 +10 -1
- holmes/plugins/prompts/_general_instructions.jinja2 +14 -0
- holmes/plugins/prompts/_permission_errors.jinja2 +1 -1
- holmes/plugins/prompts/_toolsets_instructions.jinja2 +4 -4
- holmes/plugins/prompts/generic_ask.jinja2 +4 -3
- holmes/plugins/prompts/investigation_procedure.jinja2 +210 -0
- holmes/plugins/prompts/kubernetes_workload_ask.jinja2 +2 -0
- holmes/plugins/runbooks/CLAUDE.md +85 -0
- holmes/plugins/runbooks/README.md +24 -0
- holmes/plugins/toolsets/__init__.py +19 -6
- holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +27 -0
- holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +2 -2
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +2 -1
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +3 -1
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +2 -1
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +2 -1
- holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +3 -1
- holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +2 -1
- holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +2 -1
- holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +2 -1
- holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +2 -1
- holmes/plugins/toolsets/bash/argocd/__init__.py +65 -0
- holmes/plugins/toolsets/bash/argocd/constants.py +120 -0
- holmes/plugins/toolsets/bash/aws/__init__.py +66 -0
- holmes/plugins/toolsets/bash/aws/constants.py +529 -0
- holmes/plugins/toolsets/bash/azure/__init__.py +56 -0
- holmes/plugins/toolsets/bash/azure/constants.py +339 -0
- holmes/plugins/toolsets/bash/bash_instructions.jinja2 +6 -7
- holmes/plugins/toolsets/bash/bash_toolset.py +47 -13
- holmes/plugins/toolsets/bash/common/bash_command.py +131 -0
- holmes/plugins/toolsets/bash/common/stringify.py +14 -1
- holmes/plugins/toolsets/bash/common/validators.py +91 -0
- holmes/plugins/toolsets/bash/docker/__init__.py +59 -0
- holmes/plugins/toolsets/bash/docker/constants.py +255 -0
- holmes/plugins/toolsets/bash/helm/__init__.py +61 -0
- holmes/plugins/toolsets/bash/helm/constants.py +92 -0
- holmes/plugins/toolsets/bash/kubectl/__init__.py +80 -79
- holmes/plugins/toolsets/bash/kubectl/constants.py +0 -14
- holmes/plugins/toolsets/bash/kubectl/kubectl_describe.py +38 -56
- holmes/plugins/toolsets/bash/kubectl/kubectl_events.py +28 -76
- holmes/plugins/toolsets/bash/kubectl/kubectl_get.py +39 -99
- holmes/plugins/toolsets/bash/kubectl/kubectl_logs.py +34 -15
- holmes/plugins/toolsets/bash/kubectl/kubectl_run.py +1 -1
- holmes/plugins/toolsets/bash/kubectl/kubectl_top.py +38 -77
- holmes/plugins/toolsets/bash/parse_command.py +106 -32
- holmes/plugins/toolsets/bash/utilities/__init__.py +0 -0
- holmes/plugins/toolsets/bash/utilities/base64_util.py +12 -0
- holmes/plugins/toolsets/bash/utilities/cut.py +12 -0
- holmes/plugins/toolsets/bash/utilities/grep/__init__.py +10 -0
- holmes/plugins/toolsets/bash/utilities/head.py +12 -0
- holmes/plugins/toolsets/bash/utilities/jq.py +79 -0
- holmes/plugins/toolsets/bash/utilities/sed.py +164 -0
- holmes/plugins/toolsets/bash/utilities/sort.py +15 -0
- holmes/plugins/toolsets/bash/utilities/tail.py +12 -0
- holmes/plugins/toolsets/bash/utilities/tr.py +57 -0
- holmes/plugins/toolsets/bash/utilities/uniq.py +12 -0
- holmes/plugins/toolsets/bash/utilities/wc.py +12 -0
- holmes/plugins/toolsets/coralogix/api.py +6 -6
- holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py +7 -1
- holmes/plugins/toolsets/datadog/datadog_api.py +20 -8
- holmes/plugins/toolsets/datadog/datadog_metrics_instructions.jinja2 +8 -1
- holmes/plugins/toolsets/datadog/datadog_rds_instructions.jinja2 +82 -0
- holmes/plugins/toolsets/datadog/toolset_datadog_logs.py +12 -5
- holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py +20 -11
- holmes/plugins/toolsets/datadog/toolset_datadog_rds.py +735 -0
- holmes/plugins/toolsets/datadog/toolset_datadog_traces.py +18 -11
- holmes/plugins/toolsets/git.py +15 -15
- holmes/plugins/toolsets/grafana/grafana_api.py +12 -1
- holmes/plugins/toolsets/grafana/toolset_grafana.py +5 -1
- holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +9 -4
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +12 -5
- holmes/plugins/toolsets/internet/internet.py +2 -1
- holmes/plugins/toolsets/internet/notion.py +2 -1
- holmes/plugins/toolsets/investigator/__init__.py +0 -0
- holmes/plugins/toolsets/investigator/core_investigation.py +157 -0
- holmes/plugins/toolsets/investigator/investigator_instructions.jinja2 +253 -0
- holmes/plugins/toolsets/investigator/model.py +15 -0
- holmes/plugins/toolsets/kafka.py +14 -7
- holmes/plugins/toolsets/kubernetes_logs.py +454 -25
- holmes/plugins/toolsets/logging_utils/logging_api.py +115 -55
- holmes/plugins/toolsets/mcp/toolset_mcp.py +1 -1
- holmes/plugins/toolsets/newrelic.py +8 -3
- holmes/plugins/toolsets/opensearch/opensearch.py +8 -4
- holmes/plugins/toolsets/opensearch/opensearch_logs.py +9 -2
- holmes/plugins/toolsets/opensearch/opensearch_traces.py +6 -2
- holmes/plugins/toolsets/prometheus/prometheus.py +179 -44
- holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +8 -2
- holmes/plugins/toolsets/robusta/robusta.py +4 -4
- holmes/plugins/toolsets/runbook/runbook_fetcher.py +6 -5
- holmes/plugins/toolsets/servicenow/servicenow.py +18 -3
- holmes/plugins/toolsets/utils.py +8 -1
- holmes/utils/console/logging.py +6 -1
- holmes/utils/llms.py +20 -0
- holmes/utils/stream.py +90 -0
- {holmesgpt-0.12.6.dist-info → holmesgpt-0.13.1.dist-info}/METADATA +47 -34
- {holmesgpt-0.12.6.dist-info → holmesgpt-0.13.1.dist-info}/RECORD +123 -91
- holmes/plugins/toolsets/bash/grep/__init__.py +0 -52
- holmes/utils/robusta.py +0 -9
- {holmesgpt-0.12.6.dist-info → holmesgpt-0.13.1.dist-info}/LICENSE.txt +0 -0
- {holmesgpt-0.12.6.dist-info → holmesgpt-0.13.1.dist-info}/WHEEL +0 -0
- {holmesgpt-0.12.6.dist-info → holmesgpt-0.13.1.dist-info}/entry_points.txt +0 -0
|
@@ -1,81 +1,42 @@
|
|
|
1
|
-
|
|
1
|
+
import argparse
|
|
2
|
+
from typing import Any, Optional
|
|
2
3
|
|
|
4
|
+
from holmes.plugins.toolsets.bash.common.bash_command import BashCommand
|
|
5
|
+
from holmes.plugins.toolsets.bash.common.config import BashExecutorConfig
|
|
3
6
|
from holmes.plugins.toolsets.bash.common.stringify import escape_shell_args
|
|
4
|
-
from holmes.plugins.toolsets.bash.common.validators import regex_validator
|
|
5
|
-
from holmes.plugins.toolsets.bash.kubectl.constants import (
|
|
6
|
-
SAFE_NAME_PATTERN,
|
|
7
|
-
SAFE_NAMESPACE_PATTERN,
|
|
8
|
-
SAFE_SELECTOR_PATTERN,
|
|
9
|
-
)
|
|
10
7
|
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"top"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
parser.add_argument(
|
|
47
|
-
"--sort-by",
|
|
48
|
-
type=regex_validator("sort field", SAFE_NAME_PATTERN),
|
|
49
|
-
help="Sort by cpu or memory",
|
|
50
|
-
)
|
|
51
|
-
parser.add_argument("--no-headers", action="store_true")
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def stringify_top_command(cmd: Any) -> str:
|
|
55
|
-
parts = ["kubectl", "top", cmd.resource_type]
|
|
56
|
-
|
|
57
|
-
# Add resource name if specified
|
|
58
|
-
if cmd.resource_name:
|
|
59
|
-
parts.append(cmd.resource_name)
|
|
60
|
-
|
|
61
|
-
if cmd.all_namespaces:
|
|
62
|
-
parts.append("--all-namespaces")
|
|
63
|
-
elif cmd.namespace:
|
|
64
|
-
parts.extend(["--namespace", cmd.namespace])
|
|
65
|
-
|
|
66
|
-
if cmd.selector:
|
|
67
|
-
parts.extend(["--selector", cmd.selector])
|
|
68
|
-
|
|
69
|
-
if cmd.containers:
|
|
70
|
-
parts.append("--containers")
|
|
71
|
-
|
|
72
|
-
if cmd.use_protocol_buffers:
|
|
73
|
-
parts.append("--use-protocol-buffers")
|
|
74
|
-
|
|
75
|
-
if cmd.sort_by:
|
|
76
|
-
parts.extend(["--sort-by", cmd.sort_by])
|
|
77
|
-
|
|
78
|
-
if cmd.no_headers:
|
|
79
|
-
parts.append("--no-headers")
|
|
80
|
-
|
|
81
|
-
return " ".join(escape_shell_args(parts))
|
|
9
|
+
class KubectlTopCommand(BashCommand):
|
|
10
|
+
def __init__(self):
|
|
11
|
+
super().__init__("top")
|
|
12
|
+
|
|
13
|
+
def add_parser(self, parent_parser: Any):
|
|
14
|
+
parser = parent_parser.add_parser(
|
|
15
|
+
"top",
|
|
16
|
+
help="Display resource (CPU/memory) usage",
|
|
17
|
+
exit_on_error=False,
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
"resource_type",
|
|
21
|
+
choices=["nodes", "node", "pods", "pod"],
|
|
22
|
+
help="Resource type to get usage for",
|
|
23
|
+
)
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
"options",
|
|
26
|
+
nargs=argparse.REMAINDER, # Captures all remaining arguments
|
|
27
|
+
default=[], # Default to an empty list
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def validate_command(
|
|
31
|
+
self, command: Any, original_command: str, config: Optional[BashExecutorConfig]
|
|
32
|
+
) -> None:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
def stringify_command(
|
|
36
|
+
self, command: Any, original_command: str, config: Optional[BashExecutorConfig]
|
|
37
|
+
) -> str:
|
|
38
|
+
parts = ["kubectl", "top", command.resource_type]
|
|
39
|
+
|
|
40
|
+
parts += command.options
|
|
41
|
+
|
|
42
|
+
return " ".join(escape_shell_args(parts))
|
|
@@ -1,39 +1,111 @@
|
|
|
1
1
|
import argparse
|
|
2
|
+
import logging
|
|
2
3
|
import shlex
|
|
3
4
|
from typing import Any, Optional
|
|
4
5
|
|
|
6
|
+
from holmes.plugins.toolsets.bash.common.bash_command import BashCommand
|
|
5
7
|
from holmes.plugins.toolsets.bash.common.config import BashExecutorConfig
|
|
6
|
-
from holmes.plugins.toolsets.bash.
|
|
7
|
-
from holmes.plugins.toolsets.bash.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
from holmes.plugins.toolsets.bash.kubectl import KubectlCommand
|
|
9
|
+
from holmes.plugins.toolsets.bash.aws import AWSCommand
|
|
10
|
+
from holmes.plugins.toolsets.bash.azure import AzureCommand
|
|
11
|
+
from holmes.plugins.toolsets.bash.argocd import ArgocdCommand
|
|
12
|
+
from holmes.plugins.toolsets.bash.docker import DockerCommand
|
|
13
|
+
from holmes.plugins.toolsets.bash.helm import HelmCommand
|
|
14
|
+
|
|
15
|
+
# Utilities imports - all now use Command classes
|
|
16
|
+
from holmes.plugins.toolsets.bash.utilities.wc import WCCommand
|
|
17
|
+
from holmes.plugins.toolsets.bash.utilities.cut import CutCommand
|
|
18
|
+
from holmes.plugins.toolsets.bash.utilities.sort import SortCommand
|
|
19
|
+
from holmes.plugins.toolsets.bash.utilities.uniq import UniqCommand
|
|
20
|
+
from holmes.plugins.toolsets.bash.utilities.head import HeadCommand
|
|
21
|
+
from holmes.plugins.toolsets.bash.utilities.tail import TailCommand
|
|
22
|
+
from holmes.plugins.toolsets.bash.utilities.tr import TrCommand
|
|
23
|
+
from holmes.plugins.toolsets.bash.utilities.base64_util import Base64Command
|
|
24
|
+
from holmes.plugins.toolsets.bash.utilities.jq import JqCommand
|
|
25
|
+
from holmes.plugins.toolsets.bash.utilities.sed import SedCommand
|
|
26
|
+
from holmes.plugins.toolsets.bash.utilities.grep import GrepCommand
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# All commands now use BashCommand classes
|
|
30
|
+
AVAILABLE_COMMANDS: list[BashCommand] = [
|
|
31
|
+
WCCommand(),
|
|
32
|
+
KubectlCommand(),
|
|
33
|
+
AWSCommand(),
|
|
34
|
+
AzureCommand(),
|
|
35
|
+
ArgocdCommand(),
|
|
36
|
+
DockerCommand(),
|
|
37
|
+
HelmCommand(),
|
|
38
|
+
GrepCommand(),
|
|
39
|
+
CutCommand(),
|
|
40
|
+
SortCommand(),
|
|
41
|
+
UniqCommand(),
|
|
42
|
+
HeadCommand(),
|
|
43
|
+
TailCommand(),
|
|
44
|
+
TrCommand(),
|
|
45
|
+
Base64Command(),
|
|
46
|
+
JqCommand(),
|
|
47
|
+
SedCommand(),
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
command_name_to_command_map: dict[str, BashCommand] = {
|
|
51
|
+
cmd.name: cmd for cmd in AVAILABLE_COMMANDS
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class QuietArgumentParser(argparse.ArgumentParser):
|
|
56
|
+
def __init__(self, *args, **kwargs):
|
|
57
|
+
super().__init__(*args, **kwargs)
|
|
58
|
+
|
|
59
|
+
def _print_message(self, message, file=None):
|
|
60
|
+
if message:
|
|
61
|
+
logging.debug(message.strip())
|
|
62
|
+
|
|
63
|
+
def error(self, message):
|
|
64
|
+
logging.debug(f"Error: {message}")
|
|
65
|
+
self.exit(2)
|
|
11
66
|
|
|
12
67
|
|
|
13
68
|
def create_parser() -> argparse.ArgumentParser:
|
|
14
|
-
parser =
|
|
15
|
-
|
|
69
|
+
parser = QuietArgumentParser(
|
|
70
|
+
prog="command_parser", # Set explicit program name
|
|
71
|
+
description="Parser for commands",
|
|
72
|
+
exit_on_error=False,
|
|
73
|
+
add_help=False, # Disable help to avoid conflicts with -h in subcommands
|
|
16
74
|
)
|
|
17
75
|
commands_parser = parser.add_subparsers(
|
|
18
76
|
dest="cmd", required=True, help="The tool to command (e.g., kubectl)"
|
|
19
77
|
)
|
|
20
78
|
|
|
21
|
-
|
|
22
|
-
|
|
79
|
+
# Add all BashCommand classes
|
|
80
|
+
for command in AVAILABLE_COMMANDS:
|
|
81
|
+
command.add_parser(commands_parser)
|
|
82
|
+
|
|
23
83
|
return parser
|
|
24
84
|
|
|
25
85
|
|
|
86
|
+
def validate_command(
|
|
87
|
+
command: Any, original_command: str, config: Optional[BashExecutorConfig]
|
|
88
|
+
):
|
|
89
|
+
bash_command_instance = command_name_to_command_map.get(command.cmd)
|
|
90
|
+
|
|
91
|
+
if bash_command_instance:
|
|
92
|
+
bash_command_instance.validate_command(command, original_command, config)
|
|
93
|
+
|
|
94
|
+
|
|
26
95
|
def stringify_command(
|
|
27
96
|
command: Any, original_command: str, config: Optional[BashExecutorConfig]
|
|
28
97
|
) -> str:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return
|
|
98
|
+
bash_command_instance = command_name_to_command_map.get(command.cmd)
|
|
99
|
+
|
|
100
|
+
if bash_command_instance:
|
|
101
|
+
return bash_command_instance.stringify_command(
|
|
102
|
+
command, original_command, config
|
|
103
|
+
)
|
|
33
104
|
else:
|
|
34
105
|
# This code path should not happen b/c the parsing of the command should catch an unsupported command
|
|
106
|
+
supported_commands = [cmd.name for cmd in AVAILABLE_COMMANDS]
|
|
35
107
|
raise ValueError(
|
|
36
|
-
f"Unsupported command '{command.cmd}' in {original_command}. Supported commands are:
|
|
108
|
+
f"Unsupported command '{command.cmd}' in {original_command}. Supported commands are: {', '.join(supported_commands)}"
|
|
37
109
|
)
|
|
38
110
|
|
|
39
111
|
|
|
@@ -81,23 +153,25 @@ def split_into_separate_commands(command_str: str) -> list[list[str]]:
|
|
|
81
153
|
def make_command_safe(command_str: str, config: Optional[BashExecutorConfig]) -> str:
|
|
82
154
|
commands = split_into_separate_commands(command_str)
|
|
83
155
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
156
|
+
parsed_commands = []
|
|
157
|
+
for individual_command in commands:
|
|
158
|
+
try:
|
|
159
|
+
parsed_commands.append(command_parser.parse_args(individual_command))
|
|
160
|
+
|
|
161
|
+
except SystemExit:
|
|
162
|
+
# argparse throws a SystemExit error when it can't parse command or arguments
|
|
163
|
+
# This ideally should be captured differently by ensuring all possible args
|
|
164
|
+
# are accounted for in the implementation for each command.
|
|
165
|
+
# When falling back, we raise a generic error
|
|
89
166
|
raise ValueError(
|
|
90
|
-
"The command
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
167
|
+
f"The following command failed to be parsed for safety: {command_str}"
|
|
168
|
+
) from None
|
|
169
|
+
for command in parsed_commands:
|
|
170
|
+
validate_command(command=command, original_command=command_str, config=config)
|
|
171
|
+
|
|
172
|
+
safe_commands_str = [
|
|
173
|
+
stringify_command(command=command, original_command=command_str, config=config)
|
|
174
|
+
for command in parsed_commands
|
|
175
|
+
]
|
|
96
176
|
|
|
97
|
-
|
|
98
|
-
except SystemExit:
|
|
99
|
-
# argparse throws a SystemExit error when it can't parse command or arguments
|
|
100
|
-
# This ideally should be captured differently by ensuring all possible args
|
|
101
|
-
# are accounted for in the implementation for each command.
|
|
102
|
-
# When falling back, we raise a generic error
|
|
103
|
-
raise ValueError("The command failed to be parsed for safety") from None
|
|
177
|
+
return " | ".join(safe_commands_str)
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from holmes.plugins.toolsets.bash.common.bash_command import (
|
|
2
|
+
SimpleBashCommand,
|
|
3
|
+
)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Base64Command(SimpleBashCommand):
|
|
7
|
+
def __init__(self):
|
|
8
|
+
super().__init__(
|
|
9
|
+
name="base64",
|
|
10
|
+
allowed_options=[], # Allow all options except file operations
|
|
11
|
+
denied_options=[],
|
|
12
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from holmes.plugins.toolsets.bash.common.bash_command import (
|
|
2
|
+
SimpleBashCommand,
|
|
3
|
+
)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CutCommand(SimpleBashCommand):
|
|
7
|
+
def __init__(self):
|
|
8
|
+
super().__init__(
|
|
9
|
+
name="cut",
|
|
10
|
+
allowed_options=[], # Allow all options except file operations
|
|
11
|
+
denied_options=[],
|
|
12
|
+
)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
|
|
4
|
+
from holmes.plugins.toolsets.bash.common.bash_command import BashCommand
|
|
5
|
+
from holmes.plugins.toolsets.bash.common.config import BashExecutorConfig
|
|
6
|
+
from holmes.plugins.toolsets.bash.common.stringify import escape_shell_args
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class JqCommand(BashCommand):
|
|
10
|
+
def __init__(self):
|
|
11
|
+
super().__init__("jq")
|
|
12
|
+
|
|
13
|
+
def add_parser(self, parent_parser: Any):
|
|
14
|
+
jq_parser = parent_parser.add_parser(
|
|
15
|
+
"jq",
|
|
16
|
+
help="JSON processor",
|
|
17
|
+
exit_on_error=False,
|
|
18
|
+
add_help=False, # Disable help to avoid conflicts
|
|
19
|
+
prefix_chars="\x00", # Use null character as prefix to disable option parsing
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Capture all arguments for validation
|
|
23
|
+
jq_parser.add_argument(
|
|
24
|
+
"options",
|
|
25
|
+
nargs=argparse.REMAINDER,
|
|
26
|
+
default=[],
|
|
27
|
+
help="Jq filter and options",
|
|
28
|
+
)
|
|
29
|
+
return jq_parser
|
|
30
|
+
|
|
31
|
+
def validate_command(
|
|
32
|
+
self, command: Any, original_command: str, config: Optional[BashExecutorConfig]
|
|
33
|
+
) -> None:
|
|
34
|
+
if hasattr(command, "options") and command.options:
|
|
35
|
+
validate_jq_options(command.options)
|
|
36
|
+
|
|
37
|
+
def stringify_command(
|
|
38
|
+
self, command: Any, original_command: str, config: Optional[BashExecutorConfig]
|
|
39
|
+
) -> str:
|
|
40
|
+
parts = ["jq"]
|
|
41
|
+
|
|
42
|
+
# Add validated options
|
|
43
|
+
if hasattr(command, "options") and command.options:
|
|
44
|
+
validated_options = validate_jq_options(command.options)
|
|
45
|
+
parts.extend(validated_options)
|
|
46
|
+
|
|
47
|
+
return " ".join(escape_shell_args(parts))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def validate_jq_options(options: list[str]) -> list[str]:
|
|
51
|
+
"""Validate jq CLI options - block file operations."""
|
|
52
|
+
i = 0
|
|
53
|
+
while i < len(options):
|
|
54
|
+
option = options[i]
|
|
55
|
+
|
|
56
|
+
# Block file reading operations
|
|
57
|
+
if option in {"--slurpfile", "--rawfile"}:
|
|
58
|
+
raise ValueError(f"Option {option} is not allowed for security reasons")
|
|
59
|
+
|
|
60
|
+
# Skip over option-value pairs
|
|
61
|
+
if option in {"--arg", "--argjson"} and i + 2 < len(options):
|
|
62
|
+
i += 3 # Skip option, name, value
|
|
63
|
+
continue
|
|
64
|
+
elif (
|
|
65
|
+
option.startswith("--")
|
|
66
|
+
and i + 1 < len(options)
|
|
67
|
+
and not options[i + 1].startswith("-")
|
|
68
|
+
):
|
|
69
|
+
i += 2 # Skip option and value
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
# No file arguments allowed (except filter expressions)
|
|
73
|
+
if not option.startswith("-") and "=" not in option:
|
|
74
|
+
# This could be a filter expression, allow it
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
i += 1
|
|
78
|
+
|
|
79
|
+
return options
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import re
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
from holmes.plugins.toolsets.bash.common.bash_command import BashCommand
|
|
6
|
+
from holmes.plugins.toolsets.bash.common.config import BashExecutorConfig
|
|
7
|
+
from holmes.plugins.toolsets.bash.common.stringify import escape_shell_args
|
|
8
|
+
|
|
9
|
+
# Blocked sed commands for security
|
|
10
|
+
BLOCKED_SED_COMMANDS = {
|
|
11
|
+
"w", # Write to file
|
|
12
|
+
"W", # Write first line of pattern space to file
|
|
13
|
+
"r", # Read file
|
|
14
|
+
"R", # Read one line from file
|
|
15
|
+
"e", # Execute command
|
|
16
|
+
"v", # Version (in some contexts)
|
|
17
|
+
"z", # Clear pattern space (can be misused)
|
|
18
|
+
"q", # Quit (can be misused to skip processing)
|
|
19
|
+
"Q", # Quit immediately
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# Pattern to detect dangerous sed commands
|
|
23
|
+
DANGEROUS_SED_PATTERN = re.compile(
|
|
24
|
+
r"(?:^|;|\n)\s*(?:[0-9,]*\s*)?[wWrRezvqQ](?:\s|$|/)", re.MULTILINE
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SedCommand(BashCommand):
|
|
29
|
+
def __init__(self):
|
|
30
|
+
super().__init__("sed")
|
|
31
|
+
|
|
32
|
+
def add_parser(self, parent_parser: Any):
|
|
33
|
+
sed_parser = parent_parser.add_parser(
|
|
34
|
+
"sed",
|
|
35
|
+
help="Stream editor for filtering and transforming text",
|
|
36
|
+
exit_on_error=False,
|
|
37
|
+
add_help=False, # Disable help to avoid conflicts
|
|
38
|
+
prefix_chars="\x00", # Use null character as prefix to disable option parsing
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Capture all arguments for validation
|
|
42
|
+
sed_parser.add_argument(
|
|
43
|
+
"options",
|
|
44
|
+
nargs=argparse.REMAINDER,
|
|
45
|
+
default=[],
|
|
46
|
+
help="Sed script and options",
|
|
47
|
+
)
|
|
48
|
+
return sed_parser
|
|
49
|
+
|
|
50
|
+
def validate_command(
|
|
51
|
+
self, command: Any, original_command: str, config: Optional[BashExecutorConfig]
|
|
52
|
+
) -> None:
|
|
53
|
+
if hasattr(command, "options") and command.options:
|
|
54
|
+
validate_sed_options(command.options)
|
|
55
|
+
|
|
56
|
+
def stringify_command(
|
|
57
|
+
self, command: Any, original_command: str, config: Optional[BashExecutorConfig]
|
|
58
|
+
) -> str:
|
|
59
|
+
parts = ["sed"]
|
|
60
|
+
|
|
61
|
+
# Add validated options
|
|
62
|
+
if hasattr(command, "options") and command.options:
|
|
63
|
+
validated_options = validate_sed_options(command.options)
|
|
64
|
+
parts.extend(validated_options)
|
|
65
|
+
|
|
66
|
+
return " ".join(escape_shell_args(parts))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def validate_sed_script(script: str) -> None:
|
|
70
|
+
"""Validate a sed script for safety."""
|
|
71
|
+
# Check for blocked commands - only check for actual command patterns
|
|
72
|
+
if DANGEROUS_SED_PATTERN.search(script):
|
|
73
|
+
raise ValueError("sed script contains blocked commands (w, r, e, q, etc.)")
|
|
74
|
+
|
|
75
|
+
# Check for execute command specifically (more targeted)
|
|
76
|
+
if re.search(r"(?:^|;|\n)\s*(?:[0-9,]*\s*)?e\b", script, re.MULTILINE):
|
|
77
|
+
raise ValueError("sed script contains execute commands")
|
|
78
|
+
|
|
79
|
+
# Check for file operations (simpler pattern to catch write/read commands)
|
|
80
|
+
if re.search(r"[wWrR]\s+\S", script):
|
|
81
|
+
raise ValueError("sed script contains file operations")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def validate_sed_options(options: list[str]) -> list[str]:
|
|
85
|
+
"""Validate sed CLI options - block file operations and in-place editing."""
|
|
86
|
+
i = 0
|
|
87
|
+
script_found = False
|
|
88
|
+
|
|
89
|
+
while i < len(options):
|
|
90
|
+
option = options[i]
|
|
91
|
+
|
|
92
|
+
# Check for attached/inlined forms of blocked options
|
|
93
|
+
if (option.startswith("-i") and len(option) > 2) or option.startswith(
|
|
94
|
+
"--in-place="
|
|
95
|
+
):
|
|
96
|
+
raise ValueError(
|
|
97
|
+
f"Attached in-place option {option} is not allowed for security reasons"
|
|
98
|
+
)
|
|
99
|
+
elif (option.startswith("-f") and len(option) > 2) or option.startswith(
|
|
100
|
+
"--file="
|
|
101
|
+
):
|
|
102
|
+
raise ValueError(
|
|
103
|
+
f"Attached file option {option} is not allowed for security reasons"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Block file reading and in-place editing for security
|
|
107
|
+
elif option in {"-f", "--file", "-i", "--in-place"}:
|
|
108
|
+
raise ValueError(f"Option {option} is not allowed for security reasons")
|
|
109
|
+
|
|
110
|
+
# Handle -e and --expression with attached scripts
|
|
111
|
+
elif option.startswith("-e") and len(option) > 2:
|
|
112
|
+
# Handle -eSCRIPT form
|
|
113
|
+
script = option[2:] # Extract script after "-e"
|
|
114
|
+
validate_sed_script(script)
|
|
115
|
+
script_found = True
|
|
116
|
+
i += 1
|
|
117
|
+
continue
|
|
118
|
+
elif option.startswith("--expression="):
|
|
119
|
+
# Handle --expression=SCRIPT form
|
|
120
|
+
script = option[13:] # Extract script after "--expression="
|
|
121
|
+
validate_sed_script(script)
|
|
122
|
+
script_found = True
|
|
123
|
+
i += 1
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
# Handle -e and --expression with separate arguments
|
|
127
|
+
elif option in {"-e", "--expression"}:
|
|
128
|
+
if i + 1 >= len(options) or options[i + 1].startswith("-"):
|
|
129
|
+
raise ValueError(f"Option {option} requires a script argument")
|
|
130
|
+
script = options[i + 1]
|
|
131
|
+
validate_sed_script(script)
|
|
132
|
+
script_found = True
|
|
133
|
+
i += 2
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
# Handle long options with values (--opt=val)
|
|
137
|
+
elif "=" in option and option.startswith("--"):
|
|
138
|
+
# Long option with attached value, skip as single unit
|
|
139
|
+
i += 1
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
# Handle other long options with separate values
|
|
143
|
+
elif (
|
|
144
|
+
option.startswith("--")
|
|
145
|
+
and i + 1 < len(options)
|
|
146
|
+
and not options[i + 1].startswith("-")
|
|
147
|
+
):
|
|
148
|
+
i += 2 # Skip option and value
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
# Handle sed script (non-flag argument)
|
|
152
|
+
elif not option.startswith("-"):
|
|
153
|
+
if not script_found:
|
|
154
|
+
validate_sed_script(option)
|
|
155
|
+
script_found = True
|
|
156
|
+
else:
|
|
157
|
+
# Multiple scripts not allowed
|
|
158
|
+
pass
|
|
159
|
+
i += 1
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
i += 1
|
|
163
|
+
|
|
164
|
+
return options
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from holmes.plugins.toolsets.bash.common.bash_command import (
|
|
2
|
+
SimpleBashCommand,
|
|
3
|
+
)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SortCommand(SimpleBashCommand):
|
|
7
|
+
def __init__(self):
|
|
8
|
+
super().__init__(
|
|
9
|
+
name="sort",
|
|
10
|
+
allowed_options=[],
|
|
11
|
+
denied_options=[
|
|
12
|
+
"-T",
|
|
13
|
+
"--temporary-directory",
|
|
14
|
+
],
|
|
15
|
+
)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
|
|
4
|
+
from holmes.plugins.toolsets.bash.common.bash_command import BashCommand
|
|
5
|
+
from holmes.plugins.toolsets.bash.common.config import BashExecutorConfig
|
|
6
|
+
from holmes.plugins.toolsets.bash.common.stringify import escape_shell_args
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TrCommand(BashCommand):
|
|
10
|
+
def __init__(self):
|
|
11
|
+
super().__init__("tr")
|
|
12
|
+
|
|
13
|
+
def add_parser(self, parent_parser: Any):
|
|
14
|
+
parser = parent_parser.add_parser(
|
|
15
|
+
"tr",
|
|
16
|
+
help="Translate or delete characters",
|
|
17
|
+
exit_on_error=False,
|
|
18
|
+
add_help=False, # Disable help to avoid conflicts
|
|
19
|
+
prefix_chars="\x00", # Use null character as prefix to disable option parsing
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Capture all arguments for validation
|
|
23
|
+
parser.add_argument(
|
|
24
|
+
"options",
|
|
25
|
+
nargs=argparse.REMAINDER,
|
|
26
|
+
default=[],
|
|
27
|
+
help="tr options and character sets",
|
|
28
|
+
)
|
|
29
|
+
return parser
|
|
30
|
+
|
|
31
|
+
def validate_command(
|
|
32
|
+
self, command: Any, original_command: str, config: Optional[BashExecutorConfig]
|
|
33
|
+
) -> None:
|
|
34
|
+
# tr is allowed to have character set arguments, but not file paths
|
|
35
|
+
# Block absolute paths, home-relative paths, relative paths, and common file extensions
|
|
36
|
+
blocked_extensions = (".txt", ".log", ".py", ".js", ".json")
|
|
37
|
+
|
|
38
|
+
for option in command.options:
|
|
39
|
+
if not option.startswith("-"):
|
|
40
|
+
# Allow character sets but block obvious file paths
|
|
41
|
+
if (
|
|
42
|
+
option.startswith("/")
|
|
43
|
+
or option.startswith("~")
|
|
44
|
+
or option.startswith("./")
|
|
45
|
+
or option.startswith("../")
|
|
46
|
+
or option.endswith(blocked_extensions)
|
|
47
|
+
):
|
|
48
|
+
raise ValueError(
|
|
49
|
+
"File arguments are not allowed - tr can only process piped input"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def stringify_command(
|
|
53
|
+
self, command: Any, original_command: str, config: Optional[BashExecutorConfig]
|
|
54
|
+
) -> str:
|
|
55
|
+
parts = ["tr"]
|
|
56
|
+
parts.extend(command.options)
|
|
57
|
+
return " ".join(escape_shell_args(parts))
|