项目文件夹

文件
Gelei Deng b9869307d0 Legacy multi llm base (#470)
* fix: 🐛 minor typo and build process

* feat: 🎸 [WIP] Pentest mode

* feat: 🎸 code abstraction

* feat: modernize legacy PentestGPT with native multi-LLM support (#469)

Rebuild the classic USENIX-2024 interactive PentestGPT (reasoning / generation /
parsing sessions + Pentesting Task Tree + REPL) as a standalone
`pentestgpt_legacy` package on a native per-provider LLM layer that supports the
latest 2026 models.

- llm/: BaseProvider + OpenAI-compatible / Anthropic / Gemini connectors, a
  web-verified model registry (OpenAI, Anthropic, Gemini, DeepSeek, xAI, Qwen,
  Moonshot, local Ollama), a factory, and an LLMClient bridging async providers
  to the core's synchronous send_new_message/send_message session API.
- CLI `pentestgpt-legacy`: --list-models and --smoke-test (live per-model
  round-trip matrix), plus --reasoning-model / --parsing-model / --base-url.
- Tests: 25 unit tests (mocked, no network). Live smoke test verified 22/22
  models with a configured key respond.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* fix(backend): address review on ClaudeCodeBackend subprocess handling

- _build_env: pop ANTHROPIC_API_KEY instead of setting it to "", so an empty
  value can't shadow the CLI's own auth fallback (e.g. subscription login).
- _kill_process: reap the force-killed process with os.waitpid(.., WNOHANG)
  instead of calling the proc.wait() coroutine without awaiting it (removes the
  "coroutine was never awaited" warning).
- query/_drain_stderr: drain subprocess stderr in a background task so its pipe
  buffer can't fill and deadlock the child.

Also reformats backend.py, fixing the failing Lint (ruff format) check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(docker-test): assert uv instead of Poetry in container health check

The project migrated from Poetry to uv (the Dockerfile installs uv to
/home/pentester/.local/bin, which is on PATH), so test_poetry_installed failed
with exit 127. Replace it with test_uv_installed checking `uv --version`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 15:25:45 +08:00

157 行
5.6 KiB
Python

"""CLI entry point for the modernized legacy PentestGPT.
pentestgpt-legacy # interactive session (auto-picks models)
pentestgpt-legacy --list-models # show the supported-model registry
pentestgpt-legacy --smoke-test # live round-trip every configured model
pentestgpt-legacy --reasoning-model claude-opus-4-8 --parsing-model gemini-3.5-flash
pentestgpt-legacy --reasoning-model ollama:qwen3 --base-url http://localhost:11434/v1
"""
from __future__ import annotations
import argparse
import os
import sys
from pentestgpt_legacy._version import __version__
from pentestgpt_legacy.config import (
configured_providers,
default_parsing_model,
default_reasoning_model,
)
from pentestgpt_legacy.llm.registry import PROVIDERS, models_by_provider, resolve
def _create_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="pentestgpt-legacy",
description="PentestGPT (modernized legacy) — interactive, multi-LLM pentest assistant.",
)
parser.add_argument(
"--reasoning-model",
type=str,
default=None,
help="Model for reasoning / the Pentesting Task Tree.",
)
parser.add_argument(
"--parsing-model", type=str, default=None, help="Model for summarizing tool/web output."
)
parser.add_argument(
"--generation-model",
type=str,
default=None,
help="Model for expanding tasks into steps (defaults to reasoning model).",
)
parser.add_argument(
"--base-url",
type=str,
default=None,
help="Override the API base URL for the selected models' provider "
"(useful for Ollama / OpenAI-compatible endpoints).",
)
parser.add_argument("--log-dir", type=str, default="logs", help="Directory for logs.")
parser.add_argument(
"--list-models", action="store_true", help="List all supported models and exit."
)
parser.add_argument(
"--smoke-test",
action="store_true",
help="Make a real API call to every configured model and report a matrix.",
)
parser.add_argument(
"--provider",
type=str,
default=None,
help="Restrict --smoke-test to one provider (e.g. openai, anthropic).",
)
parser.add_argument(
"--model", type=str, default=None, help="Restrict --smoke-test to a single model id."
)
parser.add_argument("--version", action="version", version=f"pentestgpt-legacy {__version__}")
return parser
def display_models() -> None:
"""Render the supported-model registry grouped by provider."""
ready = set(configured_providers())
print(f"\nPentestGPT (modernized legacy) v{__version__} — supported models\n")
for provider_key, specs in models_by_provider().items():
info = PROVIDERS[provider_key]
status = "configured" if provider_key in ready else "no key"
env_hint = ""
if info.requires_key and provider_key not in ready and info.env:
env_hint = f" (set {info.env})"
print(f"== {info.label} [{status}]{env_hint} ==")
for spec in specs:
flag = "legacy" if spec.legacy else spec.tier
ctx = f"{spec.context_window // 1000}K ctx"
note = f" — {spec.notes}" if spec.notes else ""
print(f" {spec.id:<32} [{flag:<8}] {ctx}{note}")
print()
print("Local models: pass 'ollama:<model>' (e.g. ollama:qwen3) with --base-url if needed.\n")
def _apply_base_url_override(models: list[str], base_url: str) -> None:
"""Set the per-provider *_BASE_URL env override for the selected models."""
for model_name in models:
spec = resolve(model_name)
if spec is None:
continue
info = PROVIDERS.get(spec.provider)
if info is None or (info.base_url is None and spec.provider in ("anthropic", "gemini")):
print(f"Note: --base-url is not applicable to provider '{spec.provider}'.")
continue
os.environ[f"{spec.provider.upper()}_BASE_URL"] = base_url
def run_interactive(args: argparse.Namespace) -> None:
reasoning_model = args.reasoning_model or default_reasoning_model()
parsing_model = args.parsing_model or default_parsing_model()
if reasoning_model is None or parsing_model is None:
print(
"No LLM provider is configured. Set an API key (e.g. OPENAI_API_KEY, "
"ANTHROPIC_API_KEY, GEMINI_API_KEY, DEEPSEEK_API_KEY) in your environment "
"or .env file, or run with --list-models to see options.",
file=sys.stderr,
)
sys.exit(1)
if args.base_url:
selected = [m for m in (reasoning_model, parsing_model, args.generation_model) if m]
_apply_base_url_override(selected, args.base_url)
# Imported lazily so --list-models / --smoke-test don't require prompt_toolkit/rich.
from pentestgpt_legacy.utils.pentest_gpt import pentestGPT
try:
session = pentestGPT(
log_dir=args.log_dir,
reasoning_model=reasoning_model,
parsing_model=parsing_model,
generation_model=args.generation_model,
)
session.main()
except Exception as e:
print(f"PentestGPT execution failed: {e}", file=sys.stderr)
sys.exit(1)
def main() -> None:
args = _create_parser().parse_args()
if args.list_models:
display_models()
return
if args.smoke_test:
from pentestgpt_legacy.smoke_test import run_smoke_test
sys.exit(run_smoke_test(provider=args.provider, model=args.model))
run_interactive(args)
if __name__ == "__main__":
main()