Repository navigation
fix(wiki-v2#598): refuse the native space create in remote mode - #61
Merged
Merged
Conversation
In AUTHZ_MODE=remote, POST /api/spaces/create was gated only by upstream workspace CASL on users.role, which is not PDP-backed. Remote mode relied on no fork session holding owner/admin, but a session minted before the switch to remote survives it, and the space plus its creator's admin row are projected into SpiceDB, bypassing the platform's governed create, its audit and the connector-posture bind (ADR 0028). - SpaceNativeCreateInterceptor (authz/space-create/), registered after the docmost#502 refusal in app.module.ts (seam #4): 404 for every authenticated caller before CASL, the pipe and the handler; one space.native_create_refused row via PlatformAuditClient. The body is never read; the engine role is recorded only as an enum value. Native mode is unchanged. - Fitness spec: the refused set equals the routes reaching .createSpace(; every spaces row insert and every hop up to a route pinned per method. - Shared AST helper (route-guard/ast-sink-scan.ts): a sink bound to a local is attributed to its method, for docmost#502's inventory too. - Contract smoke: a native-phase OWNER session in remote mode gets 404 and no row. Integrator README documents both refused native routes. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR
This is the fork half of the fix for cccvu/wiki-v2 issue 598. In
AUTHZ_MODE=remote, the engine's native space create (POST /api/spaces/create) now answers 404 for every authenticated caller, owner sessions included, and the refusal is audited. The integrating platform'sPOST /api/service/spacesis the only create path. Native (standalone) mode is unchanged.Context
users.role(owner/admin,WorkspaceAbilityFactory), and that check is not PDP-backed. Remote mode refused it only because no fork session held owner/admin.JwtStrategy.validatedoesn't re-check role or namespace, so a session minted before the switch to remote survives it: first-run setup mints a 90-day owner session, and this repo's contract smoke uses native-phase sessions on purpose.spacesrow is written.space_membersadmin row are captured by the outbox triggers and projected into the PDP. That bypasses the platform's governed create, its audit and its connector-posture bind (wiki-v2 ADR 0028).Related issues
users.role;What changed
No controller, service or repo is touched. The only upstream-owned edit is the composition registration in
app.module.ts(seam #4).authz/space-create/space-native-create.interceptor.ts(new, CCC-owned):SpaceNativeCreateInterceptor, a sibling of Dev docmost/docmost#502'sSpaceHardDeleteInterceptor.SpaceController.createSpace. In remote mode it returnsthrowError(() => new NotFoundException())before the handler, so CASL, theValidationPipeand the insert never run.space.native_create_refusedrow throughPlatformAuditClientdirectly (theAPI_AUDIT_ENABLEDkill switch cannot drop it):metadata { outcome: 'denied', reason: 'platform_only', route, actorRole? }.actorRoleis recorded only as a knownUserRolevalue, so an attempt by a leftover privileged session stands out.app.module.ts(seam fix(authz): harden PLATFORM_AUTHZ_TIMEOUT_MS parse (empty/typo → deny-all self-DoS) #4): registered as anAPP_INTERCEPTORright afterSpaceHardDeleteInterceptor, so after the per-principal rate limiter.AuthzModule, becauseCollabAppModulelacksPlatformAuditModule.authz/route-guard/static-route-scan.ts: acallsNativeSpaceCreatehandler-body tell.authz/route-guard/ast-sink-scan.ts(new):literalText,ownerOfand the source-file walk, shared by both fitness specs.const s = sql\…``) is now attributed to its enclosing method, not the local's name, so a sink moved between methods changes the inventory.examples/integrations/authorization-stub/contract-smoke.sh: Phase 3 check (d). Alice's native-phase OWNER session getsPOST /api/spaces/create→ 404, and there is nospacesrow.docs/integrations/authorization/README.md: "Native space routes refused in remote mode" (Dev docmost/docmost#502 delete, custom drawio server by docker not ok docmost/docmost#598 create). No OpenAPI or version change.How this was tested
pnpm exec jest src/authz/space-create src/authz/space-delete src/authz/route-guard src/authz/request-controls src/authz/mode→ 18 suites, 157 tests green:bodygetter, the role enum, native inert, non-HTTP pass-through;SpaceControlleron Fastify with an owner whose CASL allows gets 404, and CASL andcreateSpacenever run; without the interceptor it gets 200 (anti-vacuity); it composes with Dev docmost/docmost#502; native{}gets 400 and a valid create reaches the service;nest buildis clean.tsc --noEmitshows no errors in the touched files. eslint is clean.contract-smoke.shend to end:wiki-v2HEAD:FAIL: … expected 404 in remote mode, got HTTP 200andstill wrote 1 space row(s).audit_loghasspace.native_create_refused(actorRole: member, actor resolved) plusapi.poststatus 404;/v1create still returns 201;AWS / infra apply
N/A
Checklist
Scope
Tests
Docs & contracts
README/docs// an ADR), or N/A.Safety (this is a security-first, deny-by-default platform)
UPSTREAM_MODIFICATIONS.md); proprietary/authz logic stays out-of-process.Housekeeping
Known limitations / follow-ups
POST /api/service/spaces. This is documented in the README. No in-repo caller used it in remote mode.SpaceServiceinjection sites are pinned so the receiver-name match stays sound.users.role, which ismemberfor every brokered session.users.role-only class and surviving privileged sessions.Merge: this PR first (merge commit), then re-pin the gitlink in the super PR.
🤖 Generated with Claude Code