Commit Graph
467 Commits
Author SHA1 Message Date
Ruben FiszelandClaude Opus 4.8 58eccf0a80 fix: strip one guest bearer prefix and bound the raw bearer
trim_start_matches stripped every jwt_guest_ prefix, so a repeated-prefix bearer
shrank to a valid short token that verified and was then cached under the full
oversized bearer key. Strip exactly one prefix, and bound the raw bearer length
(the auth cache keys on it) before verifying or caching. The refusal test now
mints a valid signed token over the cap (which would otherwise verify, the extra
claim ignored) and a repeated-prefix bearer, so it fails if either guard regresses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
2026-09-04 16:38:41 +02:00
Ruben FiszelandClaude Opus 4.8 0937fb66ca fix: bound the guest JWT length before verifying or caching
The auth cache keys on the bearer token, and verify_for_workspace decoded a
token of any length (its header for the JWKS kid, then the body) before
rejecting it, so an oversized token could be decoded unauthenticated and, if it
verified, cached at full size. Refuse a token longer than MAX_GUEST_JWT_LEN
(8 KiB) at the top of verify_for_workspace, before the key lookup or any
signature work. Also correct the MAX_JWKS_URL_LEN doc: the bound holds because
the save path validates the URL through fetch_jwks, not because cached_jwks
checks it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
2026-09-04 16:27:01 +02:00
Ruben FiszelandClaude Opus 4.8 1db28b80b2 Merge origin/license-app-only-implicit-promotion into guest-jwt-app-entry
Parent moved (OSS 6081e286c9, EE 32841072aa). The EE change dropped
clear_guest_target_for_state (the OAuth guest cookie is state-bound and never
cleared by a callback); the OSS change was docs/tests on the guest_activity
delete. Keep this branch's ee-repo-ref at the EE companion head.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
2026-09-04 15:54:55 +02:00
Ruben FiszelandClaude Fable 5.1 6081e286c9 docs: the workspace-scoped guest_activity delete moves an instance-wide count; assert the mint records the guest
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BayTppRCstWX6qTf3LMco5
2026-09-04 15:50:41 +02:00
Ruben FiszelandClaude Opus 4.8 13e8af3f16 fix: carry the guest JWT key across a workspace rename
change_workspace_id copies workspace_settings by an explicit column list that
omitted guest_jwt_public_key and guest_jwt_jwks_url, so a rename silently cleared
the guest JWT key, the same failure the parent fixed for guest_access_enabled.
Add both columns to the copy, and extend the rename test to assert the key
travels. Sync verify()'s refusal message with the other two sites (leading `/`).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
2026-09-04 14:57:35 +02:00
Ruben FiszelandClaude Opus 4.8 02a67ff3c1 Merge origin/license-app-only-implicit-promotion into guest-jwt-app-entry
Parent adds guest_activity handling on workspace rename (UPDATE ... SET
workspace_id) and delete (DELETE FROM guest_activity), and reads the deployed
mode under the app-row lock on a rename. The rename/delete queries touch only
workspace_id, leaving the jwt_entry column this PR adds intact. Keep
guest_session_scopes relocated to windmill_api_auth::scopes rather than the
parent's re-added local copy, and sync its refusal message with the parent's.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
2026-09-04 14:41:33 +02:00
Ruben FiszelandClaude Fable 5.1 6169076d70 fix: guest_activity follows a workspace rename and goes with a workspace delete
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BayTppRCstWX6qTf3LMco5
2026-09-04 14:30:30 +02:00
Ruben FiszelandClaude Fable 5.1 cb5d00b83b fix: a workspace rename keeps the guest switch; the rename guard reads the deployed mode under the row lock
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BayTppRCstWX6qTf3LMco5
2026-09-04 14:19:19 +02:00
Ruben FiszelandClaude Opus 4.8 259f979a72 Merge origin/license-app-only-implicit-promotion into guest-jwt-app-entry
Adopt the parent's app-path guard for scopes: windmill_common::auth::
is_scope_literal_path (refuses only `:`, `,`, `*` and a leading `/`) rather than
check_proper_path, since app paths may carry spaces and `@`. The relocated
guest_session_scopes and the JWT arm's verify() both use it. The parent's
re-added local guest_session_scopes in users.rs stays relocated to
windmill_api_auth::scopes, its guard preserved there.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
2026-09-04 09:40:39 +02:00
Ruben FiszelandClaude Opus 4.8 82fc97dfc5 fix: address round-10 review on the guest JWT entry
Save the guest JWT key before the Enterprise-only default-app and rate-limit
writes, so a refused write cannot swallow a valid key change on CE. Name the
JWT entry in the Guests card summary. Complete verify()'s doc with the email
and app_path rules. Anchor the refusal suite with a positive control and make
enable_guests assert its status, so a broken fixture cannot pass it vacuously.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
2026-09-04 09:33:29 +02:00
Ruben FiszelandClaude Fable 5.1 0241ba52b4 fix: the deploy-time guest path guard checks the destination of a rename and refuses a leading slash
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BayTppRCstWX6qTf3LMco5
2026-09-04 09:32:26 +02:00
Ruben FiszelandClaude Fable 5.1 47dbd96a92 fix: only the scope grammar's own characters bar an app path from guests, refused at deploy as well as at the mint
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BayTppRCstWX6qTf3LMco5
2026-09-04 09:21:16 +02:00
Ruben Fiszel ac2fd76709 Merge remote-tracking branch 'origin/license-app-only-implicit-promotion' into guest-jwt-app-entry
# Conflicts:
#	backend/ee-repo-ref.txt
#	backend/windmill-api-users/src/users.rs
#	frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte
2026-09-04 09:18:43 +02:00
Ruben FiszelandClaude Fable 5.1 2b4631cedc fix: guests stop at the launched-by-me job grant; canonical app paths at the mint and discovery; the toggle ends on the stored value
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BayTppRCstWX6qTf3LMco5
2026-09-04 09:07:39 +02:00
Ruben FiszelandClaude Fable 5.1 fbcb0728f7 fix: a guest app path is refused at the mint if it could widen the scope; the instance toggle waits for its reload
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BayTppRCstWX6qTf3LMco5
2026-09-04 08:52:20 +02:00
Ruben Fiszel 9016816732 Merge remote-tracking branch 'origin/main' into license-app-only-implicit-promotion 2026-09-04 08:45:31 +02:00
Ruben FiszelandClaude Fable 5.1 11138284ac fix: deploy a relocked script version only when its lock changed (#10966)
* fix: deploy a relocked script version only when its lock changed

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdEb6gzCZ2qXmAQJAeMf9W

* fix: write the unchanged relock hash under the row lock and skip the phantom tally

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdEb6gzCZ2qXmAQJAeMf9W

* fix: requeue a superseded relock and read the live head past the script cache

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdEb6gzCZ2qXmAQJAeMf9W

* fix: re-read the relock head after waiting on its lock and keep module locks

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdEb6gzCZ2qXmAQJAeMf9W

* fix: bound the relock head re-read instead of reading once

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdEb6gzCZ2qXmAQJAeMf9W

* chore: refresh the sqlx cache entry for the re-indented lock write

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdEb6gzCZ2qXmAQJAeMf9W

* test: pin the waiting-relock requeue and the multi-file importer no-op

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdEb6gzCZ2qXmAQJAeMf9W

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 01:01:36 +02:00
Ruben FiszelandClaude Opus 4.8 4899e51703 fix: audit the first JWT entry across an IdP row; finish has_any_account; nits
- Gate the JWT `users.login_guest` audit on the transition to `jwt_entry = true`,
  not physical insertion: a guest who signed in through the IdP earlier the same
  day already has a `guest_activity` row with `jwt_entry = false`, and the old
  `xmax = 0` test suppressed the first JWT audit. A CTE reads the prior flag and
  the RETURNING decides it atomically in the upsert.
- Fold the signed-in mint's inline account check into `has_any_account`, so the
  helper has both callers and the two no longer diverge on lowercasing.
- Negative-cache a refused guest JWT for 30s so a replayed bearer past the cap
  does not take the instance-wide allowance advisory lock on every request.
- Update the two stale share-link header comments to the `guest.<jwt>` form,
  drop the "plan gate" rationale on the entry test's cfg, collapse the blank
  lines the SHARE_LINK_SEGMENT removal left, and prettier-format the settings
  card after the isEnterprisePlan wrapper was removed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
2026-09-03 23:08:02 +02:00
Ruben FiszelandClaude Opus 4.8 b8838564ff fix: commit guest activity before auditing; unambiguous share-link marker; isolate the allowance test
- admit_and_record_guest_jwt commits the guest_activity row before the
  best-effort audit, on a separate connection. The EE audit writer swallows an
  audit_partitioned failure but the failing statement still aborts its
  transaction, so auditing before the commit would roll the activity row back
  while the arm returned success, admitting a guest uncounted and past the
  allowance.
- The share-link guest marker is now the prefix `guest.` glued to the token
  (`/a/<path>/guest.<jwt>`, `/public/<ws>/<secret>/guest.<jwt>`). The `.` cannot
  appear in a custom-path or secret segment, so an external-JWT link whose custom
  path ends in a `guest` segment (`/a/foo/guest/<jwt>`) is read as before rather
  than hijacked. Removed the unused SHARE_LINK_SEGMENT constant.
- Moved the JWT allowance test to its own binary (app_guest_jwt_allowance.rs):
  set_plan flips a process-global license key, so a test sharing the binary with
  the existing allowance test would race under --test-threads.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
2026-09-03 22:42:58 +02:00
Ruben FiszelandClaude Opus 4.8 2907a531b2 test: a guest JWT is capped by the instance allowance; pin ee-repo-ref
The guest policy moved (parent merge): guests are free up to the instance
allowance, then metered on Enterprise and hard-capped elsewhere. The JWT arm now
calls guest_admission inside the transaction that records guest_activity (the
advisory lock spans the count check and the row), and the door re-reads
guest_session_stands (switch, instance switch, no account) for every guest
request, so the JWT arm needs nothing extra for those. The plan gate on the key
config is gone (guests are free on any plan).

Adds an allowance test: with the window full on a capped instance, a stranger's
JWT is refused (401) and a returning guest's is admitted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
2026-09-03 22:18:11 +02:00
Ruben Fiszel 60ae85419c Merge remote-tracking branch 'origin/license-app-only-implicit-promotion' into guest-jwt-app-entry
# Conflicts:
#	backend/ee-repo-ref.txt
#	backend/windmill-api-users/src/users.rs
#	backend/windmill-api-workspaces/src/workspaces.rs
#	backend/windmill-api/openapi.yaml
#	frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte
2026-09-03 22:16:50 +02:00
Ruben FiszelandClaude Fable 5.1 f626dc312f feat: a superadmin switch over guests for the whole instance; the pre-existing-user flag keeps its meaning
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BayTppRCstWX6qTf3LMco5
2026-09-03 19:37:48 +02:00
Ruben FiszelandClaude Fable 5.1 2ce9118f60 fix: the cap is exact, an account ends a guest session at the door, popups close, and guest mode survives the CLI round trip
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BayTppRCstWX6qTf3LMco5
2026-09-03 19:27:45 +02:00
Ruben FiszelandClaude Opus 4.8 cb8fa45114 fix: reuse VALID_EMAIL, guard app_path, accept base64url JWT headers
- Validate the email claim with `windmill_common::users::VALID_EMAIL` (the `usr`
  table's own constraint) plus the 254-byte bound, rather than a hand-rolled
  predicate. It requires an `@`, which is what keeps a guest's name its own
  principal, never a `u/<user>` or `g/<group>`.
- Reject an `app_path` claim carrying a scope metacharacter (`*`, `,`, `:`,
  whitespace) before authenticating: it is interpolated into `apps:read:<path>`
  and `apps:run:<path>`, where `*` or `,` would widen the guest past its one app.
- `isJwt` on the custom-path route normalises base64url before `atob`, so a
  header carrying `-`/`_` (a `kid`, a signature) is recognised instead of taken
  for a path segment; it also checks the three-segment structure.
- Drop the dead stale-key carry-forward in the blocking JWKS path (a stale-good
  entry is served by the fast path) and clean up the test's env var.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
2026-09-03 16:54:36 +00:00
Ruben FiszelandClaude Fable 5.1 d01f76b96f feat: 100 free guests per 30 days, then a quarter seat each on Enterprise and a hard cap elsewhere; superadmin guest list; refusals reach the page
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BayTppRCstWX6qTf3LMco5
2026-09-03 16:53:02 +00:00
Ruben FiszelandClaude Opus 4.8 6b3dedbe49 fix: bound the guest JWT email; serve stale JWKS during refresh
- Cap the email claim at 254 bytes as well as requiring a plain address. An
  over-255-byte email fit the signature but overflowed the guest_activity.email
  column: the activity write and the login_guest audit both failed while the
  guest was still admitted, so a guest could enter uncounted and unaudited. Now
  refused before authentication.
- Serve a stale-but-good JWKS entry while a refresh runs off the request path,
  so a slow or hanging issuer no longer stalls guest requests for the fetch
  timeout at each 15-minute TTL boundary. Only a cold or negative entry blocks,
  still under the single-flight lock; the background refresh no-ops when a fetch
  is already in flight and keeps the stale keys on failure.
- Tests: an oversized email is refused alongside the group-shaped and
  slash-in-email cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
2026-09-03 09:25:39 +00:00
Ruben FiszelandClaude Opus 4.8 388768f8c6 fix: a guest JWT email must be a plain address, not a principal
The email claim becomes the guest's username, and `username_to_permissioned_as`
reads a name with no `@` as `u/<user>` and a `group-` prefix as `g/<group>`. A
token could therefore name the guest after a user or a group. Reject anything
that is not a plain email address (an `@`, no `/`, no whitespace) so a guest's
name is only ever its own principal, never a user's or a group's. Found while
reviewing the identity path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
2026-09-03 09:13:45 +00:00
Ruben FiszelandClaude Opus 4.8 77b24b20cd fix: address review findings on the guest JWT entry
- Resolve a guest JWT on the lowercased email. Accounts are stored lowercased,
  so a mixed-case `email` claim slipped past the no-account gate and resolved an
  account holder to a guest, and split the activity rows the seat count reads.
  `has_any_account` now normalises its input too (index-friendly, not
  `lower(email)`).
- Cap the auth-cache entry for a guest JWT at 5 minutes rather than the token's
  `exp` (up to 24h). A guest JWT is revocable only by the workspace switch or by
  rotating the key; the short entry makes a rotated or cleared key bite on
  re-verification, and makes the day-keyed activity dedupe reachable across a
  midnight (the second-day row was never written).
- Audit `users.login_guest` only when the upsert freshly inserts the row
  (`xmax = 0`), decided atomically by the DB, so concurrent first requests and
  separate API nodes emit it at most once a day.
- JWKS hardening: read the body with a 1MB cap instead of buffering any size;
  an alg-less RSA key accepts the whole RSA family instead of being forced to
  RS256; a failed fetch serves the last good keys (or a short negative entry) so
  an unreachable issuer is hit at most once per 30s however much unauthenticated
  `jwt_guest_` traffic arrives, and an unknown `kid` never triggers a fetch;
  lower the fetch timeouts to 5s/10s.
- Settings copy: note that the JWKS should point at an issuer you control, since
  neither `iss` nor `aud` is bound.
- Tests: a mixed-case account and an over-24h lifetime are refused; unit tests
  pin `jwk_algorithms` (including the alg-less RSA family) and a JWK-derived key
  verifying a real token.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
2026-09-03 08:49:18 +00:00
Ruben FiszelandClaude Opus 4.8 9cb8e991eb feat: guest JWT entry for embedded apps
A second way in for a guest (companion to windmill#10929): a JWT the embedding
customer's own backend mints and signs, carried on the app's share link and
verified against a key the workspace admin configured. It needs no
identity-provider round-trip, so it works inside an iframe where popups and
third-party cookies do not. Bearer prefix jwt_guest_, stateless: verified per
request, cached until exp, no token row.

A JWT guest is the same identity as a signed-in guest: no usr row, no password
row, no seat, confined to the one app app_path names. Every guest gate applies:
the plan, the workspace switch (enforced once at the auth door via the sentinel),
the app mode (guest_app_admits), and "no account at all" (has_any_account). The
claim's workspace_id must equal the route's workspace, and a workspace-less route
never accepts it.

Claims honoured: email, workspace_id, app_path, exp (mandatory); nbf/iat
validated when present; the accepted lifetime is capped at 24h. Algorithms:
RS256/384/512, PS256/384/512, ES256/384; HS* is refused. The key is a
per-workspace setting, a PEM public key or a JWKS URL (at most one, a DB CHECK
enforces it), Enterprise-plan gated like the guest switch. The JWKS URL is
validated against private ranges and the fetch is pinned to the validated
address.

Counting: a JWT guest is recorded in guest_activity (once per email, workspace
and day, cached), marked jwt_entry, and not in unique_ext_jwt_token. A first-seen
users.login_guest audit carries the entry kind.

Narrower than jwt_ext_ by design: that key is instance-level and can assert
admin, groups and folders; a guest key is scoped to one workspace and only ever
mints guests. An app-only user a customer routes through jwt_ext_ today is
counted; through a guest JWT they become a free guest, the intended pricing
change, split out as guest_jwt_count in the telemetry so it can be measured.

Changes on the parent branch, additive: ApiAuthed.credential_expiry (a
credential's own expiry when it has no token row); guest_derived_token_constraints
caps on it; guest_session_scopes moved to windmill-api-auth::scopes and
has_any_account to windmill-common::users so the mint and the JWT arm share one
copy; the signed-in mint's login_guest audit now carries entry=idp.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
2026-09-03 08:00:25 +00:00
Ruben FiszelandClaude Fable 5.1 9b64a89cd4 fix: let operators use wmill.datatable() from within running jobs (#10931)
* fix: let operators use wmill.datatable() from within running jobs

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHR4fytgt6m4q37WCXs2Rp

* fix: refuse content-driven redirects and deferral in the operator datatable exemption

* fix: check the datatable exemption against the expanded query, not the raw content

* fix: fail closed on a language-overriding expansion and state the exemption's real scope

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 08:43:52 +02:00
Ruben FiszelandClaude Fable 5.1 4fef1195ad fix: apply object-storage test SSRF validation to all non-super-admins (#10933)
* fix: apply object-storage test SSRF validation to all non-super-admins

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJLqsE5br9r5e7qy8ULwUg

* fix: name the job-token case in object-storage test rejections

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJLqsE5br9r5e7qy8ULwUg

* fix: run object-storage connection tests with a short-lived user token

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJLqsE5br9r5e7qy8ULwUg

* fix: test object-storage resources from the browser, mint a token only for the worker test

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJLqsE5br9r5e7qy8ULwUg

* fix: resolve variable and resource references before the browser-side object-storage test

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJLqsE5br9r5e7qy8ULwUg

* fix: bound the browser-side object-storage test to 15s

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJLqsE5br9r5e7qy8ULwUg

* fix: explain object-storage test rejections and name the way out

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJLqsE5br9r5e7qy8ULwUg

* fix: keep the server-resolved address out of the object-storage test rejection

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJLqsE5br9r5e7qy8ULwUg

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 22:29:01 +02:00
Ruben Fiszel ba2c5a3dfa fix: guest controls read the plan, not the key; gate the guest tests on the features they need 2026-09-02 17:27:18 +00:00
Ruben Fiszel 87a3ed9766 fix: the guest plan gate fails closed on non-enterprise builds; settings report the effective switch 2026-09-02 17:17:49 +00:00
Ruben Fiszel 02b880a88a fix: a guest upload needs an app policy; a missing app does not skip the confinement 2026-09-02 15:34:34 +00:00
Ruben Fiszel c8183e33e5 fix: a guest uses an anonymous app as itself; S3 uploads confined by app mode 2026-09-02 15:21:17 +00:00
Ruben FiszelandClaude Opus 5 03e0504d95 refactor: enforce the guest switch once at the auth door; sign-in for a guest of another app
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BayTppRCstWX6qTf3LMco5
2026-09-02 07:58:15 +00:00
Ruben FiszelandClaude Opus 5 b317002dd6 fix: the label alone governs a guest; refuse guests with accounts; unserialize discovery
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BayTppRCstWX6qTf3LMco5
2026-09-02 07:19:41 +00:00
Ruben FiszelandClaude Opus 5 27cb199e0a fix: guest-derived tokens share one constraint set; gate sign-in on guest discovery
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BayTppRCstWX6qTf3LMco5
2026-09-02 07:01:10 +00:00
Ruben FiszelandClaude Opus 5 d78f7f6709 fix: guest tokens are not rescopable and guest embed tokens keep the sentinel
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BayTppRCstWX6qTf3LMco5
2026-09-02 06:39:16 +00:00
Ruben FiszelandClaude Opus 5 4766f979dc fix: close the relabel hole, guest embed tokens, read-path switch, custom-path entry
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BayTppRCstWX6qTf3LMco5
2026-09-01 21:59:50 +00:00
Ruben FiszelandClaude Opus 5 75bde58bb8 fix: make the guest grant a server-minted label, not a declarable scope
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BayTppRCstWX6qTf3LMco5
2026-09-01 21:51:07 +00:00
Ruben FiszelandClaude Opus 5 0edfee970b feat: guest app execution mode, a fourth role that takes no seat
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BayTppRCstWX6qTf3LMco5
2026-09-01 21:51:03 +00:00
hugocasaandClaude Opus 5 c2279db8a9 fix: allow job tokens to read the automate_username_creation setting (#10869)
* fix: let a job token read the automate_username_creation setting

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test: use an ungated global setting as the confinement control

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 11:37:37 +02:00
Ruben FiszelandClaude Opus 5 69320b28f6 perf: index the suspended-job resume test instead of filtering it (#10863)
* perf: index the suspended-job resume test instead of filtering it

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEUq14Wz4cC2NzcRyo6CNj

* fix: keep the legacy suspended index until the replacement is recorded

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEUq14Wz4cC2NzcRyo6CNj

* perf: drop the redundant suspend_until column from the suspended index

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEUq14Wz4cC2NzcRyo6CNj

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 00:39:03 +02:00
hugocasaandClaude Opus 5 46c363ffa4 fix: require admin on workspace tarball settings export (#10817)
* fix: require admin on workspace tarball settings export

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: name the refused flag in the settings export error

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 00:49:13 +02:00
Diego ImbertandClaude Opus 5 8a6dc27236 feat: configurable expiry for presigned s3 public url signatures (#10835)
* feat: configurable expiry for presigned s3 public url signatures

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015JdZFeMXLGfeFNiQgx9QvA

* fix: describe expiry_secs clamping in the spec and pin the bounds in a test

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015JdZFeMXLGfeFNiQgx9QvA

* fix: omit null expiry_secs from the python sdk sign request

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015JdZFeMXLGfeFNiQgx9QvA

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 00:47:24 +02:00
Diego ImbertandClaude Opus 5 3b2a6d7604 feat(datatables): add a down migration from the migration viewer (#10812)
* feat(datatables): add a down migration from the migration viewer

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ha889ogajX9jbqmwTaF8kD

* fix(datatables): refuse an empty down migration and keep the saved one visible

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ha889ogajX9jbqmwTaF8kD

* refactor(datatables): use unifiedSize on the new buttons and fix the lock comment

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ha889ogajX9jbqmwTaF8kD

* fix(datatables): make the add-down exemption atomic against concurrent additions

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ha889ogajX9jbqmwTaF8kD

* fix(datatables): re-test the whole observed row when an upsert skips the lock

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ha889ogajX9jbqmwTaF8kD

* fix(datatables): re-test the observed row even when none was read, and sync the down draft on the leading change

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ha889ogajX9jbqmwTaF8kD

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 22:36:32 +02:00
dc27db68de fix: require item read scope on workspace tarball export (#10797)
* fix: require item read scope on workspace tarball export

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: accept a wildcard path grant for whole-domain scope checks

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: let a wildcard path grant delegate the unqualified scope

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2026-08-22 12:06:54 +02:00
hugocasaandClaude Opus 5 40f0cab2ad fix: scope capture deletion to the workspace in the request path (#10795)
* fix: scope capture deletion to the workspace in the request path

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test: layer the capture fixture on base instead of duplicating it

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 10:01:25 +00:00
hugocasaandClaude Opus 5 25d9a20630 fix: require an unscoped token to reach the workspace encryption key (#10798)
* fix: require an unscoped token to read the workspace encryption key

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: hold the encryption key's write path to the same token bar

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: audit a workspace export only once nothing can still reject it

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore: carry the new audit operation into the served openapi spec

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 09:55:44 +00:00