QONTO-WP-0004: security hardening and scale-to-zero facade workplan
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>
This commit is contained in:
parent
3faf1fed71
commit
f1a696766b
1 changed files with 186 additions and 0 deletions
186
workplans/QONTO-WP-0004-security-hardening-and-scale-to-zero.md
Normal file
186
workplans/QONTO-WP-0004-security-hardening-and-scale-to-zero.md
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
---
|
||||
id: QONTO-WP-0004
|
||||
type: workplan
|
||||
title: "Security hardening and scale-to-zero facade for internet exposure"
|
||||
domain: infotech
|
||||
repo: qonto-assistant
|
||||
status: active
|
||||
owner: claude
|
||||
topic_slug: the-custodian
|
||||
created: "2026-07-23"
|
||||
updated: "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
|
||||
|
||||
```task
|
||||
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
|
||||
|
||||
```task
|
||||
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; `compileall` clean.
|
||||
|
||||
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
|
||||
|
||||
```task
|
||||
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
|
||||
|
||||
```task
|
||||
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
|
||||
|
||||
```task
|
||||
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
|
||||
|
||||
```task
|
||||
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
|
||||
|
||||
```task
|
||||
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`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue