@simonyea/holysheep-cli 2.1.21 → 2.1.23

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.21",
3
+ "version": "2.1.23",
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",
@@ -324,6 +324,26 @@ function forwardViaNodeProxy({ nodeProxyUrl, targetUrl, clientReq, clientRes, ex
324
324
  host: targetUrl.host,
325
325
  connection: 'close',
326
326
  }
327
+ // Build the forward-proxy path. CRS's per-node proxy at :3129 accepts an
328
+ // absolute URL as the HTTP/1.1 request target and rewrites the scheme
329
+ // internally. We ALWAYS send http:// in the path (even if targetUrl is
330
+ // https://) because:
331
+ // 1. node:3129 listens on plain HTTP and ignores the scheme of the
332
+ // request-target — it reads `targetUrl = new URL(req.url)`,
333
+ // validates hostname, and proxies upstream.
334
+ // 2. bun's `http.request` treats `path: 'https://...'` as a signal to
335
+ // issue a CONNECT tunnel (instead of a plain absolute-URI forward
336
+ // request), which silently drops the x-hs-* headers we injected
337
+ // because they would only ride on the outer CONNECT, not the inner
338
+ // TLS tunnel — yielding 403 'Missing bridge session headers'.
339
+ // (Node's `http.request` does NOT have this behaviour; the bug is
340
+ // bun-specific. Reproduced under bun 1.3.9 at AionUi's runtime.)
341
+ // Rewriting to http:// preserves correctness on both runtimes.
342
+ const forwardPath = (() => {
343
+ const clone = new URL(targetUrl.toString())
344
+ clone.protocol = 'http:'
345
+ return clone.toString()
346
+ })()
327
347
  if (ENABLE_TIMING_LOG) {
328
348
  const hsHeaders = Object.fromEntries(
329
349
  Object.entries(finalHeaders).filter(([k]) => /^x-hs-/i.test(k))
@@ -331,6 +351,7 @@ function forwardViaNodeProxy({ nodeProxyUrl, targetUrl, clientReq, clientRes, ex
331
351
  console.error(
332
352
  `[hs-claude-proxy] forward.headers ${JSON.stringify({
333
353
  target: sanitizeUrl(targetUrl),
354
+ forwardPath,
334
355
  node: sanitizeUrl(nodeProxyUrl),
335
356
  hsHeaders,
336
357
  clientHeadersKeys: Object.keys(clientReq.headers).slice(0, 20),
@@ -341,7 +362,7 @@ function forwardViaNodeProxy({ nodeProxyUrl, targetUrl, clientReq, clientRes, ex
341
362
  host: upstream.hostname,
342
363
  port: Number(upstream.port || 80),
343
364
  method: clientReq.method,
344
- path: targetUrl.toString(),
365
+ path: forwardPath,
345
366
  agent: false,
346
367
  headers: finalHeaders,
347
368
  }, (forwardRes) => {
@@ -505,6 +526,17 @@ function pipeWithCleanup(a, b) {
505
526
  function createProcessProxyServer({ sessionId, configPath = CONFIG_PATH, allowAnthropicConnect = false }) {
506
527
  const server = http.createServer(async (clientReq, clientRes) => {
507
528
  const isDirect = !clientReq.url.startsWith('http')
529
+ if (ENABLE_TIMING_LOG) {
530
+ console.error(
531
+ `[hs-claude-proxy] incoming ${JSON.stringify({
532
+ method: clientReq.method,
533
+ url: String(clientReq.url).slice(0, 120),
534
+ ua: String(clientReq.headers['user-agent'] || '').slice(0, 60),
535
+ host: clientReq.headers.host || '',
536
+ isDirect,
537
+ })}`
538
+ )
539
+ }
508
540
 
509
541
  const doForward = async (lease, attempt) => {
510
542
  const config = readConfig(configPath)
@@ -602,6 +634,15 @@ function createProcessProxyServer({ sessionId, configPath = CONFIG_PATH, allowAn
602
634
 
603
635
  server.on('connect', async (req, clientSocket, head) => {
604
636
  const target = String(req.url || '').trim()
637
+ if (ENABLE_TIMING_LOG) {
638
+ console.error(
639
+ `[hs-claude-proxy] incoming.CONNECT ${JSON.stringify({
640
+ target: target.slice(0, 120),
641
+ ua: String(req.headers['user-agent'] || '').slice(0, 60),
642
+ headers: Object.keys(req.headers).slice(0, 20),
643
+ })}`
644
+ )
645
+ }
605
646
  const [host, rawPort] = target.split(':')
606
647
  const port = Number(rawPort || 443)
607
648
  if (!host || !Number.isInteger(port) || ![80, 443].includes(port)) {
@@ -698,14 +739,32 @@ async function closeSession(configPath, sessionId) {
698
739
  if (!sessionId) return
699
740
  const config = readConfig(configPath)
700
741
  const controlPlaneUrl = getControlPlaneUrl(config)
701
- if (!controlPlaneUrl) return
742
+ if (!controlPlaneUrl) {
743
+ logProxyTiming('lease.close', { sessionId, skipped: 'no-control-plane' })
744
+ return
745
+ }
746
+ const startedAt = Date.now()
747
+ let status = 'unknown'
702
748
  try {
703
- await fetch(`${controlPlaneUrl}/session/close`, {
749
+ const response = await fetch(`${controlPlaneUrl}/session/close`, {
704
750
  method: 'POST',
705
751
  headers: { 'content-type': 'application/json' },
706
752
  body: JSON.stringify({ sessionId }),
707
753
  })
708
- } catch {}
754
+ status = response.ok ? 'ok' : `http_${response.status}`
755
+ } catch (err) {
756
+ status = `err_${err.name || 'unknown'}`
757
+ }
758
+ // Always log lease.close — unlike other events this isn't slow-path-gated
759
+ // because lease leaks silently accumulate on CRS, making it critical to
760
+ // observe every release attempt.
761
+ console.error(
762
+ `[hs-claude-proxy] lease.close ${JSON.stringify({
763
+ sessionId,
764
+ status,
765
+ durationMs: Date.now() - startedAt,
766
+ })}`
767
+ )
709
768
  }
710
769
 
711
770
  module.exports = {
@@ -60,10 +60,10 @@ const VENDOR_DIR = path.join(__dirname, 'vendor', 'aionui')
60
60
  // new CLI release, the next `hs web` invocation on user machines will detect
61
61
  // the version drift and upgrade the cache in place.
62
62
  const DEFAULT_RUNTIME_URL =
63
- 'https://mail.holysheep.ai/app/cli/aionui-runtime-v1.9.18-holysheep-hs17.tar.gz'
63
+ 'https://mail.holysheep.ai/app/cli/aionui-runtime-v1.9.18-holysheep-hs18.tar.gz'
64
64
  const DEFAULT_RUNTIME_SHA256 =
65
- '28083f135503a27437750c8f692a2700c69f8253aaf19dff259af6d1517572f4'
66
- const DEFAULT_RUNTIME_VERSION = '1.9.18-holysheep-hs17'
65
+ '402739938fb30ddf09e31ed9fd64eb1453726cc062cc5d2ab8f3fbf19f7e6e66'
66
+ const DEFAULT_RUNTIME_VERSION = '1.9.18-holysheep-hs18'
67
67
 
68
68
  function isValidRuntimeDir(dir) {
69
69
  if (!dir) return false
@@ -609,21 +609,33 @@ async function startWrapper({ port, runtimeDir, runtimeVersion, runtimeSource, b
609
609
  // CLI package root containing package.json + src/.
610
610
  const cliRoot = path.resolve(__dirname, '..', '..')
611
611
  const wrapperVersion = require('../../package.json').version
612
+ // [HolySheep fork v2.1.23] Do NOT put HOLYSHEEP_API_KEY in env — any other
613
+ // process owned by the same user can read it via `ps ewww`. Instead, the
614
+ // fork side reads ~/.holysheep/config.json (already 0644 today; will be
615
+ // tightened to 0600 in the same release). Pass only the path hint.
616
+ const credPath = path.join(os.homedir(), '.holysheep', 'config.json')
617
+ try {
618
+ // Belt-and-suspenders: ensure the creds file is not world-readable.
619
+ if (fs.existsSync(credPath)) {
620
+ fs.chmodSync(credPath, 0o600)
621
+ }
622
+ } catch { /* best effort */ }
623
+ const sanitizedParentEnv = { ...process.env }
624
+ // If the parent process already had HOLYSHEEP_API_KEY in its env (e.g. the
625
+ // user exported it in their shell), DON'T propagate it — the fork reads
626
+ // the file instead. This closes the ps-leak vector for npm-global users.
627
+ delete sanitizedParentEnv.HOLYSHEEP_API_KEY
612
628
  const aionui = spawn(bunPath, ['dist-server/server.mjs'], {
613
629
  cwd: runtimeDir,
614
630
  env: {
615
- ...process.env,
631
+ ...sanitizedParentEnv,
616
632
  PORT: String(internalPort),
617
633
  HOST: '127.0.0.1',
618
634
  ALLOW_REMOTE: '',
619
635
  NODE_ENV: 'production',
620
636
  HOLYSHEEP_CLI_ROOT: cliRoot,
621
637
  HOLYSHEEP_CLI_VERSION: wrapperVersion,
622
- // Also forward the active HolySheep API key so the fork can surface it in
623
- // Dashboard without going through /api/holysheep/status round-trip.
624
- ...(process.env.HOLYSHEEP_API_KEY ? {} : (() => {
625
- try { const k = require('../utils/config').getApiKey(); return k ? { HOLYSHEEP_API_KEY: k } : {} } catch { return {} }
626
- })()),
638
+ HOLYSHEEP_CONFIG_PATH: credPath,
627
639
  },
628
640
  stdio: ['ignore', 'pipe', 'pipe'],
629
641
  })