strix/pyproject.toml
Matthew Flanagan 4aaab2f82b
feat(auth): Entra ID user sign-in and token commands for Anthropic WIF
Add identity token sources for Anthropic Workload Identity Federation
beyond a token file:

- ANTHROPIC_IDENTITY_TOKEN_COMMAND: a command run on every exchange whose
  stdout is the JWT (run without a shell; errors never include stdout).
  Saved to the Strix config like the other ANTHROPIC_* settings.
- `strix auth login entra [--manual] [--tenant ID] [--app ID]`: PKCE
  sign-in as a Microsoft Entra ID user, using the audience app as its own
  public client so the stored refresh token only reaches that audience.
  Tokens are refreshed automatically; defaults come from
  STRIX_ENTRA_TENANT_ID / STRIX_ENTRA_APP_ID.

Source order: token file, command, literal token, Entra sign-in, GitHub
Actions OIDC. `strix auth status` and `logout` now cover both ChatGPT and
Entra, and `strix auth test [--config PATH]` runs one exchange with the
source a scan would use, reporting the source, claims and result without
printing any token.

Docs: new "Anthropic via Microsoft Entra ID" guide (Entra and Console
setup, Entra users on any host, Azure VM managed identities, everyday
use, security, troubleshooting), plus the WIF settings in the
configuration reference and `strix auth` in the CLI reference.
2026-10-12 22:22:41 +11:00

425 lines
14 KiB
TOML

