chore(types): enable warn_return_any and cast all no-any-return sites

Turn on mypy warn_return_any (pyproject) and resolve the 84 resulting
[no-any-return] errors across 43 files with typing.cast() at the return
sites — runtime no-ops that make the declared return type explicit where a
dependency (SQLAlchemy scalar/first/one, httpx .json(), subprocess, docker
SDK) hands back Any. No behavior change: no DTO/table field types altered, no
validation/coercion calls added, every cast reflects the true runtime type.

Locks in return-type strictness so the class of bug where a function silently
widens to Any can't regress. mypy decnet/ clean; adversarially verified
behavior-preserving (84 casts 1:1 with prior returns).

Bump tornado 6.5.5 -> 6.5.7 (CVE-2026-49854, transitive via snakeviz).
This commit is contained in:
2026-06-12 18:21:22 -04:00
parent 337520c7ad
commit 721122a7ef
42 changed files with 128 additions and 124 deletions

View File

@@ -6,7 +6,7 @@ Repository factory — selects a :class:`BaseRepository` implementation based on
from __future__ import annotations
import os
from typing import Any
from typing import Any, cast
from decnet.web.db.repository import BaseRepository
@@ -32,4 +32,4 @@ def get_repository(**kwargs: Any) -> BaseRepository:
raise ValueError(f"Unsupported database type: {db_type}")
from decnet.telemetry import wrap_repository
return wrap_repository(repo)
return cast(BaseRepository, wrap_repository(repo))

View File

