This commit is contained in:
Víctor Mayoral Vilches 2025-08-18 08:07:00 +02:00
commit e8f89c7f95
10 changed files with 527 additions and 235 deletions

230
README.md
View File

@ -171,6 +171,17 @@ Cybersecurity AI is a critical field, yet many groups are misguidedly pursuing i
## Learn - `CAI` Fluency
<div align="center">
<p>
<a align="center" href="" target="https://github.com/aliasrobotics/CAI">
<img
width="100%"
src="https://github.com/aliasrobotics/cai/raw/main/media/caiedu.PNG"
>
</a>
</p>
</div>
| | Description | English | Spanish |
|-------|----------------|---------|---------|
@ -391,90 +402,70 @@ 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
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)
```
@ -490,43 +481,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
@ -558,44 +553,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**.

28
fluency/my-first-hack/utils/portswiggerbot.py Normal file → Executable file
View File

@ -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 <a> 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):
"""

BIN
media/caiedu.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -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(),
),
)

View File

@ -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"))

View File

@ -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}"
)

View File

@ -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`...").

View File

@ -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)**
```

View File

@ -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"<ansicyan><b>{server_name}</b></ansicyan> "
f"<ansiwhite>({server_type})</ansiwhite>"),
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"<ansicyan>{server_name}</ansicyan> "
f"<ansiwhite>({server_type})</ansiwhite>"),
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 <tab>")
# 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 <tab>")
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"<ansiyellow><b>{transport}</b></ansiyellow> "
f"<ansiwhite>- {desc}</ansiwhite>"),
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 <tab>")
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)

View File

@ -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