9.4 KiB
| id | type | title | domain | repo | status | owner | topic_slug | created | updated | state_hub_workstream_id |
|---|---|---|---|---|---|---|---|---|---|---|
| QONTO-WP-0004 | workplan | Security hardening and scale-to-zero facade for internet exposure | infotech | qonto-assistant | active | claude | the-custodian | 2026-07-23 | 2026-07-23 | 81a476ac-06d5-4e98-a2ed-1518b16863aa |
Security hardening and scale-to-zero facade for internet exposure
Implements docs/SecurityPractice.md, written ahead of deploying
qonto-assistant to railiance01. This is the first fleet service that
must be reachable by clients outside the cluster (laptop-based agent
harness sessions) while holding a real company bank credential — a risk
class above every other agent-facing service shipped so far.
Some of this workplan's scope depends on systems this repo does not own
(key-cape, flex-auth, tenant-engine, Railiance placement). Those
dependencies are tracked explicitly per task rather than assumed away; the
kings-guard-owned portion is tracked as a separate intake against
KG-WP-0002, not duplicated here.
Task: Security Genome record and audit-stream review
id: QONTO-WP-0004-T01
status: done
priority: high
state_hub_task_id: "e93b2027-dd28-464b-8ea3-f7540f91caad"
2026-07-23: specs/security-genome.yaml written per
kings-guard/specs/NetKingdomImmuneArchitecture.md §9.1's schema —
capabilities provided/consumed, expected egress (OpenBao + Qonto API only),
data classification, recovery expectations, and explicit tolerances for
today's known-temporary gaps (shared-secret bearer auth, self-asserted actor
claims). Confirmed AuditLogger's existing event shape (actor, capability,
decision, deny_reason, latency, upstream status, policy version) already
matches the Immune Observation contract closely enough that no schema
rework is anticipated once a real consumer exists.
Done when: genome record exists and is reviewed against the blueprint schema; no code changes required for this task.
Task: Deny-escalation lockout
id: QONTO-WP-0004-T02
status: done
priority: high
state_hub_task_id: "3b10dd59-b03f-4d2b-8164-aa2a1c8c06c4"
2026-07-23: src/qonto_assistant/security_watch.py::DenyEscalationTracker
locks out an actor who triggers arg_constraint/credential_exfil policy
denials QONTO_DENY_ESCALATION_THRESHOLD times (default 3) within
QONTO_DENY_ESCALATION_WINDOW_SECONDS (default 60s), for
QONTO_DENY_ESCALATION_LOCKOUT_SECONDS (default 300s). Wired into
CapabilityService._execute ahead of the policy kernel call; on by default
via QONTO_DENY_ESCALATION_ENABLED. Ordinary denies (authz_denied,
tenant_scope, unknown_capability) never count. This needed no external
dependency — it closes a real gap using only what already exists (the audit
stream and rate limiter).
Verified: tests/test_deny_escalation.py (8 new tests: tracker unit tests
- service integration for both the escalating and non-escalating paths);
full suite
pytest→ 39 passed; REST and MCP smoke scripts both pass against fixtures;compileallclean.
Done when: threshold/window/lockout are configurable, on by default, and proven not to affect ordinary policy-deny traffic.
Task: Replace shared-secret bearer token with key-cape identity
id: QONTO-WP-0004-T03
status: done
priority: high
state_hub_task_id: "f02b1cc0-4afc-44e5-b71d-545a13a3e12b"
2026-07-24: No key-cape-side coordination was actually needed — its
/jwks is a standard RS256 endpoint. KeyCapeTokenVerifier
(src/qonto_assistant/key_cape_auth.py) fetches/caches signing keys over
httpx, validates iss/aud/exp and the IAM Profile v0.3 required claims,
and derives ActorClaims from the token. Wired into
auth.actor_claims_from_headers (the single seam both REST and MCP use); a
verified bearer token now takes precedence over self-asserted X-Actor-*
headers, and can be made mandatory via QONTO_KEY_CAPE_REQUIRED once real
tokens are issued to callers. Off by default (no
QONTO_KEY_CAPE_JWKS_URL set). QONTO_ASSISTANT_MCP_TOKEN remains a
documented local-dev/legacy fallback per docs/mcp-integration.md.
Verified: 13 new tests using a real generated RSA keypair + JWKS served
over httpx.MockTransport (valid/expired/wrong-audience/wrong-issuer/
missing-claim/unknown-key/rotated-key tokens, plus the auth.py precedence
and required-vs-optional paths).
Task: Register and enforce finance.qonto.read in flex-auth
id: QONTO-WP-0004-T04
status: done
priority: high
state_hub_task_id: "fce7bf0e-674a-461f-8f2f-42d7dde3a722"
2026-07-24: Registered qonto-assistant in flex-auth
(flex-auth/examples/qonto-assistant/, modeled directly on
examples/tenant-engine/): one resource type (finance-snapshot), one
action (finance.qonto.read), a Rego policy gating on
resource.system + action + subject.type + tenant match. Tenant
capability-role/plan liveness (VEN/CUS) is deliberately not encoded in
that policy — kept as a separate tenant-engine live-lookup check, per
both repos' policy-scope notes.
LiveAuthorizationGate (src/qonto_assistant/live_authorization.py)
combines FlexAuthCheckClient.is_allowed() (live POST /v1/check) and
TenantEngineClient.active_roles() (live GET /tenants/{id}/roles/live, gated on QONTO_TENANT_ENGINE_REQUIRED_ROLES,
default VEN,CUS) and is wired into CapabilityService._execute ahead of
the internal policy kernel. Both clients fail closed by construction.
Replaces QONTO_ASSISTANT_ENFORCE_SCOPE's cached-claim check as the live
authorization control point; off by default (no QONTO_FLEX_AUTH_URL set).
Verified beyond mocked unit tests: ran a real flex-auth serve loaded with
the registered policy (flex-auth test-policy/load-registry/check all
pass) and a real tenant-engine instance seeded with a VEN grant for
tenant:friendly:binky, and exercised this repo's actual clients against
both live processes over real HTTP — allow for the correct tenant,
live_authz_denied for a mismatched one. 28 new unit tests.
Task: Facade / scale-to-zero activator — design and reference implementation
id: QONTO-WP-0004-T05
status: todo
priority: high
state_hub_task_id: "e8a87292-488d-4871-be3e-59a5b6a38694"
Per docs/SecurityPractice.md §2/§6: a thin, always-on facade is the only
internet-facing component. It authenticates (key-cape) and authorizes
(flex-auth pre-check) before waking the real service, then scales the
backend Deployment 0→1, waits on /v1/health, proxies through, and scales
back to 0 after an idle timeout. qonto-assistant's raw REST/MCP port must
never be bound to a publicly-reachable address in any deployment.
Check whether the target railiance01 cluster already runs Knative Serving
before building a custom activator — if it does, this task becomes
integration (Knative Service manifest + autoscaling annotations) rather
than new code. If not, scope a minimal reference implementation (one
Service, one Deployment, one wake/idle controller) sized to this repo's
narrow surface, not a general-purpose PaaS.
Depends on: a decision on where this component lives (this repo, Railiance, or a new dedicated repo) and Railiance cluster capabilities (Knative or not) — flag both as open questions rather than assuming.
Done when: an unauthenticated request never triggers a backend wake; an authenticated+authorized request gets proxied through after a bounded cold-start; the backend scales back to 0 after the configured idle window.
Task: Isolation placement request to Railiance
id: QONTO-WP-0004-T06
status: done
priority: medium
state_hub_task_id: "7cd52269-ca0a-4b09-ab51-f02ed71604f2"
2026-07-24: Made the request concrete and reviewable (this task's own stated scope — not to implement scheduling itself). Delivered:
deploy/k8s/qonto-assistant/— dedicated namespace (not shared withactivity-core),Deploymentatreplicas: 0(meant to be scaled 0↔1 by the facade, T05),ClusterIP-onlyService, default-denyNetworkPolicy. Modeled onllm-connect's real deployment, tightened perdocs/SecurityPractice.md§7. Verified:kubectl kustomizerenders all six resources cleanly.- CCR-2026-0009 (in
railiance-platform,status: proposed) — a workload-scoped Kubernetes-auth access lane into the existingtenants/binky/qonto-apicredential, since CCR-2026-0008 is human/OIDC admin access only and unusable by a running pod. Mirrors CCR-2026-0003's llm-connect pattern. Paired draftClusterSecretStorealso added. Both validated againstschemas/credential-change-request.schema.yaml. railiance/app.toml— staged-promotion contract,criticality = "critical"(mandatory human approval before Stage 2 exposure and Stage 3 promotion, per the contract's own rule for production-critical workloads). Validated againstrailiance-cluster/schemas/railiance-app.schema.json.
Still pending, explicitly not this task's to resolve: CCR-2026-0009
needs platform-operator + binky-tenant-owner approval; the actual isolation
tier (I1 vs I2) is Railiance's scheduling call once the manifests are
reviewed; whether the facade (T05) co-locates in this namespace is assumed
in networkpolicy.yaml but not yet decided.
Task: Closure review
id: QONTO-WP-0004-T07
status: todo
priority: low
state_hub_task_id: "2a2da206-d182-4a72-aea4-56e58ae74c76"
Close when T03–T06 land or are explicitly deferred with a recorded reason.
T01/T02 already shipped without waiting on external dependencies. Run
statehub fix-consistency.