@@ -18,7 +18,7 @@ import os
import orjson
import uuid
from typing import Any, Optional, List
from typing import Any, Optional, List, cast
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker
@@ -183,7 +183,7 @@ class SQLModelRepository(
result = await session.execute(statement)
state = result.scalar_one_or_none()
if state:
return json.loads(state.value)
return cast(dict[str, Any], json.loads(state.value))
return None
async def set_state(self, key: str, value: Any) -> None: # noqa: ANN401

View File

@@ -10,7 +10,7 @@ from __future__ import annotations
import uuid as _uuid
from datetime import datetime, timezone
from typing import Any, Optional
from typing import Any, Optional, cast
from sqlalchemy import desc, or_, select
from sqlmodel import col
@@ -44,7 +44,7 @@ class AttackerIntelMixin(_MixinBase):
row_uuid = _uuid.uuid4().hex
session.add(AttackerIntel(uuid=row_uuid, **data))
await session.commit()
return row_uuid
return cast(str, row_uuid)
async def get_attacker_intel_row_by_uuid(
self,
@@ -54,7 +54,7 @@ class AttackerIntelMixin(_MixinBase):
result = await session.execute(
select(AttackerIntel).where(AttackerIntel.attacker_uuid == uuid)
)
return result.scalar_one_or_none()
return cast(Optional[AttackerIntel], result.scalar_one_or_none())
async def get_attacker_intel_by_uuid(
self,
@@ -67,7 +67,7 @@ class AttackerIntelMixin(_MixinBase):
row = result.scalar_one_or_none()
if not row:
return None
return row.model_dump(mode="json")
return cast(dict[str, Any], row.model_dump(mode="json"))
async def get_unenriched_attackers(
self, limit: int = 100,

View File

@@ -10,7 +10,7 @@ from __future__ import annotations
import json
import uuid as _uuid
from typing import Any, List, Optional
from typing import Any, List, Optional, cast
from sqlalchemy import desc, func, outerjoin, select
from sqlmodel import col
@@ -47,7 +47,7 @@ class AttackersCoreMixin(_MixinBase):
data = {**data, "uuid": row_uuid}
session.add(Attacker(**data))
await session.commit()
return row_uuid
return cast(str, row_uuid)
async def get_attacker_uuid_by_ip(self, ip: str) -> Optional[str]:
"""Return the :class:`Attacker` UUID for *ip*, or ``None``.
@@ -61,7 +61,7 @@ class AttackersCoreMixin(_MixinBase):
result = await session.execute(
select(col(Attacker.uuid)).where(Attacker.ip == ip)
)
return result.scalar_one_or_none()
return cast(Optional[str], result.scalar_one_or_none())
async def get_attacker_by_uuid(self, uuid: str) -> Optional[dict[str, Any]]:
async with self._session() as session:

View File

@@ -21,7 +21,7 @@ from __future__ import annotations
import uuid as _uuid
from datetime import datetime, timezone
from typing import Any, Optional
from typing import Any, Optional, cast
from sqlalchemy import func, select
from sqlmodel import col
@@ -66,7 +66,7 @@ class AttributionMixin(_MixinBase):
if attacker_row is None:
return None
if attacker_row.identity_id:
return attacker_row.identity_id
return cast(str, attacker_row.identity_id)
new_uuid = _uuid.uuid4().hex
now = datetime.now(timezone.utc)
session.add(

View File

@@ -9,7 +9,7 @@ the reads.
from __future__ import annotations
from datetime import datetime, timezone
from typing import Any, Optional
from typing import Any, Optional, cast
from sqlalchemy import desc, func, select, update
from sqlmodel import col
@@ -36,9 +36,9 @@ class CampaignsMixin(_MixinBase):
if campaign is None:
return None
if campaign.merged_into_uuid is None:
return campaign.model_dump(mode="json")
return cast(dict[str, Any], campaign.model_dump(mode="json"))
current_uuid = campaign.merged_into_uuid
return campaign.model_dump(mode="json")
return cast(dict[str, Any], campaign.model_dump(mode="json"))
async def list_campaigns(
self, limit: int = 50, offset: int = 0,

View File

@@ -4,7 +4,7 @@ from __future__ import annotations
import json
from datetime import datetime, timezone
from typing import Any, Optional
from typing import Any, Optional, cast
from sqlalchemy import desc, func, select, update
@@ -26,7 +26,7 @@ class CanaryMixin(_MixinBase):
)
row = existing.scalar_one_or_none()
if row:
return row.model_dump(mode="json")
return cast(dict[str, Any], row.model_dump(mode="json"))
row = CanaryBlob(**data)
session.add(row)
await session.commit()
@@ -155,7 +155,7 @@ class CanaryMixin(_MixinBase):
.values(state=state, last_error=last_error)
)
await session.commit()
return result.rowcount > 0
return cast(bool, result.rowcount > 0)
async def record_canary_trigger(self, data: dict[str, Any]) -> str:
# Persist the trigger row + bump the token's counters in the same
@@ -204,4 +204,4 @@ class CanaryMixin(_MixinBase):
.values(attacker_id=attacker_id)
)
await session.commit()
return result.rowcount > 0
return cast(bool, result.rowcount > 0)

View File

@@ -4,7 +4,7 @@ from __future__ import annotations
import json
from datetime import datetime, timezone
from typing import Any, List, Optional
from typing import Any, List, Optional, cast
from sqlalchemy import desc, func, or_, select, update
from sqlalchemy.exc import IntegrityError
@@ -67,7 +67,7 @@ class CredentialsCoreMixin(_MixinBase):
existing.outcome = payload["outcome"]
session.add(existing)
await session.commit()
return existing.id
return cast(int, existing.id)
row = Credential(
attacker_ip=payload["attacker_ip"],
decky_name=payload["decky_name"],
@@ -103,7 +103,7 @@ class CredentialsCoreMixin(_MixinBase):
existing2.outcome = payload["outcome"]
session2.add(existing2)
await session2.commit()
return existing2.id
return cast(int, existing2.id)
await session.refresh(row)
return row.id # type: ignore[return-value]

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
import json
import uuid as _uuid
from datetime import datetime, timezone
from typing import Any, List, Optional
from typing import Any, List, Optional, cast
from sqlalchemy import desc, func, select
from sqlmodel import col
@@ -137,7 +137,7 @@ class CredentialReuseMixin(_MixinBase):
d = existing.model_dump(mode="json")
d["inserted"] = False
d["changed"] = changed
return d
return cast(dict[str, Any], d)
async def find_credential_reuse_candidates(
self, min_targets: int = 2
@@ -236,7 +236,7 @@ class CredentialReuseMixin(_MixinBase):
except (json.JSONDecodeError, TypeError):
d[key] = []
await self._enrich_with_secret(session, [d])
return d
return cast(dict[str, Any], d)
@staticmethod
async def _enrich_with_secret(

View File

@@ -9,7 +9,7 @@ the reads.
from __future__ import annotations
from datetime import datetime, timezone
from typing import Any, Optional
from typing import Any, Optional, cast
from sqlalchemy import desc, func, select, update
from sqlmodel import col
@@ -42,10 +42,10 @@ class IdentitiesMixin(_MixinBase):
if identity is None:
return None
if identity.merged_into_uuid is None:
return identity.model_dump(mode="json")
return cast(dict[str, Any], identity.model_dump(mode="json"))
current_uuid = identity.merged_into_uuid
# Hit the hop cap — surface what we have rather than recurse.
return identity.model_dump(mode="json")
return cast(dict[str, Any], identity.model_dump(mode="json"))
async def list_identities(
self, limit: int = 50, offset: int = 0,

View File

@@ -21,7 +21,7 @@ not validate values — that happens at construction time by the BEHAVE
from __future__ import annotations
import uuid as _uuid
from typing import Any, Optional
from typing import Any, Optional, cast
from sqlalchemy import desc, func, select
from sqlmodel import col
@@ -85,7 +85,7 @@ class ObservationsMixin(_MixinBase):
row_data = {**data, "id": row_id}
session.add(ObservationRow(**row_data))
await session.commit()
return row_id
return cast(str, row_id)
async def latest_observation_per_primitive(
self, attacker_uuid: str,
@@ -178,7 +178,7 @@ class ObservationsMixin(_MixinBase):
row = (await session.execute(stmt)).scalar_one_or_none()
if not row:
return None
return row.model_dump(mode="json")
return cast(dict[str, Any], row.model_dump(mode="json"))
async def observations_for_identity_primitive(
self, identity_uuid: str, primitive: str,

View File

@@ -13,7 +13,7 @@ caller is upgrading ``False/None`` to ``True``.
from __future__ import annotations
from datetime import datetime, timezone
from typing import Optional
from typing import Optional, cast
from sqlalchemy import select
@@ -106,4 +106,4 @@ class ObservedAttachmentsMixin(_MixinBase):
row.mal_hash_match_at = now
session.add(row)
await session.commit()
return row.uuid
return cast(str, row.uuid)

View File

@@ -3,7 +3,7 @@
from __future__ import annotations
from datetime import datetime, timedelta, timezone
from typing import Any, Optional
from typing import Any, Optional, cast
from sqlalchemy import desc, func, select, update
@@ -92,7 +92,7 @@ class RealismMixin(_MixinBase):
row = result.scalars().first()
if row is None:
return None
return row.model_dump(mode="json")
return cast(dict[str, Any], row.model_dump(mode="json"))
async def get_realism_config(
self, key: str,
@@ -103,7 +103,7 @@ class RealismMixin(_MixinBase):
row = result.scalars().first()
if row is None:
return None
return row.model_dump(mode="json")
return cast(dict[str, Any], row.model_dump(mode="json"))
async def set_realism_config(
self, key: str, value: str,
@@ -157,4 +157,4 @@ class RealismMixin(_MixinBase):
row = result.scalars().first()
if row is None:
return None
return row.model_dump(mode="json")
return cast(dict[str, Any], row.model_dump(mode="json"))

View File

@@ -5,7 +5,7 @@ from __future__ import annotations
import json
import uuid
from datetime import datetime, timezone
from typing import Any, Optional
from typing import Any, Optional, cast
from sqlalchemy import select
@@ -47,7 +47,7 @@ class TarpitMixin(_MixinBase):
return None
d = row.model_dump(mode="json")
d["ports"] = json.loads(d["ports"])
return d
return cast(dict[str, Any], d)
async def delete_tarpit_rule(self, decky_name: str) -> bool:
async with self._session() as session:

View File

@@ -3,7 +3,7 @@
from __future__ import annotations
from datetime import datetime, timezone
from typing import Any, Optional
from typing import Any, Optional, cast
import orjson
from sqlalchemy import asc, desc, select, text
@@ -102,7 +102,7 @@ class TopologyMutationsMixin(_MixinBase):
_ = now
if row is None:
return None
return row.model_dump(mode="json")
return cast(dict[str, Any], row.model_dump(mode="json"))
async def mark_mutation_applied(self, mutation_id: str) -> None:
async with self._session() as session:

View File

@@ -3,7 +3,7 @@
from __future__ import annotations
from datetime import datetime, timezone
from typing import Any, Optional
from typing import Any, Optional, cast
from sqlalchemy import select, update
from sqlmodel import col
@@ -65,7 +65,7 @@ class WebhooksMixin(_MixinBase):
.values(**patch)
)
await session.commit()
return result.rowcount > 0
return cast(bool, result.rowcount > 0)
async def delete_webhook_subscription(self, uuid: str) -> bool:
async with self._session() as session: