Skip to content
Open
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
7 changes: 7 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,10 @@
**Vulnerability:** Found an unescaped identifier (`table_name`) formatted directly into a `PRAGMA table_info` query (`f"PRAGMA table_info({table_name})"`) in `db_manager.py`'s `get_table_info` function.
**Learning:** SQLite's `PRAGMA` statements do not support standard prepared statement query parameterization. This often leads to developers falling back to string formatting, which introduces SQL injection vulnerabilities if the identifier is attacker-controlled.
**Prevention:** Since parameterized identifiers are not supported in `PRAGMA`, dynamically constructed queries using identifiers must be escaped manually. This is done by doubling interior double quotes (e.g. `"` -> `""`) and wrapping the entire identifier in double quotes (e.g., `f'"{table_name.replace('"', '""')}"'`) to ensure it's safely treated as a schema identifier and not executable SQL.
## 2025-05-23 - [Critical] AST parsing replaces arbitrary eval()

**Vulnerability:** A critical command injection vulnerability was discovered in the `autograd` module. The `autograd_compute` MCP tool used `compile()` and `eval()` directly on user-provided strings. This allowed arbitrary code execution because AI agents or users could pass malicious Python code strings instead of math expressions.

**Learning:** Mathematical evaluators often misuse `eval()` because it natively supports expressions. The fix involved migrating to an AST-based parser (`ast.parse`) combined with a strict recursive evaluator (`_safe_eval`). A unique edge case in AST evaluation is that negative numbers are parsed as `ast.UnaryOp` (with `ast.USub`) rather than primitive `ast.Constant` nodes, requiring specific handler logic to extract values securely when an exponent is passed.

**Prevention:** Never use `eval()` on strings sourced outside of fully trusted codebase origins. For dynamic math, always build a whitelist-based AST evaluator or use safer functions like `ast.literal_eval`. Make sure to account for AST quirks like `ast.UnaryOp` wrapping constants.
5 changes: 2 additions & 3 deletions scripts/docs/remediate_documentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@
2. Generates missing RASP files (README, AGENTS, SPEC, PAI) with template content.
"""

logger = logging.getLogger(__name__)


import argparse
import logging
import os
from pathlib import Path

logger = logging.getLogger(__name__)

REQUIRED_DOCS = ["README.md", "AGENTS.md", "SPEC.md", "PAI.md"]

TEMPLATES = {
Expand Down
20 changes: 10 additions & 10 deletions src/codomyrmex/agents/hermes/scripts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@
prompt templates, MCP tools, and evolution-submodule data models.
"""

from codomyrmex.agents.hermes.scripts.run_status import run_status
from codomyrmex.agents.hermes.scripts.run_chat import run_chat
from codomyrmex.agents.hermes.scripts.run_stream import run_stream
from codomyrmex.agents.hermes.scripts.run_session import run_session
from codomyrmex.agents.hermes.scripts.run_template import run_template
from codomyrmex.agents.hermes.scripts.run_pipeline import run_pipeline
from codomyrmex.agents.hermes.scripts.run_evolution_bridge import run_evolution_bridge
from codomyrmex.agents.hermes.scripts.run_mcp_tools import run_mcp_tools
from codomyrmex.agents.hermes.scripts.run_pipeline import run_pipeline
from codomyrmex.agents.hermes.scripts.run_session import run_session
from codomyrmex.agents.hermes.scripts.run_status import run_status
from codomyrmex.agents.hermes.scripts.run_stream import run_stream
from codomyrmex.agents.hermes.scripts.run_template import run_template

