mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-14 08:52:21 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfde546167 | ||
|
|
526f2a2d04 |
@@ -31,6 +31,7 @@
|
||||
"@tanstack/react-router": "1.170.16",
|
||||
"@tanstack/react-start": "1.168.26",
|
||||
"@vercel/analytics": "2.0.1",
|
||||
"@vercel/functions": "3.7.3",
|
||||
"@vercel/speed-insights": "2.0.0",
|
||||
"class-variance-authority": "0.7.1",
|
||||
"clawhub-schema": "workspace:0.0.2",
|
||||
@@ -932,6 +933,14 @@
|
||||
|
||||
"@vercel/analytics": ["@vercel/analytics@2.0.1", "", { "peerDependencies": { "@remix-run/react": "^2", "@sveltejs/kit": "^1 || ^2", "next": ">= 13", "nuxt": ">= 3", "react": "^18 || ^19 || ^19.0.0-rc", "svelte": ">= 4", "vue": "^3", "vue-router": "^4" }, "optionalPeers": ["@remix-run/react", "@sveltejs/kit", "next", "nuxt", "react", "svelte", "vue", "vue-router"] }, "sha512-MTQG6V9qQrt1tsDeF+2Uoo5aPjqbVPys1xvnIftXSJYG2SrwXRHnqEvVoYID7BTruDz4lCd2Z7rM1BdkUehk2g=="],
|
||||
|
||||
"@vercel/cli-config": ["@vercel/cli-config@0.2.0", "", { "dependencies": { "xdg-app-paths": "5", "zod": "4.1.11" } }, "sha512-fJRRRB7734BDuXZ89yBEaA2ncYhH7bWX30mk04W80J6VAfQc+4iB8lyzAdaGpFV3/vNlkt9VZt+/uoQoWX6UsQ=="],
|
||||
|
||||
"@vercel/cli-exec": ["@vercel/cli-exec@1.0.0", "", { "dependencies": { "execa": "5.1.1" } }, "sha512-kQF8LGie/Hbdq9/psJxLE7owRTcqMQMhgybU04gCeR7cbQAr5t8OrjefDNColJv1QSSucFt4pLwRiARVmlOnug=="],
|
||||
|
||||
"@vercel/functions": ["@vercel/functions@3.7.3", "", { "dependencies": { "@vercel/oidc": "3.7.0" }, "peerDependencies": { "@aws-sdk/credential-provider-web-identity": "*", "ws": ">=8" }, "optionalPeers": ["@aws-sdk/credential-provider-web-identity", "ws"] }, "sha512-Bn7VPthKb6jBiGM3W2JUXDgDl/2trIanvnnxQ9dCVpDlDLvwn/+uk1TM9bSQJuZ4dKbOfusstpbM0PZeuNStsw=="],
|
||||
|
||||
"@vercel/oidc": ["@vercel/oidc@3.7.0", "", { "dependencies": { "@vercel/cli-config": "0.2.0", "@vercel/cli-exec": "1.0.0", "jose": "^5.9.6" } }, "sha512-FWmULATInXyxER8FJjtSEUdoaoeqWv9G9T4y3vraYMJNAx/ox354COtDmhiBb/XaeDjFuiwTY9ss44NRBaGjLQ=="],
|
||||
|
||||
"@vercel/speed-insights": ["@vercel/speed-insights@2.0.0", "", { "peerDependencies": { "@sveltejs/kit": "^1 || ^2", "next": ">= 13", "nuxt": ">= 3", "react": "^18 || ^19 || ^19.0.0-rc", "svelte": ">= 4", "vue": "^3", "vue-router": "^4" }, "optionalPeers": ["@sveltejs/kit", "next", "nuxt", "react", "svelte", "vue", "vue-router"] }, "sha512-jwkNcrTeafWxjmWq4AHBaptSqZiJkYU5adLC9QBSqeim0GcqDMgN5Ievh8OG1rJ6W3A4l1oiP7qr9CWxGuzu3w=="],
|
||||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.2", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.0" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg=="],
|
||||
@@ -1054,6 +1063,8 @@
|
||||
|
||||
"cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"crossws": ["crossws@0.4.5", "", { "peerDependencies": { "srvx": ">=0.11.5" }, "optionalPeers": ["srvx"] }, "sha512-wUR89x/Rw7/8t+vn0CmGDYM9TD6VtARGb0LD5jq2wjtMy1vCP4M+sm6N6TigWeTYvnA8MoW29NqqXD0ep0rfBA=="],
|
||||
|
||||
"css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="],
|
||||
@@ -1204,6 +1215,8 @@
|
||||
|
||||
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
||||
|
||||
"execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
|
||||
|
||||
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
|
||||
|
||||
"exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="],
|
||||
@@ -1236,6 +1249,8 @@
|
||||
|
||||
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
|
||||
|
||||
"get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
|
||||
|
||||
"glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
|
||||
|
||||
"globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
|
||||
@@ -1286,6 +1301,8 @@
|
||||
|
||||
"httpxy": ["httpxy@0.5.3", "", {}, "sha512-SMS9V6Sn7VWaS11lYhoAr0ceoaiolTWf4jYdJn0NJhCdKMu9R2H9Fh0LBDWBHQF6HRLI1PmaePYsjanSpE5PEw=="],
|
||||
|
||||
"human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="],
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
||||
|
||||
"ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
@@ -1312,10 +1329,14 @@
|
||||
|
||||
"is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="],
|
||||
|
||||
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||
|
||||
"is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
|
||||
|
||||
"isbot": ["isbot@5.1.41", "", {}, "sha512-9WFV/Vhh0FEj6CQ7MoHweEL9/vLKPjeoD2I2htbAjX7kbW7VJs3OCpWOVyd+JraNTWVU6/DRx2MZy2KaUNXHcg=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="],
|
||||
|
||||
"istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="],
|
||||
@@ -1434,6 +1455,8 @@
|
||||
|
||||
"mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
|
||||
|
||||
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
|
||||
|
||||
"mermaid": ["mermaid@11.15.0", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.2", "@mermaid-js/parser": "^1.1.1", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", "cytoscape": "^3.33.1", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", "dayjs": "^1.11.19", "dompurify": "^3.3.1", "es-toolkit": "^1.45.1", "katex": "^0.16.25", "khroma": "^2.1.0", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" } }, "sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw=="],
|
||||
|
||||
"micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="],
|
||||
@@ -1498,6 +1521,8 @@
|
||||
|
||||
"mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
|
||||
|
||||
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
|
||||
|
||||
"mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
|
||||
|
||||
"minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
@@ -1524,6 +1549,8 @@
|
||||
|
||||
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
||||
|
||||
"npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
|
||||
|
||||
"nypm": ["nypm@0.6.6", "", { "dependencies": { "citty": "^0.2.2", "pathe": "^2.0.3", "tinyexec": "^1.1.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-vRyr0r4cbBapw07Xw8xrj9Teq3o7MUD35rSaTcanDbW+aK2XHDgJFiU6ZTj2GBw7Q12ysdsyFss+Vdz4hQ0Y6Q=="],
|
||||
|
||||
"oauth4webapi": ["oauth4webapi@3.8.6", "", {}, "sha512-iwemM91xz8nryHti2yTmg5fhyEMVOkOXwHNqbvcATjyajb5oQxCQzrNOA6uElRHuMhQQTKUyFKV9y/CNyg25BQ=="],
|
||||
@@ -1550,6 +1577,8 @@
|
||||
|
||||
"ora": ["ora@9.4.1", "", { "dependencies": { "chalk": "^5.6.2", "cli-cursor": "^5.0.0", "cli-spinners": "^3.2.0", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.1.0", "log-symbols": "^7.0.1", "stdin-discarder": "^0.3.2", "string-width": "^8.1.0" } }, "sha512-6VlU9MLXbjVQD04AZCMX28hVtA5bUoadvUqO76MUCVA0ilwJbMiHsITRPfyVm6p/BC0Av/BXMujx39WCe1LEqw=="],
|
||||
|
||||
"os-paths": ["os-paths@4.4.0", "", {}, "sha512-wrAwOeXp1RRMFfQY8Sy7VaGVmPocaLwSFOYCGKSyo8qmJ+/yaafCl5BCA1IQZWqFSRBrKDYFeR9d/VyQzfH/jg=="],
|
||||
|
||||
"oxc-parser": ["oxc-parser@0.120.0", "", { "dependencies": { "@oxc-project/types": "^0.120.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.120.0", "@oxc-parser/binding-android-arm64": "0.120.0", "@oxc-parser/binding-darwin-arm64": "0.120.0", "@oxc-parser/binding-darwin-x64": "0.120.0", "@oxc-parser/binding-freebsd-x64": "0.120.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.120.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.120.0", "@oxc-parser/binding-linux-arm64-gnu": "0.120.0", "@oxc-parser/binding-linux-arm64-musl": "0.120.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.120.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.120.0", "@oxc-parser/binding-linux-riscv64-musl": "0.120.0", "@oxc-parser/binding-linux-s390x-gnu": "0.120.0", "@oxc-parser/binding-linux-x64-gnu": "0.120.0", "@oxc-parser/binding-linux-x64-musl": "0.120.0", "@oxc-parser/binding-openharmony-arm64": "0.120.0", "@oxc-parser/binding-wasm32-wasi": "0.120.0", "@oxc-parser/binding-win32-arm64-msvc": "0.120.0", "@oxc-parser/binding-win32-ia32-msvc": "0.120.0", "@oxc-parser/binding-win32-x64-msvc": "0.120.0" } }, "sha512-WyPWZlcIm+Fkte63FGfgFB8mAAk33aH9h5N9lphXVOHSXEBFFsmYdOBedVKly363aWABjZdaj/m9lBfEY4wt+w=="],
|
||||
|
||||
"oxfmt": ["oxfmt@0.56.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.56.0", "@oxfmt/binding-android-arm64": "0.56.0", "@oxfmt/binding-darwin-arm64": "0.56.0", "@oxfmt/binding-darwin-x64": "0.56.0", "@oxfmt/binding-freebsd-x64": "0.56.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.56.0", "@oxfmt/binding-linux-arm-musleabihf": "0.56.0", "@oxfmt/binding-linux-arm64-gnu": "0.56.0", "@oxfmt/binding-linux-arm64-musl": "0.56.0", "@oxfmt/binding-linux-ppc64-gnu": "0.56.0", "@oxfmt/binding-linux-riscv64-gnu": "0.56.0", "@oxfmt/binding-linux-riscv64-musl": "0.56.0", "@oxfmt/binding-linux-s390x-gnu": "0.56.0", "@oxfmt/binding-linux-x64-gnu": "0.56.0", "@oxfmt/binding-linux-x64-musl": "0.56.0", "@oxfmt/binding-openharmony-arm64": "0.56.0", "@oxfmt/binding-win32-arm64-msvc": "0.56.0", "@oxfmt/binding-win32-ia32-msvc": "0.56.0", "@oxfmt/binding-win32-x64-msvc": "0.56.0" }, "peerDependencies": { "svelte": "^5.0.0", "vite-plus": "*" }, "optionalPeers": ["svelte", "vite-plus"], "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-9Dv0wV3zKiyvhjD7bRKaInKmHQ1sPx3RGOjQkGFJbbdQ16576yf8qhMSO9Q9cvHcs+1NpBsRTkuDDYFFPTJ6gw=="],
|
||||
@@ -1570,6 +1599,8 @@
|
||||
|
||||
"path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
|
||||
|
||||
"path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="],
|
||||
@@ -1696,6 +1727,10 @@
|
||||
|
||||
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"shell-quote": ["shell-quote@1.8.4", "", {}, "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ=="],
|
||||
|
||||
"shiki": ["shiki@4.2.0", "", { "dependencies": { "@shikijs/core": "4.2.0", "@shikijs/engine-javascript": "4.2.0", "@shikijs/engine-oniguruma": "4.2.0", "@shikijs/langs": "4.2.0", "@shikijs/themes": "4.2.0", "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-hjNax6o/ylDy9lefQEaSDtzaT3iVNtZ3WmpQnbuQNoG4xvnSKf2kSKbihZVO4JRG1TTMejs7CmNRYlWgAL66pQ=="],
|
||||
@@ -1744,6 +1779,8 @@
|
||||
|
||||
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
|
||||
|
||||
"strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
|
||||
|
||||
"stubborn-fs": ["stubborn-fs@2.0.0", "", { "dependencies": { "stubborn-utils": "^1.0.1" } }, "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA=="],
|
||||
|
||||
"stubborn-utils": ["stubborn-utils@1.0.2", "", {}, "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg=="],
|
||||
@@ -1868,12 +1905,18 @@
|
||||
|
||||
"when-exit": ["when-exit@2.1.5", "", {}, "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"which-pm-runs": ["which-pm-runs@1.1.0", "", {}, "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA=="],
|
||||
|
||||
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
|
||||
|
||||
"ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="],
|
||||
|
||||
"xdg-app-paths": ["xdg-app-paths@5.5.1", "", { "dependencies": { "os-paths": "^4.0.1", "xdg-portable": "^7.2.0" } }, "sha512-hI3flOB4PLZIy5prbtTpirobtPE2ZtZ52szO+2mM9Efp6ErM398La+C1lIpNWDfNoQk+6Lsi6nMcCwVB7pxeMQ=="],
|
||||
|
||||
"xdg-portable": ["xdg-portable@7.3.0", "", { "dependencies": { "os-paths": "^4.0.1" } }, "sha512-sqMMuL1rc0FmMBOzCpd0yuy9trqF2yTTVe+E9ogwCSWQCdDEtQUwrZPT6AxqtsFGRNxycgncbP/xmOOSPw5ZUw=="],
|
||||
|
||||
"xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="],
|
||||
|
||||
"xmlbuilder2": ["xmlbuilder2@4.0.3", "", { "dependencies": { "@oozcitak/dom": "^2.0.2", "@oozcitak/infra": "^2.0.2", "@oozcitak/util": "^10.0.0", "js-yaml": "^4.1.1" } }, "sha512-bx8Q1STctnNaaDymWnkfQLKofs0mGNN7rLLapJlGuV3VlvegD7Ls4ggMjE3aUSWItCCzU0PEv45lI87iSigiCA=="],
|
||||
@@ -1938,6 +1981,10 @@
|
||||
|
||||
"@types/ws/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="],
|
||||
|
||||
"@vercel/cli-config/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
|
||||
|
||||
"@vercel/oidc/jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="],
|
||||
|
||||
"accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"ast-v8-to-istanbul/js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="],
|
||||
@@ -1962,6 +2009,10 @@
|
||||
|
||||
"engine.io/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||
|
||||
"execa/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
||||
|
||||
"execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||
|
||||
"hast-util-raw/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
|
||||
|
||||
"htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
@@ -66,8 +66,10 @@ function componentRateLimitCalls(runMutation: ReturnType<typeof vi.fn>) {
|
||||
|
||||
describe("getClientIp", () => {
|
||||
let prev: string | undefined;
|
||||
let prevEdgeSecret: string | undefined;
|
||||
beforeEach(() => {
|
||||
prev = process.env.TRUST_FORWARDED_IPS;
|
||||
prevEdgeSecret = process.env.CLAWHUB_EDGE_SECRET;
|
||||
});
|
||||
afterEach(() => {
|
||||
if (prev === undefined) {
|
||||
@@ -75,6 +77,11 @@ describe("getClientIp", () => {
|
||||
} else {
|
||||
process.env.TRUST_FORWARDED_IPS = prev;
|
||||
}
|
||||
if (prevEdgeSecret === undefined) {
|
||||
delete process.env.CLAWHUB_EDGE_SECRET;
|
||||
} else {
|
||||
process.env.CLAWHUB_EDGE_SECRET = prevEdgeSecret;
|
||||
}
|
||||
});
|
||||
|
||||
it("returns null when cf-connecting-ip is missing (CF-only default)", () => {
|
||||
@@ -107,34 +114,75 @@ describe("getClientIp", () => {
|
||||
expect(getClientIp(request)).toBeNull();
|
||||
});
|
||||
|
||||
it("returns first ip from cf-connecting-ip when trusted mode is enabled", () => {
|
||||
it("ignores forwarded headers when trusted mode lacks the edge secret", () => {
|
||||
const request = new Request("https://example.com", {
|
||||
headers: {
|
||||
"x-forwarded-for": "203.0.113.9",
|
||||
},
|
||||
});
|
||||
process.env.TRUST_FORWARDED_IPS = "true";
|
||||
delete process.env.CLAWHUB_EDGE_SECRET;
|
||||
expect(getClientIp(request)).toBeNull();
|
||||
});
|
||||
|
||||
it("ignores forwarded headers when the edge secret header is missing", () => {
|
||||
const request = new Request("https://example.com", {
|
||||
headers: {
|
||||
"x-forwarded-for": "203.0.113.9",
|
||||
},
|
||||
});
|
||||
process.env.TRUST_FORWARDED_IPS = "true";
|
||||
process.env.CLAWHUB_EDGE_SECRET = "edge-secret";
|
||||
expect(getClientIp(request)).toBeNull();
|
||||
});
|
||||
|
||||
it("ignores forwarded headers when the edge secret header is wrong", () => {
|
||||
const request = new Request("https://example.com", {
|
||||
headers: {
|
||||
"x-clawhub-edge-secret": "wrong",
|
||||
"x-forwarded-for": "203.0.113.9",
|
||||
},
|
||||
});
|
||||
process.env.TRUST_FORWARDED_IPS = "true";
|
||||
process.env.CLAWHUB_EDGE_SECRET = "edge-secret";
|
||||
expect(getClientIp(request)).toBeNull();
|
||||
});
|
||||
|
||||
it("falls back to cf-connecting-ip when trusted edge mode has no forwarded header", () => {
|
||||
const request = new Request("https://example.com", {
|
||||
headers: {
|
||||
"x-clawhub-edge-secret": "edge-secret",
|
||||
"cf-connecting-ip": "203.0.113.1, 198.51.100.2",
|
||||
},
|
||||
});
|
||||
process.env.TRUST_FORWARDED_IPS = "true";
|
||||
process.env.CLAWHUB_EDGE_SECRET = "edge-secret";
|
||||
expect(getClientIp(request)).toBe("203.0.113.1");
|
||||
});
|
||||
|
||||
it("uses forwarded headers when opt-in enabled", () => {
|
||||
it("uses forwarded headers when trusted edge mode is enabled", () => {
|
||||
const request = new Request("https://example.com", {
|
||||
headers: {
|
||||
"x-clawhub-edge-secret": "edge-secret",
|
||||
"x-forwarded-for": "203.0.113.9, 198.51.100.2",
|
||||
},
|
||||
});
|
||||
process.env.TRUST_FORWARDED_IPS = "true";
|
||||
process.env.CLAWHUB_EDGE_SECRET = "edge-secret";
|
||||
expect(getClientIp(request)).toBe("203.0.113.9");
|
||||
});
|
||||
|
||||
it("prefers x-forwarded-for over x-real-ip when trusted mode is enabled", () => {
|
||||
it("prefers x-forwarded-for over edge connection IP headers", () => {
|
||||
const request = new Request("https://example.com", {
|
||||
headers: {
|
||||
"x-clawhub-edge-secret": "edge-secret",
|
||||
"cf-connecting-ip": "192.0.2.44",
|
||||
"x-forwarded-for": "203.0.113.9, 198.51.100.2",
|
||||
"x-real-ip": "198.51.100.77",
|
||||
},
|
||||
});
|
||||
process.env.TRUST_FORWARDED_IPS = "true";
|
||||
process.env.CLAWHUB_EDGE_SECRET = "edge-secret";
|
||||
expect(getClientIp(request)).toBe("203.0.113.9");
|
||||
});
|
||||
});
|
||||
@@ -557,6 +605,7 @@ describe("applyRateLimit headers", () => {
|
||||
|
||||
it("scopes known-ip anonymous buckets by rate limit kind", async () => {
|
||||
vi.stubEnv("TRUST_FORWARDED_IPS", "true");
|
||||
vi.stubEnv("CLAWHUB_EDGE_SECRET", "edge-secret");
|
||||
vi.spyOn(Date, "now").mockReturnValue(4_550_000);
|
||||
const readCtx = makeRateLimitCtx({
|
||||
ip: {
|
||||
@@ -575,7 +624,10 @@ describe("applyRateLimit headers", () => {
|
||||
},
|
||||
});
|
||||
const request = new Request("https://example.com/api/v1/packages/demo/download", {
|
||||
headers: { "cf-connecting-ip": "203.0.113.1" },
|
||||
headers: {
|
||||
"x-clawhub-edge-secret": "edge-secret",
|
||||
"cf-connecting-ip": "203.0.113.1",
|
||||
},
|
||||
});
|
||||
|
||||
await applyRateLimit(readCtx, request, "read");
|
||||
|
||||
+15
-12
@@ -17,6 +17,7 @@ const RATE_LIMIT_WINDOW_MS = 60_000;
|
||||
const HTTP_RATE_LIMIT_SHARDS = 16;
|
||||
const HTTP_RATE_LIMIT_MIN_SHARD_CAPACITY = 10;
|
||||
const HTTP_RATE_LIMIT_KEY_TTL_MS = 24 * 60 * 60 * 1000;
|
||||
const EDGE_SECRET_HEADER = "x-clawhub-edge-secret";
|
||||
|
||||
type RateLimitResult = {
|
||||
allowed: boolean;
|
||||
@@ -184,25 +185,23 @@ function getAuthenticatedRateLimit(kind: RateLimitKind, user: Pick<Doc<"users">,
|
||||
}
|
||||
|
||||
export function getClientIp(request: Request): string | null {
|
||||
if (!shouldTrustClientIpHeaders()) return null;
|
||||
|
||||
const cfHeader = request.headers.get("cf-connecting-ip");
|
||||
if (cfHeader) return splitFirstIp(cfHeader);
|
||||
if (!shouldTrustClientIpHeaders(request)) return null;
|
||||
|
||||
const forwarded =
|
||||
request.headers.get("x-forwarded-for") ??
|
||||
request.headers.get("x-real-ip") ??
|
||||
request.headers.get("fly-client-ip");
|
||||
request.headers.get("fly-client-ip") ??
|
||||
request.headers.get("cf-connecting-ip");
|
||||
|
||||
return splitFirstIp(forwarded);
|
||||
}
|
||||
|
||||
function getClientIpSource(request: Request) {
|
||||
if (!shouldTrustClientIpHeaders()) return "none";
|
||||
if (request.headers.get("cf-connecting-ip")) return "cf-connecting-ip";
|
||||
if (!shouldTrustClientIpHeaders(request)) return "none";
|
||||
if (request.headers.get("x-forwarded-for")) return "x-forwarded-for";
|
||||
if (request.headers.get("x-real-ip")) return "x-real-ip";
|
||||
if (request.headers.get("fly-client-ip")) return "fly-client-ip";
|
||||
if (request.headers.get("cf-connecting-ip")) return "cf-connecting-ip";
|
||||
return "none";
|
||||
}
|
||||
|
||||
@@ -311,13 +310,17 @@ function splitFirstIp(header: string | null) {
|
||||
return trimmed || null;
|
||||
}
|
||||
|
||||
function shouldTrustClientIpHeaders() {
|
||||
function shouldTrustClientIpHeaders(request: Request) {
|
||||
const value = (process.env.TRUST_FORWARDED_IPS ?? "").trim().toLowerCase();
|
||||
// Direct Convex HTTP endpoints can be reached without ClawHub's edge. Trust
|
||||
// client IP headers only when the deployment is explicitly behind that edge.
|
||||
if (!value) return false;
|
||||
if (value === "1" || value === "true" || value === "yes") return true;
|
||||
return false;
|
||||
if (!(value === "1" || value === "true" || value === "yes")) return false;
|
||||
|
||||
// Direct Convex HTTP endpoints are public. Only trust forwarded IP headers
|
||||
// when ClawHub's edge has stripped caller-supplied headers and added this
|
||||
// deployment secret.
|
||||
const edgeSecret = process.env.CLAWHUB_EDGE_SECRET?.trim();
|
||||
if (!edgeSecret) return false;
|
||||
return request.headers.get(EDGE_SECRET_HEADER) === edgeSecret;
|
||||
}
|
||||
|
||||
function isRateLimitWriteConflict(error: unknown) {
|
||||
|
||||
+7
-3
@@ -76,9 +76,13 @@ Client guidance:
|
||||
|
||||
IP source:
|
||||
|
||||
- Uses trusted client IP headers, including `cf-connecting-ip`, only when the
|
||||
deployment explicitly enables trusted forwarded headers.
|
||||
- ClawHub uses trusted forwarding headers to identify client IPs at the edge.
|
||||
- Uses trusted client IP headers only when the deployment explicitly enables
|
||||
trusted forwarded headers and the request was marked by ClawHub's edge trust
|
||||
secret.
|
||||
- Prefers the edge-stamped `x-forwarded-for` value over connection IP headers
|
||||
so Vercel rewrites are attributed to the original client, not Vercel egress.
|
||||
- ClawHub uses trusted forwarding headers to identify client IPs at the edge;
|
||||
direct Convex-origin requests without the edge secret use fallback buckets.
|
||||
- If no trusted client IP is available, anonymous requests use fallback buckets
|
||||
scoped only by rate-limit kind. These fallback buckets do not include
|
||||
caller-supplied paths, slugs, package names, versions, query strings, or other
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/* @vitest-environment node */
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import middleware, { config } from "./middleware";
|
||||
|
||||
function getForwardedHeader(response: Response, name: string) {
|
||||
return response.headers.get(`x-middleware-request-${name}`);
|
||||
}
|
||||
|
||||
describe("Vercel API middleware", () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it("adds an edge trust secret and Vercel-derived client IP for Convex rewrites", () => {
|
||||
vi.stubEnv("CLAWHUB_EDGE_SECRET", "edge-secret");
|
||||
const response = middleware(
|
||||
new Request("https://clawhub.ai/api/v1/packages/demo", {
|
||||
headers: {
|
||||
"x-clawhub-edge-secret": "caller-secret",
|
||||
"cf-connecting-ip": "192.0.2.10",
|
||||
"x-forwarded-for": "192.0.2.11",
|
||||
"x-real-ip": "203.0.113.9",
|
||||
"fly-client-ip": "192.0.2.12",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(response.headers.get("x-middleware-next")).toBe("1");
|
||||
expect(getForwardedHeader(response, "x-clawhub-edge-secret")).toBe("edge-secret");
|
||||
expect(getForwardedHeader(response, "x-forwarded-for")).toBe("203.0.113.9");
|
||||
expect(getForwardedHeader(response, "x-real-ip")).toBe("203.0.113.9");
|
||||
expect(getForwardedHeader(response, "cf-connecting-ip")).toBeNull();
|
||||
expect(getForwardedHeader(response, "fly-client-ip")).toBeNull();
|
||||
});
|
||||
|
||||
it("strips caller-supplied trust and IP headers when edge trust is not configured", () => {
|
||||
const response = middleware(
|
||||
new Request("https://clawhub.ai/api/v1/packages/demo", {
|
||||
headers: {
|
||||
"x-clawhub-edge-secret": "caller-secret",
|
||||
"cf-connecting-ip": "192.0.2.10",
|
||||
"x-forwarded-for": "192.0.2.11",
|
||||
"x-real-ip": "203.0.113.9",
|
||||
"fly-client-ip": "192.0.2.12",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(getForwardedHeader(response, "x-clawhub-edge-secret")).toBeNull();
|
||||
expect(getForwardedHeader(response, "cf-connecting-ip")).toBeNull();
|
||||
expect(getForwardedHeader(response, "x-forwarded-for")).toBeNull();
|
||||
expect(getForwardedHeader(response, "x-real-ip")).toBeNull();
|
||||
expect(getForwardedHeader(response, "fly-client-ip")).toBeNull();
|
||||
});
|
||||
|
||||
it("runs only on Convex-backed API and feed routes", () => {
|
||||
expect(config.matcher).toEqual(["/api/:path*", "/v1/feeds/plugins", "/v1/feeds/skills"]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { ipAddress, next } from "@vercel/functions";
|
||||
|
||||
const EDGE_SECRET_HEADER = "x-clawhub-edge-secret";
|
||||
const CLIENT_IP_HEADERS = [
|
||||
"cf-connecting-ip",
|
||||
"x-forwarded-for",
|
||||
"x-real-ip",
|
||||
"fly-client-ip",
|
||||
] as const;
|
||||
|
||||
export default function middleware(request: Request): Response {
|
||||
const headers = new Headers(request.headers);
|
||||
for (const header of CLIENT_IP_HEADERS) headers.delete(header);
|
||||
headers.delete(EDGE_SECRET_HEADER);
|
||||
|
||||
const edgeSecret = process.env.CLAWHUB_EDGE_SECRET?.trim();
|
||||
const clientIp = ipAddress(request);
|
||||
if (edgeSecret && clientIp) {
|
||||
headers.set(EDGE_SECRET_HEADER, edgeSecret);
|
||||
headers.set("x-forwarded-for", clientIp);
|
||||
headers.set("x-real-ip", clientIp);
|
||||
}
|
||||
|
||||
return next({ request: { headers } });
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ["/api/:path*", "/v1/feeds/plugins", "/v1/feeds/skills"],
|
||||
};
|
||||
+3
-2
@@ -44,8 +44,8 @@
|
||||
"format": "oxfmt --write",
|
||||
"format:check": "oxfmt --check",
|
||||
"lint": "bun run lint:oxlint",
|
||||
"lint:fix": "oxlint --type-aware --tsconfig ./tsconfig.oxlint.json ./src ./convex ./packages/clawhub/src ./packages/clawhub-admin/src ./packages/schema/src --fix && bun run format",
|
||||
"lint:oxlint": "oxlint --type-aware --tsconfig ./tsconfig.oxlint.json ./src ./convex ./packages/clawhub/src ./packages/clawhub-admin/src ./packages/schema/src",
|
||||
"lint:fix": "oxlint --type-aware --tsconfig ./tsconfig.oxlint.json ./middleware.ts ./src ./convex ./packages/clawhub/src ./packages/clawhub-admin/src ./packages/schema/src --fix && bun run format",
|
||||
"lint:oxlint": "oxlint --type-aware --tsconfig ./tsconfig.oxlint.json ./middleware.ts ./src ./convex ./packages/clawhub/src ./packages/clawhub-admin/src ./packages/schema/src",
|
||||
"llms:check": "bun scripts/generate-llms-txt.ts --check",
|
||||
"llms:generate": "bun scripts/generate-llms-txt.ts",
|
||||
"preinstall": "bunx --bun only-allow@1.2.2 bun",
|
||||
@@ -98,6 +98,7 @@
|
||||
"@tanstack/react-router": "1.170.16",
|
||||
"@tanstack/react-start": "1.168.26",
|
||||
"@vercel/analytics": "2.0.1",
|
||||
"@vercel/functions": "3.7.3",
|
||||
"@vercel/speed-insights": "2.0.0",
|
||||
"class-variance-authority": "0.7.1",
|
||||
"clawhub-schema": "workspace:0.0.2",
|
||||
|
||||
+12
-4
@@ -130,11 +130,17 @@ Ensure Convex env is set (auth + embeddings):
|
||||
- `GITHUB_APP_PRIVATE_KEY`
|
||||
- Optional fallback: `GITHUB_TOKEN` (used when GitHub App auth is unavailable,
|
||||
and for arbitrary public repository lookups such as trusted-publisher setup)
|
||||
- `CLAWHUB_EDGE_SECRET` and `TRUST_FORWARDED_IPS=true` when the matching Vercel
|
||||
deployment also has the same `CLAWHUB_EDGE_SECRET`.
|
||||
|
||||
Do not set `TRUST_FORWARDED_IPS=true` while the Convex `*.convex.site` HTTP
|
||||
origin remains publicly reachable. That flag makes rate limits and download
|
||||
metrics trust forwarded client IP headers, so it is only safe behind a
|
||||
header-sanitizing edge that prevents direct origin requests.
|
||||
`TRUST_FORWARDED_IPS=true` is only effective when Convex also has
|
||||
`CLAWHUB_EDGE_SECRET`. The Vercel middleware strips caller-supplied client IP and
|
||||
edge trust headers before adding its own secret plus `x-forwarded-for` /
|
||||
`x-real-ip` for `/api/*` and hosted feed rewrites. Convex prefers that
|
||||
edge-stamped `x-forwarded-for` value over connection IP headers, because the
|
||||
connection into Convex may be Vercel rather than the original client. Direct
|
||||
`*.convex.site` calls without the secret remain in the missing-IP fallback
|
||||
bucket.
|
||||
|
||||
## 2) Deploy web app (Vercel)
|
||||
|
||||
@@ -163,6 +169,8 @@ This repo currently uses `vercel.json` rewrites:
|
||||
For self-host:
|
||||
|
||||
- update `vercel.json` to your deployment's Convex site URL.
|
||||
- set the same `CLAWHUB_EDGE_SECRET` in Vercel and Convex before enabling
|
||||
`TRUST_FORWARDED_IPS=true` in Convex.
|
||||
|
||||
## 4) Registry discovery
|
||||
|
||||
|
||||
+10
-6
@@ -9,12 +9,16 @@ telemetry writes, delete/undelete mutations, or search queries.
|
||||
|
||||
Client IP headers are not trustworthy on direct Convex HTTP endpoints. Treat
|
||||
`cf-connecting-ip`, `x-forwarded-for`, `x-real-ip`, and `fly-client-ip` as
|
||||
trusted only when the deployment explicitly enables trusted forwarded headers.
|
||||
Do not enable that opt-in while the Convex `*.convex.site` HTTP origin remains
|
||||
publicly reachable. Without the opt-in, anonymous traffic must use conservative
|
||||
missing-IP fallback buckets scoped only by rate-limit kind. Missing-IP buckets
|
||||
must not include user-controlled paths, dynamic path segments, query parameters,
|
||||
package names, skill slugs, or artifact versions.
|
||||
trusted only when the deployment explicitly enables trusted forwarded headers
|
||||
and the request carries ClawHub's edge trust secret. The Vercel middleware owns
|
||||
that boundary: it strips caller-supplied client IP and edge trust headers before
|
||||
adding `x-clawhub-edge-secret`, `x-forwarded-for`, and `x-real-ip` for Convex
|
||||
rewrites. Convex prefers the edge-stamped `x-forwarded-for` over connection IP
|
||||
headers so Vercel rewrites are limited by the original client IP, not the Vercel
|
||||
egress IP. Direct `*.convex.site` callers without the secret must continue to
|
||||
use conservative missing-IP fallback buckets scoped only by rate-limit kind.
|
||||
Missing-IP buckets must not include user-controlled paths, dynamic path
|
||||
segments, query parameters, package names, skill slugs, or artifact versions.
|
||||
Artifact-specific download scoping is only safe after the caller has an
|
||||
authenticated identity or a trusted client IP.
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"include": [
|
||||
"middleware.ts",
|
||||
"src",
|
||||
"convex",
|
||||
"packages/clawhub/src",
|
||||
|
||||
Reference in New Issue
Block a user