Tracks implementation of docs/SecurityPractice.md. T01 (Security Genome record) and T02 (deny-escalation lockout) are already done, shipped in the prior commit -- both were dependency-free. T03-T06 depend on key-cape, flex-auth, tenant-engine, and a Railiance placement decision respectively, and are flagged as such rather than assumed completable from this repo alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
7.4 KiB
| id | type | title | domain | repo | status | owner | topic_slug | created | updated |
|---|---|---|---|---|---|---|---|---|---|
| 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 |
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
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
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: todo
priority: high
Verify key-cape-issued IAM Profile tokens at the request boundary (facade
or assistant, depending on where T05 lands the auth check) in place of
QONTO_ASSISTANT_MCP_TOKEN. Derive X-Actor-*-equivalent claims from the
verified token instead of trusting self-asserted headers — closing the gap
docs/mcp-integration.md's auth-model section calls out explicitly.
Depends on: key-cape exposing a client-verifiable token/JWKS surface
this service can validate against — needs coordination with key-cape, not
something this repo can complete alone.
Done when: a request bearing a valid key-cape token is accepted with
claims derived from the token; the shared-secret bearer path is
demoted to fixture/local-dev-only, matching what docs/mcp-integration.md
already documents as the intended boundary.
Task: Register and enforce finance.qonto.read in flex-auth
id: QONTO-WP-0004-T04
status: todo
priority: high
Register finance.qonto.read as a flex-auth resource. Call flex-auth
for a live decision on this resource rather than relying on
QONTO_ASSISTANT_ENFORCE_SCOPE's current cached-claim check — mirroring
tenant-engine's own reasoning that a stale grant is not an acceptable risk
for money-adjacent actions, applied here to read access. Replace the
hardcoded default_tenant_id="binky" assumption in config.py with a live
tenant-engine capability-role + plan lookup (VEN/CUS) so a lapsed
tenant loses read access on the next request, not whenever a cache expires.
Depends on: flex-auth resource registration and a tenant-engine
lookup/cache API this repo can call — both external, not something this
repo can complete alone.
Done when: an unauthorized tenant/role is denied by a live flex-auth
decision, not a locally cached scope check; policy tests cover both the
allow and the newly-live-checked deny path.
Task: Facade / scale-to-zero activator — design and reference implementation
id: QONTO-WP-0004-T05
status: todo
priority: high
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: todo
priority: medium
Per docs/SecurityPractice.md §7: request I1 "Reinforced" (dedicated node
pool / sandboxed runtime) at minimum for the qonto-assistant deployment on
railiance01, given tenant-confidential financial-data classification,
internet reachability via the facade, and sole custody of the bank
credential. I2 "Dedicated" (own namespace/keys) is worth considering; the
placement decision belongs to whoever owns Railiance scheduling for this
workload, not this repo.
Depends on: a Railiance-side placement decision — this task's job is to make the request concrete and reviewable, not to implement scheduling.
Done when: a placement decision is recorded (even if the decision is "I0 for now, revisit before going live") and referenced back into this workplan.
Task: Closure review
id: QONTO-WP-0004-T07
status: todo
priority: low
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.