You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(dropguard): name-convention guard on every customer-data drop + migration-safety CI gate (truehomie D3) (#56)
* feat(dropguard): name-convention guard on every customer-data drop + migration-safety CI gate (truehomie D3)
Layer 2 of the truehomie-db DROP hardening (layer 1 = the guardedDrop audit
chokepoint, PR #50). The chokepoint made every sanctioned drop AUDITABLE;
this makes a MIS-TARGETED drop UNEXECUTABLE:
- internal/dropguard: charset+denylist validation of naming tokens and final
db/user identifiers. Refuses empty/garbage tokens, SQL metacharacters,
system databases (postgres, template0/1, instant_customers,
instant_platform, mongo admin/local/config) and admin roles
(instanode_admin, instant_cust, doadmin, postgres, redis "default").
Deliberately permissive on token SHAPE (uuid, dashless hex, pool-*, e2e
cohorts all pass) so no legitimate legacy deprovision can wedge — this
repo auto-deploys.
- server.guardedDrop: validates the poolident-resolved naming token BEFORE
dispatch; refusal = error + `provisioner.drop.refused` log event +
instant_provisioner_drop_total{outcome="refused"}.
- postgres local Deprovision + cleanupProvisionPartial (non-chokepoint
rollback path), dedicated deprovisionLocal: validate the FINAL constructed
dbName/username via validateDropTargets right before DROP.
- mongo Deprovision: token guard before connect + per-candidate name guard
(refused canonical = hard error; refused legacy candidate = skip).
- redis local/dedicated DELUSER: skip any non-tenant-shaped username
(ACL DELUSER "default" would brick the shared pod).
- scripts/check-migration-safety.sh + ci.yml step: destructive DDL
(DROP TABLE/DATABASE/SCHEMA/COLUMN/ROLE/USER, TRUNCATE, DELETE FROM
without WHERE) in migrations/*.sql fails CI unless the file carries
`-- destructive: acknowledged <reason>`. Self-test fixture suite included.
No behavior change for legitimate deprovision flows (regression tests assert
uuid/dashless/pool/e2e token shapes still reach the backend). New refusal
branches are unit-tested in every package; dropguard itself is 100% covered.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(pool): chokepoint contract on the pool reaper's deprovision dispatch
pool.deprovisionBacking is the ONE customer-infra drop dispatch that does not
pass through server.guardedDrop (no gRPC request). Give it the same contract:
dropguard naming-token validation (refusal = provisioner.drop.refused) and
the `provisioner.drop` audit event emitted BEFORE the backend executes, with
caller="pool_reaper" and the proto-style resource_type strings so one NR
query covers RPC drops and pool-reap drops alike. Without this, every pool
reap of a failed postgres item would page the new customer-db-destructive-ddl
NR alert as an unsanctioned DROP (and was itself an un-audited drop path).
Test fixture fix: fakeRows.Scan filled every string dest with "postgres",
which dropguard now (correctly) refuses as a pool naming token — fill
positionally so pool_token carries a valid pool-token shape.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dropguard): identifier length bound is an absurdity bound (128), not postgres's 63
The live-cluster CI suite provisions tokens like tok<nano>_<TestName>
(>63 bytes with the usr_ prefix) — postgres truncates such identifiers
CONSISTENTLY on CREATE and DROP, so they round-trip fine in reality, and the
63-byte refusal wedged their Deprovision (4 coverage-job test failures).
Keep the cap purely as an absurdity bound at 128. Verified the four failing
live tests pass against a real postgres 16.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Manas Srivastava <[email protected]>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
0 commit comments