Creating or editing a trigger that points at a data table under roles was
accepted, and its listener then retried the refused connection every 30
seconds forever. The save is now refused, and a trigger that reaches such a
data table anyway (re-enabled, or cloned into a fork) is disabled by its
listener with the reason, as a missing replication slot is.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A replication stream reads every row of every table whatever the data table's
roles grant, and its listener checks access only when it connects. Rather than
chase every way access can change and bounce the streams each one affects, a
data table now carries one or the other:
- a Postgres trigger or capture cannot be created on, or connect to, a data
table under roles;
- roles cannot be turned on while an enabled trigger or a live capture reads
the data table, its own or a fork's through its pointer. The refusal names
each one to disable.
This removes the stream bounces on roles edits and on data table and workspace
deletion, and the trigger gate that admitted admins. The fork schema baseline
fix from the same review round is kept.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A data table backed by the instance database resolved to exactly one Postgres connection,
`custom_instance_user`, for everyone who could reach it at all. There was no way to say
this job reads, that one writes, this one never sees the salaries table.
A data table role is now a real Postgres login on the cluster, defined once for the
instance by a superadmin and named exactly as they named it. A script that declares
`-- role analytics` connects as `analytics`, and Postgres decides what it may touch —
grants are ordinary SQL. Windmill answers only "may this caller ask for this role", from
the tenant lists on the data table entry: `u/alice`, `g/analysts`, `f/finance` or `*`.
A data table with no `permissions` block behaves exactly as before.
Everything that opens a connection on someone's behalf goes through one chokepoint,
`get_datatable_resource_from_db`, which takes the identity explicitly and fails closed when
there is none. The role logs in as itself — never `SET ROLE`, which a script could
`RESET ROLE` its way out of.
A fork's data table entry becomes a pointer at the workspace that governs it rather than a
copy of it. The settings clone used to hand a fork a byte-identical entry naming the
parent's database, which a fork admin could edit to grant themselves `admin` there; a
pointer has nothing local to edit, and its tenants are evaluated as a member of the
governing workspace, by email. `permissions` is stripped from the workspace export and
ignored on import: tenants name principals of one workspace, and a settings push is not
where an access decision should be made.
Operations that see the whole database whatever the roles grant stay with the governing
workspace's admins: editing the roles, a migration that declares none, and opening a
replication stream for a Postgres trigger or capture.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
Attach custom-instance datatables in the DuckDB executor through a DuckDB
secret instead of an inline connection string, and route postgres triggers on
custom-instance datatables through a dedicated custom_instance_replication_user
role (with its own auto-generated password in global_settings). Normalize
custom_instance_user attributes on server boot.
Claude-Session: https://claude.ai/code/session_01Tp6NNNinCB8dwWqGaFXDRF
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* refactor: extract windmill-api into 4 subcrates (api-auth, store, api-sse, api-jobs)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: eliminate refresh_token OnceLock bridge in windmill-store
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: eliminate FromRequestParts OnceLock bridge in windmill-api-auth
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: wire subcrates into workspace and clean up unused re-exports
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve cargo check --all-features errors in subcrate wiring
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* sqlx
* all
* chore: update ee-repo-ref for warning fixes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract windmill-trigger crate and expand windmill-api-jobs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: extract windmill-trigger-kafka crate from windmill-api
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: extract windmill-trigger-postgres crate from windmill-api
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: extract windmill-trigger-websocket and windmill-trigger-mqtt crates
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: extract windmill-trigger-nats, sqs, gcp, and email crates
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: extract windmill-trigger-http crate from windmill-api
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: move token creation and permission helpers to windmill-api-auth
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: extract windmill-native-triggers crate from windmill-api
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* sqlx
* all
* refactor: extract windmill-api-embeddings crate and fix CI warnings
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: resolve type mismatch in oauth2_oss and remaining warnings
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: use correct HTTP_CLIENT config in embeddings crate (30s timeout, cert override)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* all
* fix: gate oauth_refresh_ee on oauth2 feature to fix warnings
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* all
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>