diff --git a/DISCLAIMER b/DISCLAIMER new file mode 100644 index 00000000..2fb9b399 --- /dev/null +++ b/DISCLAIMER @@ -0,0 +1,31 @@ +## Legal disclaimer for CAI + +### **1. Compliance & Intended Use** +CAI is an AI-powered cybersecurity evaluation tool intended to assist in security assessments of robotic and other automated systems. It is designed to operate under a "human-on-the-loop" principle, ensuring oversight and mitigating risks associated with autonomous decision-making. + +The software is developed in alignment with **ethical AI principles** and is intended to support compliance with the **EU AI Act** and other applicable cybersecurity regulations. However, it is the sole responsibility of the user to ensure adherence to all relevant laws and standards when deploying or modifying this software. + +### **2. Open Source Notice & Responsibility** +This source code is provided under the license terms detailed in the accompanying `LICENSE` file. Portions of CAI are released as **open-source** to encourage transparency and community collaboration. + +Please note: +- The software is provided "**as is**", without warranty of any kind. +- The authors and contributors assume **no liability** for any damages, data loss, or legal consequences arising from its use. +- Users must conduct **appropriate testing and validation** before deploying this software in any production or mission-critical environment. + +### **3. Security & Ethical Use** +- **Non-Disruptive Use Only:** CAI is intended for research, education, and evaluation purposes. It must **not** be used to disrupt, manipulate, or interfere with live production systems. +- **Authorized Access Required:** Cybersecurity assessments or testing using this tool must be conducted **only with explicit permission** from the system owner. +- **Qualified Use:** This software should be used by **trained professionals** following industry best practices and applicable organizational security policies. + +### **4. Limitations** +- Use of this tool does **not guarantee** full protection against cyber threats or compliance with any regulatory framework. +- It is **not a substitute** for formal cybersecurity audits, penetration testing services, or legal compliance assessments. +- Users are fully responsible for the consequences of how they apply or modify the code. + +### **5. Contact & Contributions** +We welcome community contributions to improve CAI. To contribute, report bugs, or request clarification on legal or compliance issues, please use [GitHub Issues/Support Email]. + +--- + +By downloading, using, or modifying this source code, you agree to the terms of the `LICENSE` and the limitations outlined in this disclaimer. \ No newline at end of file diff --git a/README.md b/README.md index ce0f0230..efccb017 100644 --- a/README.md +++ b/README.md @@ -10,21 +10,9 @@

