feat: move to python 3.14 baseline, patch remaining dependabot alerts

- requires-python >=3.14, pyupgrade --py314-plus, Dockerfile python:3.14-slim
- test matrix 3.14 only
- fastapi ^0.141.1 + starlette 1.6.0 (unblocks 5 starlette alerts)
- python-multipart ^0.0.31 (4 alerts)
- fix TemplateResponse for starlette 1.x request-first signature
- drop teyit hook: abandoned, incompatible with python 3.14
This commit is contained in:
Alexander Myasoedov
2026-08-18 19:14:05 +03:00
parent 11024e6fca
commit 1ef131421e
16 changed files with 37 additions and 56 deletions
-1
View File
@@ -16,7 +16,6 @@ jobs:
strategy:
matrix:
python-version:
- "3.12"
- "3.14"
steps:
- uses: actions/checkout@v3
+2 -6
View File
@@ -6,7 +6,7 @@ repos:
rev: v3.21.2
hooks:
- id: pyupgrade
args: [--py312-plus]
args: [--py314-plus]
- repo: https://github.com/psf/black
rev: 26.3.1
@@ -64,11 +64,7 @@ repos:
rev: v2.6.0
hooks:
- id: pycln
- repo: https://github.com/isidentical/teyit
rev: 0.4.3
hooks:
- id: teyit
language_version: python3.14
- repo: https://github.com/python-poetry/poetry
rev: '2.4.1'
+3 -3
View File
@@ -1,5 +1,5 @@
# Build stage
FROM python:3.12-slim AS builder
FROM python:3.14-slim AS builder
WORKDIR /app
@@ -26,7 +26,7 @@ RUN pip install --upgrade pip setuptools wheel
RUN pip install --no-cache-dir -r requirements.txt
# Runtime stage
FROM python:3.12-slim
FROM python:3.14-slim
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
@@ -35,7 +35,7 @@ ENV PYTHONUNBUFFERED=1
WORKDIR /app
# Copy only the necessary files from the builder stage
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=builder /usr/local/lib/python3.14/site-packages /usr/local/lib/python3.14/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
# Copy application code
+2 -2
View File
@@ -9,7 +9,7 @@ class AttackRuleSeverity(Enum):
HIGH = "high"
@classmethod
def from_string(cls, value: str) -> "AttackRuleSeverity":
def from_string(cls, value: str) -> AttackRuleSeverity:
try:
return cls(value.lower())
except ValueError:
@@ -28,7 +28,7 @@ class AttackRule:
metadata: dict[str, Any] = field(default_factory=dict)
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "AttackRule":
def from_dict(cls, data: dict[str, Any]) -> AttackRule:
severity = AttackRuleSeverity.from_string(data.get("severity", "medium"))
return cls(
name=data["name"],
-2
View File
@@ -1,7 +1,5 @@
"""Utilities to keep cache-to-disk storage in a writable, predictable location."""
from __future__ import annotations
import os
from pathlib import Path
-1
View File
@@ -1,4 +1,3 @@
from __future__ import annotations
import logging
from typing import Any, Protocol
+1 -1
View File
@@ -155,7 +155,7 @@ class LLMSpec(BaseModel):
try:
body_json = json.loads(self.body)
return body_json.get("model", "unknown")
except (json.JSONDecodeError, TypeError):
except json.JSONDecodeError, TypeError:
return "unknown"
@property
+1 -1
View File
@@ -28,7 +28,7 @@ class Scan(BaseModel):
# Set and managed by the backend
secrets: dict[str, str] = Field(default_factory=dict)
def with_secrets(self, secrets) -> "Scan":
def with_secrets(self, secrets) -> Scan:
match secrets:
case dict():
self.secrets.update(secrets)
+4 -4
View File
@@ -66,7 +66,7 @@ MAX_INJECTION_ATTEMPTS = settings_var("fuzzer.max_injection_attempts", 20)
async def generate_prompts(
prompts: list[str] | AsyncGenerator,
) -> AsyncGenerator[str, None]:
) -> AsyncGenerator[str]:
"""
Asynchronously generates and yields individual prompts.
@@ -227,7 +227,7 @@ async def scan_module(
optimize: bool = False,
stop_event: asyncio.Event | None = None,
token_counter: dict[str, int] | None = None,
) -> AsyncGenerator[dict[str, Any], None]:
) -> AsyncGenerator[dict[str, Any]]:
"""
Scan a single module.
@@ -390,7 +390,7 @@ async def perform_single_shot_scan(
stop_event: asyncio.Event | None = None,
secrets: dict[str, str] | None = None,
inline_datasets: list[dict[str, Any]] | None = None,
) -> AsyncGenerator[str, None]:
) -> AsyncGenerator[str]:
"""
Perform a standard security scan using a given request factory.
@@ -491,7 +491,7 @@ async def perform_many_shot_scan(
probe_frequency: float = 0.2,
max_ctx_length: int = 10_000,
secrets: dict[str, str] | None = None,
) -> AsyncGenerator[str, None]:
) -> AsyncGenerator[str]:
"""
Perform a multi-step security scan with probe injection.
@@ -86,7 +86,7 @@ class HybridRefusalClassifier:
detector: RefusalDetector,
weight: float = 1.0,
name: str | None = None,
) -> "HybridRefusalClassifier":
) -> HybridRefusalClassifier:
"""Add a detection method with specified weight.
Args:
@@ -4,8 +4,6 @@ Provides a small, dependency-free detector for responses that may contain
sensitive personal or credential material.
"""
from __future__ import annotations
import re
from dataclasses import dataclass
from re import Pattern
@@ -27,8 +27,6 @@ Example configuration::
threshold = 3
"""
from __future__ import annotations
import importlib
from collections import OrderedDict
from collections.abc import Callable, Mapping
@@ -5,8 +5,6 @@ to break out of Docker/Kubernetes container isolation (capability probing,
mount inspection, kernel/exploit reconnaissance).
"""
from __future__ import annotations
import re
from dataclasses import dataclass
from re import Pattern
+1 -1
View File
@@ -70,7 +70,7 @@ def get_static_file(filepath: Path, content_type: str | None = None) -> FileResp
@router.get("/", response_class=HTMLResponse)
async def root(request: Request) -> Response:
"""Serve the main index.html template."""
return templates.TemplateResponse("index.html", {"request": request})
return templates.TemplateResponse(request, "index.html")
@router.get("/main.js")
Generated
+19 -24
View File
@@ -148,7 +148,6 @@ attrs = ">=17.3.0"
frozenlist = ">=1.1.1"
multidict = ">=4.5,<7.0"
propcache = ">=0.2.0"
typing_extensions = {version = ">=4.4", markers = "python_version < \"3.13\""}
yarl = ">=1.17.0,<2.0"
[package.extras]
@@ -168,7 +167,6 @@ files = [
[package.dependencies]
frozenlist = ">=1.1.0"
typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""}
[[package]]
name = "annotated-doc"
@@ -239,7 +237,6 @@ files = [
[package.dependencies]
idna = ">=2.8"
sniffio = ">=1.1"
typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
[package.extras]
trio = ["trio (>=0.26.1)"]
@@ -987,26 +984,27 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth
[[package]]
name = "fastapi"
version = "0.122.0"
version = "0.141.1"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
optional = false
python-versions = ">=3.8"
python-versions = ">=3.10"
groups = ["main"]
files = [
{file = "fastapi-0.122.0-py3-none-any.whl", hash = "sha256:a456e8915dfc6c8914a50d9651133bd47ec96d331c5b44600baa635538a30d67"},
{file = "fastapi-0.122.0.tar.gz", hash = "sha256:cd9b5352031f93773228af8b4c443eedc2ac2aa74b27780387b853c3726fb94b"},
{file = "fastapi-0.141.1-py3-none-any.whl", hash = "sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3"},
{file = "fastapi-0.141.1.tar.gz", hash = "sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1"},
]
[package.dependencies]
annotated-doc = ">=0.0.2"
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
starlette = ">=0.40.0,<0.51.0"
pydantic = ">=2.9.0"
starlette = ">=0.46.0"
typing-extensions = ">=4.8.0"
typing-inspection = ">=0.4.2"
[package.extras]
all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[standard-no-fastapi-cloud-cli] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.32)", "httpx (>=0.23.0,<1.0.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "uvicorn[standard] (>=0.12.0)"]
standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.32)", "fastar (>=0.9.0)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[standard-no-fastapi-cloud-cli] (>=0.0.32)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
[[package]]
name = "fastjsonschema"
@@ -3878,7 +3876,6 @@ files = [
[package.dependencies]
pytest = ">=8.2,<10"
typing-extensions = {version = ">=4.12", markers = "python_version < \"3.13\""}
[package.extras]
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"]
@@ -3960,14 +3957,14 @@ testing = ["covdefaults (>=2.3)", "coverage (>=7.5.4)", "pytest (>=8.3.5)", "pyt
[[package]]
name = "python-multipart"
version = "0.0.27"
version = "0.0.31"
description = "A streaming multipart parser for Python"
optional = false
python-versions = ">=3.10"
groups = ["main"]
files = [
{file = "python_multipart-0.0.27-py3-none-any.whl", hash = "sha256:6fccfad17a27334bd0193681b369f476eda3409f17381a2d65aa7df3f7275645"},
{file = "python_multipart-0.0.27.tar.gz", hash = "sha256:9870a6a8c5a20a5bf4f07c017bd1489006ff8836cff097b6933355ee2b49b602"},
{file = "python_multipart-0.0.31-py3-none-any.whl", hash = "sha256:8408153d68a9773291fc1da39a8b85a50044bddbabd2dd72e9229776b7b15e28"},
{file = "python_multipart-0.0.31.tar.gz", hash = "sha256:fc631183bb13e56db3158a4909908dfb2e23565286744e798241e63750e5d680"},
]
[[package]]
@@ -4266,7 +4263,6 @@ files = [
[package.dependencies]
attrs = ">=22.2.0"
rpds-py = ">=0.7.0"
typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""}
[[package]]
name = "requests"
@@ -4761,22 +4757,21 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
[[package]]
name = "starlette"
version = "0.50.0"
version = "1.6.0"
description = "The little ASGI library that shines."
optional = false
python-versions = ">=3.10"
groups = ["main"]
files = [
{file = "starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca"},
{file = "starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca"},
{file = "starlette-1.6.0-py3-none-any.whl", hash = "sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c"},
{file = "starlette-1.6.0.tar.gz", hash = "sha256:d4e3ac5e546444960c710297a3c9fc3f7ebae1b7e963f3d36173b49da535be9b"},
]
[package.dependencies]
anyio = ">=3.6.2,<5"
typing-extensions = {version = ">=4.10.0", markers = "python_version < \"3.13\""}
[package.extras]
full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"]
full = ["httpx (>=0.27.0,<0.29.0)", "httpx2 (>=2.0.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"]
[[package]]
name = "tabulate"
@@ -5399,5 +5394,5 @@ propcache = ">=0.2.1"
[metadata]
lock-version = "2.1"
python-versions = ">=3.12,<4.0"
content-hash = "dc2edc6c72835e82a8954273da5ae9e1c231686ff73886a9f962410a384b3f1f"
python-versions = ">=3.14,<4.0"
content-hash = "c49467950cab4b9cc0f103ef570770421926fa223ecc155620d9863a9677407b"
+3 -3
View File
@@ -6,7 +6,7 @@ authors = [{ name = "Alexander Miasoiedov", email = "msoedov@gmail.com" }]
maintainers = [{ name = "Alexander Miasoiedov", email = "msoedov@gmail.com" }]
license = "Apache-2.0"
readme = "Readme.md"
requires-python = ">=3.12,<4.0"
requires-python = ">=3.14,<4.0"
keywords = [
"LLM vulnerability scanner",
"llm security",
@@ -32,7 +32,7 @@ agentic_security = "agentic_security.__main__:main"
packages = [{ include = "agentic_security", from = "." }]
[tool.poetry.dependencies]
fastapi = "^0.122.0"
fastapi = "^0.141.1"
uvicorn = "^0.38.0"
fire = "0.7.1"
loguru = "^0.7.3"
@@ -48,7 +48,7 @@ scikit-optimize = "^0.10.2"
scikit-learn = "^1.7.2"
numpy = ">=1.24.3,<3.0.0"
jinja2 = "^3.1.4"
python-multipart = "^0.0.27"
python-multipart = "^0.0.31"
tomli = "^2.3.0"
rich = "^14.2.0"
gTTS = "^2.5.4"