[project]
name = "strix-agent"
version = "1.7.0"
description = "Open-source AI Hackers for your apps"
readme = "README.md"
license = "Apache-2.0"
requires-python = ">=3.12"
authors = [
{ name = "Strix", email = "hi@usestrix.com" },
]
keywords = [
"cybersecurity",
"security",
"vulnerability",
"scanner",
"pentest",
"agent",
"ai",
"cli",
]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Information Technology",
"Intended Audience :: System Administrators",
"Topic :: Security",
"License :: OSI Approved :: Apache Software License",
"Environment :: Console",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
dependencies = [
"openai-agents[litellm]>=0.19.0,<0.20",
"openai>=2.45.0,<3",
"litellm>=1.101.0",
"google-auth>=2.0.0",
"boto3>=1.28.0",
"pydantic>=2.11.3",
"pydantic-settings>=2.13.0",
"rich",
"docker>=7.1.0",
"requests>=2.32.0",
"cvss>=3.2",
"caido-sdk-client>=0.2.0",
"markdown-it-py>=3.0.0",
"reportlab>=4.0",
"pypdf>=5.0",
# Cap <49: 49.x drops the universal2 macOS wheel (arm64-only), which breaks
# the Intel macOS (macos-x86_64) release build's `uv sync --frozen`.
"cryptography>=48.0.1,<49",
"pyyaml>=6.0",
]
[project.scripts]
strix = "strix.interface.main:main"
[dependency-groups]
dev = [
"mypy>=1.16.0",
"ruff>=0.11.13",
"pyright>=1.1.401",
"bandit>=1.8.3",
"pre-commit>=4.2.0",
"pyinstaller>=6.17.0; python_version >= '3.12' and python_version < '3.15'",
"pytest>=8.3",
"pytest-asyncio>=0.24",
"types-requests>=2.32",
]
[tool.pytest.ini_options]
asyncio_mode = "auto"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["strix"]
# The prebuilt viewer bundle under strix/interface/viewer/static/ ships automatically
# (hatchling includes non-.py files under the package). The Vite SOURCE lives
# under the package dir too (strix/interface/viewer/frontend/) but must never ship in the wheel.
exclude = [
"strix/interface/viewer/frontend",
"strix/interface/viewer/frontend/**",
# Go TUI SOURCE lives under the package dir but must never ship in the wheel;
# the compiled sidecar is force-included as strix/bin/strix-tui instead.
"strix/interface/tui/cmd/**",
"strix/interface/tui/internal/**",
"strix/interface/tui/go.mod",
"strix/interface/tui/go.sum",
]
[tool.hatch.build.targets.wheel.hooks.custom]
path = "scripts/tui_sidecar_hook.py"
# ============================================================================
# Type Checking Configuration
# ============================================================================
[tool.mypy]
python_version = "3.12"
strict = true
strict_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_return_any = true
warn_unreachable = true
disallow_untyped_defs = true
disallow_any_generics = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_untyped_decorators = true
no_implicit_optional = true
warn_unused_configs = true
show_error_codes = true
show_column_numbers = true
pretty = true
# Allow some flexibility for third-party libraries
[[tool.mypy.overrides]]
module = [
"litellm.*",
"rich.*",
"jinja2.*",
"cvss.*",
"docker.*",
"caido_sdk_client.*",
"pydantic_settings.*",
"reportlab.*",
"pypdf.*",
"yaml.*",
"pygments.*",
]
ignore_missing_imports = true
disable_error_code = ["import-untyped"]
[[tool.mypy.overrides]]
module = ["tests.*"]
disallow_untyped_decorators = false
# ============================================================================
# Ruff Configuration (Fast Python Linter & Formatter)
# ============================================================================
[tool.ruff]
target-version = "py312"
line-length = 100
extend-exclude = [
".git",
".mypy_cache",
".ruff_cache",
"__pycache__",
"build",
"dist",
"migrations",
]
[tool.ruff.lint]
# Enable comprehensive rule sets
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # Pyflakes
"I", # isort
"N", # pep8-naming
"UP", # pyupgrade
"YTT", # flake8-2020
"S", # flake8-bandit
"BLE", # flake8-blind-except
"FBT", # flake8-boolean-trap
"B", # flake8-bugbear
"A", # flake8-builtins
"COM", # flake8-commas
"C4", # flake8-comprehensions
"DTZ", # flake8-datetimez
"T10", # flake8-debugger
"EM", # flake8-errmsg
"FA", # flake8-future-annotations
"ISC", # flake8-implicit-str-concat
"ICN", # flake8-import-conventions
"G", # flake8-logging-format
"INP", # flake8-no-pep420
"PIE", # flake8-pie
"T20", # flake8-print
"PYI", # flake8-pyi
"Q", # flake8-quotes
"RSE", # flake8-raise
"RET", # flake8-return
"SLF", # flake8-self
"SIM", # flake8-simplify
"TID", # flake8-tidy-imports
"TCH", # flake8-type-checking
"ARG", # flake8-unused-arguments
"PTH", # flake8-use-pathlib
"ERA", # eradicate
"PD", # pandas-vet
"PGH", # pygrep-hooks
"PL", # Pylint
"TRY", # tryceratops
"FLY", # flynt
"PERF", # Perflint
"RUF", # Ruff-specific rules
]
ignore = [
"S101", # Use of assert
"S104", # Possible binding to all interfaces
"S301", # Use of pickle
"COM812", # Missing trailing comma (handled by formatter)
"ISC001", # Single line implicit string concatenation (handled by formatter)
"PLR0913", # Too many arguments to function call
"TRY003", # Avoid specifying long messages outside the exception class
"EM101", # Exception must not use a string literal
"EM102", # Exception must not use an f-string literal
"FBT001", # Boolean positional arg in function definition
"FBT002", # Boolean default positional argument in function definition
"G004", # Logging statement uses f-string
"PLR2004", # Magic value used in comparison
"SLF001", # Private member accessed
]
[tool.ruff.lint.per-file-ignores]
# Test doubles use fixture tokens/passwords and match a callee signature whose
# args they intentionally ignore.
"tests/test_viewer_auth.py" = ["S105", "S106", "ARG001"]
"tests/test_cloud_cli.py" = ["S105", "ARG001"]
"tests/test_codex_auth.py" = ["S105", "S106", "SLF001"]
"tests/test_entra_user.py" = ["S105", "S106", "SLF001"]
# Hatchling loads the build hook by path, not as an importable package.
"scripts/tui_sidecar_hook.py" = ["INP001"]
# Stdlib HTTP handler overrides (do_GET/do_POST).
"strix/interface/auth_cli.py" = ["N802"]
"tests/test_codex_streaming.py" = ["N802"]
"tests/test_disable_streaming.py" = ["N802"]
"tests/test_tool_call_ids.py" = ["N802"]
"tests/test_tool_call_limits.py" = ["N802", "SLF001"]
"tests/test_stream_idle_timeout.py" = ["N802", "SLF001"]
"tests/test_unknown_tool_recovery.py" = ["N802"]
"tests/test_report_pdf.py" = ["S105", "S106"]
# Fake MCP server matches the SDK's MCPServer signature; its args are unused.
"tests/test_mcp_client.py" = ["S105", "S106", "ARG002"]
# MCP connection request in a test carries a dummy bearer token.
"tests/test_runner_root_prompt.py" = ["S106"]
# Stdlib HTTP handler overrides (do_GET/do_POST) and lazy imports that avoid a
# circular dependency with strix.telemetry / strix.interface.viewer.report_pdf.
"strix/interface/viewer/server.py" = ["N802", "PLC0415"]
"strix/interface/cloud/payment_proxy.py" = ["N802"]
# Lazy telemetry import to avoid importing PostHog before the viewer starts.
"strix/interface/viewer/cli.py" = ["PLC0415"]
# Lazy imports inside functions to avoid circular dependency with
# strix.telemetry / strix.report.dedupe / cvss.
"strix/tools/notes/tools.py" = ["PLC0415", "TC002"]
"strix/tools/finish/tool.py" = ["PLC0415", "TC002"]
"strix/tools/reporting/tool.py" = ["PLC0415", "TC002"]
# Lazy imports of strix.tools.mcp.client avoid a circular import (client imports
# the session module at module load).
"strix/tools/mcp/session.py" = ["PLC0415"]
# call_mcp is a chain of guard clauses that each return an error string.
"strix/tools/mcp/agent_tools.py" = ["PLR0911"]
"strix/tools/**/*.py" = [
"ARG001", # Unused function argument (tools may have unused args for interface consistency)
]
# Custom Docker subclass duplicates parent body; some imports are for annotations.
# Backend factories import their backend's deps lazily so deployments
# that pick a different backend don't need every backend's libs installed.
"strix/runtime/backends.py" = ["PLC0415"]
"strix/runtime/docker_client.py" = [
"TC002", # Manifest, Container imported for annotations
"TC003", # uuid imported for annotation
]
# SDK function-tool wrappers: the SDK calls get_type_hints() at registration
# time to derive the JSON schema, which evaluates annotations at runtime —
# so RunContextWrapper / Tool / TResponseInputItem must be imported eagerly,
# not under TYPE_CHECKING.
"strix/tools/todo/tools.py" = ["TC002"]
"strix/tools/thinking/tool.py" = ["TC002"]
"strix/tools/web_search/tool.py" = ["TC002"]
"strix/tools/proxy/tools.py" = ["TC002", "PLR0911"]
# The generated Caido GraphQL schema is slow to import, so the SDK is imported
# on first proxy call instead of at module scope (keeps it off the launch path).
"strix/tools/proxy/caido_api.py" = ["PLC0415"]
"strix/runtime/caido_bootstrap.py" = ["PLC0415"]
"strix/tools/agents_graph/tools.py" = ["TC002"]
"strix/agents/factory.py" = ["TC002"]
# Entry point: ``Path`` is used at runtime by the typing of the
# session_manager call; importing under TYPE_CHECKING would defer
# resolution past where mypy needs it.
"strix/core/runner.py" = ["TC003", "PLR0912", "PLR0915", "PLC0415"]
# ReportState carries scan artifact/report fields and
# a runtime ``Callable`` annotation on ``vulnerability_found_callback``.
"strix/report/state.py" = ["TC003", "PLR0912", "PLR0915", "E501", "PERF401", "PLC0415"]
"strix/report/usage.py" = ["PLC0415"]
# LiteLLM and the Docker SDK are imported on first use, not at module scope:
# both cost seconds to import and neither is needed until a model call is made
# (or, for Docker, unless the Docker runtime backend is in use).
"strix/core/execution.py" = ["PLC0415"]
"strix/report/pricing.py" = ["PLC0415"]
"strix/llm/compaction.py" = ["PLC0415"]
"strix/llm/context_budget.py" = ["PLC0415"]
# Lazy import of strix.config.models avoids a circular dependency between the
# report pipeline and the config layer.
"strix/report/dedupe.py" = ["PLC0415"]
"strix/telemetry/logging.py" = ["PLC0415"]
"strix/config/models.py" = ["PLC0415"]
# LiteLLM is imported lazily: the request log is wired at startup on every
# route, including the native OpenAI ones that never load LiteLLM.
"strix/llm/request_log.py" = ["PLC0415"]
# Heavy inference deps (httpx, openai) imported lazily so auth-status checks
# don't pull them in.
"strix/config/codex.py" = ["PLC0415"]
# fcntl (cross-process refresh lock) is POSIX-only, so it is imported where used.
"strix/config/entra_user.py" = ["PLC0415"]
# Interface utility branches per scope-mode / target-type combination;
# splitting would obscure the decision tree without simplifying it.
"strix/interface/utils.py" = ["PLR0912", "BLE001", "PLC0415"]
# CLI / TUI / main keep extensive lazy imports + broad exception
# swallows for resilience around terminal-rendering errors.
"strix/interface/cli.py" = ["BLE001", "PLC0415"]
"strix/interface/scan_setup.py" = ["PLC0415"]
"strix/interface/main.py" = ["BLE001", "PLC0415", "PLR0912", "PLR0915"]
"strix/interface/cli_args.py" = ["BLE001", "PLC0415", "PLR0912", "PLR0915"]
"strix/interface/environment.py" = ["BLE001", "PLC0415", "PLR0912", "PLR0915"]
# The Go TUI runtime and backend controller import interface modules lazily so
# the sidecar entry point stays fast and avoids circular imports.
"strix/interface/interactive.py" = ["PLC0415"]
"strix/interface/tui/runtime.py" = ["PLC0415"]
"strix/interface/tui/backend/controller.py" = ["PLC0415"]
[tool.ruff.lint.isort]
force-single-line = false
lines-after-imports = 2
known-first-party = ["strix"]
known-third-party = ["pydantic"]
[tool.ruff.lint.pylint]
max-args = 8
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"
# ============================================================================
# PyRight Configuration (Alternative type checker)
# ============================================================================
[tool.pyright]
include = ["strix"]
exclude = ["**/__pycache__", "build", "dist"]
pythonVersion = "3.12"
pythonPlatform = "Linux"
typeCheckingMode = "strict"
reportMissingImports = true
reportMissingTypeStubs = false
reportGeneralTypeIssues = true
reportPropertyTypeMismatch = true
reportFunctionMemberAccess = true
reportMissingParameterType = true
reportMissingTypeArgument = true
reportIncompatibleMethodOverride = true
reportIncompatibleVariableOverride = true
reportInconsistentConstructor = true
reportOverlappingOverload = true
reportConstantRedefinition = true
reportImportCycles = true
reportUnusedImport = true
reportUnusedClass = true
reportUnusedFunction = true
reportUnusedVariable = true
reportDuplicateImport = true
# ============================================================================
# Black Configuration (Code Formatter)
# ============================================================================
[tool.black]
line-length = 100
target-version = ['py312']
include = '\\.pyi?$'
extend-exclude = '''
/(
# directories
\.eggs
| \.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| build
| dist
)/
'''
# ============================================================================
# isort Configuration (Import Sorting)
# ============================================================================
[tool.isort]
profile = "black"
line_length = 100
multi_line_output = 3
include_trailing_comma = true
force_grid_wrap = 0
use_parentheses = true
ensure_newline_before_comments = true
known_first_party = ["strix"]
known_third_party = ["pydantic", "litellm"]
# ============================================================================
# Bandit Configuration (Security Linting)
# ============================================================================
[tool.bandit]
# Tests are covered by ruff's flake8-bandit rules (see per-file-ignores above),
# which is where fixture tokens and loopback URL opens are already waived.
exclude_dirs = ["docs", "build", "dist", "tests"]
skips = ["B101", "B601", "B404", "B603", "B607"] # Skip assert, shell injection, subprocess import and partial path checks
severity = "medium"