-## 🎯 Milestones -[![](https://img.shields.io/badge/HTB_ranking-top_90_Spain_(5_days)-red.svg)](https://app.hackthebox.com/users/2268644) -[![](https://img.shields.io/badge/HTB_ranking-top_50_Spain_(6_days)-red.svg)](https://app.hackthebox.com/users/2268644) -[![](https://img.shields.io/badge/HTB_ranking-top_30_Spain_(7_days)-red.svg)](https://app.hackthebox.com/users/2268644) -[![](https://img.shields.io/badge/HTB_ranking-top_500_World_(7_days)-red.svg)](https://app.hackthebox.com/users/2268644) -[![](https://img.shields.io/badge/HTB_"Human_vs_AI"_CTF-top_1_(AIs)_world-red.svg)](https://ctf.hackthebox.com/event/2000/scoreboard) -[![](https://img.shields.io/badge/HTB_"Human_vs_AI"_CTF-top_1_Spain-red.svg)](https://ctf.hackthebox.com/event/2000/scoreboard) -[![](https://img.shields.io/badge/HTB_"Human_vs_AI"_CTF-top_20_World-red.svg)](https://ctf.hackthebox.com/event/2000/scoreboard) -[![](https://img.shields.io/badge/HTB_"Human_vs_AI"_CTF-750_$-yellow.svg)](https://ctf.hackthebox.com/event/2000/scoreboard) -[![](https://img.shields.io/badge/Mistral_AI_Robotics_Hackathon-2500_$-yellow.svg)](https://lu.ma/roboticshack?tk=RuryKF) -[![](https://img.shields.io/badge/Bug_rewards-250_$-yellow.svg)](https://github.com/aliasrobotics/cai) -## 📦 Package Attributes [![version](https://badge.fury.io/py/cai-framework.svg)](https://badge.fury.io/py/cai-framework) -[![downloads](https://img.shields.io/pypi/dm/cai-framework)](https://pypistats.org/packages/cai-framework) +[![downloads](https://static.pepy.tech/badge/cai-framework)](https://pepy.tech/projects/cai-framework) [![Linux](https://img.shields.io/badge/Linux-Supported-brightgreen?logo=linux&logoColor=white)](https://github.com/aliasrobotics/cai) [![OS X](https://img.shields.io/badge/OS%20X-Supported-brightgreen?logo=apple&logoColor=white)](https://github.com/aliasrobotics/cai) [![Windows](https://img.shields.io/badge/Windows-Supported-brightgreen?logo=windows&logoColor=white)](https://github.com/aliasrobotics/cai) @@ -37,20 +25,29 @@ -A lightweight, ergonomic framework for building bug bounty-ready Cybersecurity AIs (CAIs). - -| CAI with `alias0` on ROS message injection attacks in MiR-100 robot | CAI with `alias0` on API vulnerability discovery at Mercado Libre | -|-----------------------------------------------|---------------------------------| -| [![asciicast](https://asciinema.org/a/dNv705hZel2Rzrw0cju9HBGPh.svg)](https://asciinema.org/a/dNv705hZel2Rzrw0cju9HBGPh) | [![asciicast](https://asciinema.org/a/9Hc9z1uFcdNjqP3bY5y7wO1Ww.svg)](https://asciinema.org/a/9Hc9z1uFcdNjqP3bY5y7wO1Ww) | - - -| CAI on JWT@PortSwigger CTF — Cybersecurity AI | CAI on HackableII Boot2Root CTF — Cybersecurity AI | -|-----------------------------------------------|---------------------------------| -| [![asciicast](https://asciinema.org/a/713487.svg)](https://asciinema.org/a/713487) | [![asciicast](https://asciinema.org/a/713485.svg)](https://asciinema.org/a/713485) | +Cybersecurity AI (CAI) is a lightweight, open-source framework that empowers security professionals to build and deploy AI-powered offensive and defensive automation. CAI is the *de facto* framework for AI Security, already used by thousands of individual users and hundreds of organizations. Whether you're a security researcher, ethical hacker, IT professional, or organization looking to enhance your security posture, CAI provides the building blocks to create specialized AI agents that can assist with mitigation, vulnerability discovery, exploitation, and security assessment. +**Key Features:** +- 🤖 **300+ AI Models**: Support for OpenAI, Anthropic, DeepSeek, Ollama, and more +- 🔧 **Built-in Security Tools**: Ready-to-use tools for reconnaissance, exploitation, and privilege escalation +- 🏆 **Battle-tested**: Proven in HackTheBox CTFs, bug bounties, and real-world security [case studies](https://aliasrobotics.com/case-studies-robot-cybersecurity.php) +- 🎯 **Agent-based Architecture**: Modular design with specialized agents for different security tasks > [!NOTE] -> We encourage you to read CAI's the technical report at https://arxiv.org/pdf/2504.06017. +> Read the technical report: [CAI: An Open, Bug Bounty-Ready Cybersecurity AI](https://arxiv.org/pdf/2504.06017) + + +| [`OT` - CAI and alias0 on: Ecoforest Heat Pumps](https://aliasrobotics.com/case-study-ecoforest.php) | [`Robotics` - CAI and alias0 on: Mobile Industrial Robots (MiR)](https://aliasrobotics.com/case-study-cai-mir.php) | +|------------------------------------------------|---------------------------------| +| CAI discovers critical vulnerability in Ecoforest heat pumps allowing unauthorized remote access and potential catastrophic failures. AI-powered security testing reveals exposed credentials and DES encryption weaknesses affecting all of their deployed units across Europe. | CAI-powered security testing of MiR (Mobile Industrial Robot) platform through automated ROS message injection attacks. This study demonstrates how AI-driven vulnerability discovery can expose unauthorized access to robot control systems and alarm triggers. | +| [![](https://aliasrobotics.com/img/case-study-portada-ecoforest.png)](https://aliasrobotics.com/case-study-ecoforest.php) | [![](https://aliasrobotics.com/img/case-study-portada-mir-cai.png)](https://aliasrobotics.com/case-study-cai-mir.php) | + +| [`IT` (Web) - CAI and alias0 on: Mercado Libre's e-commerce](https://aliasrobotics.com/case-study-mercado-libre.php) | [`OT` - CAI and alias0 on: MQTT broker](https://aliasrobotics.com/case-study-cai-mqtt-broker.php) | +|------------------------------------------------|---------------------------------| +| CAI-powered API vulnerability discovery at Mercado Libre through automated enumeration attacks. This study demonstrates how AI-driven security testing can expose user data exposure risks in e-commerce platforms at scale. | CAI-powered testing exposed critical flaws in an MQTT broker within a Dockerized OT network. Without authentication, CAI subscribed to temperature and humidity topics and injected false values, corrupting data shown in Grafana dashboards. | +| [![](https://aliasrobotics.com/img/case-study-portada-mercado-libre.png)](https://aliasrobotics.com/case-study-mercado-libre.php) | [![](https://aliasrobotics.com/img/case-study-portada-mqtt-broker-cai.png)](https://aliasrobotics.com/case-study-cai-mqtt-broker.php) | + + > [!WARNING] > :warning: CAI is in active development, so don't expect it to work flawlessly. Instead, contribute by raising an issue or [sending a PR](https://github.com/aliasrobotics/cai/pulls). @@ -62,9 +59,9 @@ A lightweight, ergonomic framework for building bug bounty-ready Cybersecurity A ## :bookmark: Table of Contents - [Cybersecurity AI (`CAI`)](#cybersecurity-ai-cai) - - [🎯 Milestones](#-milestones) - - [📦 Package Attributes](#-package-attributes) - [:bookmark: Table of Contents](#bookmark-table-of-contents) + - [🎯 Milestones](#-milestones) + - [PoCs](#pocs) - [Motivation](#motivation) - [:bust\_in\_silhouette: Why CAI?](#bust_in_silhouette-why-cai) - [Ethical principles behind CAI](#ethical-principles-behind-cai) @@ -101,6 +98,29 @@ A lightweight, ergonomic framework for building bug bounty-ready Cybersecurity A +## 🎯 Milestones +[![](https://img.shields.io/badge/HTB_ranking-top_90_Spain_(5_days)-red.svg)](https://app.hackthebox.com/users/2268644) +[![](https://img.shields.io/badge/HTB_ranking-top_50_Spain_(6_days)-red.svg)](https://app.hackthebox.com/users/2268644) +[![](https://img.shields.io/badge/HTB_ranking-top_30_Spain_(7_days)-red.svg)](https://app.hackthebox.com/users/2268644) +[![](https://img.shields.io/badge/HTB_ranking-top_500_World_(7_days)-red.svg)](https://app.hackthebox.com/users/2268644) +[![](https://img.shields.io/badge/HTB_"Human_vs_AI"_CTF-top_1_(AIs)_world-red.svg)](https://ctf.hackthebox.com/event/2000/scoreboard) +[![](https://img.shields.io/badge/HTB_"Human_vs_AI"_CTF-top_1_Spain-red.svg)](https://ctf.hackthebox.com/event/2000/scoreboard) +[![](https://img.shields.io/badge/HTB_"Human_vs_AI"_CTF-top_20_World-red.svg)](https://ctf.hackthebox.com/event/2000/scoreboard) +[![](https://img.shields.io/badge/HTB_"Human_vs_AI"_CTF-750_$-yellow.svg)](https://ctf.hackthebox.com/event/2000/scoreboard) +[![](https://img.shields.io/badge/Mistral_AI_Robotics_Hackathon-2500_$-yellow.svg)](https://lu.ma/roboticshack?tk=RuryKF) + +## PoCs +| CAI with `alias0` on ROS message injection attacks in MiR-100 robot | CAI with `alias0` on API vulnerability discovery at Mercado Libre | +|-----------------------------------------------|---------------------------------| +| [![asciicast](https://asciinema.org/a/dNv705hZel2Rzrw0cju9HBGPh.svg)](https://asciinema.org/a/dNv705hZel2Rzrw0cju9HBGPh) | [![asciicast](https://asciinema.org/a/9Hc9z1uFcdNjqP3bY5y7wO1Ww.svg)](https://asciinema.org/a/9Hc9z1uFcdNjqP3bY5y7wO1Ww) | + + +| CAI on JWT@PortSwigger CTF — Cybersecurity AI | CAI on HackableII Boot2Root CTF — Cybersecurity AI | +|-----------------------------------------------|---------------------------------| +| [![asciicast](https://asciinema.org/a/713487.svg)](https://asciinema.org/a/713487) | [![asciicast](https://asciinema.org/a/713485.svg)](https://asciinema.org/a/713485) | + +More case studies and PoCs are available at [https://aliasrobotics.com/case-studies-robot-cybersecurity.php](https://aliasrobotics.com/case-studies-robot-cybersecurity.php). + ## Motivation ### :bust_in_silhouette: Why CAI? The cybersecurity landscape is undergoing a dramatic transformation as AI becomes increasingly integrated into security operations. **We predict that by 2028, AI-powered security testing tools will outnumber human pentesters**. This shift represents a fundamental change in how we approach cybersecurity challenges. *AI is not just another tool - it's becoming essential for addressing complex security vulnerabilities and staying ahead of sophisticated threats. As organizations face more advanced cyber attacks, AI-enhanced security testing will be crucial for maintaining robust defenses.* @@ -167,6 +187,7 @@ Cybersecurity AI is a critical field, yet many groups are misguidedly pursuing i >

