Commit Graph
8 Commits
Author SHA1 Message Date
DottaandPaperclip 3a726e676f feat: enforce private task permissions across execution and data (#10633)
Enforce private task and project access across direct reads, search, execution, files, plugins, live delivery, and sharing mutations. Preserve downward-only sharing, current responsible-user authorization, and audited emergency access. Bind historical draft assets with migration 0314.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-07 07:06:40 -05:00
3abe9e2134 build(deps): bump zod from 3.25.76 to 4.4.3 (#11719)
Bumps [zod](https://github.com/colinhacks/zod) from 3.25.76 to 4.4.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/colinhacks/zod/releases">zod's
releases</a>.</em></p>
<blockquote>
<h2>v4.4.3</h2>
<h2>Commits:</h2>
<ul>
<li>4c2fa95ce3f3390fbc522324e406b4e9e89b88f9 docs: use Zernio primary
wordmark for gold sponsor logo</li>
<li>2aeec83eb135e3a83756e973ef44845fc5a455d2 docs: prune lapsed gold
sponsors and rebalance logo sizing</li>
<li>7391be88ac1ee5cd02057f5ccc012a1f5df4efd0 docs: prune lapsed
silver/bronze sponsors and add active ones</li>
<li>2c703322a21b4e2b12f33f49ea8430c451a68b4f docs: normalize bronze
sponsor logos to github avatar pattern</li>
<li>9195250cab0e7950efe39c3926d6c203b4b0a170 docs: remove Mintlify from
bronze sponsors (churned)</li>
<li>b8dffe9e62f17e6571e6249d05cc5102b54d94e4 docs: remove Numeric and
Speakeasy (2+ missed monthly cycles)</li>
<li>1cab69383fcdeae2a366d5e2a2fc4d8fc765d168 fix(v4): restore catch
handling for absent object keys (<a
href="https://redirect.github.com/colinhacks/zod/issues/5937">#5937</a>)
(<a
href="https://redirect.github.com/colinhacks/zod/issues/5939">#5939</a>)</li>
<li>c2be4f819064eed62c7c350a2d399b5faecd15f8 fix(v4): generalize
optin/fallback to transform; restore preprocess on absent keys (<a
href="https://redirect.github.com/colinhacks/zod/issues/5941">#5941</a>)</li>
<li>f3c9ec03ba7a28ae72d25cc295f38674bee0f559 4.4.3</li>
<li>1fb56a5c18c27102dbc92260a4007c7732a0ccca docs: document release
procedure in AGENTS.md</li>
</ul>
<h2>v4.4.2</h2>
<h2>Commits:</h2>
<ul>
<li>0c62df0ea19fd05abdf90473e9eef7eea530fab2 Clean up docs navigation
and stale labels (<a
href="https://redirect.github.com/colinhacks/zod/issues/5901">#5901</a>)</li>
<li>20cc794895cc8604fe0c87d83a5d1c3f89fad0ac chore: add security policy
and refresh tooling deps</li>
<li>6fbe07b0177efdd1bf1c0b05160e70d7a0702337 fix(docs): heading anchor
links now include the hash so it doesnt scoll all the way up, follows
navbar logic (<a
href="https://redirect.github.com/colinhacks/zod/issues/5791">#5791</a>)</li>
<li>4bbed1b1c73eca4ce9e59b1189ed236aa6c8b5bd Tighten discriminated union
option typing</li>
<li>bbac3e567e7fccfaaf7cdc97f1ce30c295e2c908 Update PR guidance for
agents</li>
<li>cf0dc942a32805c292fff59ade20a7ace980735a Merge remote-tracking
branch 'origin/main' into fix-discriminated-union-key-constraint</li>
<li>292c894a5fd2aa42e527900b83d8d7a3009a709c docs: add Zernio gold
sponsor</li>
<li>1fc9f311c28dcf80d0bb5a36b177086cbc3d8eca docs: document codec
inversion</li>
<li>1373c85da9aeff704a9762d27bc58699618aefb7 docs: remove AI disclosure
guidance</li>
<li>e20d02b473c08e3a4e557bc610b1b5fac079b649 chore: ignore triage
notes</li>
<li>e58ea4d91b1dfe8194b73508203213cbc7e9c936 docs: test Zod Mini tab
code heights</li>
<li>905761a5d127e8d5dd2ebb3bc88c75cb0b8149ff docs: document preprocess
input type narrowing</li>
<li>bf64bac850d4dee2b7dde7e64909d5d796d32043 chore: tighten test
guidance in AGENTS.md</li>
<li>8ec4e73f4c4693b6361ad591be40fb41eb8a9f95 chore: update play.ts
scratch</li>
<li>02c2baf7d0d615872fa4528a8020603b71211702 Make z.preprocess defer
optionality to inner schema (<a
href="https://redirect.github.com/colinhacks/zod/issues/5929">#5929</a>)</li>
<li>88015df8e25c44fb5385eb3ef28935119cd5edea fix(docs): drop deprecated
<code>baseUrl</code> from tsconfig</li>
<li>c59d4474e3b4cad1b323462186cf607178ce8267 4.4.2</li>
</ul>
<h2>v4.4.1</h2>
<h2>Commits:</h2>
<ul>
<li>481f7be4238c83ed58183f921b2646f340a91c6a ci: gate release publishing
on full test workflow</li>
<li>95ccab423aec720b2523c3a64cdc7e3204537cc7 test(v3): restore optional
undefined expectations</li>
<li>cede2c63739a5823d6aa5093d291e9a111da943d fix(v4): reject tuple holes
before required defaults (<a
href="https://redirect.github.com/colinhacks/zod/issues/5900">#5900</a>)</li>
<li>edd0bf0f5ada4a8dc581c259407d7bbad0a71ea7 release: 4.4.1</li>
<li>180d83d1dbe6a59260710cc8637a3dea2281ee56 docs: remove Jazz featured
sponsor</li>
</ul>
<h2>v4.4.0</h2>
<h2>4.4.0</h2>
<p>This is a minor release with a wide set of correctness and soundness
fixes. Some fixes intentionally make Zod stricter, so code that depended
on previously accepted invalid or ambiguous inputs may need small
updates.</p>
<h2>Potentially breaking bug fixes</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/colinhacks/zod/commit/1fb56a5c18c27102dbc92260a4007c7732a0ccca"><code>1fb56a5</code></a>
docs: document release procedure in AGENTS.md</li>
<li><a
href="https://github.com/colinhacks/zod/commit/f3c9ec03ba7a28ae72d25cc295f38674bee0f559"><code>f3c9ec0</code></a>
4.4.3</li>
<li><a
href="https://github.com/colinhacks/zod/commit/c2be4f819064eed62c7c350a2d399b5faecd15f8"><code>c2be4f8</code></a>
fix(v4): generalize optin/fallback to transform; restore preprocess on
absent...</li>
<li><a
href="https://github.com/colinhacks/zod/commit/1cab69383fcdeae2a366d5e2a2fc4d8fc765d168"><code>1cab693</code></a>
fix(v4): restore catch handling for absent object keys (<a
href="https://redirect.github.com/colinhacks/zod/issues/5937">#5937</a>)
(<a
href="https://redirect.github.com/colinhacks/zod/issues/5939">#5939</a>)</li>
<li><a
href="https://github.com/colinhacks/zod/commit/b8dffe9e62f17e6571e6249d05cc5102b54d94e4"><code>b8dffe9</code></a>
docs: remove Numeric and Speakeasy (2+ missed monthly cycles)</li>
<li><a
href="https://github.com/colinhacks/zod/commit/9195250cab0e7950efe39c3926d6c203b4b0a170"><code>9195250</code></a>
docs: remove Mintlify from bronze sponsors (churned)</li>
<li><a
href="https://github.com/colinhacks/zod/commit/2c703322a21b4e2b12f33f49ea8430c451a68b4f"><code>2c70332</code></a>
docs: normalize bronze sponsor logos to github avatar pattern</li>
<li><a
href="https://github.com/colinhacks/zod/commit/7391be88ac1ee5cd02057f5ccc012a1f5df4efd0"><code>7391be8</code></a>
docs: prune lapsed silver/bronze sponsors and add active ones</li>
<li><a
href="https://github.com/colinhacks/zod/commit/2aeec83eb135e3a83756e973ef44845fc5a455d2"><code>2aeec83</code></a>
docs: prune lapsed gold sponsors and rebalance logo sizing</li>
<li><a
href="https://github.com/colinhacks/zod/commit/4c2fa95ce3f3390fbc522324e406b4e9e89b88f9"><code>4c2fa95</code></a>
docs: use Zernio primary wordmark for gold sponsor logo</li>
<li>Additional commits viewable in <a
href="https://github.com/colinhacks/zod/compare/v3.25.76...v4.4.3">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~GitHub%20Actions">GitHub Actions</a>, a new
releaser for zod since your current version.</p>
</details>
<br />

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Priya Raman <priya@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-21 00:04:14 -07:00
DottaandPaperclip 8087661bb8 fix: bound workspace Git scans (#11572)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Workspaces let users and agents inspect files that belong to an
issue
> - Changed-file views use full-tree Git status scans
> - Many issue views could start those scans at the same time and make
the server unresponsive
> - Route-level limits did not protect the process or coalesce work for
one repository
> - This pull request adds one bounded scheduler for every expensive
workspace Git scan
> - It also starts browser scans only when the file panel is open and
visible
> - The benefit is bounded child-process use and responsive health
checks during request storms

## Linked Issues or Issue Description

**What happened?**

Many changed-file requests could start full `git status --porcelain=v1
-z --untracked-files=all` scans at the same time. One production
incident produced about 270 direct Git child processes. The Node process
stayed alive but stopped answering health requests in time.

**Expected behavior**

Paperclip must bound expensive Git work across all companies, actors,
issues, repositories, and browser tabs. Duplicate requests for one
worktree must share work. Excess requests must fail fast with a
retryable response. Hidden or closed file panels must not start scans.

**Steps to reproduce**

1. Open changed-file views for many issue and actor keys.
2. Send requests for two large workspace roots at the same time.
3. Observe that route-level limiter keys allow many full Git scans to
run together.
4. Observe delayed health responses and accumulated Git children.

**Paperclip version or commit**

Reproduced on master before commit `43ab441f0f`.

**Deployment mode**

Self-hosted server with local workspace repositories.

## What Changed

- Add a process-wide scheduler with configurable concurrency, queue
capacity, timeout, and cache TTL.
- Add fair admission, a bounded queue, canonical worktree keys,
single-flight joins, and bounded result caching.
- Add subprocess timeouts, TERM-to-KILL escalation, bounded output,
waiter cancellation, and slot cleanup.
- Route full-tree status work from file resources, workspace runtime,
execution workspaces, and adapter overlay sync through the scheduler.
- Return stable retryable `503` and `504` error codes for saturation and
timeout.
- Add structured logs with safe workspace hashes, durations, queue
state, cache use, joins, and terminal outcomes.
- Gate UI queries on panel and document visibility. Cancel queries on
close, hide, unmount, and workspace change.
- Disable focus and reconnect bursts. Keep one explicit refresh action
and a retryable unavailable state.
- Document the 10-second default freshness tradeoff and all
configuration variables.
- Add unit, route, UI, adapter, and deterministic 500-request load
coverage.

## Verification

- `pnpm -r typecheck`
- `pnpm build`
- `pnpm check:token-gates`
- `pnpm --filter @paperclipai/server exec vitest run
src/services/workspace-git-operation-scheduler.test.ts
src/__tests__/file-resources-git-scan-load.test.ts --reporter=dot` — 16
tests passed.
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/WorkspaceFileBrowser.test.tsx
src/lib/page-visibility.test.ts --reporter=dot` — 38 tests passed.
- `pnpm --filter @paperclipai/adapter-utils exec vitest run
src/git-workspace-sync.test.ts --reporter=dot` — 16 tests passed.
- Existing file-resource, workspace-runtime, and execution-workspace
regression selections passed.
- Two cleanup safety regressions prove failed scans preserve the
worktree before archive and at the final deletion fence.
- Before: the incident produced about 270 Git children and health
requests timed out.
- After: 500 concurrent requests across 500 issue keys, 73 actors, and
two roots started two underlying scans. Peak scan concurrency was 2. All
500 requests succeeded. Health p99 was 4.94 ms. The harness found zero
unreaped children.
- The full local Vitest run passed 4,267 tests. Ten existing fixed-port
HTTPS exposure tests could not run because this host already owns
Tailnet listeners on ports 42000 and 52000. Clean GitHub CI is the final
full-suite result.
- Latest-head GitHub CI passed all required test, typecheck, build,
canary, e2e, policy, and security gates.
- Greptile completed at 5/5 with zero unresolved comments,
recommendations, or follow-ups.

## Risks

- Changed-file results can be up to 10 seconds old by default. Explicit
refresh remains available.
- A full queue returns a retryable `503` instead of waiting without a
bound.
- A scan that exceeds the default 8-second deadline returns a retryable
`504` and terminates its process group.
- Operators can tune all limits with documented environment variables.
Safe defaults protect local and shared servers.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex, GPT-5 family. The runtime does not expose the exact
deployment ID or context-window size. High reasoning, tool use, and code
execution were enabled.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-17 22:11:30 -05:00
DottaandPaperclip 7ea2068ef8 fix(files): only highlight accessible workspace file links (#11090)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Task comments can contain references to files in project and
execution workspaces.
> - Paperclip detected path-shaped inline code and showed it as an
actionable file chip.
> - The UI did not first confirm that the current board session could
open the file.
> - Missing, denied, ambiguous, remote, and unsupported files therefore
looked actionable and failed after a click.
> - This pull request adds an issue-scoped availability check and
promotes only confirmed files to chips.
> - The benefit is that the task thread shows a file action only when
that action can succeed.

## Linked Issues or Issue Description

**What happened?**

Task comments promoted path-shaped inline code to file chips before
Paperclip checked the file. A chip could point to a missing, denied,
ambiguous, remote, or non-previewable file. The action then failed after
the user selected it.

**Expected behavior**

Paperclip must show a file chip only after the server confirms that the
current board session can open the exact file reference. All other
path-shaped text must stay ordinary inline code.

**Steps to reproduce**

1. Add a task comment that contains inline code with a missing or
inaccessible workspace path.
2. Open the task thread as a board user.
3. Observe that the path looks like an actionable file chip.
4. Select the chip and observe that the file cannot open.

**Paperclip version or commit**

`19be4cf927` and earlier.

**Deployment mode**

Local dev and self-hosted server.

**Access context**

Board user.

## What Changed

- Added shared request, response, and validation contracts for batched
workspace-file availability checks.
- Added an issue-scoped server endpoint that resolves file references
with company, issue, workspace, and preview-access checks.
- Added bounded batch concurrency and tests for missing, denied,
ambiguous, remote, unsupported, and available files.
- Added an issue-scoped UI availability registry that deduplicates,
batches, caches, and invalidates file checks.
- Changed task-comment markdown rendering so only confirmed files get
chip styling and file-viewer behavior.
- Bound each chip to the exact workspace target that passed the
availability check.

## Verification

- `pnpm exec vitest run
packages/shared/src/workspace-file-resource.test.ts
server/src/__tests__/file-resources.test.ts
ui/src/components/MarkdownBody.test.tsx
ui/src/components/WorkspaceFileMarkdownBody.availability.test.tsx
ui/src/lib/remark-workspace-file-refs.test.ts
ui/src/lib/workspace-file-availability.test.ts` — 93 passed, 35 skipped.
- `pnpm check:token-gates` — clean.
- `pnpm -r typecheck` — passed.
- `pnpm build` — passed.
- `pnpm test:run` — all server and UI groups passed. One unchanged CLI
test saw the run-injected static AWS credentials and expected only its
local `AWS_PROFILE`. The same test passed, 8 of 8, after removing only
`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` from its process
environment.

## Risks

- File chips now appear after an asynchronous availability check, so
path-shaped text can briefly render as inline code.
- Availability results use the existing 30-second file-resource cache
window. File-resource invalidation forces a new check.
- The endpoint limits each request to 100 references and the client
chunks larger sets.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex with GPT-5. The service did not expose a more specific
model ID or context-window size. The agent used high-reasoning mode,
repository tools, command execution, and test execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-11 12:11:45 -04:00
DottaandPaperclip b07b2994cc feat: stamp responsible users on activity logs (#9731)
## Thinking Path

> - Paperclip is the control plane people use to manage AI-agent
companies and their work
> - The activity log is the generic audit spine for mutations across the
control plane
> - Activity rows identify agents and runs, but they do not persist the
responsible human upstream
> - Relying only on run joins loses attribution after run pruning and
misses agent API-key actions outside a run
> - This pull request resolves responsible-user attribution when each
activity row is written and stores it directly
> - The benefit is durable, queryable agent audit feeds without
rewriting historical provenance

## Linked Issues or Issue Description

### Problem or motivation

Agent activity records do not persist the responsible user, so
attribution can disappear when runs are pruned and no-run API-key
mutations cannot be attributed correctly.

### Proposed solution

Resolve attribution for each new activity row from the run, related
issue, active agent API key, or company default, in that order, and
persist the result directly.

### Alternatives considered

Read-time joins alone were rejected because pruned runs lose durable
attribution and out-of-run agent-key actions have no run to join.
Historical backfill was rejected because it would invent provenance.

### Roadmap alignment

This strengthens the durable audit-trail direction described in
`ROADMAP.md` without adding a new product surface.

## What Changed

- Added nullable `activity_log.responsible_user_id` plus
company/agent/time and company/responsible-user/time indexes.
- Added an idempotent forward-only migration with no historical
backfill.
- Added centralized write-time resolution: heartbeat run → issue
attribution → active agent API key → company default.
- Propagated authenticated API-key IDs through existing request-backed
`logActivity()` calls.
- Added unit coverage for every fallback and an embedded-Postgres
assertion for the no-run API-key stamping path.

## Verification

- `pnpm --filter @paperclipai/db typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/activity-log-responsible-user.test.ts
src/__tests__/authz-company-access.test.ts`
- Initial focused verification: 25 tests passed; migration safety
passed.
- Follow-up regression verification: 54 focused
attribution/company-skill/environment/issue-tree/tool-gateway tests
passed; server typecheck passed.
- GitHub: full build, typecheck, server shards, serialized suites, e2e,
security, and policy checks passed.

## Risks

- Adding two indexes to an existing large table can hold a write lock
while the transactional migration runs. The migration safety
suppressions document why `CONCURRENTLY` is unavailable under the
current Drizzle migration runner.
- Historical rows remain nullable by design; this avoids inventing
provenance and keeps the migration forward-only.
- API-key attribution requires request-backed activity call sites to
pass the authenticated key ID; this PR mechanically updates the existing
actor-based activity calls and covers the no-run path with integration
testing.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex, `gpt-5.4`; context-window size was not exposed by the
runtime. Medium reasoning with repository editing, terminal execution,
and test execution capabilities was used.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-07-16 20:54:55 -05:00
Jannes StubbemannandClaude Opus 4.7 7f2ed0ad90 security(server): close cross-tenant existence oracle (404 instead of 403) (#3967)
## Thinking Path

> - Paperclip orchestrates AI agents for zero-human companies
> - In a multi-tenant deployment, route handlers that take a resource id
(`issue`, `goal`, `project`, `approval`, etc.) look the resource up by
id and then call `assertCompanyAccess` on its `companyId` — 404 if it
doesn't exist, 403 if it exists in another tenant
> - The split status codes are a classic *existence oracle*: any
authenticated user can enumerate ids across tenants by probing for the
403/404 boundary, mapping out which issues, labels, approvals, etc.
exist in other customers' tenants even when they cannot read the
contents
> - The right fix is a single uniform 404 for both "not found" and
"found but cross-tenant", which collapses the oracle but still preserves
write-path checks (active membership, viewer-readonly) for *authorized*
tenants
> - This pull request adds a non-throwing `hasCompanyAccess(req,
companyId)` helper plus a `getAccessibleResource` wrapper that ~130
handlers across 14 route files now use, folding the access check into
the existence check while still running `assertCompanyAccess` for
authorized tenants so viewer-readonly / inactive-membership rejections
fire unchanged on write paths
> - The benefit is closing a multi-tenant information leak without
breaking write-path security or single-tenant local-first behavior

## Linked Issues or Issue Description

Refs #709 — asks for company-scope regression coverage across
approval/activity/access routes, because a subtle route refactor could
leak cross-tenant data; this PR hardens exactly those surfaces (uniform
404 across 14 route files including `approvals`, `activity`, `secrets`)
and updates cross-tenant expectations in test files. It does not add the
full coverage matrix #709 asks for — hence Refs, not Closes.

No existing issue covers the oracle itself — described in-PR:

- Route handlers returned 404 for "not found" but 403 for "exists in
another tenant", a classic *existence oracle*: any authenticated user
could enumerate ids across tenants by probing the 403/404 boundary.
- That maps out which issues, labels, approvals, etc. exist in other
customers' tenants even when their contents are unreadable.
- Fix: a uniform 404 for both cases, while keeping write-path checks
(active membership, viewer-readonly) for authorized tenants.

## What Changed

- **`server/src/routes/authz.ts`** — new `hasCompanyAccess(req,
companyId): boolean` helper alongside the existing
`assertCompanyAccess`. Docstring spells out the two-step pattern (404
gate, then `assertCompanyAccess` for write-path checks). The helper
mirrors `assertCompanyAccess`'s company-scope semantics exactly — in
particular, signed-in instance admins do **not** get blanket access to
companies they are not a member of (the repo's `authz-company-access`
tests pin that behavior for `assertCompanyAccess`; an earlier draft of
the helper accidentally widened it for reads).
- **`getAccessibleResource(req, res, lookup, notFoundMessage)`** — the
safe thing is now the easy thing. One helper wraps the whole pattern
(uniform 404 for missing/cross-tenant, then `assertCompanyAccess` for
write-path membership checks) and ~130 handlers across 14 route files
use it:
  ```ts
const goal = await getAccessibleResource(req, res, svc.getById(id),
"Goal not found");
  if (!goal) return;
  ```
Files: `activity`, `agents`, `approvals`, `assets`, `costs`,
`environments`, `execution-workspaces`, `file-resources`, `goals`,
`issue-tree-control`, `issues`, `projects`, `routines`, `secrets`.
Handlers with bespoke not-found behavior (the legacy `200 []` contract,
audit-logged denials in `file-resources`, null-returning authz helpers)
compose `hasCompanyAccess` directly using the documented two-step
pattern:
  ```ts
// step 1: close the oracle (uniform 404 for both not-found and
cross-tenant)
  if (!existing || !hasCompanyAccess(req, existing.companyId)) {
    res.status(404).json({ error: "Goal not found" });
    return;
  }
// step 2: enforce write-path membership checks for authorised tenants
(no-op on GET)
  assertCompanyAccess(req, existing.companyId);
  ```
Routes where `companyId` comes from *request input*
(`req.params.companyId`, `req.body.companyId`, e.g. in `companies.ts`
and `plugins.ts`) deliberately retain plain `assertCompanyAccess` —
there's no existence oracle to close because the companyId is an input,
not a discovered value.
- **Full-sweep coverage** — a scripted audit of every
`assertCompanyAccess(req, <resource>.companyId)` call site in
`server/src/routes/` found ~55 lookup-then-assert pairs the first pass
missed; all are now gated. Notable ones: the
`/secret-provider-configs/:id` CRUD routes, the agents
instructions-bundle/config-revision/skills-sync routes (which check
access via the `assertCanUpdateAgent` / `assertCanReadAgent` /
`assertCanManageInstructionsPath` helpers), `POST
/heartbeat-runs/:runId/watchdog-decisions`, `GET
/issues/:id/cost-summary`, the environment + environment-lease GET
routes, all six issue-tree-control routes, ~24 issue sub-resource routes
(document annotations, interactions, approvals links, recovery actions,
plan decompositions, lock/unlock), and the three workspace file-resource
routes (these throw `notFound` instead of `forbidden` inside their
audit-logging wrappers, so denied attempts are still activity-logged
server-side while the client sees a uniform 404).
- **Helpers made self-defending** — `assertCanUpdateAgent` /
`assertCanReadAgent` / `assertCanManageInstructionsPath` (agents) and
`assertCanManage{Project,Execution}WorkspaceRuntimeServices` throw
`notFound` for cross-tenant resources before their `assertCompanyAccess`
step, so a future caller that forgets the route-level gate still can't
reopen the oracle.
- **Pattern enforcement** — new `authz-existence-oracle-guard.test.ts`
statically scans `server/src/routes/*.ts` and fails CI on any
`assertCompanyAccess(req, <resource>.companyId)` call that is not
preceded by a `hasCompanyAccess` gate, with an explicit allowlist (plus
staleness check) for the request-input cases. New routes that regress to
the 403/404 split fail the suite with a message pointing at the
documented pattern.
- **Tests** — cross-tenant expectations updated from 403→404 where
routes are now gated; new `hasCompanyAccess` unit tests in
`authz-company-access.test.ts` pin the
instance-admin/local-implicit/agent/none semantics in lockstep with
`assertCompanyAccess`; `write-path-membership.test.ts` (added in an
earlier round) confirms viewer/inactive users are still rejected on
writes.
- **One legacy-contract preserve** — `GET /heartbeat-runs/:runId/issues`
still returns `200 []` for both "doesn't exist" and "cross-tenant" so
the legacy contract is preserved while the oracle stays closed.

## Verification

- `pnpm run typecheck` — PASS.
- `pnpm -F @paperclipai/server exec vitest run` — full server suite
green locally apart from 4 pre-existing local-environment failures
(`paperclip-skill-utils` ×2 and `workspace-runtime` ×1 are
cwd/git-environment dependent — verified identical on a clean checkout
of the base; `heartbeat-process-recovery` is the known macOS flake).
- The new `authz-existence-oracle-guard` test sweeps
`server/src/routes/*.ts` and confirms no remaining
`assertCompanyAccess(resource.companyId)` site without a
`hasCompanyAccess` gate; the only allowlisted holdouts take `companyId`
from request input.

## Risks

- **API contract narrowing.** Any client that specifically checked for
`403` on cross-tenant access now sees `404`. This is a strict narrowing
(one status instead of two for the same negative outcome) and matches
what a client should expect for any id it can't access.
- **Write-path checks preserved.** `assertCompanyAccess` still runs
after the 404 gate on write routes, so viewer-readonly /
inactive-membership rejections fire unchanged for legitimate users.
- **Instance-admin scope unchanged.** `hasCompanyAccess` denies
signed-in instance admins without an explicit membership, exactly like
`assertCompanyAccess` (pinned by unit tests) — so the gate introduces no
new read access for admins.
- **Single-tenant local-first deploys** behave identically — the helper
short-circuits to `true` for `local_implicit` sessions.
- No new env vars, no deployment-mode switch.

## Model Used

Claude Opus 4.7 (1M context), extended thinking mode; completeness sweep
+ instance-admin parity fix by Claude Fable 5 (1M context).

## Checklist

- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] Thinking path traces from project context to this change
- [x] Model used specified
- [x] Checked ROADMAP.md — part of the multi-tenant hardening initiative
- [x] Tests run locally and pass
- [x] Added/updated cross-tenant 404 expectations across test files
- [x] No UI changes
- [x] Documented risks above
- [x] Will address all Greptile and reviewer comments before merge

Part of the multi-tenant hardening initiative — see also #5864
(per-company JWT keys) and #5865 (plugin tables `company_id`).

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-14 15:53:09 -07:00
Dotta 8f7282066e Add workspace file downloads
Add first-class workspace file downloads, broader attachment content-type support, and the stream-lifetime limiter fix from PR review.
2026-06-26 06:05:57 -05:00
468edd8b22 Add workspace file viewer and artifact links (#7681)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent work is issue-centered, and reviewers often need to inspect
files, artifacts, and path references produced during that work.
> - Before this branch, workspace-relative paths and artifact file
references were not first-class inspectable objects in the board UI.
> - Safe file viewing needs shared resource contracts, server-side
workspace boundary checks, and UI that opens files without exposing
arbitrary host paths.
> - The workspace file viewer branch needed to stay as one active PR and
be rebased onto current `paperclipai/paperclip:master` for review.
> - This pull request adds the workspace file resource API, issue-page
file viewer and browser, markdown file-reference links, and artifact
file chips.
> - The benefit is that board users can inspect relevant files from
issue context while preserving workspace boundaries and auditability.

## Linked Issues or Issue Description

No public GitHub issue exists for this branch. Internal Paperclip
issues: `PAP-1953`, `PAP-10539`, `PAP-10733`.

Problem / motivation:
- Board users need to open workspace-relative files mentioned by agents
or attached as work-product metadata without switching to a terminal.
- The UI needs to support both direct file-path opening and workspace
browsing/searching from an issue page.
- The server must enforce company access, workspace boundaries, size
limits, rate limits, and safe audit logging.

Related PR:
- Prior closed attempt: #4442
- Single active PR for this branch: #7681

## What Changed

- Added shared workspace file resource types, validators, and
workspace-file `resourceRef` metadata validation for work products.
- Added server routes/services for resolving, listing, and previewing
workspace-relative files with access checks, scan caps, list-specific
limits, and audit logging.
- Added the issue file viewer provider, sheet, workspace browser,
command-palette action, markdown workspace-file autolinks, and artifact
file chips.
- Updated issue workspace UI and stories/tests for file browsing and
workspace file opening.
- Rebased the branch onto current `paperclipai/paperclip:master` and
updated the existing single PR branch.
- Addressed current-head Greptile follow-ups by applying `offset`
consistently across search/recent/changed file listings, restoring
stopped-service port ownership checks before auto-port reuse, and
stabilizing the workspace browser pagination test.

## Verification

Current local verification after rebase to `public/master`:
- `pnpm exec vitest run packages/shared/src/work-product.test.ts
server/src/__tests__/file-resources.test.ts
server/src/__tests__/instance-settings-routes.test.ts
server/src/__tests__/instance-settings-service.test.ts
server/src/__tests__/workspace-runtime.test.ts
ui/src/components/FileViewerSheet.test.tsx
ui/src/components/FileViewerSheet.copy.test.tsx
ui/src/components/WorkspaceFileBrowser.test.tsx
ui/src/components/WorkspaceFileMarkdownBody.test.tsx
ui/src/context/FileViewerContext.test.ts
ui/src/lib/remark-workspace-file-refs.test.ts
ui/src/lib/workspace-file-parser.test.ts
ui/src/components/IssueWorkspaceCard.test.tsx` - 13 files passed, 197
tests passed.
- `pnpm -r --filter @paperclipai/shared --filter @paperclipai/server
--filter @paperclipai/ui typecheck` - passed.
- `pnpm exec vitest run ui/src/components/WorkspaceFileBrowser.test.tsx`
- 1 file passed, 25 tests passed.
- `pnpm exec vitest run server/src/__tests__/file-resources.test.ts
server/src/__tests__/workspace-runtime.test.ts` - 2 files passed, 90
tests passed.
- `pnpm -r --filter @paperclipai/server typecheck` - passed.
- Confirmed branch is `0` behind and `46` ahead of current
`public/master` after rebase and follow-up commits.
- Confirmed the PR diff does not include `pnpm-lock.yaml`.
- Confirmed the PR diff does not include `.github/workflows` changes.
- Searched GitHub for duplicate or related workspace file viewer
PRs/issues; #4442 is the prior closed attempt and this PR is the single
active PR for the branch.
- No screenshots were committed; the task explicitly asked not to add
design screenshots or images unless they were part of the work.

Current remote verification on head
`a698a7bc10137baf7d25bd5722e1d6e0343387c1`:
- Greptile Review - success, 64 files reviewed, 0 comments added, no
unresolved Greptile review threads.
- PR workflow `verify` - success.
- Typecheck + Release Registry, General tests, workspace test shards,
serialized server suites, Build, Canary Dry Run, e2e, Socket, and Snyk -
success.
- `security-review` - neutral, with output saying a draft advisory was
filed for maintainer review and is not a merge block.
- `commitperclip PR Review / review` - cancelled after the security gate
detected flags and timed out while creating/reviewing the advisory. I
reran it once and it cancelled the same way; no actionable code/test
failure was exposed in the job logs.

## Risks

- This is a broad UI/server feature PR, so review needs to pay attention
to route authorization, workspace boundary handling, and markdown
autolink false positives.
- Workspace browsing intentionally caps list results and scan depth;
very large workspaces may require users to refine search terms.
- Remote workspace preview remains unavailable until remote file-access
support is implemented.
- The neutral commitperclip security-review advisory needs maintainer
review, but the check output says it is not a merge block.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected - check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex, GPT-5 coding agent in a Paperclip/Codex local tool-use
environment, medium reasoning, with shell/GitHub CLI tool use for branch
inspection, verification, rebase, PR update, Greptile review, and CI
inspection.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-09 17:17:43 -05:00