Files
PaperClipAI/server
514d8738cf fix: keep the JWT run-mismatch audit when the claimed run is missing (#13183)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Rejected agent JWTs whose run header disagrees with the signed claim
are audited into `activity_log` so operators can see spoof attempts
> - The audit insert writes the claimed run id into `run_id`, which is a
real foreign key to `heartbeat_runs`; a bogus claim violates the FK and
the catch swallows it, so the audit row is silently lost exactly when it
matters most
> - This pull request verifies the claimed run exists before trusting it
into the FK column; the claim itself stays in `entityId` and `details`
> - The benefit is a reliable audit trail for rejected credentials

## Linked Issues or Issue Description

Refs #10498 — the anonymous fall-through half was fixed on master
already (every failed-credential branch now returns an explicit
401/403/422); this closes the residual FK-swallowed audit defect.

## What Changed

- `server/src/middleware/auth.ts`: `auditAgentJwtRunHeaderMismatch`
looks up the claimed run id and populates `runId` only when the row
exists; `entityId` and `details.claimRunId` carry the claim either way.
- `server/src/__tests__/agent-auth-middleware.test.ts`: a mismatch whose
claimed run does not exist still produces the audit row with `runId`
omitted.

## Verification

- `pnpm --filter @paperclipai/server vitest run
src/__tests__/agent-auth-middleware.test.ts` — 17 tests pass.
- Before: a run-header mismatch whose claimed run id was absent from
`heartbeat_runs` violated the `activity_log.run_id` FK, and the catch
swallowed it — no audit row. After: the row lands with `runId` null and
the claim preserved in `entityId`/`details`.

## Risks

- Low risk. One extra indexed select on a rare rejection path; the audit
payload is unchanged apart from `runId` now being conditional on
existence.

## Model Used

- Anthropic Claude — SWE-2 Max agent via Devin CLI, tool use and code
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: Waseem Ilyas <1478353+Waseemilyas@users.noreply.github.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-15 15:43:56 -07:00
..