Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -800,7 +800,7 @@ cyber-autoagent/
│ │ └── utils.py # UI utilities and analysis
│ ├── interfaces/
│ │ └── react/ # React terminal interface
│ └── operation_plugins/ # Security modules (general, ctf)
│ └── operation_plugins/ # Security modules (web, ctf)
├── docs/ # Documentation
│ ├── architecture.md # Agent architecture and tools
│ ├── memory.md # Memory system (Mem0 backends)
Expand All @@ -813,10 +813,10 @@ cyber-autoagent/
├── uv.lock # Dependency lockfile
├── .env.example # Environment configuration template
├── outputs/ # Unified output directory (auto-created)
│ └── <target>/ # Target-specific organization
│ ├── OP_<id>/ # Operation-specific files
│ │ ├── report.md # Security findings (when generated)
│ │ ├── cyber_operations.log # Operation log
│ └── <target>/ # Target-specific organization
│ ├── OP_<id>/ # Operation-specific files
│ │ ├── security_assessment_report.md # Security findings (when generated)
│ │ ├── cyber_operations.log # Operation log
│ │ ├── artifacts/ # Ad-hoc files
│ │ └── tools/ # Custom tools created by agent
│ └── memory/ # Cross-operation memory
Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ FROM cyber-autoagent-tools:latest

LABEL org.opencontainers.image.title="Cyber-AutoAgent"
LABEL org.opencontainers.image.description="Autonomous AI agent for ethical security assessments"
LABEL org.opencontainers.image.version="0.5.1"
LABEL org.opencontainers.image.version="0.6.0"
LABEL org.opencontainers.image.source="https://github.com/double16/cyber-autoagent"
LABEL org.opencontainers.image.licenses="MIT"

Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile.tools
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ FROM kalilinux/kali-rolling

LABEL org.opencontainers.image.title="Cyber-AutoAgent Tools"
LABEL org.opencontainers.image.description="Autonomous AI agent for ethical security assessments"
LABEL org.opencontainers.image.version="0.5.1"
LABEL org.opencontainers.image.version="0.6.0"
LABEL org.opencontainers.image.source="https://github.com/double16/cyber-autoagent"
LABEL org.opencontainers.image.licenses="MIT"

Expand Down
3 changes: 3 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ services:
- CYBER_CONVERSATION_PRESERVE_LAST=${CYBER_CONVERSATION_PRESERVE_LAST}
- CYBER_AGENT_OUTPUT_DIR=${CYBER_AGENT_OUTPUT_DIR}
- BYPASS_TOOL_CONSENT=${BYPASS_TOOL_CONSENT}
- CYBER_PLUGIN_PATH=/app/external_plugins:/app/home_plugins

# Memory configuration
- MEM0_API_KEY=${MEM0_API_KEY}
Expand Down Expand Up @@ -120,6 +121,8 @@ services:
# Use :delegated for Docker Desktop on macOS to fix permission issues
- ../outputs:/app/outputs:delegated
- ../tools:/app/tools:delegated
- ../external_plugins:/app/external_plugins:delegated
- ${HOME}/.cyber-autoagent/modules:/app/home_plugins:delegated
- root_cache:/root/.cache

# Network configuration
Expand Down
2 changes: 1 addition & 1 deletion docs/context_management.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ outputs/<target>/<operation>/
│ └── ...
├── logs/
│ └── cyber_operations.log
└── report.md
└── security_assessment_report.md
```

### Artifact Lifecycle
Expand Down
6 changes: 3 additions & 3 deletions docs/memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,9 @@ mem0_memory(
### Operation Output Structure
```
./outputs/<target>/<operation_id>/
├── artifacts/ # Operation artifacts
├── report.md # Final assessment report
└── logs/ # Operation logs
├── artifacts/ # Operation artifacts
├── security_assessment_report.md # Final assessment report
└── logs/ # Operation logs
└── cyber_operations.log
```

Expand Down
4 changes: 2 additions & 2 deletions docs/prompt_management.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ parser.add_argument(
## Module Structure

```
src/modules/operation_plugins/
src/modules/operation_plugins/ (CYBER_PLUGIN_PATH, ~/.cyber-autoagent/modules/)
├── web/
│ ├── execution_prompt.md # Domain-specific system prompt
│ ├── report_prompt.md # Report generation guidance
Expand Down Expand Up @@ -296,7 +296,7 @@ sequenceDiagram
T-->>A: Structured report sections
A->>A: Generate final report markdown
A-->>E: Complete report with findings
E->>E: Write report.md to operation directory
E->>E: Write security_assessment_report.md to operation directory
```

The report generation uses a dedicated `build_report_sections` tool that retrieves evidence from memory, applies module-specific domain lenses, and produces structured sections for the report agent to format.
Expand Down
4 changes: 2 additions & 2 deletions docs/prompt_optimizer.md
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ prompt_optimizer(
outputs/<target>/OP_<id>/
├── execution_prompt_optimized.txt # Copied from module template, then evolves
├── adaptive_prompt.json # Optional overlay directives
├── report.md # Final assessment report
├── security_assessment_report.md # Final assessment report
├── cyber_operations.log # Operation log with all events
├── artifacts/ # Ad-hoc files created during operation
└── tools/ # Custom tools created by agent
Expand All @@ -416,7 +416,7 @@ src/modules/operation_plugins/<module>/

### Isolation Model
- **Operation Isolation**: Each operation gets a copy of execution_prompt.md as execution_prompt_optimized.txt
- **Template Preservation**: Master templates in `operation_plugins/` remain unchanged
- **Template Preservation**: Master templates in operation plugins remain unchanged
- **Cross-Operation Learning**: Memory system provides context across operations for the same target
- **Overlay System**: Optional `adaptive_prompt.json` provides temporary directive overlays with TTL support

Expand Down
2 changes: 1 addition & 1 deletion docs/user-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ Login: admin@cyber-autoagent.com / changeme
outputs/
└── <target>/
├── OP_<timestamp>/
│ ├── report.md # Assessment report
│ ├── security_assessment_report.md # Assessment report
│ ├── logs/
│ │ └── cyber_operations.log
│ └── utils/
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "cyber-autoagent"
version = "0.5.1"
version = "0.6.0"
description = "Cyber-AutoAgent: Autonomous AI agent for ethical security assessments"
readme = "README.md"
authors = [
Expand Down
2 changes: 1 addition & 1 deletion src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Cyber-AutoAgent - Autonomous Cybersecurity Assessment Tool
"""

__version__ = "0.5.1"
__version__ = "0.6.0"
__author__ = "Patrick Double"
__credits__ = ["Aaron Brown (original author)"]
__license__ = "MIT"
2 changes: 1 addition & 1 deletion src/modules/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Cyber-AutoAgent modules package."""

__version__ = "0.5.1"
__version__ = "0.6.0"
__author__ = "Patrick Double"
__credits__ = ["Aaron Brown (original author)"]
__license__ = "MIT"
2 changes: 1 addition & 1 deletion src/modules/interfaces/react/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/modules/interfaces/react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cyber-autoagent-cli",
"version": "0.5.1",
"version": "0.6.0",
"description": "Cyber-AutoAgent CLI with React + Ink",
"type": "module",
"main": "./dist/index.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export class ErrorBoundary extends Component<Props, State> {
<Box flexDirection="column" width="100%">
{/* Always show header for visual continuity */}
<Header
version="0.5.1"
version="0.6.0"
terminalWidth={80}
nightly={false}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/modules/interfaces/react/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const compactLogo = `🔐 Cyber-AutoAgent`;
const ultraCompactLogo = `🔐 CAA`;

export const Header: React.FC<HeaderProps> = React.memo(({
version ="0.5.1",
version ="0.6.0",
terminalWidth = 80,
nightly = false,
exitNotice = false
Expand Down
4 changes: 2 additions & 2 deletions src/modules/interfaces/react/src/components/MainAppView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ export const MainAppView: React.FC<MainAppViewProps> = ({
<Box key={item}>
<Header
key={`app-header-${staticKey}`}
version="0.5.1"
version="0.6.0"
terminalWidth={appState.terminalDisplayWidth}
nightly={false}
exitNotice={Boolean((appState as any).exitNotice)}
Expand All @@ -207,7 +207,7 @@ export const MainAppView: React.FC<MainAppViewProps> = ({
<Box>
<Header
key={`app-header-${staticKey}`}
version="0.5.1"
version="0.6.0"
terminalWidth={appState.terminalDisplayWidth}
nightly={false}
exitNotice={Boolean((appState as any).exitNotice)}
Expand Down
11 changes: 6 additions & 5 deletions src/modules/operation_plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,10 @@ system_prompt = f"""
Modules are discovered from multiple sources:

**Search Paths:**
1. Built-in: `src/modules/operation_plugins/`
2. User-defined: `~/.cyberagent/modules/`
3. Custom: `CYBERAGENT_MODULE_PATHS` environment variable
1. Python CLI Custom: `CYBER_PLUGIN_PATH` environment variable, multiple paths separated by `:`
2. Docker Custom: `./external_plugins/` (mounted by docker-compose.yml)
3. User-defined: `~/.cyber-autoagent/modules/` (mounted by docker-compose.yml)
4. Built-in: `src/modules/operation_plugins/`

**Validation Requirements:**
- Valid `module.yaml` file
Expand Down Expand Up @@ -200,8 +201,8 @@ Report prompts guide structure and emphasis:
### Directory Setup

```bash
mkdir -p ~/.cyberagent/modules/custom_module/tools
cd ~/.cyberagent/modules/custom_module
mkdir -p ~/.cyber-autoagent/modules/custom_module/tools
cd ~/.cyber-autoagent/modules/custom_module
```

### Minimal Module
Expand Down
4 changes: 2 additions & 2 deletions src/modules/prompts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ This directory stores the Markdown and text templates that form the building blo

## Plugin Loading Workflow (Operation Modules)

The agent's capabilities are extended through **Operation Modules**, which are self-contained plugins located in `/src/modules/operation_plugins/`. The `ModulePromptLoader` in `factory.py` manages them as follows:
The agent's capabilities are extended through **Operation Modules**, which are self-contained plugins. The `ModulePromptLoader` in `factory.py` manages them as follows:

1. **Discovery**: The loader scans the `operation_plugins` directory. Each subdirectory is considered a potential module.
1. **Discovery**: The loader scans `CYBER_PLUGIN_PATH`, `~/.cyber-autoagent/modules/`, `operation_plugins` directory. Each subdirectory is considered a potential module.
2. **Validation**: For each discovered module, the loader checks for a valid structure, typically requiring at least a `module.yaml`, `execution_prompt.md`, or `report_prompt.md` to be considered valid.
3. **Loading**: When a module is selected for an operation, the loader reads its files:
- **`module.yaml`**: Contains metadata like the module's `name` and `description`.
Expand Down
98 changes: 71 additions & 27 deletions src/modules/prompts/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -758,10 +758,34 @@ class ModulePromptLoader:

def __init__(self, templates_dir: Optional[Path] = None):
self.templates_dir = templates_dir or (Path(__file__).parent / "templates")
# Base dir for operation plugins: modules/operation_plugins
self.plugins_dir = (
Path(__file__).parent.parent / "operation_plugins"
).resolve()
# Support multiple module roots via CYBER_PLUGIN_PATH (PATH-style, ':' separated).
# Search order: CYBER_PLUGIN_PATH entries first, then built-in modules/operation_plugins last.
default_plugins_dir = (Path(__file__).parent.parent / "operation_plugins").resolve()

raw_paths = os.getenv("CYBER_PLUGIN_PATH", "")
plugin_dirs: List[Path] = []

def _add_dir(p: Path) -> None:
try:
rp = p.expanduser().resolve()
except Exception:
rp = p.expanduser()
# De-dupe while preserving order
if rp not in plugin_dirs:
plugin_dirs.append(rp)

for part in raw_paths.split(":"):
s = part.strip()
if not s:
continue
_add_dir(Path(s))

_add_dir(Path("~/.cyber-autoagent/modules/"))

# Always include the built-in operation_plugins directory LAST
_add_dir(default_plugins_dir)

self.plugin_dirs = plugin_dirs
# Track sources for observability
self.last_loaded_execution_prompt_source: Optional[str] = None
self.last_loaded_report_prompt_source: Optional[str] = None
Expand Down Expand Up @@ -816,23 +840,26 @@ def load_module_execution_prompt(
# continue to local resolution
pass

# 2) Prefer operation_plugins/<module>/execution_prompt first to avoid noisy missing-template warnings
# 2) Prefer module roots to avoid noisy missing-template warnings
local_candidate: Optional[Path] = None
try:
p = self.plugins_dir / module_name / "execution_prompt.md"
if p.exists() and p.is_file():
local_candidate = p
except Exception:
# best-effort
local_candidate = None
local_module_dir: Optional[Path] = None
for base in self.plugin_dirs:
try:
p = base / module_name / "execution_prompt.md"
if p.exists() and p.is_file():
local_candidate = p
local_module_dir = p.parent
break
except Exception:
continue

# If Langfuse is enabled but remote was missing, try to seed from local
if _lf_enabled() and local_candidate is not None:
try:
content = local_candidate.read_text(encoding="utf-8").strip()
if content:
rname = _lf_module_prompt_name(module_name, "execution")
tags = _read_module_yaml_for_tags(self.plugins_dir / module_name)
tags = _read_module_yaml_for_tags(local_module_dir)
created = _lf_create_prompt_version(
name=rname,
prompt_text=content,
Expand Down Expand Up @@ -894,25 +921,32 @@ def load_module_report_prompt(self, module_name: str) -> str:
except Exception:
pass

# 1) Local candidate
# 1) Local candidate (search CYBER_PLUGIN_PATH roots first, default last)
local_candidate: Optional[Path] = None
try:
path = self.plugins_dir / module_name / "report_prompt.md"
if path.exists() and path.is_file():
local_candidate = path
except Exception as e:
logger.debug(
"Failed to enumerate module report prompt for '%s': %s", module_name, e
)
local_candidate = None
local_module_dir: Optional[Path] = None
for base in self.plugin_dirs:
try:
path = base / module_name / "report_prompt.md"
if path.exists() and path.is_file():
local_candidate = path
local_module_dir = path.parent
break
except Exception as e:
logger.debug(
"Failed to enumerate module report prompt for '%s' in '%s': %s",
module_name,
base,
e,
)
continue

# If Langfuse is enabled but remote missing, seed from local
if _lf_enabled() and local_candidate is not None:
try:
content = local_candidate.read_text(encoding="utf-8").strip()
if content:
rname = _lf_module_prompt_name(module_name, "report")
tags = _read_module_yaml_for_tags(self.plugins_dir / module_name)
tags = _read_module_yaml_for_tags(local_module_dir)
created = _lf_create_prompt_version(
name=rname,
prompt_text=content,
Expand Down Expand Up @@ -945,14 +979,24 @@ def discover_module_tools(self, module_name: str) -> Tuple[List[str], Optional[L
results: List[str] = []
allowed_tools: Optional[List[str]] = None
try:
tools_dir = self.plugins_dir / module_name / "tools"
if not (tools_dir.exists() and tools_dir.is_dir()):
module_root: Optional[Path] = None
tools_dir: Optional[Path] = None
for base in self.plugin_dirs:
try:
td = base / module_name / "tools"
if td.exists() and td.is_dir():
tools_dir = td
module_root = base / module_name
break
except Exception:
continue
if tools_dir is None or module_root is None:
return results, None

# Attempt to read module.yaml to honor a tools allowlist
try:
for fname in ("module.yaml", "module.yml"):
ypath = self.plugins_dir / module_name / fname
ypath = module_root / fname
if ypath.exists() and ypath.is_file():
data = yaml.safe_load(ypath.read_text(encoding="utf-8")) # type: ignore[no-untyped-call]
if isinstance(data, dict) and isinstance(
Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"OLLAMA_HOST",
"MAX_COMPLETION_TOKENS",
"MAX_TOKENS",
"ENABLE_OBSERVABILITY",
):
os.environ.pop(_var, None)

Expand Down
Loading