Skip to content

fix: make bounty module portable across platforms and LLM providers#3

Open
Copilot wants to merge 2 commits intomainfrom
copilot/analyse-repo-axes-amelioration
Open

fix: make bounty module portable across platforms and LLM providers#3
Copilot wants to merge 2 commits intomainfrom
copilot/analyse-repo-axes-amelioration

Conversation

Copy link
Copy Markdown

Copilot AI commented Mar 6, 2026

The bounty module had three hard-coded, macOS-specific assumptions making it non-portable: a Homebrew-pinned httpx binary path, Unix-only ffuf wordlist locations, and baked-in LLM model names with no override mechanism.

Changes

scanner.py — portable tool resolution

  • _resolve_httpx_bin(): HTTPX_BIN env var → shutil.which("httpx-toolkit")shutil.which("httpx") → Homebrew path as last resort
  • _resolve_ffuf_wordlist(): explicit arg → FFUF_WORDLIST env var → 4 platform-aware default paths (Debian/Ubuntu, Red Hat, macOS Homebrew, ~/SecLists)
  • Moved import ssl / import socket to module level

factory.py — configurable LLM models

  • Added _resolve_model(agent_name, default_model) wired into all 8 BOUNTY_AGENTS
  • Per-agent override: FIRM_<AGENT_NAME_UPPER>_MODEL (e.g. FIRM_HUNT_DIRECTOR_MODEL)
  • Global fallback: FIRM_DEFAULT_MODEL
# Run the full bounty firm on Linux with a custom model and wordlist
FIRM_DEFAULT_MODEL=gpt-4o \
FFUF_WORDLIST=/opt/wordlists/common.txt \
HTTPX_BIN=/usr/local/bin/httpx \
  firm bounty run --target example.com

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Co-authored-by: romainsantoli-web <246739959+romainsantoli-web@users.noreply.github.com>
Copilot AI changed the title [WIP] Analyze repository for general improvement opportunities fix: make bounty module portable across platforms and LLM providers Mar 6, 2026
@romainsantoli-web
Copy link
Copy Markdown
Owner

@copilot Fait un test et vérifie les changements

Copy link
Copy Markdown
Author

Copilot AI commented Mar 6, 2026

@copilot Fait un test et vérifie les changements

35/35 tests passed, lint clean ✅

Breakdown of the 15 new tests added for the changes:

Suite Tests What's verified
TestResolveHttpxBin 3 HTTPX_BIN env override, empty-env fallback, shutil.which path
TestResolveFfufWordlist 6 explicit path, FFUF_WORDLIST env var, missing file, no wordlist message, end-to-end scan_ffuf with env wordlist
TestResolveModel 5 default model, per-agent FIRM_<AGENT>_MODEL override, global FIRM_DEFAULT_MODEL, priority order, hyphen→underscore mapping

All pre-existing 20 tests continue to pass unchanged.

@romainsantoli-web
Copy link
Copy Markdown
Owner

@copilot fait le pr puis le main

@romainsantoli-web romainsantoli-web marked this pull request as ready for review March 6, 2026 08:25
Copilot AI review requested due to automatic review settings March 6, 2026 08:25
Copy link
Copy Markdown
Author

Copilot AI commented Mar 6, 2026

@copilot fait le pr puis le main

Je n'ai pas la possibilité de fusionner une PR ni de pousser directement sur main — seul un owner/maintainer du dépôt peut faire ça via l'interface GitHub.

La PR est prête : 35/35 tests passent et le lint est propre. Tu peux la merger toi-même en cliquant sur "Merge pull request" sur la page de la PR.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR makes the bounty module portable across platforms and LLM providers by replacing three hard-coded macOS-specific assumptions: a Homebrew-pinned httpx binary path, Unix-only ffuf wordlist locations, and baked-in LLM model names with no override mechanism. All three are now resolved via environment variable overrides with sensible platform-aware fallback chains.

Changes:

  • scanner.py: Added _resolve_httpx_bin() and _resolve_ffuf_wordlist() functions that resolve tool paths via env vars → PATH lookup → platform-specific fallbacks, replacing hard-coded macOS paths. Moved ssl/socket imports to module level and cleaned up unused imports.
  • factory.py: Added _resolve_model() to support per-agent (FIRM_<NAME>_MODEL) and global (FIRM_DEFAULT_MODEL) env var overrides for LLM model names, wired into all 8 agent specs.
  • Test files: Added comprehensive unit tests for all three resolution functions and their integration with the scanning tools.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/firm/bounty/tools/scanner.py Replaced hard-coded httpx path and ffuf wordlist with portable resolution functions; moved ssl/socket to module-level imports; cleaned up unused imports.
