mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-15 12:28:10 +02:00
## Thinking Path > - Paperclip helps people manage AI agents and their work. > - Routines can start that work when another app sends a webhook. > - Local and private URLs often cannot receive events from public services. > - HTTPS alone does not make a Tailscale address public. > - This pull request explains these limits during setup and editing. > - Users can still finish setup for senders on their own network. ## Linked Issues or Issue Description Refs #13637. Webhook setup needs a clear warning when the generated URL appears local, private, or unencrypted. The warning must explain how to make the endpoint reachable without blocking private-network use. ## What Changed - Add a shared warning banner to the Connect, Check connection, and Edit webhook views. - Distinguish localhost, private network addresses and domains, HTTP, and Tailscale hostnames. - Explain the difference between Tailscale Serve and Funnel. Link to the Paperclip HTTPS guide. - Add five full-page Storybook examples, design guide examples, and documentation. - Add URL classification tests and a regression test that finishes setup despite the warning. ## Verification - Passed 41 focused URL and trigger-flow tests. - Passed workspace typecheck, workspace build, token gates, and Storybook build. - Browser-tested the Tailscale story through Check connection, Finish setup, and Edit webhook. The warning stays visible and does not block setup. - Open Product / Routines / Webhooks stories 11–15 to review the warning states. - All 54 PR checks passed, including the full test matrix and eight browser shards; two optional Storybook jobs were skipped by workflow policy. - The server supervisor readiness test timed out once in CI, then passed on rerun and locally (6 tests). - The duplicate local full-suite run was stopped after the complete CI matrix passed. - Greptile: 5/5 on the current commit, with no unresolved review comments. ## Risks - URL checks are hints. They do not test DNS, firewall rules, or actual reachability. - A Tailscale hostname can serve either private Serve traffic or public Funnel traffic. The warning explains this uncertainty and permits both. - No API, schema, authentication, or webhook delivery behavior changes. ## Model Used OpenAI Codex, based on GPT-6, with reasoning, repository tools, shell execution, and browser testing. The exact deployment model ID and context window size are not exposed in this session. ## 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>
38 lines
1.8 KiB
TypeScript
38 lines
1.8 KiB
TypeScript
export type WebhookUrlWarningReason = "loopback" | "private" | "tailscale" | "https" | "invalid";
|
|
|
|
function ipv4Warning(parts: number[]): WebhookUrlWarningReason | null {
|
|
const [a, b] = parts;
|
|
if (a === 127 || a === 0) return "loopback";
|
|
if (a === 10 || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168)
|
|
|| (a === 169 && b === 254) || (a === 100 && b >= 64 && b <= 127)) return "private";
|
|
return null;
|
|
}
|
|
|
|
/** URL hints only: DNS, firewall rules, and Tailscale Funnel cannot be inferred here. */
|
|
export function webhookUrlWarningReason(value: string): WebhookUrlWarningReason | null {
|
|
if (!value) return null;
|
|
let url: URL;
|
|
try { url = new URL(value); } catch { return "invalid"; }
|
|
if (url.protocol !== "http:" && url.protocol !== "https:") return "invalid";
|
|
const host = url.hostname.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
if (host === "localhost" || host.endsWith(".localhost") || host === "::1" || host === "::") return "loopback";
|
|
if (host.includes(":")) {
|
|
// URL normalizes IPv4-mapped IPv6 addresses to two hexadecimal words.
|
|
const mapped = /^::ffff:([\da-f]{1,4}):([\da-f]{1,4})$/.exec(host);
|
|
if (mapped) {
|
|
const high = parseInt(mapped[1], 16), low = parseInt(mapped[2], 16);
|
|
const warning = ipv4Warning([high >> 8, high & 255, low >> 8, low & 255]);
|
|
if (warning) return warning;
|
|
}
|
|
if (/^(f[cd][\da-f]{2}|fe[89ab][\da-f]|fec[\da-f]):/.test(host)) return "private";
|
|
} else if (/^\d+\.\d+\.\d+\.\d+$/.test(host)) {
|
|
const warning = ipv4Warning(host.split(".").map(Number));
|
|
if (warning) return warning;
|
|
} else {
|
|
if (host.endsWith(".ts.net")) return "tailscale";
|
|
if (!host.includes(".") || /\.(local|internal|lan|home|corp|intranet|private|test)$/.test(host)
|
|
|| host.endsWith(".home.arpa")) return "private";
|
|
}
|
|
return url.protocol === "https:" ? null : "https";
|
|
}
|