Ctrl K
ComunidadGuías…

pyproject.toml

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
[project]
name = "soc-ai"
version = "1.6.0"
description = "Self-hosted, privacy-respecting LLM-powered triage assistant for Security Onion"
readme = "README.md"
requires-python = ">=3.12,<3.13"
license = { text = "Apache-2.0" }
authors = [{ name = "soc-ai contributors" }]
keywords = ["security-onion", "soc", "llm", "siem", "mcp", "triage"]
classifiers = [
    "Development Status :: 3 - Alpha",
    "License :: OSI Approved :: Apache Software License",
    "Programming Language :: Python :: 3.12",
    "Topic :: Security",
    "Framework :: FastAPI",
    "Typing :: Typed",
]

dependencies = [
    # web framework
    "fastapi>=0.115.0",
    "uvicorn[standard]>=0.32.0",
    "sse-starlette>=2.1.0",
    "python-multipart>=0.0.18",
    # data validation / config
    "pydantic>=2.9.0",
    "pydantic-settings>=2.6.0",
    # agent + MCP
    # pydantic-ai went 1.0 well before this floor mattered; the resolved
    # client is 1.91.x. Pin the major so a fresh resolve can't silently
    # cross into a future breaking 2.x.
    # The slim distribution with only the OpenAI extra: the agent talks to
    # LiteLLM and the Oracle through the OpenAI-compatible client and imports
    # nothing else from the provider set. The full `pydantic-ai` meta-package
    # pulls every provider SDK it supports, plus Temporal and Logfire, into
    # the image and the lockfile: ~80 packages nothing here imports, and where
    # the advisory-flagged transitive pins (pyasn1, google-auth, authlib)
    # came from.
    "pydantic-ai-slim[openai]>=1.0,<2",
    # mcp 2.x is a split package (mcp + mcp-types) that removes
    # `mcp.server.fastmcp`: the module is a stub that raises ModuleNotFoundError
    # because FastMCP became `mcp.server.mcpserver.MCPServer`. That is the one
    # import soc_ai.mcp_server makes, so cap the major here as well; a bare
    # `uv lock --upgrade` otherwise takes the MCP server down with no diff
    # in this file to review.
    "mcp>=1.1.0,<2",
    # HTTP / search clients
    "httpx>=0.27.0",
    "geoip2>=4.8.0",
    # MUST track the SO grid's ES major. SO ships Elasticsearch 8.x; a 9.x
    # client refuses to talk to an 8.x cluster (compatible-with header
    # rejection) and 9.x also drops the `body=` search/index kwarg this
    # client still uses. Cap below 9 until the grid itself moves to ES 9.
    "elasticsearch[async]>=8.16.0,<9",
    # OQL grammar
    "lark>=1.2.0",
    # YAML parsing
    "pyyaml>=6.0.2",
    # logging
    "structlog>=24.4.0",
    "sqlalchemy[asyncio]>=2.0.36",
    "aiosqlite>=0.20",
    "alembic>=1.14",
    "bcrypt>=4.2",
    "jinja2>=3.1",
    "python-ulid>=2.7",
    "dpkt>=1.9.8",
    # Fernet encryption for secret config overrides at rest (Danger Zone).
    "cryptography>=43",
    # Semver comparison for the About page's opt-in GitHub update check
    # (soc_ai.webui.updates). Ubiquitous + already resolved transitively; declared
    # here because updates.py imports it directly.
    "packaging>=24.0",
]

[project.optional-dependencies]
# The PostgreSQL store (SOC_AI_DATABASE_URL). SQLite needs nothing extra.
# The container image installs it, so the compose `postgres` profile works.
postgres = ["asyncpg>=0.30"]
# The tier 3 estate model (soc_ai/hunting/estate_model): peer groups by
# clustering and an estate outlier score per host. The container image
# installs it. The app imports it only when the estate model runs, and the
# job logs one line and does nothing when the extra is absent. The major is
# capped like the other wide dependencies here: a fresh resolve must not cross
# into a breaking 2.x of scikit-learn or a 3.x of numpy with no diff to review.
ml = ["scikit-learn>=1.5,<2", "numpy>=2.0,<3"]

[project.urls]
Homepage = "https://github.com/nuk3s/soc-ai"
Repository = "https://github.com/nuk3s/soc-ai"
Issues = "https://github.com/nuk3s/soc-ai/issues"
Changelog = "https://github.com/nuk3s/soc-ai/blob/main/CHANGELOG.md"

[dependency-groups]
dev = [
    "pytest>=8.3.0",
    "pytest-asyncio>=0.24.0",
    "pytest-cov>=6.0.0",
    "respx>=0.21.0",
    "ruff>=0.8.0",
    "mypy>=1.13.0",
    "playwright>=1.59.0",
    "pytest-playwright>=0.7.2",
    "pre-commit>=3.8.0",
    "pytest-xdist>=3.6",
]
docs = [
    # Documentation site (Material for MkDocs). Build/serve with:
    #   uv run --group docs mkdocs serve
    #   uv run --group docs mkdocs build --strict
    "mkdocs-material>=9.5",
]

