fix(page-templates): defense-in-depth workspace checks (#36)

Consistency hardening from #17 review (not currently exploitable):
- toggleTemplate now explicitly rejects a page outside the caller's workspace
  (page.workspaceId !== user.workspaceId -> NotFound, avoiding existence leak)
  instead of relying solely on the space-membership model.
- PageTemplateReferencesRepo.deleteByReferenceAndSources is now workspace-scoped
  (adds a workspaceId filter + param), matching the 'scope by workspaceId
  everywhere' invariant; the sole caller threads its workspaceId.
The PAGE_TEMPLATE_THROTTLER limit is intentionally left as-is (the issue's
throttle item was 'consider only'; no change without usage data).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
claude code agent 227
2026-06-20 21:42:49 +03:00
parent a15cccf557
commit 79d096ed7a
3 changed files with 10 additions and 0 deletions
@@ -67,6 +67,12 @@ export class PageTemplateController {
throw new NotFoundException('Page not found');
}
if (page.workspaceId !== user.workspaceId) {
// Defense-in-depth: never act on a page outside the caller's workspace.
// Use NotFound (not Forbidden) to avoid leaking cross-workspace existence.
throw new NotFoundException('Page not found');
}
await this.pageAccessService.validateCanEdit(page, user);
const isTemplate =
@@ -317,6 +317,7 @@ export class TransclusionService {
if (toDelete.length > 0) {
await this.pageTemplateReferencesRepo.deleteByReferenceAndSources(
referencePageId,
workspaceId,
toDelete,
trx,
);