+ | | Description | English | Spanish | @@ -177,7 +198,6 @@ Cybersecurity AI is a critical field, yet many groups are misguidedly pursuing i | **Episode 3**: Vibe-Hacking Tutorial | "My first Hack" - A Vibe-Hacking guide for newbies. We demonstrate a simple web security hack using a default agent and show how to leverage tools and interpret CIA output with the help of the CAI Python API. You'll also learn to compare different LLM models to find the best fit for your hacking endeavors. | [![Watch the video](https://img.youtube.com/vi/9vZ_Iyex7uI/0.jpg)](https://www.youtube.com/watch?v=9vZ_Iyex7uI&list=PLLc16OUiZWd4RuFdN5_Wx9xwjCVVbopzr&index=1) | [![Watch the video](https://img.youtube.com/vi/iAOMaI1ftiA/0.jpg)](https://www.youtube.com/watch?v=iAOMaI1ftiA&list=PLLc16OUiZWd4RuFdN5_Wx9xwjCVVbopzr&index=2) | | **Episode 4**: Intro ReAct | The Evolution of LLMs - Learn how LLMs evolved from basic language models to advanced multiagency AI systems. From basic LLMs to Chain-of-Thought and Reasoning LLMs towards ReAct and Multi-Agent Architectures. Get to know the basic terms | [![Watch the video](https://img.youtube.com/vi/tLdFO1flj_o/0.jpg)](https://www.youtube.com/watch?v=tLdFO1flj_o&list=PLLc16OUiZWd4RuFdN5_Wx9xwjCVVbopzr&index=13) | | | **Episode 5**: CAI on CTF challenges | Dive into Capture The Flag (CTF) competitions using CAI. Learn how to leverage AI agents to solve various cybersecurity challenges including web exploitation, cryptography, reverse engineering, and forensics. Discover how to configure CAI for competitive hacking scenarios and maximize your CTF performance with intelligent automation. | [![Watch the video](https://img.youtube.com/vi/MrXTQ0e2to4/0.jpg)](https://www.youtube.com/watch?v=MrXTQ0e2to4&list=PLLc16OUiZWd4RuFdN5_Wx9xwjCVVbopzr&index=13) | [![Watch the video](https://img.youtube.com/vi/r9US_JZa9_c/0.jpg)](https://www.youtube.com/watch?v=r9US_JZa9_c&list=PLLc16OUiZWd4RuFdN5_Wx9xwjCVVbopzr&index=12) | -| **Episode 6**: CAI Fluency Framework | Introducing CAI Fluency: An Educational Framework for Cybersecurity AI - Learn about the comprehensive educational platform designed to democratize cybersecurity AI knowledge. Explore the theoretical foundations, the 4 C's competencies (Command, Communication, Critique, Custody), and the three modalities of Human-AI interaction in security contexts. | *Coming soon* | *Coming soon* | | | | | | | **Annex 1**: `CAI` 0.5.x release | Introduce version 0.5 of `CAI` including new multi-agent functionality, new commands such as `/history`, `/compact`, `/graph` or `/memory` and a case study showing how `CAI` found a critical security flaw in OT heap pumps spread around the world. | [![Watch the video](https://img.youtube.com/vi/OPFH0ANUMMw/0.jpg)](https://www.youtube.com/watch?v=OPFH0ANUMMw) | [![Watch the video](https://img.youtube.com/vi/Q8AI4E4gH8k/0.jpg)](https://www.youtube.com/watch?v=Q8AI4E4gH8k) | | **Annex 2**: `CAI` 0.4.x release and `alias0` | Introducing version 0.4 of `CAI` with *streaming* and improved MCP support. We also introduce `alias0`, the Privacy-First Cybersecurity AI, a Model-of-Models Intelligence that implements a Privacy-by-Design architecture and obtains state-of-the-art results in cybersecurity benchmarks. | [![Watch the video](https://img.youtube.com/vi/NZjzfnvAZcc/0.jpg)](https://www.youtube.com/watch?v=NZjzfnvAZcc) | | 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/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/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)** +``` 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) 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