@karpeleslab/teamclaude 1.0.7 → 1.0.9

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.
@@ -0,0 +1,85 @@
1
+ // Zero-dependency `fetch` shim that routes upstream requests through the sx.org
2
+ // proxy when it is enabled. With sx disabled it IS global fetch (byte-for-byte
3
+ // the same behavior), so the default path is unchanged.
4
+ //
5
+ // Node's global fetch can't use a CONNECT proxy without `undici` (a dependency —
6
+ // and "zero dependencies" is a project feature), so when sx is enabled we issue
7
+ // the request with `https.request` over a tunneled TLS socket and return a small
8
+ // object exposing exactly the fetch-Response surface src/server.js relies on:
9
+ // `status`, `headers.get()/.entries()`, `text()`, `arrayBuffer()`, and `body`
10
+ // (a web ReadableStream, so streamResponse()'s getReader()/cancel() is untouched).
11
+
12
+ import https from 'node:https';
13
+ import { Readable } from 'node:stream';
14
+ import { tunnelTls } from './sx.js';
15
+
16
+ // `useProxy` is decided by the caller (it varies per attempt — e.g. direct first,
17
+ // then via sx after a 429). With it false, or sx unprovisioned, this is plain fetch.
18
+ export function upstreamFetch(url, opts = {}, sx = null, useProxy = false) {
19
+ if (!sx || !useProxy || !sx.isProvisioned()) return fetch(url, opts);
20
+ return proxiedFetch(url, opts, sx);
21
+ }
22
+
23
+ function proxiedFetch(url, opts, sx) {
24
+ const u = new URL(url);
25
+ const proxy = sx.getProxy();
26
+
27
+ // Custom agent: every socket is a TLS connection tunneled through sx.org.
28
+ const agent = new https.Agent({ keepAlive: true });
29
+ agent.createConnection = (_options, cb) => {
30
+ // sx.tlsOptions is undefined in production (system CAs verify api.anthropic.com);
31
+ // tests inject a CA here to reach a self-signed upstream.
32
+ tunnelTls({ proxy, targetHost: u.hostname, targetPort: Number(u.port) || 443, tlsOptions: sx.tlsOptions || {} })
33
+ .then((sock) => cb(null, sock))
34
+ .catch((err) => cb(err));
35
+ return undefined; // socket delivered asynchronously via cb
36
+ };
37
+
38
+ return new Promise((resolve, reject) => {
39
+ const req = https.request(
40
+ u,
41
+ { method: opts.method || 'GET', headers: opts.headers || {}, agent },
42
+ (res) => resolve(makeResponse(res)),
43
+ );
44
+ req.once('error', reject);
45
+
46
+ const body = opts.body;
47
+ const method = (opts.method || 'GET').toUpperCase();
48
+ if (body == null || method === 'GET' || method === 'HEAD') req.end();
49
+ else if (typeof body === 'string' || Buffer.isBuffer(body) || body instanceof Uint8Array) req.end(Buffer.from(body));
50
+ else req.end(String(body));
51
+ });
52
+ }
53
+
54
+ // Wrap a Node IncomingMessage as the subset of a fetch Response that server.js uses.
55
+ function makeResponse(res) {
56
+ const web = Readable.toWeb(res); // single web stream — one consumer either way
57
+ const collect = async () => {
58
+ const chunks = [];
59
+ const reader = web.getReader();
60
+ for (;;) {
61
+ const { done, value } = await reader.read();
62
+ if (done) break;
63
+ chunks.push(Buffer.from(value));
64
+ }
65
+ return Buffer.concat(chunks);
66
+ };
67
+ return {
68
+ status: res.statusCode,
69
+ headers: makeHeaders(res.headers),
70
+ body: web,
71
+ async text() { return (await collect()).toString('utf8'); },
72
+ async arrayBuffer() { const b = await collect(); return b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength); },
73
+ };
74
+ }
75
+
76
+ // res.headers already has lowercased keys; values are string | string[] (set-cookie).
77
+ function makeHeaders(h) {
78
+ const flat = (v) => (Array.isArray(v) ? v.join(', ') : v);
79
+ const entries = function* () { for (const [k, v] of Object.entries(h)) yield [k, flat(v)]; };
80
+ return {
81
+ get: (name) => { const v = h[name.toLowerCase()]; return v == null ? null : flat(v); },
82
+ entries,
83
+ [Symbol.iterator]: entries,
84
+ };
85
+ }
package/src/x509.js ADDED
@@ -0,0 +1,166 @@
1
+ // Minimal pure-JS X.509 certificate generation (no external deps).
2
+ //
3
+ // node:crypto can create keypairs and sign, but cannot issue certificates, so
4
+ // we hand-encode the (small) ASN.1 DER cert envelope and sign the TBS with the
5
+ // issuer key. Used only to mint a local CA + a leaf for the MITM proxy, which
6
+ // the launched claude process trusts via NODE_EXTRA_CA_CERTS. Nothing here is a
7
+ // general-purpose ASN.1 library — just what these two certs need.
8
+
9
+ import { generateKeyPairSync, sign as cryptoSign, randomBytes } from 'node:crypto';
10
+
11
+ // ── ASN.1 DER primitives ──────────────────────────────────────
12
+
13
+ function derLen(n) {
14
+ if (n < 0x80) return Buffer.from([n]);
15
+ const bytes = [];
16
+ let x = n;
17
+ while (x > 0) { bytes.unshift(x & 0xff); x = Math.floor(x / 256); }
18
+ return Buffer.from([0x80 | bytes.length, ...bytes]);
19
+ }
20
+
21
+ function tlv(tag, content) {
22
+ return Buffer.concat([Buffer.from([tag]), derLen(content.length), content]);
23
+ }
24
+
25
+ const seq = (items) => tlv(0x30, Buffer.concat(items));
26
+ const set = (items) => tlv(0x31, Buffer.concat(items));
27
+ const NULL = Buffer.from([0x05, 0x00]);
28
+ const bool = (v) => tlv(0x01, Buffer.from([v ? 0xff : 0x00]));
29
+ const octet = (buf) => tlv(0x04, buf);
30
+ const bitString = (buf) => tlv(0x03, Buffer.concat([Buffer.from([0]), buf])); // 0 unused bits
31
+ const utf8 = (s) => tlv(0x0c, Buffer.from(s, 'utf8'));
32
+ const explicit = (n, content) => tlv(0xa0 | n, content); // [n] constructed
33
+ const ctxPrim = (n, content) => tlv(0x80 | n, content); // [n] primitive
34
+
35
+ function integer(buf) {
36
+ let b = Buffer.isBuffer(buf) ? Buffer.from(buf) : Buffer.from([buf]);
37
+ let i = 0;
38
+ while (i < b.length - 1 && b[i] === 0) i++; // strip leading zeros
39
+ b = b.subarray(i);
40
+ if (b[0] & 0x80) b = Buffer.concat([Buffer.from([0]), b]); // keep positive
41
+ return tlv(0x02, b);
42
+ }
43
+
44
+ function oid(dotted) {
45
+ const parts = dotted.split('.').map(Number);
46
+ const out = [40 * parts[0] + parts[1]];
47
+ for (let i = 2; i < parts.length; i++) {
48
+ let v = parts[i];
49
+ const group = [v & 0x7f];
50
+ v = Math.floor(v / 128);
51
+ while (v > 0) { group.unshift((v & 0x7f) | 0x80); v = Math.floor(v / 128); }
52
+ out.push(...group);
53
+ }
54
+ return tlv(0x06, Buffer.from(out));
55
+ }
56
+
57
+ function utcTime(date) {
58
+ const z = (n) => String(n).padStart(2, '0');
59
+ const s = `${z(date.getUTCFullYear() % 100)}${z(date.getUTCMonth() + 1)}${z(date.getUTCDate())}` +
60
+ `${z(date.getUTCHours())}${z(date.getUTCMinutes())}${z(date.getUTCSeconds())}Z`;
61
+ return tlv(0x17, Buffer.from(s, 'ascii'));
62
+ }
63
+
64
+ function pem(der, label) {
65
+ const b64 = der.toString('base64').replace(/(.{64})/g, '$1\n').replace(/\n$/, '');
66
+ return `-----BEGIN ${label}-----\n${b64}\n-----END ${label}-----\n`;
67
+ }
68
+
69
+ // ── cert pieces ───────────────────────────────────────────────
70
+
71
+ const SIG_ALG = seq([oid('1.2.840.113549.1.1.11'), NULL]); // sha256WithRSAEncryption
72
+
73
+ function nameCN(cn) {
74
+ return seq([set([seq([oid('2.5.4.3'), utf8(cn)])])]); // RDNSequence with one CN
75
+ }
76
+
77
+ function ext(extOid, critical, valueDer) {
78
+ const items = [oid(extOid)];
79
+ if (critical) items.push(bool(true));
80
+ items.push(octet(valueDer));
81
+ return seq(items);
82
+ }
83
+
84
+ // keyUsage BIT STRING from named bit positions (bit 0 = MSB of first byte).
85
+ function keyUsage(bits) {
86
+ const max = Math.max(...bits);
87
+ const nbytes = Math.floor(max / 8) + 1;
88
+ const bytes = Buffer.alloc(nbytes);
89
+ for (const b of bits) bytes[Math.floor(b / 8)] |= 0x80 >> (b % 8);
90
+ const unused = nbytes * 8 - (max + 1);
91
+ return tlv(0x03, Buffer.concat([Buffer.from([unused]), bytes]));
92
+ }
93
+
94
+ function buildCert({ subjectCN, issuerCN, spkiDer, signKey, isCA, altDnsNames = [], days }) {
95
+ const now = new Date();
96
+ const notBefore = new Date(now.getTime() - 60 * 60 * 1000); // 1h back for clock skew
97
+ const notAfter = new Date(now.getTime() + days * 24 * 60 * 60 * 1000);
98
+
99
+ const extList = [];
100
+ extList.push(ext('2.5.29.19', true, isCA ? seq([bool(true)]) : seq([]))); // basicConstraints
101
+ extList.push(ext('2.5.29.15', true, isCA
102
+ ? keyUsage([0, 5, 6]) // digitalSignature, keyCertSign, cRLSign
103
+ : keyUsage([0, 2]))); // digitalSignature, keyEncipherment
104
+ if (!isCA) {
105
+ extList.push(ext('2.5.29.37', false, seq([oid('1.3.6.1.5.5.7.3.1')]))); // extKeyUsage serverAuth
106
+ if (altDnsNames.length) {
107
+ extList.push(ext('2.5.29.17', false, seq(altDnsNames.map((d) => ctxPrim(2, Buffer.from(d)))))); // SAN dNSName
108
+ }
109
+ }
110
+
111
+ const tbs = seq([
112
+ explicit(0, integer(Buffer.from([2]))), // version v3
113
+ integer(randomBytes(16)), // serial
114
+ SIG_ALG,
115
+ nameCN(issuerCN),
116
+ seq([utcTime(notBefore), utcTime(notAfter)]),
117
+ nameCN(subjectCN),
118
+ spkiDer, // SubjectPublicKeyInfo (already DER)
119
+ explicit(3, seq(extList)),
120
+ ]);
121
+
122
+ const signature = cryptoSign('sha256', tbs, signKey); // RSASSA-PKCS1-v1_5
123
+ return pem(seq([tbs, SIG_ALG, bitString(signature)]), 'CERTIFICATE');
124
+ }
125
+
126
+ // ── public API ────────────────────────────────────────────────
127
+
128
+ function newRsaKey() {
129
+ const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
130
+ return {
131
+ privateKey,
132
+ keyPem: privateKey.export({ type: 'pkcs8', format: 'pem' }),
133
+ spkiDer: publicKey.export({ type: 'spki', format: 'der' }),
134
+ };
135
+ }
136
+
137
+ export function createCA(cn = 'TeamClaude Local CA') {
138
+ const key = newRsaKey();
139
+ const certPem = buildCert({
140
+ subjectCN: cn, issuerCN: cn, spkiDer: key.spkiDer, signKey: key.privateKey,
141
+ isCA: true, days: 3650,
142
+ });
143
+ return { cn, certPem, keyPem: key.keyPem, privateKey: key.privateKey };
144
+ }
145
+
146
+ export function createLeaf(hosts, ca) {
147
+ const list = Array.isArray(hosts) ? hosts : [hosts];
148
+ const key = newRsaKey();
149
+ const certPem = buildCert({
150
+ subjectCN: list[0], issuerCN: ca.cn, spkiDer: key.spkiDer, signKey: ca.privateKey,
151
+ isCA: false, altDnsNames: list, days: 825,
152
+ });
153
+ return { certPem, keyPem: key.keyPem };
154
+ }
155
+
156
+ /** Generate a fresh CA + a leaf covering `hosts` (string or array). Returns PEM strings. */
157
+ export function generateCertChain(hosts) {
158
+ const ca = createCA();
159
+ const leaf = createLeaf(hosts, ca);
160
+ return {
161
+ caCertPem: ca.certPem,
162
+ caKeyPem: ca.keyPem,
163
+ leafCertPem: leaf.certPem,
164
+ leafKeyPem: leaf.keyPem,
165
+ };
166
+ }