Commit Graph

7 Commits

Author SHA1 Message Date
Dylan Roy 6a546e8a9a
fix(server): align agent run JWT default TTL with documented 48h default (#10176)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Local adapters (claude_local, codex_local) run agent heartbeats as
child processes, with a short-lived run JWT injected as
`PAPERCLIP_API_KEY` at spawn time
> - That JWT is minted exactly once, when the adapter spawns the process
— its TTL must therefore cover the entire wall-clock life of the run,
not just a prompt startup
> - On laptops the gap between spawn and first real execution can be
huge: a timer heartbeat scheduled while the lid is closed fires during a
~2s macOS dark wake, the machine re-sleeps immediately, and the frozen
child only executes during a later, longer wake — over an hour of
wall-clock delay in observed runs
> - The server's default TTL was 1h, so those sessions started with an
already-expired `PAPERCLIP_API_KEY` and every control-plane call 401'd;
the agent had to recover by manually minting a fresh key
> - The 1h default was also a spec drift: the CLI `env` command
(`DEFAULT_AGENT_JWT_TTL_SECONDS`) and the agent-authentication design
doc both document 172800s (48h)
> - This pull request realigns the server default to 48h and documents
the host-suspension constraint at the mint site and in the regression
test
> - The benefit is that lid-closed/suspended-host heartbeat runs come up
with a valid credential, and the three places that state the default now
agree

## Linked Issues or Issue Description

No public GitHub issue exists for this; per the bug-report template:

- **What happened:** A timer-driven heartbeat run on a MacBook (lid
closed, on battery) was invoked during a ~2s dark wake. The adapter
spawned the CLI and logged init within 2s, then the host re-slept and
the session sat frozen for ~64 minutes until a longer dark wake let it
execute. By then the injected run JWT (1h TTL, minted at spawn) had
expired, so every API call from the agent returned 401 and the run could
only recover via a manually minted key. A second agent's run the same
night showed the identical signature (output timestamps exactly matching
`pmset -g log` dark-wake windows).
- **Expected behavior:** A run that starts late because the host was
suspended should still have a valid `PAPERCLIP_API_KEY` when it finally
executes.
- **Steps to reproduce:** Run Paperclip on a laptop with a
`claude_local` agent on a timer heartbeat; close the lid on battery
overnight; observe a run invoked during a dark wake whose session
executes >1h later with an expired token (compare run-log timestamps to
`pmset -g log` sleep/wake entries).
- **Version/commit:** current `master` (14f20be9); local trusted
deployment mode.

Related context: #5864 introduced per-company signing keys in this same
module (no TTL changes).

## What Changed

- `server/src/agent-auth-jwt.ts`: default `ttlSeconds` for local agent
run JWTs raised from `60 * 60` (1h) to `60 * 60 * 48` (48h), matching
`DEFAULT_AGENT_JWT_TTL_SECONDS` in `cli/src/commands/env.ts` and
`doc/plans/2026-02-18-agent-authentication-implementation.md`; comment
documents why the TTL must cover host-suspension gaps
- `server/src/agent-auth-jwt.ts`: stale "~1h by default" reference in
the legacy-fallback guidance updated to 48h
- `server/src/__tests__/agent-auth-jwt.test.ts`: default-TTL regression
test updated to assert 48h and explain the constraint
- `PAPERCLIP_AGENT_JWT_TTL_SECONDS` remains the explicit override knob;
operators who set it see no behavior change

## Verification

- `cd server && pnpm vitest run src/__tests__/agent-auth-jwt.test.ts
src/__tests__/agent-auth-middleware.test.ts` — 24/24 pass locally
- Review that the three default sources now agree:
`server/src/agent-auth-jwt.ts` (`60 * 60 * 48`),
`cli/src/commands/env.ts` (`DEFAULT_AGENT_JWT_TTL_SECONDS = "172800"`),
design doc (`default: 172800`)
- Manual: on a laptop, set no TTL env, trigger a heartbeat, `echo
$PAPERCLIP_API_KEY` inside the run and decode the JWT — `exp - iat` is
172800

## Risks

- Longer-lived bearer tokens widen the leak window if a run token is
exfiltrated. Mitigations already in place: tokens are
per-company/per-instance signed (#5864), bound to a `run_id`, and never
persisted server-side. Operators wanting shorter tokens keep the
`PAPERCLIP_AGENT_JWT_TTL_SECONDS` override.
- The legacy master-secret fallback window guidance ("disable ~one TTL
after deploy") lengthens accordingly; the comment now states 48h
explicitly.
- Follow-up ideas intentionally out of scope: rejecting run JWTs whose
run has terminated (server-side revocation check), and holding a power
assertion (`caffeinate`-style) for the duration of local adapter runs so
dark-wake-spawned runs keep the host awake.

## Model Used

- Claude (Anthropic) — Fable 5, model ID `claude-fable-5`, via Claude
Code 2.1.x under Paperclip's `claude_local` adapter; extended thinking
and full tool use (shell, file edits, test execution) 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
- [ ] 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-12 16:44:20 -07:00
Dotta b13eb5b2b5
Skill Studio: three-pane skill IDE with sandboxed test runs (#9241)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Skills Manager gives operators a reusable skill layer, but
iteration still required manual edits, ad hoc prompts, and indirect run
inspection.
> - Skill authors need a focused workflow for editing skill files,
saving representative test inputs, and running those inputs through an
agent without exposing harness tasks as normal company work.
> - The backend therefore needs durable test inputs, reusable run
templates, hidden harness issues, scoped run execution, retention
metadata, and read-containment rules around hidden work.
> - The frontend needs a three-pane Studio that keeps skill files, saved
inputs/templates, and run output/history visible together while
preserving the existing design system and token rules.
> - This pull request ships that Skill Studio surface end to end:
database migrations, shared contracts, server APIs/services, hidden
harness execution behavior, UI routes/components, and focused tests.
> - The benefit is faster and safer skill iteration, with inspectable
outputs and fewer ways for internal harness work to leak into normal
task lists, costs, or adjacent read APIs.

## Linked Issues or Issue Description

No public GitHub issue exists for this feature. Feature request summary:

- Problem: Skill authors need to edit and test company skills in one
place instead of switching between the skill detail page, task creation,
run output, and manual prompt history.
- Proposed solution: Add a Skill Studio workbench with saved inputs,
reusable templates, hidden sandboxed test runs, live run status, output
inspection, run history, rerun/delete controls, and frontmatter-aware
editing.
- Expected users: Paperclip operators and agent-company maintainers who
create, fork, import, and tune skills.
- Related public PRs: Supersedes #9205, which was replaced so the public
PR branch name follows contributor policy.
- Duplicate search: searched public GitHub issues and PRs for "Skill
Studio"; no other active public issue or PR directly covers this
feature.

## What Changed

- Added database migrations for Skill Studio test inputs, test runs,
test run retention, and reusable run templates.
- Added shared Skill Studio types, validators, route helpers,
frontmatter utilities, and status handling.
- Added server services and routes for saved inputs, test runs,
templates, reruns, terminal-run deletion, hidden harness issue
execution, and run-detail hydration.
- Strengthened hidden-issue read containment across issue-adjacent
routes and cost rollups used by skill test harness work.
- Added the Skill Studio UI with skill file editing, frontmatter
editing, saved inputs, templates, run creation/cancel/rerun/delete
flows, output rendering, history, route support, and responsive pane
behavior.
- Added focused backend, shared, and UI tests for the new APIs, routing
logic, editor/run behavior, hidden-issue containment, and migration
safety.
- Rebased onto current `master`, removed the generated lockfile diff
from the PR, and verified no workflow files are changed.

## Verification

- [x] `pnpm --filter @paperclipai/db check:migrations`
- [x] `pnpm check:token-gates`
- [x] `pnpm exec vitest run
server/src/__tests__/company-skills-service.test.ts
server/src/__tests__/company-skills-routes.test.ts
server/src/__tests__/company-skill-test-runs-service.test.ts
ui/src/lib/skill-studio.test.ts ui/src/pages/SkillStudio.test.tsx` — 5
files, 132 tests passed
- [x] Greptile review on the latest PR head
- [x] GitHub PR checks on the latest PR head

## Risks

- Medium risk because this is a broad feature touching database schema,
server orchestration, issue visibility, and a large UI surface.
- Hidden harness issue containment is security-sensitive; this PR
includes regression coverage for adjacent read paths and cost rollups.
- The new migrations are additive and use idempotent guards where
applicable, but deployed databases that previously tested draft
migration numbers should still be checked carefully.
- The UI depends on a new resizable panels package in `ui/package.json`;
the lockfile is intentionally left to repository automation.

> 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 with shell, git, and GitHub CLI tool
use. Earlier feature commits include assistance from other Paperclip
coding agents; this PR preparation, rebase, cleanup commit, and PR body
were completed by OpenAI Codex in a Paperclip worktree.

## 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>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 13:08:56 -05:00
Dotta 57a7da81ee
[codex] Isolate run JWTs by control-plane instance (#9162)
Bind local agent run JWT signing and validation to the issuing Paperclip instance while preserving rollout compatibility for legacy tokens.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-07-07 09:27:19 -05:00
Dotta ad961227f5
feat(secrets): add user-specific runtime secrets (#8825)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent runs often need provider credentials, API tokens, and other
environment-bound secrets.
> - Company-level secrets work for shared credentials, but they do not
model values that should differ by human operator.
> - Without a user-scoped model, a run can dispatch without knowing
whether the responsible human has supplied the needed value.
> - Paperclip also needs run attribution to make those user-scoped
runtime checks deterministic and auditable.
> - This pull request adds user-specific secret definitions, per-user
values, environment bindings, responsible-user attribution, and runtime
resolution gates.
> - The benefit is that teams can define the secret once, let each user
provide their own value, and block runs before dispatch when required
user secrets or active definitions are unavailable.

## Linked Issues or Issue Description

Refs #224
Refs #6057

This PR implements user-specific secret support as a core
secret-management capability rather than a one-off adapter setting. It
is related to existing public work on company secrets UI and runtime
secret refs, but is distinct because the value is owned by the
responsible user and resolved at run dispatch time.

Related PR search before opening found existing secrets work such as
#1550, #8256, #8614, #8634, and #8647; none of those add the full
user-secret definition/value/runtime gate covered here.

## What Changed

- Added user-secret definitions and per-user "My secrets" values,
keeping stored values out of access metadata.
- Added `user_secret_ref` environment bindings and UI affordances to
pick them alongside existing secret refs.
- Added responsible-user runtime resolution so user-secret refs resolve
against the human responsible for the run.
- Added pre-dispatch missing-secret gates so runs fail before adapter
dispatch when required user values are absent or definitions are
inactive.
- Added low-trust allowlist hardening for user-secret runtime access.
- Added issue, routine, run, and agent API key responsible-user
attribution and fail-closed dispatch behavior when attribution cannot be
resolved.
- Added denial-copy mapping so responsible-user authorization failures
surface as actionable run outcomes instead of opaque setup failures.
- Added OpenAPI documentation for the user-secret routes.
- Rebases cleanly on current `master`; migrations were renumbered
incrementally as `0128_user_specific_secrets`,
`0129_agent_api_key_responsible_user`, and
`0130_run_responsible_user_invariant` after upstream `0126`/`0127`
migrations.
- Removed previously committed local design screenshots so the PR
contains code/docs/tests only.

## Verification

- PASS: PR head `2527febd106bcf3ca264ca0da7fca491084192d6` is based on
`paperclipai/paperclip:master`.
- PASS: `git diff --check`
- PASS: `git diff --name-only public/master...HEAD | rg
'^(pnpm-lock\\.yaml|\\.github/workflows/|screenshots/)' || true`
produced no files.
- PASS: migration journal audit confirmed unique indexes through `130`
with tail entries `0126_issue_comment_derived_attribution`,
`0127_environment_custom_images_instance_scoped`,
`0128_user_specific_secrets`, `0129_agent_api_key_responsible_user`, and
`0130_run_responsible_user_invariant`.
- PASS: `pnpm --filter @paperclipai/ui typecheck`
- PASS: `pnpm --filter @paperclipai/server typecheck`
- PASS: `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-responsible-user-invariant.test.ts`
- PASS: `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-active-run-output-watchdog.test.ts
src/__tests__/heartbeat-stale-queue-invalidation.test.ts
src/__tests__/heartbeat-workspace-finalize-branch.test.ts
src/__tests__/issue-monitor-scheduler.test.ts`
- PASS: `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-comment-wake-batching.test.ts
src/__tests__/heartbeat-retry-scheduling.test.ts
src/__tests__/heartbeat-accepted-plan-workspace-refresh.test.ts
src/__tests__/heartbeat-plugin-environment.test.ts`
- PASS: `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/low-trust-red-team-routes.test.ts`
- PASS: `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/secrets-service.test.ts` (55 tests)
- PASS: `pnpm vitest run server/src/__tests__/secrets-routes.test.ts
server/src/__tests__/secrets-service.test.ts` (89 tests after final
Greptile cleanup fixes)
- PASS: `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-issue-liveness-escalation.test.ts` (17 tests
after the final rebase CI fix)
- PASS: focused server Vitest batches covering heartbeat recovery,
project env, plugin env, routines, low-trust, pipelines, monitors,
watchdog, and stale queue paths.
- PASS: GitHub checks are green on
`2527febd106bcf3ca264ca0da7fca491084192d6`, including Typecheck +
Release Registry, Build, General tests, serialized server suites, e2e,
Canary Dry Run, verify, security checks, and Greptile Review.
- PASS: Greptile Review completed successfully on
`2527febd106bcf3ca264ca0da7fca491084192d6` with Confidence Score 5/5,
and GraphQL review-thread audit returned zero unresolved non-outdated
threads.

## Risks

- Runtime behavior now depends on a run having a correct responsible
user; missing or incorrect responsibility assignment can block runs
before adapter dispatch.
- `user_secret_ref` bindings intentionally expose metadata without
values, but UI/API callers may need to handle the new binding kind
explicitly.
- External secret providers and IAM policies are not automatically
provisioned by this PR; operators still need to configure provider-side
access for non-local vaults.
- The PR is broad across db/shared/server/UI/runtime paths, so release
validation should include both API and UI secret workflows before merge.
- The migration renumbering is intentionally incremental after upstream
migrations; the branch migrations use guarded
column/table/index/constraint creation so users who tested the older
draft numbering should not hit duplicate DDL for the existing objects.

> 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-based coding agent (`gpt-5`), Codex local adapter
with shell/tool use and code execution. Context window and internal
reasoning mode are not exposed by the runtime.

## 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>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 05:58:20 -05:00
Jannes Stubbemann 70357b961f
feat(security): per-company JWT signing keys for multi-tenant isolation (#5864)
## Thinking Path

> - Paperclip orchestrates AI agents for zero-human companies
> - Agents authenticate to the server with a JWT signed by the
deployment's master secret
> - In a multi-tenant deployment, all agents from every tenant are
signed with the *same* key, so a leak (CI/staging dump, hostile
contractor with infra access, supply-chain) lets the attacker mint
tokens for *any* tenant
> - The same master secret also issued tokens with a 48-hour TTL, giving
any leaked token a two-day window of validity even after rotation
> - This pull request derives a per-company signing key via
`HMAC-SHA256(master, "jwt:<companyId>")` and reduces the default TTL to
1h; the verifier tries the per-company key first and falls back to the
master secret only for tokens issued before this change so no agent gets
locked out on deploy
> - The benefit is multi-tenant key isolation (a leak of one company's
derived key cannot forge tokens for another) and a tighter blast-radius
on any leaked token, with zero local-first impact (single-tenant deploys
derive their one company's key the same way and continue to work
unchanged)

## Linked Issues or Issue Description

Refs #5288 — a separate key-hygiene finding in the same module
(`agent-auth-jwt.ts` falls back to `BETTER_AUTH_SECRET` as the JWT
signing secret). Related agent-JWT trust-model concern, but not fixed by
this PR — the master-secret fallback selection is unchanged here.

No existing issue covers this PR's problem directly — described in-PR:

- In a multi-tenant deployment, agents from every tenant get JWTs signed
with the *same* master key, so a single leak (CI/staging dump, hostile
contractor, supply chain) lets the attacker mint tokens for *any*
tenant.
- The same master secret issued tokens with a 48-hour TTL, giving any
leaked token a two-day validity window even after rotation.
- Fix: derive a per-company signing key via `HMAC-SHA256(master,
"jwt:<companyId>")` and reduce the default TTL to 1h, with a
master-secret verification fallback so pre-existing tokens are not
locked out on deploy.

## What Changed

- **`server/src/agent-auth-jwt.ts`**
- New `deriveCompanySigningKey(masterSecret, companyId)` — `HMAC-SHA256`
with domain-separated input (`jwt:<companyId>`) so the master secret can
be safely reused for other HMAC purposes in the future without
cross-protocol risk.
  - `signAgentJwt` always signs with the derived per-company key.
- `verifyAgentJwt` reads `company_id` from the token's (untrusted) claim
payload, looks up the candidate derived key, and verifies. If that fails
AND a master secret is set, it falls back to verifying with the raw
master secret — pre-existing tokens validate until they expire.
Verification still fails if the signature doesn't bind.
- Default TTL: `60 * 60 * 48` → `60 * 60`. Existing
`PAPERCLIP_AGENT_JWT_TTL_SECONDS` override still wins.
- **`PAPERCLIP_AGENT_JWT_DISABLE_LEGACY_FALLBACK`** (optional, default
off) — operators set this ~one TTL after deploying to sunset the
master-secret verification fallback entirely, closing the window in
which a leaked master secret could forge arbitrary-`exp` tokens for any
tenant.
- **`server/src/__tests__/agent-auth-jwt.test.ts`** (6 new cases)
- Per-company isolation via tamper: token for company A fails when
verified for company B.
- Legacy-token verification path: tokens signed with the raw master
secret still verify.
  - Default TTL is 1h.
- Legacy fallback toggle: master-secret tokens accepted when unset,
rejected when enabled, and per-company tokens unaffected either way.

## Verification

- `pnpm --filter @paperclipai/server run typecheck` — clean.
- `npx vitest run agent-auth-jwt` — 11/11 pass (6 new + 5 existing).
- Manual: token signed for company A under per-company key fails when
verified against company B's derived key.

## Risks

- **Backward-compatible verification**, so no agent gets locked out on
deploy — but operators relying on hot-swapping the master secret should
note that pre-existing tokens *will* keep validating against the master
key until their TTL elapses, unless
`PAPERCLIP_AGENT_JWT_DISABLE_LEGACY_FALLBACK=true` is set to end the
fallback window explicitly.
- **TTL reduction is a default, not a hard cap.** Operators who relied
on the 48h window can override via env. If 1h is too aggressive for
upstream taste, happy to gate the change behind an env var.
- **No new required env vars.** Single-tenant local-first deploys derive
one company's key the same way and behave identically to today.
- **Domain-separated HMAC input** (`jwt:<companyId>`) means the master
secret can be safely reused for other future HMAC purposes without
cross-protocol risk.

## Model Used

Claude Opus 4.7 (1M context), extended thinking mode; rebase +
legacy-fallback sunset documentation 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 (`agent-auth-jwt` 11/11)
- [x] Added per-company-isolation, legacy-fallback, and TTL-default
tests
- [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 #3967
(cross-tenant 404 oracle) and #5865 (plugin tables `company_id`).

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-11 18:00:22 -07:00
ErgonaWorks 81ff9fb311 fix(agent-auth): fall back to BETTER_AUTH_SECRET when PAPERCLIP_AGENT_JWT_SECRET is absent
`jwtConfig()` in `agent-auth-jwt.ts` only read `PAPERCLIP_AGENT_JWT_SECRET`.
Deployments that set `BETTER_AUTH_SECRET` (required for authenticated mode)
but omit the separate `PAPERCLIP_AGENT_JWT_SECRET` variable received the
warning "local agent jwt secret missing or invalid; running without injected
PAPERCLIP_API_KEY" on every `claude_local` / `codex_local` heartbeat run,
leaving agents unable to call the API.

Every other auth path in the server (`better-auth.ts`, `index.ts`) already
falls back from `BETTER_AUTH_SECRET` to cover this case — align `jwtConfig()`
with the same pattern.

Adds a test for the fallback path.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 19:10:00 +00:00
Forgotten fe6a8687c1 Implement local agent JWT authentication for adapters
Add HS256 JWT-based authentication for local adapters (claude_local, codex_local)
so agents authenticate automatically without manual API key configuration. The
server mints short-lived JWTs per heartbeat run and injects them as PAPERCLIP_API_KEY.
The auth middleware verifies JWTs alongside existing static API keys.

Includes: CLI onboard/doctor JWT secret management, env command for deployment,
config path resolution from ancestor directories, dotenv loading on server startup,
event payload secret redaction, multi-status issue filtering, and adapter transcript
parsing for thinking/user message kinds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 16:46:45 -06:00