src/firm/bounty/factory.py Added _resolve_model() for env-var-based LLM model overrides; wired into all BOUNTY_AGENTS entries; updated module docstring.
tests/test_bounty_tools.py Added TestResolveHttpxBin and TestResolveFfufWordlist test classes covering env var, PATH, and fallback resolution.
tests/test_bounty_factory.py Added TestResolveModel test class covering per-agent, global, and default model resolution.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 65 to 113
BOUNTY_AGENTS: list[AgentSpec] = [
AgentSpec(
name="hunt-director",
model="claude-sonnet-4-20250514",
model=_resolve_model("hunt-director", "claude-sonnet-4-20250514"),
initial_authority=0.90,
description="Campaign coordinator — plans phases, assigns targets, synthesises results.",
),
AgentSpec(
name="recon-agent",
model="gpt-4.1",
model=_resolve_model("recon-agent", "gpt-4.1"),
initial_authority=0.70,
description="Reconnaissance specialist — subdomains, ports, tech stack, URL crawling.",
),
AgentSpec(
name="web-hunter",
model="claude-sonnet-4-20250514",
model=_resolve_model("web-hunter", "claude-sonnet-4-20250514"),
initial_authority=0.65,
description="Web vulnerability hunter — SQLi, XSS, SSRF, IDOR on web apps.",
),
AgentSpec(
name="api-hunter",
model="gpt-4o",
model=_resolve_model("api-hunter", "gpt-4o"),
initial_authority=0.65,
description="API vulnerability hunter — auth bypass, BOLA, rate limiting, GraphQL.",
),
AgentSpec(
name="code-auditor",
model="o4-mini",
model=_resolve_model("code-auditor", "o4-mini"),
initial_authority=0.60,
description="Static code auditor — semgrep, pattern-based detection, dependency audit.",
),
AgentSpec(
name="mobile-hunter",
model="claude-sonnet-4-20250514",
model=_resolve_model("mobile-hunter", "claude-sonnet-4-20250514"),
initial_authority=0.55,
description="Mobile app security — APK/IPA analysis, certificate pinning, local storage.",
),
AgentSpec(
name="web3-hunter",
model="gpt-4.1",
model=_resolve_model("web3-hunter", "gpt-4.1"),
initial_authority=0.55,
description="Smart contract / blockchain security — reentrancy, flash loans, oracle manipulation.",
),
AgentSpec(
name="report-writer",
model="gpt-4o",
model=_resolve_model("report-writer", "gpt-4o"),
initial_authority=0.40,
description="Report writer — crafts clear, detailed H1 reports with reproduction steps.",
),
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_resolve_model() is called at module import time for each agent in BOUNTY_AGENTS. This means env var overrides (FIRM_*_MODEL, FIRM_DEFAULT_MODEL) are "frozen" at first import. For the documented CLI workflow (env vars set before firm bounty ...), this works because cli.py uses lazy imports. However, programmatic callers who set env vars and then call create_bounty_firm() after the module has already been imported won't see the updated models. Consider deferring model resolution into create_bounty_firm() so it always reads current env vars, or document this limitation explicitly.

Copilot uses AI. Check for mistakes.
monkeypatch.setenv("HTTPX_BIN", "/custom/httpx")
assert _resolve_httpx_bin() == "/custom/httpx"

def test_env_var_empty_falls_through(self, monkeypatch, tmp_path):
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tmp_path fixture parameter is declared but never used in this test. It can be removed.

Suggested change
def test_env_var_empty_falls_through(self, monkeypatch, tmp_path):
def test_env_var_empty_falls_through(self, monkeypatch):

Copilot uses AI. Check for mistakes.
Comment on lines +112 to +117
_DEFAULT_FFUF_WORDLISTS: list[str] = [
"/usr/share/seclists/Discovery/Web-Content/common.txt", # Debian/Ubuntu
"/usr/share/SecLists/Discovery/Web-Content/common.txt", # some distros
str(Path.home() / "SecLists/Discovery/Web-Content/common.txt"), # macOS/user install
"/opt/homebrew/share/seclists/Discovery/Web-Content/common.txt", # Homebrew
]
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Path.home() is now called at module import time (as part of _DEFAULT_FFUF_WORDLISTS), whereas previously the equivalent Path.home() call was inside the scan_ffuf function body and only executed when that tool was invoked. If Path.home() raises a RuntimeError (e.g., in a containerized environment with no HOME set), it will now prevent the entire scanner module from being imported, rather than just causing that one tool call to fail. Consider computing this path lazily inside _resolve_ffuf_wordlist() instead, or wrapping it with a try/except at module level.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants