Auth previously used the shared IRIS_TOKEN as the security principal: a leaked token meant access to all devices, and a compromised device could not be isolated. Gateway: - pairing.py: devices.token column (in-place migration) + revoked denylist table; issue_token (idempotent, 64 hex), token_for, reissue_token, revoke/unrevoke/is_revoked/list_revoked. The token never leaks into device dicts (push fan-out / listings). - http_server.py: auth accepts the shared token (bootstrap/legacy) OR the device's own token (both constant-time); a revoked device_id is rejected with 401 before either comparison. On SSE open (pairing) the per-device token is minted and returned in hello.ack. - protocol.py: hello_ack(..., device_token). - adapter.py: setup flow (hermes gateway setup -> Iris) now offers 'Remove a paired device?' on an existing setup: numbered select menu (last option = exit the removal loop), confirmation, back to the menu for further removals. - tools/iris_devices.py: operator CLI (list / revoke / unrevoke / reissue), stdlib only. App: - SecureStore.deviceToken (Android: EncryptedSharedPreferences; Desktop: second keyring slot iris-device-token / device_token.enc). - HelloAckPayload.deviceToken; GatewayClient stores it on hello and presents it instead of the shared token from then on (live provider in HttpGateway); savePairing/clear wipe it for re-pairing. Docs: 09 §9.3 stretch -> implemented (revocation semantics, both control surfaces), 04 hello.ack example, frames.schema.json, M7 row 13. Tests: 8 new Python tests (issuance, acceptance, revocation, isolation, unrevoke, registry unit x2, setup-flow menu) - 94/94 pass; 2 new Kotlin wire tests - green. Live-verified against a running gateway (hello.ack token matches devices.db; revoke -> 401 even with shared token; unrevoke -> 200; setup TUI both paths).
49 lines
1.7 KiB
TOML
49 lines
1.7 KiB
TOML
# Lint config for the android gateway plugin.
|
|
#
|
|
# Run from the repo root (uses the hermes-agent venv's ruff):
|
|
# hermes-agent/.venv/bin/python -m ruff check gateway-plugin
|
|
#
|
|
# The rule set is deliberately broad (pycodestyle, pyflakes, isort, pyupgrade,
|
|
# bugbear, flake8-simplify, pylint, return, comprehensions). Thresholds below
|
|
# reflect the plugin's real shape: it is a single large dispatch surface
|
|
# (adapter.py) plus a wire-protocol layer (protocol.py) whose frame builders
|
|
# mirror the schema, so the complexity ceilings are set just above the current
|
|
# maxima rather than an idealized small-function target.
|
|
|
|
line-length = 100
|
|
|
|
[lint]
|
|
select = [
|
|
"E", # pycodestyle errors
|
|
"W", # pycodestyle warnings
|
|
"F", # pyflakes
|
|
"I", # isort
|
|
"UP", # pyupgrade
|
|
"B", # flake8-bugbear
|
|
"SIM", # flake8-simplify
|
|
"PL", # pylint
|
|
"RET", # flake8-return
|
|
"C4", # flake8-comprehensions
|
|
]
|
|
|
|
# The plugin intentionally defers hermes-runtime imports into function bodies
|
|
# (they are only available once the plugin is loaded inside the gateway, and
|
|
# some are optional/try-imported). Top-level import placement does not apply.
|
|
ignore = ["PLC0415"]
|
|
|
|
[lint.pylint]
|
|
# Current maxima in the codebase: 22 branches, 64 statements, 9 returns,
|
|
# 8 args (protocol.py:252 frame builder is the lone 11-arg outlier, noqa'd).
|
|
max-branches = 24
|
|
max-statements = 70
|
|
max-returns = 9
|
|
max-args = 8
|
|
|
|
[lint.per-file-ignores]
|
|
# The e2e / ws_probe drivers are assertion scripts: scenario numbers and
|
|
# control-flow sprawl are intentional and not worth refactoring.
|
|
"tests/**" = ["PLR2004", "PLR0911", "PLR0912", "PLR0913", "PLR0915", "PLW1510"]
|
|
# The device-admin CLI is a small operator script: argv length checks are
|
|
# its natural shape.
|
|
"tools/**" = ["PLR2004"]
|