Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ OPENROUTER_API_KEY=sk-or-...
AGENTFIELD_SERVER=http://localhost:8080
AGENTFIELD_API_KEY=

# AForge exec is the default. Set HARNESS_PROVIDER=opencode to roll back.
HARNESS_PROVIDER=aforge
AGENTFIELD_AFORGE_COMMAND=exec
# CLOUDSECURITY_AFORGE_BIN=/absolute/path/to/aforge

# Optional: Model overrides
HARNESS_MODEL=openrouter/moonshotai/kimi-k2.5
AI_MODEL=openrouter/moonshotai/kimi-k2.5
Expand Down
10 changes: 8 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
ARG AFORGE_IMAGE=ghcr.io/agent-field/aforge-v2:chat-v2-exec
FROM ${AFORGE_IMAGE} AS aforge


FROM python:3.11-slim AS builder

ENV PYTHONDONTWRITEBYTECODE=1 \
Expand All @@ -14,7 +18,7 @@ COPY pyproject.toml README.md ./
COPY src/ src/

RUN pip install --no-cache-dir --prefix=/install \
"agentfield>=0.1.0" \
"agentfield @ git+https://github.com/Agent-Field/agentfield.git@bfd34426d1bdec3cbbfa946682a22ef0a1b91503#subdirectory=sdk/python" \
"pydantic>=2.0" \
"httpx>=0.27" \
"python-dotenv>=1.0" \
Expand All @@ -27,7 +31,8 @@ FROM python:3.11-slim AS runtime
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
AGENTFIELD_SERVER=http://agentfield:8080 \
HARNESS_PROVIDER=opencode \
HARNESS_PROVIDER=aforge \
AGENTFIELD_AFORGE_COMMAND=exec \
HARNESS_MODEL=openrouter/moonshotai/kimi-k2.5 \
AI_MODEL=openrouter/moonshotai/kimi-k2.5 \
PORT=8005 \
Expand Down Expand Up @@ -55,6 +60,7 @@ RUN mkdir -p /home/cloudsecurity/.config/opencode && \
chown -R cloudsecurity:cloudsecurity /home/cloudsecurity/.config

COPY --from=builder /install /usr/local
COPY --from=aforge /aforge /usr/local/bin/aforge
COPY src/ /app/src/

USER cloudsecurity
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,9 @@ See [`docs/GITHUB_ACTIONS.md`](docs/GITHUB_ACTIONS.md) for full Tier 1 and Tier
| `AGENTFIELD_SERVER` | No | `http://localhost:8080` | AgentField control plane URL |
| `NODE_ID` | No | `cloudsecurity` | Agent node identifier |
| `OPENROUTER_API_KEY` | Yes | - | Model provider credential |
| `CLOUDSECURITY_PROVIDER` | No | `opencode` | Harness provider override |
| `CLOUDSECURITY_PROVIDER` | No | `aforge` | Harness provider override; use `opencode` for rollback |
| `AGENTFIELD_AFORGE_COMMAND` | No | `exec` | AForge headless command (`do` remains an explicit override) |
| `CLOUDSECURITY_AFORGE_BIN` | No | `aforge` | Path to the AForge executable |
| `CLOUDSECURITY_MODEL` | No | `openrouter/minimax/minimax-m2.5` | Harness model |
| `CLOUDSECURITY_AI_MODEL` | No | `CLOUDSECURITY_MODEL`/`AI_MODEL` fallback | `.ai()` gate model |
| `CLOUDSECURITY_MAX_TURNS` | No | `50` | Max turns per harness call |
Expand Down
8 changes: 8 additions & 0 deletions agentfield-package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ user_environment:
description: Control-plane API key (if auth is enabled)
type: secret
scope: global
- name: HARNESS_PROVIDER
description: Coding-agent harness provider (aforge by default; opencode for rollback)
default: aforge
- name: AGENTFIELD_AFORGE_COMMAND
description: AForge headless command
default: exec
- name: CLOUDSECURITY_AFORGE_BIN
description: Optional path to the AForge binary (defaults to aforge on PATH)
- name: HARNESS_MODEL
description: Model the harness uses
default: openrouter/moonshotai/kimi-k2.5
Expand Down
3 changes: 2 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ services:
- AGENTFIELD_SERVER=http://agentfield:8080
- AGENTFIELD_API_KEY=${AGENTFIELD_API_KEY:-}
- AGENT_CALLBACK_URL=http://cloudsecurity-af:8005
- HARNESS_PROVIDER=opencode
- HARNESS_PROVIDER=${HARNESS_PROVIDER:-aforge}
- AGENTFIELD_AFORGE_COMMAND=${AGENTFIELD_AFORGE_COMMAND:-exec}
- HARNESS_MODEL=${HARNESS_MODEL:-openrouter/moonshotai/kimi-k2.5}
- AI_MODEL=${AI_MODEL:-openrouter/moonshotai/kimi-k2.5}
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
Expand Down
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ license = "Apache-2.0"
requires-python = ">=3.11"
authors = [{ name = "AgentField", email = "hello@agentfield.dev" }]
dependencies = [
"agentfield>=0.1.0",
"agentfield @ git+https://github.com/Agent-Field/agentfield.git@bfd34426d1bdec3cbbfa946682a22ef0a1b91503#subdirectory=sdk/python",
"pydantic>=2.0",
"httpx>=0.27",
"pyhcl2>=2.0",
Expand All @@ -29,6 +29,9 @@ dev = [
[tool.hatch.build.targets.wheel]
packages = ["src/cloudsecurity_af"]

[tool.hatch.metadata]
allow-direct-references = true

[tool.ruff]
target-version = "py311"
line-length = 120
Expand Down
1 change: 1 addition & 0 deletions src/cloudsecurity_af/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
max_turns=_ai_config.max_turns,
env=_ai_config.provider_env(),
opencode_bin=_ai_config.opencode_bin,
aforge_bin=_ai_config.aforge_bin,
permission_mode="auto",
),
ai_config=AIConfig(
Expand Down
9 changes: 8 additions & 1 deletion src/cloudsecurity_af/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def from_input(cls, scan_input: CloudSecurityInput, repo_path: str) -> ScanConfi

class AIIntegrationConfig(BaseModel):
provider: str = Field(
default_factory=lambda: os.getenv("CLOUDSECURITY_PROVIDER", os.getenv("HARNESS_PROVIDER", "opencode"))
default_factory=lambda: os.getenv("CLOUDSECURITY_PROVIDER", os.getenv("HARNESS_PROVIDER", "aforge"))
)
harness_model: str = Field(
default_factory=lambda: os.getenv(
Expand All @@ -102,6 +102,12 @@ class AIIntegrationConfig(BaseModel):
)
max_turns: int = Field(default_factory=lambda: int(os.getenv("CLOUDSECURITY_MAX_TURNS", "50")))
opencode_bin: str = Field(default_factory=lambda: os.getenv("CLOUDSECURITY_OPENCODE_BIN", "opencode"))
aforge_bin: str = Field(
default_factory=lambda: os.getenv(
"CLOUDSECURITY_AFORGE_BIN",
os.getenv("AFORGE_BIN", "aforge"),
)
)

@classmethod
def from_env(cls) -> AIIntegrationConfig:
Expand All @@ -125,6 +131,7 @@ def provider_env(self) -> dict[str, str]:
"AZURE_SUBSCRIPTION_ID",
)
env: dict[str, str] = {key: value for key in env_keys if (value := os.getenv(key))}
env["AGENTFIELD_AFORGE_COMMAND"] = os.getenv("AGENTFIELD_AFORGE_COMMAND", "exec")
xdg = os.getenv("XDG_DATA_HOME") or os.path.join(tempfile.gettempdir(), "opencode-shared-data")
os.makedirs(xdg, exist_ok=True)
env["XDG_DATA_HOME"] = xdg
Expand Down
28 changes: 26 additions & 2 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,40 @@
import pytest

from cloudsecurity_af.config import (
BudgetConfig,
DepthProfile,
DEPTH_CHAIN_LIMITS,
DEPTH_HUNTER_MAP,
DEPTH_PROVER_CAPS,
AIIntegrationConfig,
BudgetConfig,
DepthProfile,
ScanConfig,
)
from cloudsecurity_af.schemas.input import CloudSecurityInput


def test_aforge_exec_is_the_default_harness(monkeypatch: pytest.MonkeyPatch) -> None:
for key in (
"CLOUDSECURITY_PROVIDER",
"HARNESS_PROVIDER",
"CLOUDSECURITY_AFORGE_BIN",
"AFORGE_BIN",
"AGENTFIELD_AFORGE_COMMAND",
):
monkeypatch.delenv(key, raising=False)

config = AIIntegrationConfig.from_env()

assert config.provider == "aforge"
assert config.aforge_bin == "aforge"
assert config.provider_env()["AGENTFIELD_AFORGE_COMMAND"] == "exec"


def test_opencode_remains_an_explicit_rollback(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("HARNESS_PROVIDER", "opencode")

assert AIIntegrationConfig.from_env().provider == "opencode"


class TestDepthProfile:
def test_enum_values(self) -> None:
assert DepthProfile.QUICK.value == "quick"
Expand Down