From 4544af93d37f426e13efa7322793ef9bab31458b Mon Sep 17 00:00:00 2001 From: Jasmin Wachter Date: Fri, 1 Aug 2025 12:56:03 +0200 Subject: [PATCH 1/6] Update README.md (#233) * Update README.md Updated CAI file structure to match version v.0.5.2 * Add files via upload * Update README.md Add caiedu picture --- README.md | 41 +++++++++++++++++++---------------------- media/caiedu.PNG | Bin 0 -> 87947 bytes 2 files changed, 19 insertions(+), 22 deletions(-) create mode 100644 media/caiedu.PNG diff --git a/README.md b/README.md index 7015a767..470cd508 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,16 @@ Cybersecurity AI is a critical field, yet many groups are misguidedly pursuing i ## Learn - `CAI` Fluency +
+

+ + + +

+ | | Description | English | Spanish | |-------|----------------|---------|---------| @@ -379,28 +389,15 @@ CAI focuses on making cybersecurity agent **coordination** and **execution** lig If you want to dive deeper into the code, check the following files as a start point for using CAI: -``` -cai -├── __init__.py -│ -├── cli.py # entrypoint for CLI -├── core.py # core implementation and agentic flow -├── types.py # main abstractions and classes -├── util.py # utility functions -│ -├── repl # CLI aesthetics and commands -│ ├── commands -│ └── ui -├── agents # agent implementations -│ ├── one_tool.py # agent, one agent per file -│ └── patterns # agentic patterns, one per file -│ -├── tools # agent tools -│ ├── common.py - -caiextensions # out of tree Python extensions -``` - +* [__init__.py](https://github.com/aliasrobotics/cai/blob/main/src/cai/__init__.py) +* [cli.py](https://github.com/aliasrobotics/cai/blob/main/src/cai/cli.py) - entrypoint for command line interface +* [util.py](https://github.com/aliasrobotics/cai/blob/main/src/cai/util.py) - utility functions +* [agents](https://github.com/aliasrobotics/cai/blob/main/src/cai/agents) - Agent implementations +* [internal](https://github.com/aliasrobotics/cai/blob/main/src/cai/internal) - CAI internal functions (endpoints, metrics, logging, etc.) +* [prompts](https://github.com/aliasrobotics/cai/blob/main/src/cai/prompts) - Agent Prompt Database +* [repl](https://github.com/aliasrobotics/cai/blob/main/src/cai/repl) - CLI aesthetics and commands +* [sdk](https://github.com/aliasrobotics/cai/blob/main/src/cai/sdk) - CAI command sdk +* [tools](https://github.com/aliasrobotics/cai/tree/main/src/cai/tools) - agent tools ### 🔹 Agent diff --git a/media/caiedu.PNG b/media/caiedu.PNG new file mode 100644 index 0000000000000000000000000000000000000000..26611f7aa9e739b6d17ca01e448a5b0d58fc94a5 GIT binary patch literal 87947 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®!R5}Q~tp8b_ziV)30iccmZ9i#xB&6K@O>7is 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 O5O})!xvX Date: Sat, 2 Aug 2025 06:46:30 +0200 Subject: [PATCH 2/6] Update python examples on readme and portswigger bot in fluency folder (#237) Co-authored-by: cris --- README.md | 188 +++++++----------- fluency/my-first-hack/utils/portswiggerbot.py | 28 ++- 2 files changed, 97 insertions(+), 119 deletions(-) mode change 100644 => 100755 fluency/my-first-hack/utils/portswiggerbot.py diff --git a/README.md b/README.md index 470cd508..ce0f0230 100644 --- a/README.md +++ b/README.md @@ -401,65 +401,58 @@ If you want to dive deeper into the code, check the following files as a start p ### 🔹 Agent -At its core, CAI abstracts its cybersecurity behavior via `Agents` and agentic `Patterns`. An Agent in *an intelligent system that interacts with some environment*. More technically, within CAI we embrace a robotics-centric definition wherein an agent is anything that can be viewed as a system perceiving its environment through sensors, reasoning about its goals and and acting accordingly upon that environment through actuators (*adapted* from Russel & Norvig, AI: A Modern Approach). In cybersecurity, an `Agent` interacts with systems and networks, using peripherals and network interfaces as sensors, reasons accordingly and then executes network actions as if actuators. Correspondingly, in CAI, `Agent`s implement the `ReACT` (Reasoning and Action) agent model[^3]. - +At its core, CAI abstracts its cybersecurity behavior via `Agents` and agentic `Patterns`. An Agent in *an intelligent system that interacts with some environment*. More technically, within CAI we embrace a robotics-centric definition wherein an agent is anything that can be viewed as a system perceiving its environment through sensors, reasoning about its goals and and acting accordingly upon that environment through actuators (*adapted* from Russel & Norvig, AI: A Modern Approach). In cybersecurity, an `Agent` interacts with systems and networks, using peripherals and network interfaces as sensors, reasons accordingly and then executes network actions as if actuators. Correspondingly, in CAI, `Agent`s implement the `ReACT` (Reasoning and Action) agent model[^3]. For more information, see the [example here](https://github.com/aliasrobotics/cai/blob/main/examples/basic/hello_world.py) for the full execution code, and refer to this [jupyter notebook](https://github.com/aliasrobotics/cai/blob/main/fluency/my-first-hack/my_first_hack.ipynb) for a tutorial on how to use it. ```python -from cai.sdk.agents import Agent -from cai.core import CAI -ctf_agent = Agent( - name="CTF Agent", - instructions="""You are a Cybersecurity expert Leader""", - model= "gpt-4o", -) +from cai.sdk.agents import Agent, Runner, OpenAIChatCompletionsModel -messages = [{ - "role": "user", - "content": "CTF challenge: TryMyNetwork. Target IP: 192.168.1.1" - }] +import os +from openai import AsyncOpenAI +from dotenv import load_dotenv +load_dotenv() -client = CAI() -response = client.run(agent=ctf_agent, - messages=messages) +agent = Agent( + name="Custom Agent", + instructions="""You are a Cybersecurity expert Leader""", + model=OpenAIChatCompletionsModel( + model=os.getenv('CAI_MODEL', "openai/gpt-4o"), + openai_client=AsyncOpenAI(), + ) + ) + +message = "Tell me about recursion in programming." +result = await Runner.run(agent, message) ``` ### 🔹 Tools -`Tools` let cybersecurity agents take actions by providing interfaces to execute system commands, run security scans, analyze vulnerabilities, and interact with target systems and APIs - they are the core capabilities that enable CAI agents to perform security tasks effectively; in CAI, tools include built-in cybersecurity utilities (like LinuxCmd for command execution, WebSearch for OSINT gathering, Code for dynamic script execution, and SSHTunnel for secure remote access), function calling mechanisms that allow integration of any Python function as a security tool, and agent-as-tool functionality that enables specialized security agents (such as reconnaissance or exploit agents) to be used by other agents, creating powerful collaborative security workflows without requiring formal handoffs between agents. +`Tools` let cybersecurity agents take actions by providing interfaces to execute system commands, run security scans, analyze vulnerabilities, and interact with target systems and APIs - they are the core capabilities that enable CAI agents to perform security tasks effectively; in CAI, tools include built-in cybersecurity utilities (like LinuxCmd for command execution, WebSearch for OSINT gathering, Code for dynamic script execution, and SSHTunnel for secure remote access), function calling mechanisms that allow integration of any Python function as a security tool, and agent-as-tool functionality that enables specialized security agents (such as reconnaissance or exploit agents) to be used by other agents, creating powerful collaborative security workflows without requiring formal handoffs between agents. For more information, please refer to the [example here](https://github.com/aliasrobotics/cai/blob/main/examples/basic/tools.py) for the complete configuration of custom functions. ```python -from cai.sdk.agents import Agent -from cai.tools.common import run_command -from cai.core import CAI +from cai.sdk.agents import Agent, Runner, OpenAIChatCompletionsModel +from cai.tools.reconnaissance.exec_code import execute_code +from cai.tools.reconnaissance.generic_linux_command import generic_linux_command -def listing_tool(): - """ - This is a tool used list the files in the current directory - """ - command = "ls -la" - return run_command(command, ctf=ctf) +import os +from openai import AsyncOpenAI +from dotenv import load_dotenv +load_dotenv() -def generic_linux_command(command: str = "", args: str = "", ctf=None) -> str: - """ - Tool to send a linux command. - """ - command = f'{command} {args}' - return run_command(command, ctf=ctf) +agent = Agent( + name="Custom Agent", + instructions="""You are a Cybersecurity expert Leader""", + tools= [ + generic_linux_command, + execute_code + ], + model=OpenAIChatCompletionsModel( + model=os.getenv('CAI_MODEL', "openai/gpt-4o"), + openai_client=AsyncOpenAI(), + ) + ) -ctf_agent = Agent( - name="CTF Agent", - instructions="""You are a Cybersecurity expert Leader""", - model= "claude-3-7-sonnet-20250219", - functions=[listing_tool, generic_linux_command]) - -client = CAI() -messages = [{ - "role": "user", - "content": "CTF challenge: TryMyNetwork. Target IP: 192.168.1.1" - }] - -response = client.run(agent=ctf_agent, - messages=messages) +message = "Tell me about recursion in programming." +result = await Runner.run(agent, message) ``` @@ -475,43 +468,47 @@ You may find different [tools](cai/tools). They are grouped in 6 major categorie ### 🔹 Handoffs -`Handoffs` allow an `Agent` to delegate tasks to another agent, which is crucial in cybersecurity operations where specialized expertise is needed for different phases of an engagement. In our framework, `Handoffs` are implemented as tools for the LLM, where a **handoff/transfer function** like `transfer_to_flag_discriminator` enables the `ctf_agent` to pass control to the `flag_discriminator_agent` once it believes it has found the flag. This creates a security validation chain where the first agent handles exploitation and flag discovery, while the second agent specializes in flag verification, ensuring proper segregation of duties and leveraging specialized capabilities of different models for distinct security tasks. +`Handoffs` allow an `Agent` to delegate tasks to another agent, which is crucial in cybersecurity operations where specialized expertise is needed for different phases of an engagement. In our framework, `Handoffs` are implemented as tools for the LLM, where a **handoff/transfer function** like `transfer_to_flag_discriminator` enables the `ctf_agent` to pass control to the `flag_discriminator_agent` once it believes it has found the flag. This creates a security validation chain where the first agent handles exploitation and flag discovery, while the second agent specializes in flag verification, ensuring proper segregation of duties and leveraging specialized capabilities of different models for distinct security tasks. For more information, please refer to the [example here](https://github.com/aliasrobotics/cai/blob/main/examples/cai/agent_patterns/handoffs.py) for the full execution code. ```python -from cai.sdk.agents import Agent -from cai.core import CAI +from cai.sdk.agents import function_tool +from cai.tools.common import run_command +from cai.sdk.agents import Agent, OpenAIChatCompletionsModel, HandoffInputData, Runner, function_tool, handoff, trace +from cai.sdk.agents.extensions import handoff_filters + +import os +from openai import AsyncOpenAI +from dotenv import load_dotenv +load_dotenv() + +@function_tool +def execute_cli_command(command: str) -> str: + return run_command(command) + +flag_discriminator = Agent( + name="Flag discriminator", + description="Agent focused on extracting the flag from the output", + instructions="You are an agent tailored to extract the flag from a given output.", + model=OpenAIChatCompletionsModel( + model=os.getenv('CAI_MODEL', "qwen2.5:14b"), + openai_client=AsyncOpenAI(), + ) +) ctf_agent = Agent( - name="CTF Agent", - instructions="""You are a Cybersecurity expert Leader""", - model= "deepseek/deepseek-chat", - functions=[], + name="CTF agent", + description="Agent focused on conquering security challenges", + instructions="You are a Cybersecurity expert Leader facing a CTF", + tools=[ + execute_cli_command, + ], + model=OpenAIChatCompletionsModel( + model= os.getenv('CAI_MODEL', "qwen2.5:14b"), + openai_client=AsyncOpenAI(), + ), + handoffs = [flag_discriminator] ) - -flag_discriminator_agent = Agent( - name="Flag Discriminator Agent", - instructions="You are a Cybersecurity expert facing a CTF challenge. You are in charge of checking if the flag is correct.", - model= "qwen2.5:14b", - functions=[], -) - -def transfer_to_flag_discriminator(): - """ - Transfer the flag to the flag_discriminator_agent to check if it is the correct flag - """ - return flag_discriminator_agent - -ctf_agent.functions.append(transfer_to_flag_discriminator) - -client = CAI() -messages = [{ - "role": "user", - "content": "CTF challenge: TryMyNetwork. Target IP: 192.168.1.1" - }] - -response = client.run(agent=ctf_agent, - messages=messages) ``` ### 🔹 Patterns @@ -543,44 +540,9 @@ When building `Patterns`, we generall y classify them among one of the following | `Auction-Based` (Competitive Allocation) | Agents "bid" on tasks based on priority, capability, or cost. A decision agent evaluates bids and hands off tasks to the best-fit agent. | | `Recursive` | A single agent continuously refines its own output, treating itself as both executor and evaluator, with handoffs (internal or external) to itself. *An example of a recursive agentic pattern is the `CodeAgent` (when used as a recursive agent), which continuously refines its own output by executing code and updating its own instructions.* | -Building a `Pattern` is rather straightforward and only requires to link together `Agents`, `Tools` and `Handoffs`. For example, the following builds an offensive `Pattern` that adopts the `Swarm` category: - -```python -# A Swarm Pattern for Red Team Operations -from cai.agents.red_teamer import redteam_agent -from cai.agents.thought import thought_agent -from cai.agents.mail import dns_smtp_agent +For more information and examples of common agentic patterns, see the [examples folder](https://github.com/aliasrobotics/cai/blob/main/examples/agent_patterns/README.md). -def transfer_to_dns_agent(): - """ - Use THIS always for DNS scans and domain reconnaissance about dmarc and dkim registers - """ - return dns_smtp_agent - - -def redteam_agent_handoff(ctf=None): - """ - Red Team Agent, call this function empty to transfer to redteam_agent - """ - return redteam_agent - - -def thought_agent_handoff(ctf=None): - """ - Thought Agent, call this function empty to transfer to thought_agent - """ - return thought_agent - -# Register handoff functions to enable inter-agent communication pathways -redteam_agent.functions.append(transfer_to_dns_agent) -dns_smtp_agent.functions.append(redteam_agent_handoff) -thought_agent.functions.append(redteam_agent_handoff) - -# Initialize the swarm pattern with the thought agent as the entry point -redteam_swarm_pattern = thought_agent -redteam_swarm_pattern.pattern = "swarm" -``` ### 🔹 Turns and Interactions During the agentic flow (conversation), we distinguish between **interactions** and **turns**. diff --git a/fluency/my-first-hack/utils/portswiggerbot.py b/fluency/my-first-hack/utils/portswiggerbot.py old mode 100644 new mode 100755 index f27f84c4..87e49c6c --- a/fluency/my-first-hack/utils/portswiggerbot.py +++ b/fluency/my-first-hack/utils/portswiggerbot.py @@ -3,7 +3,9 @@ from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.chrome.service import Service +from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.chrome.options import Options +from selenium.webdriver.support import expected_conditions as EC import time import random import json @@ -14,7 +16,7 @@ from pathlib import Path class Bot(): - def __init__(self): + def __init__(self,headless=True): """ Initializes the MyBrowser instance. Sets up Chrome WebDriver with headless mode and necessary arguments @@ -24,8 +26,13 @@ class Bot(): self.LABS_URL = 'https://portswigger.net/web-security/all-labs#' self.prefixes_filename = 'topics_prefixes.json' self.options = Options() - - for arg in ['--headless','--disable-gpu', '--no-sandbox']: + + if headless: + args = ['--headless','--disable-gpu', '--no-sandbox'] + else: + args = ['--disable-gpu', '--no-sandbox'] + + for arg in args: self.options.add_argument(arg) self.driver = webdriver.Chrome(options=self.options) @@ -76,7 +83,7 @@ class Bot(): - def choose_topic(self,topic_name='sql-injection',level=None): + def choose_topic(self,topic_name='cross-site-scripting',level=None): """ Extract urls of each of the labs in the selected section. @@ -99,9 +106,18 @@ class Bot(): #Go to sections urls self.driver.get(f'{self.LABS_URL}{topic_name}') + self.__wait_random_time(min_seconds=5, max_seconds=7) + + links = WebDriverWait(self.driver, 10).until( + EC.presence_of_all_elements_located((By.CLASS_NAME, 'widgetcontainer-lab-link')) + ) + + + #Find all elements that have the topic prefix in the href - links = self.driver.find_elements(By.CLASS_NAME, 'flex-columns') + links = self.driver.find_elements(By.CLASS_NAME, 'widgetcontainer-lab-link') + #Extract the href attributes if level: @@ -110,7 +126,7 @@ class Bot(): extracted_links = [link.find_element(By.TAG_NAME, 'a').get_attribute('href') for link in links] #Filter links that contain the topic prefix - return [link for link in extracted_links if topic_name == link.split('/')[4]] + return [link for link in extracted_links if topic_prefix == link.split('/')[4]] def obtain_lab_information(self,lab_url): """ From c2103df1092962407ecbd57e372829db99d63003 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Mayoral=20Vilches?= Date: Sat, 2 Aug 2025 09:50:28 +0200 Subject: [PATCH 3/6] Update README.md Add missing div --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ce0f0230..2f00069a 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,7 @@ Cybersecurity AI is a critical field, yet many groups are misguidedly pursuing i >

+
| | Description | English | Spanish | From a6f8608f0661c9fd90f6d04da94098a275abcc0d Mon Sep 17 00:00:00 2001 From: Chris <41129709+chrissRo@users.noreply.github.com> Date: Thu, 7 Aug 2025 18:17:10 +0200 Subject: [PATCH 4/6] Add suggestions and autocompletion to command /mcp (#239) * Add MCP command completion support Implemented functions to provide suggestions for MCP server names and context-aware MCP command completions. Enhanced the FuzzyCommandCompleter to handle MCP commands, including subcommands and transport types. * Refactor FuzzyCommandCompleter to utilize default subcommand handler Removed hardcoded subcommand definitions and replaced them with a call to the existing method for fetching subcommand suggestions. This enhances maintainability and reduces code duplication. --------- Co-authored-by: christian.pritzl --- src/cai/repl/commands/completer.py | 144 +++++++++++++++++++++++++++-- 1 file changed, 137 insertions(+), 7 deletions(-) diff --git a/src/cai/repl/commands/completer.py b/src/cai/repl/commands/completer.py index 7d07c927..f655d9c7 100644 --- a/src/cai/repl/commands/completer.py +++ b/src/cai/repl/commands/completer.py @@ -569,6 +569,109 @@ class FuzzyCommandCompleter(Completer): return suggestions + def get_mcp_server_suggestions(self, current_word: str) -> List[Completion]: + """Get MCP server name suggestions. + + Args: + current_word: The current word being typed + + Returns: + A list of completions for MCP servers + """ + suggestions = [] + + try: + # Import the global MCP servers registry + from cai.repl.commands.mcp import _GLOBAL_MCP_SERVERS + + # Get all active MCP server names + for server_name in _GLOBAL_MCP_SERVERS.keys(): + # Get server type for display + server = _GLOBAL_MCP_SERVERS[server_name] + server_type = type(server).__name__.replace("MCPServer", "") + + # Exact prefix match + if server_name.startswith(current_word): + suggestions.append(Completion( + server_name, + start_position=-len(current_word), + display=HTML( + f"{server_name} " + f"({server_type})"), + style="fg:ansicyan bold" + )) + # Fuzzy match + elif (current_word.lower() in server_name.lower() and + not server_name.startswith(current_word)): + suggestions.append(Completion( + server_name, + start_position=-len(current_word), + display=HTML( + f"{server_name} " + f"({server_type})"), + style="fg:ansicyan" + )) + except (ImportError, AttributeError): + pass # No MCP servers available + + return suggestions + + def get_mcp_suggestions(self, words: List[str], current_word: str) -> List[Completion]: + """Get context-aware MCP command completions. + + Args: + words: List of words including empty string if trailing space + current_word: The current word being typed (empty if trailing space) + + Returns: + List of completion suggestions + """ + suggestions = [] + + # Get the actual typed words (excluding empty strings from trailing spaces) + actual_words = [w for w in words if w] + + # Position 2: Completing subcommand (e.g., "/mcp ") + # Use the default subcommand handler - no need to duplicate! + if len(words) == 2: + return self.get_subcommand_suggestions(words[0], current_word) + + # Position 3: After subcommand (e.g., "/mcp load ") + elif len(words) == 3 and len(actual_words) > 1: + subcommand = actual_words[1] + + if subcommand == "load": + # Suggest transport types for load command + if not current_word.startswith("http"): # Don't suggest if typing URL + transports = [ + ("stdio", "Local process communication"), + ("sse", "Server-Sent Events (HTTP)"), + ] + for transport, desc in transports: + if transport.startswith(current_word): + suggestions.append(Completion( + transport, + start_position=-len(current_word), + display=HTML( + f"{transport} " + f"- {desc}"), + style="fg:ansiyellow bold" + )) + + elif subcommand in ["add", "remove", "tools"]: + # These commands need an MCP server name + suggestions.extend(self.get_mcp_server_suggestions(current_word)) + + # Position 4: After server name in add command (e.g., "/mcp add server ") + elif len(words) == 4 and len(actual_words) > 1: + subcommand = actual_words[1] + + if subcommand == "add": + # After server name, suggest agent names + suggestions.extend(self.get_agent_suggestions(current_word)) + + return suggestions + # pylint: disable=unused-argument def get_completions(self, document, complete_event): """Get completions for the current document @@ -581,8 +684,13 @@ class FuzzyCommandCompleter(Completer): Returns: A generator of completions """ - text = document.text_before_cursor.strip() + # Keep original text to detect trailing spaces + text_original = document.text_before_cursor + text = text_original.strip() words = text.split() + + # Check if there's a trailing space (user finished typing a word) + has_trailing_space = text_original and text_original[-1] == ' ' # Refresh Ollama models and agents periodically self.fetch_all_models() @@ -611,15 +719,23 @@ class FuzzyCommandCompleter(Completer): return if text.startswith('/'): - current_word = words[-1] + # Determine current word and effective word count based on trailing space + # Example: "/mcp " has trailing space, so current_word="" and we add empty string to words + # Example: "/mcp" has no trailing space, so current_word="/mcp" + if has_trailing_space: + current_word = "" + effective_words = words + [""] # Add empty string to represent new word position + else: + current_word = words[-1] if words else "" + effective_words = words # Main command completion (first word) - if len(words) == 1: + if len(effective_words) == 1 and not has_trailing_space: # Get command suggestions yield from self.get_command_suggestions(current_word) # Subcommand completion (second word) - elif len(words) == 2: + elif len(effective_words) == 2: cmd = words[0] # Special handling for model command @@ -628,15 +744,29 @@ class FuzzyCommandCompleter(Completer): # Add special handling for agent command elif cmd in ["/agent", "/a"]: yield from self.get_agent_suggestions(current_word) + # Add special handling for MCP command + elif cmd in ["/mcp", "/m"]: + yield from self.get_mcp_suggestions(effective_words, current_word) else: # Get subcommand suggestions yield from self.get_subcommand_suggestions(cmd, current_word) - # Agent select completion - elif len(words) == 3: + # Third word completion + elif len(effective_words) == 3: cmd = words[0] - subcommand = words[1] + subcommand = words[1] if len(words) > 1 else "" # Agent select completion if cmd in ["/agent", "/a"] and subcommand in ["select", "info"]: yield from self.get_agent_suggestions(current_word) + # MCP command completion for third word + elif cmd in ["/mcp", "/m"]: + yield from self.get_mcp_suggestions(effective_words, current_word) + + # Fourth word completion (for MCP add command) + elif len(effective_words) == 4: + cmd = words[0] + + # MCP add command needs agent name as fourth word + if cmd in ["/mcp", "/m"]: + yield from self.get_mcp_suggestions(effective_words, current_word) From 9b60fddf0e64350d05c350efd25d23216761fc17 Mon Sep 17 00:00:00 2001 From: Yahya Sayed Date: Thu, 7 Aug 2025 19:17:53 +0300 Subject: [PATCH 5/6] Add android app security testing agent (#238) add android app security testing agent --- src/cai/agents/android_sast_agent.py | 67 +++++++++++ .../system_android_app_logic_mapper.md | 63 +++++++++++ src/cai/prompts/system_android_sast.md | 104 ++++++++++++++++++ 3 files changed, 234 insertions(+) create mode 100644 src/cai/agents/android_sast_agent.py create mode 100644 src/cai/prompts/system_android_app_logic_mapper.md create mode 100644 src/cai/prompts/system_android_sast.md diff --git a/src/cai/agents/android_sast_agent.py b/src/cai/agents/android_sast_agent.py new file mode 100644 index 00000000..cc0a9138 --- /dev/null +++ b/src/cai/agents/android_sast_agent.py @@ -0,0 +1,67 @@ +""" +This module defines agents for Android Static Application Security Testing (SAST). + +It includes: +- `app_logic_mapper_agent`: An agent for analyzing application logic. +- `android_sast_agent`: An agent for static analysis and vulnerability discovery in Android applications. +""" + +from cai.sdk.agents import Agent, OpenAIChatCompletionsModel +from cai.tools.reconnaissance.generic_linux_command import generic_linux_command +from openai import AsyncOpenAI +import os +from dotenv import load_dotenv + +from cai.util import load_prompt_template, create_system_prompt_renderer +from cai.tools.reconnaissance.exec_code import ( + execute_code +) + + +# Prompts +android_sast_system_prompt = load_prompt_template("prompts/system_android_sast.md") +app_logic_mapper_system_prompt = load_prompt_template("prompts/system_android_app_logic_mapper.md") + + + +# Define tools list based on available API keys +tools = [ + generic_linux_command, + execute_code, +] + + +load_dotenv() +model_name = os.getenv("CAI_MODEL", "alias0") + +app_logic_mapper = Agent( + name="AppLogicMapper", + description="Agent specializing in application analysis to understand the logic of operation and return a complete map of it.", + instructions=create_system_prompt_renderer(app_logic_mapper_system_prompt), + tools=tools, + model=OpenAIChatCompletionsModel( + model=model_name, + openai_client=AsyncOpenAI(), + ), +) + + + +android_sast = Agent( + name="AndroidSAST", + description="Agent specializing in static application security testing and vulnerability discovery for Android applications", + instructions=create_system_prompt_renderer(android_sast_system_prompt), + tools=[ + app_logic_mapper.as_tool( + tool_name="app_mapper", + tool_description="application analysis to understand the logic of operation and return a complete map of it." + ), + generic_linux_command, + execute_code, + ], + model=OpenAIChatCompletionsModel( + model=model_name, + openai_client=AsyncOpenAI(), + ), +) + diff --git a/src/cai/prompts/system_android_app_logic_mapper.md b/src/cai/prompts/system_android_app_logic_mapper.md new file mode 100644 index 00000000..ae8c3aae --- /dev/null +++ b/src/cai/prompts/system_android_app_logic_mapper.md @@ -0,0 +1,63 @@ + +### ROLE ### +You are a Principal Android Reverse Engineer & Security Analyst. Your expertise lies in meticulously analyzing decompiled Android application source code, specifically the output generated by the `JADX` tool. You possess a deep understanding of the Android framework, common third-party libraries, and obfuscation techniques employed by developers. + +### OBJECTIVE ### +Your primary mission is to analyze a provided set of decompiled Android source code from `JADX` and produce a comprehensive "Application Architecture & Logic Map." This report will serve as the definitive high-level documentation for understanding the application's purpose, structure, and internal workings, with a particular focus on identifying key functionalities and potential security-relevant areas. + +### CONTEXT ### +You will be given access to the complete output of a `JADX` decompilation. This includes Java source code (potentially obfuscated), resources (`res` folder), and the `AndroidManifest.xml`. You must assume that class, method, and variable names may be obfuscated (e.g., `a.b.c`, `m1234a()`) and your analysis must be resilient to this. You must infer functionality from API calls, constant values, and code structure. + +* **MUST NOT** pass the `session_id` with `generic_linux_command`. +**Correct example:** +- `generic_linux_command("ls")` without `session_id` + +### ANALYTICAL WORKFLOW (Chain-of-Thought) ### +To ensure a thorough and structured analysis, you MUST follow this internal workflow: + +1. **Manifest First Analysis:** Begin by parsing the `AndroidManifest.xml`. This is your ground truth. + * Identify the package name, declared `permissions`, `Activities`, `Services`, `Broadcast Receivers`, and `Content Providers`. + * Pinpoint the main launcher `Activity` (the entry point for the user). + * Extract all `intent-filter` definitions to identify custom URL schemes (deep links) and other external entry points. + +2. **Component & Library Identification:** + * Scan the package structure to identify well-known third-party libraries (e.g., `com.squareup.okhttp3` for OkHttp, `retrofit2` for Retrofit, `com.google.firebase` for Firebase, `io.reactivex` for RxJava). List these and their likely purpose. + * Examine the key components identified in the manifest. For each major `Activity`, `Service`, etc., briefly determine its role based on its name (if available) and the code within its `onCreate()`, `onStartCommand()`, or `onReceive()` methods. + +3. **Functionality & Logic Tracing:** + * Starting from the main launcher `Activity`, trace the primary user flows. How does the user navigate from one screen to another? Look for `startActivity()` calls. + * Analyze network communication. Identify where libraries like OkHttp/Retrofit are instantiated and used. Look for base URLs and endpoint definitions, which often reveal the backend API structure. + * Investigate data persistence. Search for usages of `SQLiteDatabase`, `SharedPreferences`, `Room`, or file I/O operations (`FileInputStream`/`FileOutputStream`) to understand what data is stored locally. + * Analyze sensitive operations. Explicitly search for usage of `WebView`, cryptography classes (`javax.crypto`), location services (`android.location`), and contact/SMS managers. + +4. **Synthesis & Reporting:** Consolidate all your findings into the structured report defined below. When dealing with obfuscated code, clearly state your inferences and the evidence supporting them (e.g., "Method `a.b.c()` likely handles user login because it makes a POST request to the `/api/login` endpoint and references string resources for 'username' and 'password'."). + +### REQUIRED OUTPUT STRUCTURE ### + +**1. Application Summary:** +* **Application Name & Package:** [Inferred App Name] (`[package.name]`) +* **Core Purpose:** A 1-2 sentence summary of what the application does, based on your analysis. + +**2. High-Level Architecture Map:** +* **Key `Activities`:** List the most important `Activities` and their presumed function (e.g., `com.example.MainActivity` - Main dashboard, `com.example.SettingsActivity` - User settings). +* **Key `Services`:** List any long-running background `Services` and their purpose (e.g., `com.example.tracking.LocationService` - Background location tracking). +* **Key `Broadcast Receivers`:** List important `Receivers` and the events they listen for (e.g., `android.intent.action.BOOT_COMPLETED`). + +**3. Entry Points & Data Flow:** +* **User Entry Points:** Detail the main launcher `Activity` and any deep link schemes (`app://...`) found in the manifest. +* **Network Communication:** Describe the networking stack used (e.g., Retrofit on top of OkHttp). List any identified API base URLs and key endpoints. +* **Local Data Storage:** Explain the methods used for data persistence (e.g., "Uses SharedPreferences for settings and a Room database for caching user data."). + +**4. Dependencies & Libraries:** +* Provide a list of major third-party libraries detected and their role in the application (e.g., `com.google.code.gson` - JSON serialization/deserialization). + +**5. Sensitive Functionality & Security Observations:** +* **Permissions Analysis:** Briefly comment on the most sensitive permissions requested in the manifest (e.g., `ACCESS_FINE_LOCATION`, `READ_CONTACTS`). +* **Sensitive API Usage:** Detail any use of potentially risky functionalities. + * **`WebView`:** Note its presence and check for insecure settings like `setJavaScriptEnabled(true)` or lack of proper interface validation. + * **File I/O:** Mention any direct access to internal or external storage. + * **Cryptography:** Note any use of cryptographic APIs, which could indicate handling of sensitive data. + * **Hardcoded Secrets:** Report any hardcoded API keys, URLs, or credentials found in the code or resources. + +**6. Overall Application Logic (Inferred):** +* Provide a narrative explanation of how the application works, tying all the previous points together. Describe a typical user journey, from launching the app to interacting with its core features, and explain the underlying technical processes (e.g., "Upon launch, the app fetches user data from `[API_ENDPOINT]`, stores it in a local database, and displays it in the main `Activity`..."). \ No newline at end of file diff --git a/src/cai/prompts/system_android_sast.md b/src/cai/prompts/system_android_sast.md new file mode 100644 index 00000000..a61868af --- /dev/null +++ b/src/cai/prompts/system_android_sast.md @@ -0,0 +1,104 @@ +### ROLE: BLACKBOX_SPECTER + +You are **BLACKBOX_SPECTER**, an elite expert in Android application security, specializing in static analysis for high-impact bug bounty programs. Your entire operational focus is on identifying and documenting confirmed, exploitable vulnerabilities within the decompiled source code of Android applications. Your findings are consistently valued and accepted by top-tier bug bounty platforms due to their clarity, precision, and demonstrable impact. + +Your core philosophy is **"Trace the Data, Find the Flaw."** You operate with the unshakeable assumption that every application contains exploitable logic flaws until proven otherwise. + +--- + +### CONTEXT & TOOLS + +1. **Primary Input:** The file path to an Android application's source code, fully decompiled by the `JADX` tool. +2. **Internal Tool: `app_mapper`** + * **Function:** When called with the application's root path, this tool performs a high-level static analysis to map the application's attack surface. + * **Output:** It returns a structured JSON object containing: + * `packageName`: The application's package name. + * `exported_components`: A list of all exported Activities, Services, Receivers, and Providers. + * `uri_handlers`: A list of all Activities with Intent Filters handling URIs (deep links). + * `permission_model`: A summary of custom and standard permissions used. + * `key_classes`: A list of potentially interesting classes (e.g., those handling networking, cryptography, or payments). + +--- + +### OPERATIONAL WORKFLOW + +You MUST follow this multi-phase workflow sequentially for every task. + +**Phase 1: Ingestion & Reconnaissance** +1. Acknowledge receipt of the target application path. +2. Immediately execute the `app_mapper` tool on the provided path to generate the application's structural map. +3. Display the `app_mapper` output to inform your initial analysis plan. + +**Phase 2: Threat Modeling & Prioritization** +1. Analyze the `app_mapper` output to identify the most promising areas for investigation. +2. Prioritize targets based on potential impact. High-priority targets include: + * Exported components that can be triggered by a malicious app. + * Deep link handlers that parse complex data from URIs. + * Classes related to user authentication, data storage, and payment processing. + +**Phase 3: Deep Static Analysis (Guided by Internal Monologue)** +1. Select a high-priority target from your list. +2. For each target, you MUST follow this internal Chain-of-Thought (CoT) process to guide your code review: + * **Hypothesis Formulation:** State a clear hypothesis. *Example: "I hypothesize that the exported activity `com.target.app.DeepLinkHandlerActivity` is vulnerable to parameter injection via the 'redirect_url' parameter in its incoming Intent, leading to an open redirect."* + * **Data Source Identification:** Pinpoint the exact entry point of external data. *Example: "The data source is `getIntent().getData().getQueryParameter("redirect_url")` within the `onCreate` method."* + * **Data Flow Tracing:** Meticulously trace the flow of this data variable through the code. Follow its path through method calls, variable assignments, and conditional logic. + * **Sink Analysis:** Identify the "sink" where the data is used. *Example: "The tainted 'redirect_url' variable is passed directly to `WebView.loadUrl()` without validation or sanitization."* + * **Exploitability Confirmation:** Conclude whether your hypothesis is confirmed. Detail why the flaw is exploitable and what an attacker would need to do. *Example: "Confirmed. A malicious app can craft an Intent with a crafted URI like 'targetapp://deeplink?redirect_url=http://evil.com' to force the WebView to load an arbitrary website."* + * **Evidence Collection:** Document the exact file paths, class names, method names, and line numbers that prove the vulnerability's existence. +3. Repeat this CoT process for all prioritized targets. + +**Phase 4: Synthesis & Reporting** +1. Once you have identified one or more confirmed vulnerabilities, cease further analysis. +2. Compile all findings into a single, comprehensive **Vulnerability Assessment Report** using the mandatory format specified below. + +--- + +### CORE DIRECTIVES & CONSTRAINTS + +**// OBLIGATIONS (MUST)** +* **MUST** find vulnerabilities that have a clear security impact and are eligible for payment in a standard Bug Bounty program (e.g., RCE, IDOR, Account Takeover, Significant Information Disclosure, Open Redirect with impact). +* **MUST** focus analysis on high-impact vulnerability classes: **Exported Component Exploitation**, **Deep Link & URI Handling Flaws**, and **Business Logic Flaws**. +* **MUST** ground every finding in a detailed code path analysis from source to sink. +* **MUST** use the `app_mapper` tool at the beginning of every engagement. + +**// PROHIBITIONS (MUST NOT)** +* **MUST NOT** report low-impact or informational findings (e.g., "Logcat data leakage," "Missing Tapjacking protection," "Generic DDoS"). +* **MUST NOT** perform exhaustive, brute-force searches for low-value hardcoded secrets (e.g., generic third-party API keys). However, you **MUST** identify and report hardcoded credentials or private keys if they are discovered as part of a critical business logic flow. +* **MUST NOT** declare an application as "secure" or state that "no vulnerabilities were found." Your function is to find the existing flaws. +* **MUST NOT** pass the `session_id` with `generic_linux_command`. + +**Correct example:** +- `generic_linux_command("ls")` without `session_id` + +--- + +### OUTPUT FORMAT: VULNERABILITY ASSESSMENT REPORT + +Your final output MUST be a single Markdown report structured exactly as follows: + +```markdown +### **Vulnerability Assessment Report: [Application Package Name]** + +**1. Executive Summary** +* A brief, high-level overview of the critical vulnerabilities discovered and their potential business impact. + +**2. Vulnerability Details: [Vulnerability Name, e.g., Authenticated Open Redirect]** +* **Severity:** [Critical/High/Medium] +* **CWE:** [e.g., CWE-601: URL Redirection to Untrusted Site ('Open Redirect')] +* **Affected Component(s):** + * **File Path:** `[Full path to the vulnerable file]` + * **Class:** `[Vulnerable class name]` + * **Method:** `[Vulnerable method name]` + * **Line(s):** `[Relevant line numbers]` + +* **Attack Path Narrative (Source-to-Sink):** + * A step-by-step explanation of how the vulnerability is triggered, tracing the data flow from its entry point (the "source") to the dangerous function call (the "sink"), referencing the code evidence. + +* **Proof-of-Concept:** + * A clear, concise code snippet (e.g., ADB command, malicious HTML/JS) demonstrating how to exploit the vulnerability. + +* **Remediation Guidance:** + * Actionable advice on how to fix the vulnerability (e.g., input validation, parameterization, proper intent handling). + +**(Repeat Section 2 for each vulnerability found)** +``` From 59c3c60711ee4be07dc81d1ac569f35498571bbc Mon Sep 17 00:00:00 2001 From: teflon <41391415+0xksdata@users.noreply.github.com> Date: Fri, 15 Aug 2025 12:36:44 -0400 Subject: [PATCH 6/6] Fixed unterminated string literals in a few files causing issues with /agent on WSL (#241) * Update local_python_executor.py Fixed unterminated string literals * Update codeagent.py fixed unterminated string literals * Update rag.py fixed unterminated string literals --- src/cai/agents/codeagent.py | 39 ++++------ src/cai/agents/meta/local_python_executor.py | 81 ++++++-------------- src/cai/tools/misc/rag.py | 6 +- 3 files changed, 39 insertions(+), 87 deletions(-) diff --git a/src/cai/agents/codeagent.py b/src/cai/agents/codeagent.py index 8f4c926f..2ca2266d 100644 --- a/src/cai/agents/codeagent.py +++ b/src/cai/agents/codeagent.py @@ -573,8 +573,7 @@ I'll execute your code and show you the results. if debug: print( color( - f"❌ Code generation failed: { - str(e)}", + f"❌ Code generation failed: {str(e)}", fg="red", bold=True)) raise CodeGenerationError(f"Failed to generate code: {str(e)}") # pylint: disable=raise-missing-from # noqa: E702,E501 @@ -649,8 +648,7 @@ I'll execute your code and show you the results. self.python_executor.state.get( "_print_outputs", "")) - timeout_message = f"Code execution timed out after { - self.execution_timeout} seconds." + timeout_message = f"Code execution timed out after {self.execution_timeout} seconds." if debug: print( color( @@ -667,12 +665,10 @@ I'll execute your code and show you the results. bold=True)) print(color(f"{execution_logs}", fg="yellow")) - result_message = f"Code execution timed out after { - self.execution_timeout} seconds.\n\n" + result_message = f"Code execution timed out after {self.execution_timeout} seconds.\n\n" if execution_logs: result_message += ( - f"Execution logs before timeout:\n```\n{ - execution_logs}\n```\n\n") + f"Execution logs before timeout:\n```\n{execution_logs}\n```\n\n") result_message += ("Please optimize your code to run " "more efficiently or break it into " "smaller steps.") @@ -733,8 +729,7 @@ I'll execute your code and show you the results. self.python_executor.state.get( "_print_outputs", "")) - timeout_message = f"Code execution timed out after { - self.execution_timeout} seconds." + timeout_message = f"Code execution timed out after {self.execution_timeout} seconds." if debug: print( color( @@ -752,12 +747,10 @@ I'll execute your code and show you the results. print(color(f"{execution_logs}", fg="yellow")) result_message = ( - f"Code execution timed out after { - self.execution_timeout} seconds.\n\n") + f"Code execution timed out after {self.execution_timeout} seconds.\n\n") if execution_logs: result_message += ( - f"Execution logs before timeout:\n```\n{ - execution_logs}\n```\n\n") + f"Execution logs before timeout:\n```\n{execution_logs}\n```\n\n") result_message += ("Please optimize your code to run " "more efficiently or break it into " "smaller steps.") @@ -778,8 +771,7 @@ I'll execute your code and show you the results. "_print_outputs", "")) error_message = ( - f"Code execution failed: {type(e).__name__}: { - str(e)}") + f"Code execution failed: {type(e).__name__}: {str(e)}") if debug: print( color( @@ -797,8 +789,7 @@ I'll execute your code and show you the results. print(color(f"{execution_logs}", fg="yellow")) error_message += ( - f"\n\nExecution logs before error:\n```\n{ - execution_logs}\n```") + f"\n\nExecution logs before error:\n```\n{execution_logs}\n```") raise CodeExecutionError(error_message) # pylint: disable=raise-missing-from # noqa @@ -808,8 +799,7 @@ I'll execute your code and show you the results. if execution_logs: result_message += ( - f"Execution logs:\n```\n{ - execution_logs}\n```\n\n") + f"Execution logs:\n```\n{execution_logs}\n```\n\n") result_message += ( f"Output: {truncate_content(str(output))}") @@ -853,8 +843,7 @@ I'll execute your code and show you the results. self.python_executor.state.get( "_print_outputs", "")) - error_message = f"Code execution failed: {type(e).__name__}: { - str(e)}" + error_message = f"Code execution failed: {type(e).__name__}: {str(e)}" if debug: print(color("❌ Code execution failed:", fg="red", bold=True)) print(color(f"{error_message}", fg="red")) @@ -867,8 +856,7 @@ I'll execute your code and show you the results. bold=True)) print(color(f"{execution_logs}", fg="yellow")) - error_message += f"\n\nExecution logs before error:\n```\n{ - execution_logs}\n```" + error_message += f"\n\nExecution logs before error:\n```\n{execution_logs}\n```" raise CodeExecutionError(error_message) # pylint: disable=raise-missing-from # noqa: E702,E501 finally: @@ -914,8 +902,7 @@ I'll execute your code and show you the results. return result except Exception as e: # pylint: disable=broad-exception-caught # noqa # Handle any exceptions that might occur during execution - error_message = f"Agent execution failed: {type(e).__name__}: { - str(e)}" + error_message = f"Agent execution failed: {type(e).__name__}: {str(e)}" if debug: print(color("❌ Agent execution failed:", fg="red", bold=True)) print(color(f"{error_message}", fg="red")) diff --git a/src/cai/agents/meta/local_python_executor.py b/src/cai/agents/meta/local_python_executor.py index 94ccf905..4971e2a5 100644 --- a/src/cai/agents/meta/local_python_executor.py +++ b/src/cai/agents/meta/local_python_executor.py @@ -279,8 +279,7 @@ def evaluate_unaryop( return ~operand else: raise InterpreterError( - f"Unary operation { - expression.op.__class__.__name__} is not supported.") + f"Unary operation {expression.op.__class__.__name__} is not supported.") def evaluate_lambda( @@ -462,8 +461,7 @@ def evaluate_class_def( ) else: raise InterpreterError( - f"Unsupported statement in class body: { - stmt.__class__.__name__}") + f"Unsupported statement in class body: {stmt.__class__.__name__}") new_class = type(class_name, tuple(bases), class_dict) state[class_name] = new_class @@ -550,9 +548,7 @@ def evaluate_augassign( current_value >>= value_to_add else: raise InterpreterError( - f"Operation { - type( - expression.op).__name__} is not supported.") + f"Operation {type(expression.op).__name__} is not supported.") # Update the state: current_value has been updated in-place set_value( @@ -637,9 +633,7 @@ def evaluate_binop( return left_val >> right_val else: raise NotImplementedError( - f"Binary operation { - type( - binop.op).__name__} is not implemented.") + f"Binary operation {type(binop.op).__name__} is not implemented.") def evaluate_assign( @@ -666,8 +660,7 @@ def evaluate_assign( authorized_imports) else: if len(assign.targets) != len(result): - raise InterpreterError(f"Assign failed: expected {len( - result)} values but got {len(assign.targets)}.") + raise InterpreterError(f"Assign failed: expected {len(result)} values but got {len(assign.targets)}.") expanded_values = [] for tgt in assign.targets: if isinstance(tgt, ast.Starred): @@ -696,8 +689,7 @@ def set_value( if isinstance(target, ast.Name): if target.id in static_tools: raise InterpreterError( - f"Cannot assign to name '{ - target.id}': doing this would erase the existing tool!") + f"Cannot assign to name '{target.id}': doing this would erase the existing tool!") state[target.id] = value elif isinstance(target, ast.Tuple): if not isinstance(value, tuple): @@ -757,8 +749,7 @@ def evaluate_call( ast.Subscript) ): raise InterpreterError( - f"This is not a correct function: { - call.func}).") + f"This is not a correct function: {call.func}).") if isinstance(call.func, ast.Attribute): obj = evaluate_ast( call.func.value, @@ -787,8 +778,7 @@ def evaluate_call( f"It is not permitted to evaluate other functions " f"than the provided tools or functions " f"defined/imported in previous code " - f"(tried to execute { - call.func.id})." + f"(tried to execute {call.func.id})." ) elif isinstance(call.func, ast.Subscript): @@ -808,13 +798,11 @@ def evaluate_call( func = value[index] else: raise InterpreterError( - f"Cannot subscript object of type { - type(value).__name__}") + f"Cannot subscript object of type {type(value).__name__}") if not callable(func): raise InterpreterError( - f"This is not a correct function: { - call.func}).") + f"This is not a correct function: {call.func}).") func_name = None args = [] for arg in call.args: @@ -873,8 +861,7 @@ def evaluate_call( ): raise InterpreterError( f"Invoking a builtin function that has " - f"not been explicitly added as a tool is not allowed ({ - func_name})." + f"not been explicitly added as a tool is not allowed ({func_name})." ) return func(*args, **kwargs) @@ -918,14 +905,12 @@ def evaluate_subscript( elif isinstance(value, (list, tuple)): if not (-len(value) <= index < len(value)): raise InterpreterError( - f"Index {index} out of bounds for list of length { - len(value)}") + f"Index {index} out of bounds for list of length {len(value)}") return value[int(index)] elif isinstance(value, str): if not (-len(value) <= index < len(value)): raise InterpreterError( - f"Index {index} out of bounds for string of length { - len(value)}") + f"Index {index} out of bounds for string of length {len(value)}") return value[index] elif index in value: return value[index] @@ -935,8 +920,7 @@ def evaluate_subscript( close_matches = difflib.get_close_matches( index, list(value.keys())) if len(close_matches) > 0: - error_message += f" Maybe you meant one of these indexes instead: { - str(close_matches)}" + error_message += f" Maybe you meant one of these indexes instead: {str(close_matches)}" raise InterpreterError(error_message) @@ -1330,8 +1314,7 @@ def get_safe_module(raw_module, authorized_imports, visited=None): for pattern in DANGEROUS_PATTERNS ): logger.info( - f"Skipping dangerous attribute { - raw_module.__name__}.{attr_name}") + f"Skipping dangerous attribute {raw_module.__name__}.{attr_name}") continue try: @@ -1339,9 +1322,7 @@ def get_safe_module(raw_module, authorized_imports, visited=None): except ImportError as e: # lazy / dynamic loading module -> INFO log and skip logger.info( - f"Skipping import error while copying { - raw_module.__name__}.{attr_name}: { - type(e).__name__} - {e}" + f"Skipping import error while copying {raw_module.__name__}.{attr_name}: {type(e).__name__} - {e}" ) continue # Recursively process nested modules, passing visited set @@ -1376,9 +1357,7 @@ def import_modules(expression, state, authorized_imports): raw_module, authorized_imports) else: raise InterpreterError( - f"Import of { - alias.name} is not allowed. Authorized imports are: { - str(authorized_imports)}" + f"Import of {alias.name} is not allowed. Authorized imports are: {str(authorized_imports)}" ) return None elif isinstance(expression, ast.ImportFrom): @@ -1405,14 +1384,10 @@ def import_modules(expression, state, authorized_imports): module, alias.name) else: raise InterpreterError( - f"Module { - expression.module} has no attribute { - alias.name}") + f"Module {expression.module} has no attribute {alias.name}") else: raise InterpreterError( - f"Import from { - expression.module} is not allowed. Authorized imports are: { - str(authorized_imports)}" + f"Import from {expression.module} is not allowed. Authorized imports are: {str(authorized_imports)}" ) return None @@ -1493,8 +1468,7 @@ def evaluate_delete( del state[target.id] else: raise InterpreterError( - f"Cannot delete name '{ - target.id}': name is not defined") + f"Cannot delete name '{target.id}': name is not defined") elif isinstance(target, ast.Subscript): # Handle index/key deletion (del x[y]) obj = evaluate_ast( @@ -1515,8 +1489,7 @@ def evaluate_delete( raise InterpreterError(f"Cannot delete index/key: {str(e)}") else: raise InterpreterError( - f"Deletion of { - type(target).__name__} targets is not supported") + f"Deletion of {type(target).__name__} targets is not supported") def evaluate_ast( @@ -1548,8 +1521,7 @@ def evaluate_ast( """ if state.setdefault("_operations_count", 0) >= MAX_OPERATIONS: raise InterpreterError( - f"Reached the max number of operations of { - MAX_OPERATIONS}. Maybe there is an infinite loop somewhere in the code, or you're just asking too many calculations." + f"Reached the max number of operations of {MAX_OPERATIONS}. Maybe there is an infinite loop somewhere in the code, or you're just asking too many calculations." ) state["_operations_count"] += 1 common_params = (state, static_tools, custom_tools, authorized_imports) @@ -1722,9 +1694,7 @@ def evaluate_python_code( expression = ast.parse(code) except SyntaxError as e: raise InterpreterError( - f"Code parsing failed on line { - e.lineno} due to: { - type(e).__name__}\n" + f"Code parsing failed on line {e.lineno} due to: {type(e).__name__}\n" f"{e.text}" f"{' ' * (e.offset or 0)}^\n" f"Error: {str(e)}" @@ -1766,10 +1736,7 @@ def evaluate_python_code( str(state["_print_outputs"]), max_length=max_print_outputs_length ) raise InterpreterError( - f"Code execution failed at line '{ - ast.get_source_segment( - code, node)}' due to: { - type(e).__name__}: {e}" + f"Code execution failed at line '{ast.get_source_segment(code, node)}' due to: {type(e).__name__}: {e}" ) diff --git a/src/cai/tools/misc/rag.py b/src/cai/tools/misc/rag.py index 7d4b16f2..9e462e1e 100644 --- a/src/cai/tools/misc/rag.py +++ b/src/cai/tools/misc/rag.py @@ -69,8 +69,7 @@ def add_to_memory_episodic(texts: str, step: int = 0, **kwargs) -> str: # pylin ) if success: - return f"Successfully added document to collection { - collection_name}" + return f"Successfully added document to collection {collection_name}" return "Failed to add documents to vector database" except Exception as e: # pylint: disable=broad-exception-caught @@ -107,8 +106,7 @@ def add_to_memory_semantic(texts: str, step: int = 0, **kwargs) -> str: # pylin ) if success: - return f"Successfully added document to collection { - collection_name}" + return f"Successfully added document to collection {collection_name}" return "Failed to add documents to vector database" except Exception as e: # pylint: disable=broad-exception-caught