[project.scripts]
soc-ai = "soc_ai.cli:main"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["soc_ai"]

[tool.ruff]
line-length = 100
target-version = "py312"
src = ["soc_ai", "tests"]

[tool.ruff.lint]
select = [
    "E", "F", "W",  # pycodestyle / pyflakes
    "I",            # isort
    "B",            # bugbear
    "UP",           # pyupgrade
    "S",            # bandit (security)
    "ASYNC",        # async-correctness
    "RUF",          # ruff-specific
    "PL",           # pylint
    "PT",           # pytest-style
    "SIM",          # simplify
    "TCH",          # type-checking imports
]
ignore = [
    "PLR0913",      # too many arguments - security tools have wide signatures
    "PLR0911",      # too many return statements
    "S101",         # asserts ok in tests (per-file rule below covers tests)
    "TC001",        # type-checking blocks for app imports - causes runtime gaps
    "TC002",        # type-checking blocks for third-party imports - same
    "TC003",        # type-checking blocks for stdlib imports - same
    "PLR2004",      # magic-value-comparison - status codes etc are self-documenting
    "B008",         # Depends() in defaults is FastAPI's documented idiom
    "BLE001",       # bare except is sometimes the right call (top-level SSE wrapper)
]

[tool.ruff.lint.pylint]
max-branches = 20

[tool.ruff.lint.per-file-ignores]
# Tests favour readability over strict style: broad pytest.raises, compound
# asserts, and en-dashes in comments/strings are fine in test code.
"tests/**" = ["S", "PLR2004", "ASYNC230", "PLR0915", "PLC0415", "RUF001", "RUF003", "PT011", "PT018"]
"scripts/**" = ["S", "PLR2004", "PLC0415", "E501"]
# `×` is intentional in the report headings (cross-tab tables) and
# in meta_analysis size estimates (".. × 5K tokens").
"soc_ai/eval/report.py" = ["RUF001", "RUF003"]
"soc_ai/eval/meta_analysis.py" = ["RUF002", "RUF003"]
# `×` is the intentional "fired N× times" count multiplier in the
# detection-tuning reason/summary strings + docs (same as the webui count badge).
"soc_ai/webui/detection_tuning.py" = ["RUF001", "RUF002", "RUF003"]
"soc_ai/tools/rule_tuning.py" = ["RUF001", "RUF002", "RUF003"]
"soc_ai/tools/tuning_heuristic.py" = ["RUF001", "RUF002", "RUF003"]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"

[tool.mypy]
python_version = "3.12"
strict = true
warn_unused_configs = true
warn_unused_ignores = true
warn_redundant_casts = true
warn_no_return = true
warn_unreachable = true
strict_equality = true
files = ["soc_ai"]

# Third-party packages without bundled type stubs. PyYAML ships no inline
# types and we don't vendor types-PyYAML; the YAML we load (blocklists,
# synth loader) is validated downstream, so treating it as untyped here is
# safe and keeps strict mode on everywhere else.
[[tool.mypy.overrides]]
module = ["yaml"]
ignore_missing_imports = true

# dpkt ships no py.typed and no stub package; it is a pure-Python decode
# library with no C surface — treating it as untyped is safe.
[[tool.mypy.overrides]]
module = ["dpkt"]
ignore_missing_imports = true

# scikit-learn ships no py.typed and no stub package. numpy and scipy ship
# types, but all three come from the optional `ml` extra, and a checkout
# without the extra must still type-check. The estate model converts every
# value it returns to a builtin type, so an untyped import loses nothing.
[[tool.mypy.overrides]]
module = ["sklearn.*", "numpy", "numpy.*", "scipy.*"]
ignore_missing_imports = true

[tool.pytest.ini_options]
asyncio_mode = "auto"
# `--ignore=tests/browser` keeps the default coverage-gated run from collecting
# the Playwright E2E smoke: it needs the seeded demo stack + chromium and would
# tank coverage. It runs in a DEDICATED CI job (`browser-smoke`) via `-m browser`.
# `--ignore=tests/scale` does the same for the scale harness. It runs a
# 2,000-host estate for minutes, in its own CI job (`scale`) via `-m scale`.
addopts = "-ra --strict-markers --strict-config --cov=soc_ai --cov-report=term-missing --cov-fail-under=80 --ignore=tests/browser --ignore=tests/scale"
testpaths = ["tests"]
markers = [
    "slow: marks tests as slow",
    "integration: requires a live SO grid (skipped in CI)",
    "browser: marks browser/E2E smoke tests (own CI job; needs the demo stack + chromium)",
    "scale: marks the scale harness budget tests (own CI job; a synthetic estate of 2,000 hosts)",
    "postgres: store tests that also run on PostgreSQL when SOC_AI_TEST_DATABASE_URL is set",
    "sqlite_only: reads SQLite internals; the PostgreSQL run skips it",
]

[tool.coverage.run]
source = ["soc_ai"]
branch = true

[tool.coverage.report]
exclude_lines = [
    "pragma: no cover",
    "if TYPE_CHECKING:",
    "raise NotImplementedError",
    "\\.\\.\\.",
]