@simonyea/holysheep-cli 2.1.29 → 2.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simonyea/holysheep-cli",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.31",
|
|
4
4
|
"description": "Claude Code/Cursor/Cline API relay for China \u2014 \u00a51=$1, WeChat/Alipay payment, no credit card, no VPN. One command setup for all AI coding tools.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node tests/droid.test.js && node tests/workspace-store.test.js && node tests/runtime-stale-upgrade.test.js && node tests/hermes.test.js && node tests/preflight.test.js",
|
|
@@ -205,6 +205,26 @@ function buildAuthHeaders(config, lease) {
|
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
// [HolySheep fork v2.1.29 / hs22] Rewrite User-Agent away from claude-cli/*.
|
|
209
|
+
// CRS enforces a "Claude Code CLI must use hs claude" policy keyed on UA —
|
|
210
|
+
// when it sees claude-cli/* or claude-code/*, it 403s even with valid bridge
|
|
211
|
+
// headers, under the assumption the user bypassed the wrapper. Every forward
|
|
212
|
+
// path (node-proxy + direct-https fallback) now normalises UA to
|
|
213
|
+
// holysheep-cli so the wrapper identity is preserved end-to-end.
|
|
214
|
+
function sanitizeClaudeClientHeaders(headers, config) {
|
|
215
|
+
const out = { ...(headers || {}) }
|
|
216
|
+
for (const k of Object.keys(out)) {
|
|
217
|
+
if (/^x-forwarded-|^x-real-ip$|^forwarded$|^via$/i.test(k)) {
|
|
218
|
+
delete out[k]
|
|
219
|
+
continue
|
|
220
|
+
}
|
|
221
|
+
if (/^user-agent$/i.test(k) && /claude-cli|claude-code/i.test(String(out[k] || ''))) {
|
|
222
|
+
out[k] = `holysheep-cli/${(config && config.cliVersion) || 'process-proxy'} (hs-claude-proxy)`
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return out
|
|
226
|
+
}
|
|
227
|
+
|
|
208
228
|
function deriveNodeProxyUrl(lease) {
|
|
209
229
|
if (process.env.HS_CLAUDE_NODE_PROXY_OVERRIDE) {
|
|
210
230
|
return String(process.env.HS_CLAUDE_NODE_PROXY_OVERRIDE).replace(/\/+$/, '')
|
|
@@ -244,12 +264,7 @@ function forwardDirectHttps({ config, lease, clientReq, clientRes, trace }) {
|
|
|
244
264
|
// (or a proxy earlier in the chain could tag the request) and leak the
|
|
245
265
|
// loopback origin / real client IP to api.holysheep.ai. The upstream
|
|
246
266
|
// doesn't need them — it authenticates off bridge headers + API key.
|
|
247
|
-
const sanitized =
|
|
248
|
-
for (const k of Object.keys(sanitized)) {
|
|
249
|
-
if (/^x-forwarded-|^x-real-ip$|^forwarded$|^via$/i.test(k)) {
|
|
250
|
-
delete sanitized[k]
|
|
251
|
-
}
|
|
252
|
-
}
|
|
267
|
+
const sanitized = sanitizeClaudeClientHeaders(clientReq.headers, config)
|
|
253
268
|
const headers = {
|
|
254
269
|
...sanitized,
|
|
255
270
|
...buildAuthHeaders(config, lease),
|
|
@@ -375,8 +390,14 @@ function forwardViaNodeProxy({ nodeProxyUrl, targetUrl, clientReq, clientRes, ex
|
|
|
375
390
|
stallTimer = setTimeout(() => failWithAnthropicError('upstream stream stalled'), STALL_TIMEOUT_MS)
|
|
376
391
|
}
|
|
377
392
|
|
|
393
|
+
// [HolySheep fork v2.1.29 / hs22] Same UA-rewrite logic as forwardDirectHttps.
|
|
394
|
+
// Even when we go through node4:3129, the forward-proxy delivers the UA
|
|
395
|
+
// verbatim to upstream CRS; keeping claude-cli/* gets us 403.
|
|
396
|
+
// `extraHeaders` already contains bridge auth via buildAuthHeaders.
|
|
397
|
+
// `config` may be undefined here because forwardViaNodeProxy doesn't take
|
|
398
|
+
// one; use a minimal stub — cliVersion is optional.
|
|
378
399
|
const finalHeaders = {
|
|
379
|
-
...clientReq.headers,
|
|
400
|
+
...sanitizeClaudeClientHeaders(clientReq.headers, { cliVersion: undefined }),
|
|
380
401
|
...extraHeaders,
|
|
381
402
|
host: targetUrl.host,
|
|
382
403
|
connection: 'close',
|
|
@@ -595,6 +616,64 @@ function createProcessProxyServer({ sessionId, configPath = CONFIG_PATH, allowAn
|
|
|
595
616
|
)
|
|
596
617
|
}
|
|
597
618
|
|
|
619
|
+
// [HolySheep fork v2.1.30 / hs22b] Buffer the incoming request body ONCE
|
|
620
|
+
// so retries + direct-https fallback can re-send it. Before this, each
|
|
621
|
+
// retry called clientReq.pipe(forwardReq) on a stream that had already
|
|
622
|
+
// been consumed by the previous attempt, making the second/third/...
|
|
623
|
+
// retry (and the fallback) hit CRS with an EMPTY body — upstream
|
|
624
|
+
// responded 400 "model is required" and the user saw no reply. Only
|
|
625
|
+
// the very first (often doomed-to-ECONNREFUSED) attempt had the real
|
|
626
|
+
// body; every retry+fallback arrived empty.
|
|
627
|
+
//
|
|
628
|
+
// Buffer into memory before any forward. Typical claude-agent-acp
|
|
629
|
+
// POST bodies are 5-40 KB so this is fine. Override from wire type
|
|
630
|
+
// to Buffer so downstream functions can pipe(from body stream)
|
|
631
|
+
// multiple times by wrapping a fresh Readable each time.
|
|
632
|
+
let bufferedBody = null
|
|
633
|
+
if (clientReq.method && !['GET', 'HEAD', 'OPTIONS'].includes(clientReq.method.toUpperCase())) {
|
|
634
|
+
try {
|
|
635
|
+
const chunks = []
|
|
636
|
+
for await (const chunk of clientReq) chunks.push(chunk)
|
|
637
|
+
bufferedBody = Buffer.concat(chunks)
|
|
638
|
+
} catch (e) {
|
|
639
|
+
// client disconnected mid-read; bail out early.
|
|
640
|
+
if (!clientRes.headersSent) {
|
|
641
|
+
clientRes.writeHead(400, { 'content-type': 'text/plain' })
|
|
642
|
+
clientRes.end('client disconnected while uploading body: ' + (e && e.message))
|
|
643
|
+
}
|
|
644
|
+
return
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
// Shadow `clientReq` with a wrapper that yields a fresh Readable body
|
|
648
|
+
// stream each time `.pipe()` is called. This keeps the rest of the
|
|
649
|
+
// handler unchanged (node-proxy + direct-fallback both still use
|
|
650
|
+
// `clientReq.pipe(upstream)`).
|
|
651
|
+
const { Readable } = require('stream')
|
|
652
|
+
const makeBodyStream = () => {
|
|
653
|
+
if (bufferedBody === null) return null
|
|
654
|
+
const r = new Readable({ read() {} })
|
|
655
|
+
r.push(bufferedBody)
|
|
656
|
+
r.push(null)
|
|
657
|
+
return r
|
|
658
|
+
}
|
|
659
|
+
const originalClientReq = clientReq
|
|
660
|
+
clientReq = new Proxy(originalClientReq, {
|
|
661
|
+
get(target, prop, receiver) {
|
|
662
|
+
if (prop === 'pipe') {
|
|
663
|
+
return (dest, opts) => {
|
|
664
|
+
const s = makeBodyStream()
|
|
665
|
+
if (!s) {
|
|
666
|
+
// GET/HEAD — just end the dest
|
|
667
|
+
if (dest && typeof dest.end === 'function') dest.end()
|
|
668
|
+
return dest
|
|
669
|
+
}
|
|
670
|
+
return s.pipe(dest, opts)
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return Reflect.get(target, prop, receiver)
|
|
674
|
+
},
|
|
675
|
+
})
|
|
676
|
+
|
|
598
677
|
const doForward = async (lease, attempt) => {
|
|
599
678
|
const config = readConfig(configPath)
|
|
600
679
|
const nodeProxyUrl = deriveNodeProxyUrl(lease)
|