__all__ = [
"run_status",
"run_chat",
"run_stream",
"run_session",
"run_template",
"run_pipeline",
"run_evolution_bridge",
"run_mcp_tools",
"run_pipeline",
"run_session",
"run_status",
"run_stream",
"run_template",
]
16 changes: 10 additions & 6 deletions src/codomyrmex/agents/hermes/scripts/run_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,13 @@ def run_chat(
Dict with keys: ``status``, ``content``, ``error``,
``metadata``, ``elapsed_s``.
"""
client = HermesClient(config={
"hermes_backend": backend,
"hermes_model": model,
"hermes_timeout": timeout,
})
client = HermesClient(
config={
"hermes_backend": backend,
"hermes_model": model,
"hermes_timeout": timeout,
}
)

start = time.time()
try:
Expand Down Expand Up @@ -70,7 +72,9 @@ def run_chat(

def main() -> None:
"""CLI entry point β€” pass prompt as first argument."""
prompt = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else "Say hello in one sentence."
prompt = (
" ".join(sys.argv[1:]) if len(sys.argv) > 1 else "Say hello in one sentence."
)
result = run_chat(prompt)
print(json.dumps(result, indent=2, default=str))
sys.exit(0 if result["status"] == "success" else 1)
Expand Down
14 changes: 5 additions & 9 deletions src/codomyrmex/agents/hermes/scripts/run_evolution_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def run_evolution_bridge() -> dict[str, Any]:

# ── 2. ConstraintValidator ───────────────────────────────────────
try:
from evolution.core.constraints import ConstraintValidator, ConstraintResult
from evolution.core.constraints import ConstraintResult, ConstraintValidator

config_for_cv = EvolutionConfig(
max_skill_size=100,
Expand All @@ -74,16 +74,14 @@ def run_evolution_bridge() -> dict[str, Any]:
grown = "short" + " extra" * 20
growth_results = validator.validate_all(grown, "skill", baseline_text=baseline)
growth_blocked = any(
not r.passed and r.constraint_name == "growth_limit"
for r in growth_results
not r.passed and r.constraint_name == "growth_limit" for r in growth_results
)

# Test skill structure check
valid_skill = "---\nname: test\ndescription: demo\n---\n\n# Body\nContent here."
struct_results = validator.validate_all(valid_skill, "skill")
struct_ok = any(
r.passed and r.constraint_name == "skill_structure"
for r in struct_results
r.passed and r.constraint_name == "skill_structure" for r in struct_results
)

results["components"]["constraints"] = {
Expand Down Expand Up @@ -134,7 +132,7 @@ def run_evolution_bridge() -> dict[str, Any]:

# ── 4. EvalExample + EvalDataset ─────────────────────────────────
try:
from evolution.core.dataset_builder import EvalExample, EvalDataset
from evolution.core.dataset_builder import EvalDataset, EvalExample

ex = EvalExample(
task_input="Review this PR",
Expand Down Expand Up @@ -191,9 +189,7 @@ def run_evolution_bridge() -> dict[str, Any]:
results["components"]["skill_helpers"] = {"status": "error", "error": str(exc)}

# ── Summary ──────────────────────────────────────────────────────
ok_count = sum(
1 for c in results["components"].values() if c.get("status") == "ok"
)
ok_count = sum(1 for c in results["components"].values() if c.get("status") == "ok")
total = len(results["components"])
results["status"] = "success" if ok_count == total else "partial"
results["components_ok"] = f"{ok_count}/{total}"
Expand Down
7 changes: 5 additions & 2 deletions src/codomyrmex/agents/hermes/scripts/run_mcp_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ def run_mcp_tools(
skills_result = hermes_skills_list()
results["skills_tool"] = {
"status": skills_result.get("status"),
"has_output_or_message": "output" in skills_result or "message" in skills_result,
"has_output_or_message": "output" in skills_result
or "message" in skills_result,
}

# ── Summary ──────────────────────────────────────────────────────
Expand All @@ -75,7 +76,9 @@ def run_mcp_tools(

def main() -> None:
"""CLI entry point."""
prompt = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else "Say hello in one sentence."
prompt = (
" ".join(sys.argv[1:]) if len(sys.argv) > 1 else "Say hello in one sentence."
)
result = run_mcp_tools(prompt=prompt)
print(json.dumps(result, indent=2, default=str))

Expand Down
12 changes: 9 additions & 3 deletions src/codomyrmex/agents/hermes/scripts/run_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@
import time
from typing import Any

from codomyrmex.agents.hermes.scripts.run_status import run_status
from codomyrmex.agents.hermes.scripts.run_chat import run_chat
from codomyrmex.agents.hermes.scripts.run_session import run_session
from codomyrmex.agents.hermes.scripts.run_template import render_template, list_available_templates
from codomyrmex.agents.hermes.scripts.run_status import run_status
from codomyrmex.agents.hermes.scripts.run_template import (
list_available_templates,
render_template,
)


def run_pipeline(
Expand Down Expand Up @@ -89,7 +92,10 @@ def run_pipeline(
"Give a Python example.",
]
session_result = run_session(
session_prompts, db_path=db_path, backend=backend, model=model,
session_prompts,
db_path=db_path,
backend=backend,
model=model,
)
results["stages"]["session"] = {
"status": session_result["status"],
Expand Down
16 changes: 11 additions & 5 deletions src/codomyrmex/agents/hermes/scripts/run_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ def run_session(
"Now explain it for a 5-year-old.",
]

client = HermesClient(config={
"hermes_backend": backend,
"hermes_model": model,
})
client = HermesClient(
config={
"hermes_backend": backend,
"hermes_model": model,
}
)

session = HermesSession(metadata={"backend": client.active_backend})
store = SQLiteSessionStore(db_path=db_path)
Expand All @@ -65,7 +67,11 @@ def run_session(
try:
request = AgentRequest(prompt=prompt_text)
response = client.execute(request)
content = response.content if response.is_success() else f"[error] {response.error}"
content = (
response.content
if response.is_success()
else f"[error] {response.error}"
)
except HermesError as exc:
content = f"[error] {exc}"

Expand Down
10 changes: 6 additions & 4 deletions src/codomyrmex/agents/hermes/scripts/run_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ def run_status(*, backend: str = "auto", model: str = "hermes3") -> dict[str, An
Dict with keys: ``active_backend``, ``cli_available``,
``ollama_available``, ``ollama_model``, ``success``.
"""
client = HermesClient(config={
"hermes_backend": backend,
"hermes_model": model,
})
client = HermesClient(
config={
"hermes_backend": backend,
"hermes_model": model,
}
)
status = client.get_hermes_status()
return {
"active_backend": client.active_backend,
Expand Down
16 changes: 10 additions & 6 deletions src/codomyrmex/agents/hermes/scripts/run_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,13 @@ def run_stream(
Dict with keys: ``status``, ``lines``, ``line_count``,
``elapsed_s``, ``backend``.
"""
client = HermesClient(config={
"hermes_backend": backend,
"hermes_model": model,
"hermes_timeout": timeout,
})
client = HermesClient(
config={
"hermes_backend": backend,
"hermes_model": model,
"hermes_timeout": timeout,
}
)

lines: list[str] = []
start = time.time()
Expand Down Expand Up @@ -76,7 +78,9 @@ def main() -> None:
result = run_stream(prompt)
for line in result["lines"]:
print(line)
print(f"\n--- {result['line_count']} lines in {result['elapsed_s']}s ({result['backend']}) ---")
print(
f"\n--- {result['line_count']} lines in {result['elapsed_s']}s ({result['backend']}) ---"
)
sys.exit(0 if result["status"] == "success" else 1)


Expand Down
10 changes: 6 additions & 4 deletions src/codomyrmex/agents/hermes/scripts/run_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,12 @@ def run_template(
"""
rendered = render_template(template_name, variables)

client = HermesClient(config={
"hermes_backend": backend,
"hermes_model": model,
})
client = HermesClient(
config={
"hermes_backend": backend,
"hermes_model": model,
}
)

start = time.time()
try:
Expand Down
Loading