mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-27 11:20:42 +01:00
The prior enforce pass protected six plugin issue-read methods but left four
issue-derived read paths gated only on companyId, so a plugin with the read
capability and a known private issue ID could still exfiltrate private content
and relationship metadata (OWASP API BOLA / incomplete mediation):
- issues.getRelations returned relation summaries naming private targets
- issues.getSubtree returned private descendant rows plus their document
summaries, relations, active-run and assignee metadata (and leaked a private
root's existence)
- issues.listInteractions returned private interaction payloads
- issueDocuments.list/get returned private document bodies
Centralize the synthetic non-member check into shared helpers
(requirePluginReadableIssue, pluginReadableIssueIdsByIds,
pluginRedactRelationSummary) and route every issue-derived read through them
rather than maintaining a per-method allowlist:
- private direct roots are indistinguishable from missing ("Issue not found")
- getSubtree filters private descendants BEFORE fetching relations/documents/
runs/assignees
- relation edges (getRelations, getSubtree, getOrchestrationSummary, and the
summaries echoed by setBlockedBy/addBlockers/removeBlockers) drop unreadable
targets, recursing into nested terminal blockers
- getOrchestrationSummary refactored onto the same helpers (no behavior change)
Add one old-code-failing regression test per bypass (private-root not-found,
open subtree with a private child + document, direct private document/
interaction reads, private blocker-edge redaction); keeps the eight existing
tests. Re-inventory documented in doc/ISSUE-PRIVACY.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>