claude-use 5.1.0 → 5.2.0
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/README.md +2 -0
- package/dist/cli.cjs +2813 -19202
- package/dist/index.cjs +288 -58
- package/dist/index.mjs +286 -56
- package/dist/types/frontdoor/connect.d.ts +3 -3
- package/dist/types/frontdoor/x509.d.ts +11 -0
- package/dist/types/launcher/ports.d.ts +2 -0
- package/dist/types/paths.d.ts +6 -0
- package/package.json +1 -3
package/dist/index.mjs
CHANGED
|
@@ -1759,12 +1759,283 @@ function recoveryDiagnostics(recovery, identity) {
|
|
|
1759
1759
|
}
|
|
1760
1760
|
|
|
1761
1761
|
// src/frontdoor/connect.ts
|
|
1762
|
+
import { X509Certificate as X509Certificate2, createPrivateKey as createPrivateKey2 } from "node:crypto";
|
|
1762
1763
|
import * as fs from "node:fs";
|
|
1763
1764
|
import * as http from "node:http";
|
|
1764
1765
|
import * as https from "node:https";
|
|
1765
|
-
import * as
|
|
1766
|
+
import * as net2 from "node:net";
|
|
1766
1767
|
import * as tls from "node:tls";
|
|
1767
|
-
|
|
1768
|
+
|
|
1769
|
+
// src/frontdoor/x509.ts
|
|
1770
|
+
import { createHash, createPrivateKey, generateKeyPairSync, randomBytes, sign, X509Certificate } from "node:crypto";
|
|
1771
|
+
import net from "node:net";
|
|
1772
|
+
var TAG_BOOLEAN = 1;
|
|
1773
|
+
var TAG_INTEGER = 2;
|
|
1774
|
+
var TAG_BIT_STRING = 3;
|
|
1775
|
+
var TAG_OCTET_STRING = 4;
|
|
1776
|
+
var TAG_NULL = 5;
|
|
1777
|
+
var TAG_OID = 6;
|
|
1778
|
+
var TAG_UTF8_STRING = 12;
|
|
1779
|
+
var TAG_UTC_TIME = 23;
|
|
1780
|
+
var TAG_GENERALIZED_TIME = 24;
|
|
1781
|
+
var TAG_SEQUENCE = 48;
|
|
1782
|
+
var TAG_SET = 49;
|
|
1783
|
+
var TAG_VERSION = 160;
|
|
1784
|
+
var TAG_EXTENSIONS = 163;
|
|
1785
|
+
var TAG_GENERAL_NAME_DNS = 130;
|
|
1786
|
+
var TAG_GENERAL_NAME_IP = 135;
|
|
1787
|
+
var TAG_KEY_IDENTIFIER = 128;
|
|
1788
|
+
var OID_COMMON_NAME = "2.5.4.3";
|
|
1789
|
+
var OID_SHA256_WITH_RSA = "1.2.840.113549.1.1.11";
|
|
1790
|
+
var OID_SUBJECT_KEY_IDENTIFIER = "2.5.29.14";
|
|
1791
|
+
var OID_KEY_USAGE = "2.5.29.15";
|
|
1792
|
+
var OID_SUBJECT_ALT_NAME = "2.5.29.17";
|
|
1793
|
+
var OID_BASIC_CONSTRAINTS = "2.5.29.19";
|
|
1794
|
+
var OID_AUTHORITY_KEY_IDENTIFIER = "2.5.29.35";
|
|
1795
|
+
var OID_EXT_KEY_USAGE = "2.5.29.37";
|
|
1796
|
+
var OID_SERVER_AUTH = "1.3.6.1.5.5.7.3.1";
|
|
1797
|
+
var VERSION_3 = 2;
|
|
1798
|
+
var SERIAL_NUMBER_BYTES = 16;
|
|
1799
|
+
var RSA_MODULUS_BITS = 2048;
|
|
1800
|
+
var BITS_PER_BYTE = 8;
|
|
1801
|
+
var BYTE_MASK = 255;
|
|
1802
|
+
var HIGH_BIT = 128;
|
|
1803
|
+
var BASE_128_PAYLOAD_MASK = 127;
|
|
1804
|
+
var FIRST_ARC_WEIGHT = 40;
|
|
1805
|
+
var SHORT_FORM_MAX_LENGTH = 127;
|
|
1806
|
+
var MS_PER_SECOND = 1e3;
|
|
1807
|
+
var UTC_TIME_LAST_YEAR = 2049;
|
|
1808
|
+
var YEAR_DIGITS = 4;
|
|
1809
|
+
var TWO_DIGIT_YEAR_DIGITS = 2;
|
|
1810
|
+
var COMPACT_TIME_DIGITS = 14;
|
|
1811
|
+
var KEY_USAGE = { digitalSignature: 0, keyEncipherment: 2, keyCertSign: 5, cRLSign: 6 };
|
|
1812
|
+
function tlv(tag, content) {
|
|
1813
|
+
const length = content.length;
|
|
1814
|
+
if (length <= SHORT_FORM_MAX_LENGTH) {
|
|
1815
|
+
return Buffer.concat([Buffer.from([tag, length]), content]);
|
|
1816
|
+
}
|
|
1817
|
+
const lengthBytes = [];
|
|
1818
|
+
for (let remaining = length; remaining > 0; remaining = Math.floor(remaining / (BYTE_MASK + 1))) {
|
|
1819
|
+
lengthBytes.unshift(remaining & BYTE_MASK);
|
|
1820
|
+
}
|
|
1821
|
+
return Buffer.concat([Buffer.from([tag, HIGH_BIT | lengthBytes.length, ...lengthBytes]), content]);
|
|
1822
|
+
}
|
|
1823
|
+
function sequence(...parts) {
|
|
1824
|
+
return tlv(TAG_SEQUENCE, Buffer.concat(parts));
|
|
1825
|
+
}
|
|
1826
|
+
function unsignedInteger(magnitude) {
|
|
1827
|
+
let start = 0;
|
|
1828
|
+
while (start < magnitude.length - 1 && magnitude[start] === 0) {
|
|
1829
|
+
start += 1;
|
|
1830
|
+
}
|
|
1831
|
+
const trimmed = magnitude.subarray(start);
|
|
1832
|
+
const first = trimmed[0];
|
|
1833
|
+
const padded = first !== void 0 && (first & HIGH_BIT) !== 0 ? Buffer.concat([Buffer.from([0]), trimmed]) : Buffer.from(trimmed);
|
|
1834
|
+
return tlv(TAG_INTEGER, padded);
|
|
1835
|
+
}
|
|
1836
|
+
function smallInteger(value) {
|
|
1837
|
+
return unsignedInteger(Buffer.from([value]));
|
|
1838
|
+
}
|
|
1839
|
+
function oid(dotted) {
|
|
1840
|
+
const arcs = dotted.split(".").map(Number);
|
|
1841
|
+
const [first, second, ...rest] = arcs;
|
|
1842
|
+
if (first === void 0 || second === void 0 || arcs.some((arc) => !Number.isInteger(arc) || arc < 0)) {
|
|
1843
|
+
throw new Error(`invalid object identifier ${dotted}`);
|
|
1844
|
+
}
|
|
1845
|
+
const encoded = [];
|
|
1846
|
+
for (const arc of [first * FIRST_ARC_WEIGHT + second, ...rest]) {
|
|
1847
|
+
const groups = [arc & BASE_128_PAYLOAD_MASK];
|
|
1848
|
+
for (let remaining = Math.floor(arc / HIGH_BIT); remaining > 0; remaining = Math.floor(remaining / HIGH_BIT)) {
|
|
1849
|
+
groups.unshift(remaining & BASE_128_PAYLOAD_MASK | HIGH_BIT);
|
|
1850
|
+
}
|
|
1851
|
+
encoded.push(...groups);
|
|
1852
|
+
}
|
|
1853
|
+
return tlv(TAG_OID, Buffer.from(encoded));
|
|
1854
|
+
}
|
|
1855
|
+
function boolean(value) {
|
|
1856
|
+
return tlv(TAG_BOOLEAN, Buffer.from([value ? BYTE_MASK : 0]));
|
|
1857
|
+
}
|
|
1858
|
+
function octetString(content) {
|
|
1859
|
+
return tlv(TAG_OCTET_STRING, content);
|
|
1860
|
+
}
|
|
1861
|
+
function wholeBitString(content) {
|
|
1862
|
+
return tlv(TAG_BIT_STRING, Buffer.concat([Buffer.from([0]), content]));
|
|
1863
|
+
}
|
|
1864
|
+
function time(instant) {
|
|
1865
|
+
const iso = new Date(Math.floor(instant.getTime() / MS_PER_SECOND) * MS_PER_SECOND).toISOString();
|
|
1866
|
+
const compact = `${iso.replace(/[-:T]/g, "").slice(0, COMPACT_TIME_DIGITS)}Z`;
|
|
1867
|
+
if (instant.getUTCFullYear() > UTC_TIME_LAST_YEAR) {
|
|
1868
|
+
return tlv(TAG_GENERALIZED_TIME, Buffer.from(compact, "ascii"));
|
|
1869
|
+
}
|
|
1870
|
+
return tlv(TAG_UTC_TIME, Buffer.from(compact.slice(YEAR_DIGITS - TWO_DIGIT_YEAR_DIGITS), "ascii"));
|
|
1871
|
+
}
|
|
1872
|
+
function commonNameOnly(commonName) {
|
|
1873
|
+
return sequence(tlv(TAG_SET, sequence(oid(OID_COMMON_NAME), tlv(TAG_UTF8_STRING, Buffer.from(commonName, "utf8")))));
|
|
1874
|
+
}
|
|
1875
|
+
var SIGNATURE_ALGORITHM = sequence(oid(OID_SHA256_WITH_RSA), tlv(TAG_NULL, Buffer.alloc(0)));
|
|
1876
|
+
function extension(extensionOid, critical, value) {
|
|
1877
|
+
return sequence(oid(extensionOid), ...critical ? [boolean(true)] : [], octetString(value));
|
|
1878
|
+
}
|
|
1879
|
+
function keyUsageExtension(bits) {
|
|
1880
|
+
const highest = Math.max(...bits);
|
|
1881
|
+
const byteCount = Math.floor(highest / BITS_PER_BYTE) + 1;
|
|
1882
|
+
const bytes = Buffer.alloc(byteCount);
|
|
1883
|
+
for (const bit of bits) {
|
|
1884
|
+
const index = Math.floor(bit / BITS_PER_BYTE);
|
|
1885
|
+
bytes[index] = (bytes[index] ?? 0) | HIGH_BIT >> bit % BITS_PER_BYTE;
|
|
1886
|
+
}
|
|
1887
|
+
const unusedBits = BITS_PER_BYTE - 1 - highest % BITS_PER_BYTE;
|
|
1888
|
+
return extension(OID_KEY_USAGE, true, tlv(TAG_BIT_STRING, Buffer.concat([Buffer.from([unusedBits]), bytes])));
|
|
1889
|
+
}
|
|
1890
|
+
var IPV6_GROUPS = 8;
|
|
1891
|
+
var HEX_RADIX = 16;
|
|
1892
|
+
var BYTES_PER_IPV6_GROUP = 2;
|
|
1893
|
+
function ipv6Groups(address) {
|
|
1894
|
+
const [head = "", tail] = address.split("::");
|
|
1895
|
+
const split = (text) => text === "" ? [] : text.split(":");
|
|
1896
|
+
const expand = (groups) => groups.flatMap((group) => {
|
|
1897
|
+
if (!net.isIPv4(group)) {
|
|
1898
|
+
return [Number.parseInt(group, HEX_RADIX)];
|
|
1899
|
+
}
|
|
1900
|
+
const [a = 0, b = 0, c = 0, d = 0] = group.split(".").map(Number);
|
|
1901
|
+
return [a * (BYTE_MASK + 1) + b, c * (BYTE_MASK + 1) + d];
|
|
1902
|
+
});
|
|
1903
|
+
const before = expand(split(head));
|
|
1904
|
+
const after = tail === void 0 ? [] : expand(split(tail));
|
|
1905
|
+
const filler = tail === void 0 ? [] : Array.from({ length: IPV6_GROUPS - before.length - after.length }, () => 0);
|
|
1906
|
+
return [...before, ...filler, ...after];
|
|
1907
|
+
}
|
|
1908
|
+
function ipBytes(address) {
|
|
1909
|
+
if (net.isIPv4(address)) {
|
|
1910
|
+
return Buffer.from(address.split(".").map(Number));
|
|
1911
|
+
}
|
|
1912
|
+
if (!net.isIPv6(address)) {
|
|
1913
|
+
throw new Error(`${address} is not an IP address`);
|
|
1914
|
+
}
|
|
1915
|
+
const bytes = Buffer.alloc(IPV6_GROUPS * BYTES_PER_IPV6_GROUP);
|
|
1916
|
+
ipv6Groups(address).forEach((group, index) => {
|
|
1917
|
+
bytes.writeUInt16BE(group, index * BYTES_PER_IPV6_GROUP);
|
|
1918
|
+
});
|
|
1919
|
+
return bytes;
|
|
1920
|
+
}
|
|
1921
|
+
function keyIdentifier(publicKey) {
|
|
1922
|
+
return createHash("sha1").update(publicKey.export({ type: "pkcs1", format: "der" })).digest();
|
|
1923
|
+
}
|
|
1924
|
+
function readElement(buffer, offset) {
|
|
1925
|
+
const tag = buffer[offset];
|
|
1926
|
+
const first = buffer[offset + 1];
|
|
1927
|
+
if (tag === void 0 || first === void 0) {
|
|
1928
|
+
throw new Error("truncated DER");
|
|
1929
|
+
}
|
|
1930
|
+
let length = first;
|
|
1931
|
+
let contentStart = offset + 2;
|
|
1932
|
+
if ((first & HIGH_BIT) !== 0) {
|
|
1933
|
+
const count = first & BASE_128_PAYLOAD_MASK;
|
|
1934
|
+
length = 0;
|
|
1935
|
+
for (let index = 0; index < count; index += 1) {
|
|
1936
|
+
length = length * (BYTE_MASK + 1) + (buffer[offset + 2 + index] ?? 0);
|
|
1937
|
+
}
|
|
1938
|
+
contentStart = offset + 2 + count;
|
|
1939
|
+
}
|
|
1940
|
+
return { tag, start: offset, contentStart, end: contentStart + length };
|
|
1941
|
+
}
|
|
1942
|
+
function subjectNameOf(certificateDer) {
|
|
1943
|
+
const certificate = readElement(certificateDer, 0);
|
|
1944
|
+
let cursor = readElement(certificateDer, certificate.contentStart);
|
|
1945
|
+
const tbsEnd = cursor.end;
|
|
1946
|
+
cursor = readElement(certificateDer, cursor.contentStart);
|
|
1947
|
+
if (cursor.tag === TAG_VERSION) {
|
|
1948
|
+
cursor = readElement(certificateDer, cursor.end);
|
|
1949
|
+
}
|
|
1950
|
+
const fieldsBeforeSubject = 4;
|
|
1951
|
+
for (let skipped = 1; skipped < fieldsBeforeSubject; skipped += 1) {
|
|
1952
|
+
cursor = readElement(certificateDer, cursor.end);
|
|
1953
|
+
}
|
|
1954
|
+
const subject = readElement(certificateDer, cursor.end);
|
|
1955
|
+
if (subject.end > tbsEnd) {
|
|
1956
|
+
throw new Error("certificate has no subject");
|
|
1957
|
+
}
|
|
1958
|
+
return Buffer.from(certificateDer.subarray(subject.start, subject.end));
|
|
1959
|
+
}
|
|
1960
|
+
function toPem(label, der) {
|
|
1961
|
+
const body = Buffer.from(der).toString("base64").match(/.{1,64}/g) ?? [];
|
|
1962
|
+
return `-----BEGIN ${label}-----
|
|
1963
|
+
${body.join("\n")}
|
|
1964
|
+
-----END ${label}-----
|
|
1965
|
+
`;
|
|
1966
|
+
}
|
|
1967
|
+
function randomSerial() {
|
|
1968
|
+
const bytes = randomBytes(SERIAL_NUMBER_BYTES);
|
|
1969
|
+
bytes[0] = (bytes[0] ?? 0) & BASE_128_PAYLOAD_MASK | HIGH_BIT >> 1;
|
|
1970
|
+
return bytes;
|
|
1971
|
+
}
|
|
1972
|
+
function assemble(spec) {
|
|
1973
|
+
const tbs = sequence(
|
|
1974
|
+
tlv(TAG_VERSION, smallInteger(VERSION_3)),
|
|
1975
|
+
unsignedInteger(randomSerial()),
|
|
1976
|
+
SIGNATURE_ALGORITHM,
|
|
1977
|
+
spec.issuerName,
|
|
1978
|
+
sequence(time(spec.notBefore), time(spec.notAfter)),
|
|
1979
|
+
spec.subjectName,
|
|
1980
|
+
spec.subjectPublicKey.export({ type: "spki", format: "der" }),
|
|
1981
|
+
tlv(TAG_EXTENSIONS, sequence(...spec.extensions))
|
|
1982
|
+
);
|
|
1983
|
+
const signature = sign("sha256", tbs, spec.signingKey);
|
|
1984
|
+
return sequence(tbs, SIGNATURE_ALGORITHM, wholeBitString(signature));
|
|
1985
|
+
}
|
|
1986
|
+
function subjectKeyIdentifierExtension(publicKey) {
|
|
1987
|
+
return extension(OID_SUBJECT_KEY_IDENTIFIER, false, octetString(keyIdentifier(publicKey)));
|
|
1988
|
+
}
|
|
1989
|
+
function basicConstraintsExtension(isCa) {
|
|
1990
|
+
return extension(OID_BASIC_CONSTRAINTS, true, sequence(...isCa ? [boolean(true)] : []));
|
|
1991
|
+
}
|
|
1992
|
+
function rsaKeyPair() {
|
|
1993
|
+
return generateKeyPairSync("rsa", { modulusLength: RSA_MODULUS_BITS });
|
|
1994
|
+
}
|
|
1995
|
+
function keyPem(privateKey) {
|
|
1996
|
+
return privateKey.export({ type: "pkcs8", format: "pem" });
|
|
1997
|
+
}
|
|
1998
|
+
function issueCaCertificate(commonName, notBefore, notAfter) {
|
|
1999
|
+
const { publicKey, privateKey } = rsaKeyPair();
|
|
2000
|
+
const name = commonNameOnly(commonName);
|
|
2001
|
+
const der = assemble({
|
|
2002
|
+
subjectPublicKey: publicKey,
|
|
2003
|
+
subjectName: name,
|
|
2004
|
+
issuerName: name,
|
|
2005
|
+
signingKey: privateKey,
|
|
2006
|
+
notBefore,
|
|
2007
|
+
notAfter,
|
|
2008
|
+
extensions: [basicConstraintsExtension(true), keyUsageExtension([KEY_USAGE.keyCertSign, KEY_USAGE.cRLSign]), subjectKeyIdentifierExtension(publicKey)]
|
|
2009
|
+
});
|
|
2010
|
+
return { certPem: toPem("CERTIFICATE", der), keyPem: keyPem(privateKey) };
|
|
2011
|
+
}
|
|
2012
|
+
function issueLeafCertificate(ca, names, notBefore, notAfter) {
|
|
2013
|
+
const [commonName] = names;
|
|
2014
|
+
if (commonName === void 0) {
|
|
2015
|
+
throw new Error("a leaf certificate needs at least one name");
|
|
2016
|
+
}
|
|
2017
|
+
const caCertificate = new X509Certificate(ca.certPem);
|
|
2018
|
+
const caPrivateKey = createPrivateKey(ca.keyPem);
|
|
2019
|
+
const { publicKey, privateKey } = rsaKeyPair();
|
|
2020
|
+
const generalNames = names.map((name) => net.isIP(name) === 0 ? tlv(TAG_GENERAL_NAME_DNS, Buffer.from(name, "ascii")) : tlv(TAG_GENERAL_NAME_IP, ipBytes(name)));
|
|
2021
|
+
const der = assemble({
|
|
2022
|
+
subjectPublicKey: publicKey,
|
|
2023
|
+
subjectName: commonNameOnly(commonName),
|
|
2024
|
+
issuerName: subjectNameOf(caCertificate.raw),
|
|
2025
|
+
signingKey: caPrivateKey,
|
|
2026
|
+
notBefore,
|
|
2027
|
+
notAfter,
|
|
2028
|
+
extensions: [
|
|
2029
|
+
basicConstraintsExtension(false),
|
|
2030
|
+
keyUsageExtension([KEY_USAGE.digitalSignature, KEY_USAGE.keyEncipherment]),
|
|
2031
|
+
extension(OID_EXT_KEY_USAGE, false, sequence(oid(OID_SERVER_AUTH))),
|
|
2032
|
+
extension(OID_SUBJECT_ALT_NAME, false, sequence(...generalNames)),
|
|
2033
|
+
subjectKeyIdentifierExtension(publicKey),
|
|
2034
|
+
extension(OID_AUTHORITY_KEY_IDENTIFIER, false, sequence(tlv(TAG_KEY_IDENTIFIER, keyIdentifier(caCertificate.publicKey))))
|
|
2035
|
+
]
|
|
2036
|
+
});
|
|
2037
|
+
return { certPem: toPem("CERTIFICATE", der), keyPem: keyPem(privateKey) };
|
|
2038
|
+
}
|
|
1768
2039
|
|
|
1769
2040
|
// src/frontdoor/capability.ts
|
|
1770
2041
|
var CONNECT_PROXY_REALM = "claude-use front door";
|
|
@@ -1967,7 +2238,6 @@ var CONNECT_INTERCEPT_HOST = "api.anthropic.com";
|
|
|
1967
2238
|
var ROUTED_PATH_PREFIX = "/v1/";
|
|
1968
2239
|
var HTTPS_PORT = 443;
|
|
1969
2240
|
var MAX_PORT = 65535;
|
|
1970
|
-
var SERIAL_NUMBER_BYTES = 16;
|
|
1971
2241
|
var HTTP_BAD_GATEWAY = 502;
|
|
1972
2242
|
var CA_KEY_FILE_MODE = 384;
|
|
1973
2243
|
var CONNECT_LINE_TOKENS = 3;
|
|
@@ -2039,50 +2309,10 @@ function forwardableHeaders(headers) {
|
|
|
2039
2309
|
return result;
|
|
2040
2310
|
}
|
|
2041
2311
|
function generateCa(now) {
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
cert.publicKey = keys.publicKey;
|
|
2045
|
-
cert.serialNumber = forge.util.bytesToHex(forge.random.getBytesSync(SERIAL_NUMBER_BYTES));
|
|
2046
|
-
cert.validity.notBefore = new Date(now.getTime() - MS_PER_DAY);
|
|
2047
|
-
cert.validity.notAfter = new Date(now.getTime() + CA_VALIDITY_DAYS * MS_PER_DAY);
|
|
2048
|
-
const subject = [{ name: "commonName", value: CA_SUBJECT_COMMON_NAME }];
|
|
2049
|
-
cert.setSubject(subject);
|
|
2050
|
-
cert.setIssuer(subject);
|
|
2051
|
-
cert.setExtensions([
|
|
2052
|
-
{ name: "basicConstraints", cA: true, critical: true },
|
|
2053
|
-
{ name: "keyUsage", keyCertSign: true, cRLSign: true, critical: true },
|
|
2054
|
-
{ name: "subjectKeyIdentifier" }
|
|
2055
|
-
]);
|
|
2056
|
-
cert.sign(keys.privateKey, forge.md.sha256.create());
|
|
2057
|
-
return { certPem: forge.pki.certificateToPem(cert), keyPem: forge.pki.privateKeyToPem(keys.privateKey) };
|
|
2058
|
-
}
|
|
2059
|
-
var SAN_TYPE_DNS = 2;
|
|
2060
|
-
var SAN_TYPE_IP = 7;
|
|
2312
|
+
return issueCaCertificate(CA_SUBJECT_COMMON_NAME, new Date(now.getTime() - MS_PER_DAY), new Date(now.getTime() + CA_VALIDITY_DAYS * MS_PER_DAY));
|
|
2313
|
+
}
|
|
2061
2314
|
function mintLeaf(ca, names, now) {
|
|
2062
|
-
|
|
2063
|
-
if (commonName === void 0) {
|
|
2064
|
-
throw new Error("a leaf certificate needs at least one name");
|
|
2065
|
-
}
|
|
2066
|
-
const caCert = forge.pki.certificateFromPem(ca.certPem);
|
|
2067
|
-
const caKey = forge.pki.privateKeyFromPem(ca.keyPem);
|
|
2068
|
-
const keys = forge.pki.rsa.generateKeyPair({ bits: 2048 });
|
|
2069
|
-
const cert = forge.pki.createCertificate();
|
|
2070
|
-
cert.publicKey = keys.publicKey;
|
|
2071
|
-
cert.serialNumber = forge.util.bytesToHex(forge.random.getBytesSync(SERIAL_NUMBER_BYTES));
|
|
2072
|
-
cert.validity.notBefore = new Date(now.getTime() - MS_PER_DAY);
|
|
2073
|
-
cert.validity.notAfter = new Date(now.getTime() + LEAF_VALIDITY_DAYS * MS_PER_DAY);
|
|
2074
|
-
cert.setSubject([{ name: "commonName", value: commonName }]);
|
|
2075
|
-
cert.setIssuer(caCert.subject.attributes);
|
|
2076
|
-
cert.setExtensions([
|
|
2077
|
-
{ name: "basicConstraints", cA: false, critical: true },
|
|
2078
|
-
{ name: "keyUsage", digitalSignature: true, keyEncipherment: true, critical: true },
|
|
2079
|
-
{ name: "extKeyUsage", serverAuth: true },
|
|
2080
|
-
{ name: "subjectAltName", altNames: names.map((name) => net.isIP(name) === 0 ? { type: SAN_TYPE_DNS, value: name } : { type: SAN_TYPE_IP, ip: name }) },
|
|
2081
|
-
{ name: "subjectKeyIdentifier" },
|
|
2082
|
-
{ name: "authorityKeyIdentifier", keyLocatorFields: [] }
|
|
2083
|
-
]);
|
|
2084
|
-
cert.sign(caKey, forge.md.sha256.create());
|
|
2085
|
-
return { certPem: forge.pki.certificateToPem(cert), keyPem: forge.pki.privateKeyToPem(keys.privateKey) };
|
|
2315
|
+
return issueLeafCertificate(ca, names, new Date(now.getTime() - MS_PER_DAY), new Date(now.getTime() + LEAF_VALIDITY_DAYS * MS_PER_DAY));
|
|
2086
2316
|
}
|
|
2087
2317
|
function createLeafCache(ca, now) {
|
|
2088
2318
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -2107,8 +2337,8 @@ function ensureCa(store, generate) {
|
|
|
2107
2337
|
}
|
|
2108
2338
|
function caParses(ca) {
|
|
2109
2339
|
try {
|
|
2110
|
-
|
|
2111
|
-
|
|
2340
|
+
new X509Certificate2(ca.certPem);
|
|
2341
|
+
createPrivateKey2(ca.keyPem);
|
|
2112
2342
|
return true;
|
|
2113
2343
|
} catch {
|
|
2114
2344
|
return false;
|
|
@@ -2314,7 +2544,7 @@ function realConnectEffects() {
|
|
|
2314
2544
|
const tlsAgent = new https.Agent({ keepAlive: true });
|
|
2315
2545
|
const listenOnce = async (port, onSocket) => await new Promise((resolve, reject) => {
|
|
2316
2546
|
const accepted = /* @__PURE__ */ new Set();
|
|
2317
|
-
const server =
|
|
2547
|
+
const server = net2.createServer((socket) => {
|
|
2318
2548
|
accepted.add(socket);
|
|
2319
2549
|
socket.on("close", () => {
|
|
2320
2550
|
accepted.delete(socket);
|
|
@@ -2387,7 +2617,7 @@ function realConnectEffects() {
|
|
|
2387
2617
|
};
|
|
2388
2618
|
},
|
|
2389
2619
|
connectTcp: async (host, port) => await new Promise((resolve, reject) => {
|
|
2390
|
-
const socket =
|
|
2620
|
+
const socket = net2.connect({ host, port });
|
|
2391
2621
|
socket.once("connect", () => {
|
|
2392
2622
|
resolve(socket);
|
|
2393
2623
|
});
|
|
@@ -2452,8 +2682,8 @@ function realConnectCertStore(paths) {
|
|
|
2452
2682
|
return {
|
|
2453
2683
|
loadCa: () => {
|
|
2454
2684
|
const certPem = read(paths.frontdoorCaCertFile);
|
|
2455
|
-
const
|
|
2456
|
-
return certPem === void 0 ||
|
|
2685
|
+
const keyPem2 = read(paths.frontdoorCaKeyFile);
|
|
2686
|
+
return certPem === void 0 || keyPem2 === void 0 ? void 0 : { certPem, keyPem: keyPem2 };
|
|
2457
2687
|
},
|
|
2458
2688
|
writeCa: (ca) => {
|
|
2459
2689
|
fs.mkdirSync(paths.frontdoorCaDir, { recursive: true });
|
|
@@ -2468,7 +2698,7 @@ function realConnectCertStore(paths) {
|
|
|
2468
2698
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2469
2699
|
|
|
2470
2700
|
// src/headroom/state.ts
|
|
2471
|
-
import { createHash } from "node:crypto";
|
|
2701
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2472
2702
|
import path6 from "node:path";
|
|
2473
2703
|
import { z as z4 } from "zod";
|
|
2474
2704
|
var HEADROOM_ANTHROPIC_UPSTREAM = "https://api.anthropic.com";
|
|
@@ -2530,7 +2760,7 @@ function headroomUpstreams(providers, frontDoorOrigin2) {
|
|
|
2530
2760
|
return upstreams;
|
|
2531
2761
|
}
|
|
2532
2762
|
function hashAllowlist(allowlist) {
|
|
2533
|
-
return
|
|
2763
|
+
return createHash2("sha256").update(allowlist.join("\n")).digest("hex");
|
|
2534
2764
|
}
|
|
2535
2765
|
function readAllProviders(fs2, providersDir) {
|
|
2536
2766
|
const result = [];
|
|
@@ -2708,7 +2938,7 @@ function ensureHeadroom(params) {
|
|
|
2708
2938
|
}
|
|
2709
2939
|
|
|
2710
2940
|
// src/headroom/settings.ts
|
|
2711
|
-
import { createHash as
|
|
2941
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2712
2942
|
function settingsOf(config) {
|
|
2713
2943
|
return {
|
|
2714
2944
|
...config.mode === void 0 ? {} : { mode: config.mode },
|
|
@@ -2733,7 +2963,7 @@ function settingsEnv(settings) {
|
|
|
2733
2963
|
return settings.rolloutChannel === void 0 ? {} : { HEADROOM_ROLLOUT_CHANNEL: settings.rolloutChannel };
|
|
2734
2964
|
}
|
|
2735
2965
|
function hashSettings(settings) {
|
|
2736
|
-
return
|
|
2966
|
+
return createHash3("sha256").update(JSON.stringify([...settingsArgs(settings), JSON.stringify(settingsEnv(settings))])).digest("hex");
|
|
2737
2967
|
}
|
|
2738
2968
|
|
|
2739
2969
|
// src/headroom/supervisor.ts
|
|
@@ -69,7 +69,7 @@ export interface LeafCert {
|
|
|
69
69
|
readonly certPem: string;
|
|
70
70
|
readonly keyPem: string;
|
|
71
71
|
}
|
|
72
|
-
/** Generates the local CA: one RSA keypair, self-signed, CA-only by basicConstraints and keyUsage, with a random serial.
|
|
72
|
+
/** Generates the local CA: one RSA keypair, self-signed, CA-only by basicConstraints and keyUsage, with a random serial. Built on `node:crypto` alone (see `x509.ts`), no openssl, so the same code works on every platform claude-use ships to. */
|
|
73
73
|
export declare function generateCa(now: Readonly<Date>): CaMaterial;
|
|
74
74
|
/** The names the front door's provider listener answers to: the loopback address every provider session's base URL names, plus the loopback hostname for a client that spells it that way. */
|
|
75
75
|
export declare const LOOPBACK_LEAF_NAMES: readonly string[];
|
|
@@ -77,7 +77,7 @@ export declare const LOOPBACK_LEAF_NAMES: readonly string[];
|
|
|
77
77
|
* Mints a TLS leaf signed by the CA: its own RSA keypair, server-auth usage only, the first name as the subject's common name and every name in the SAN. A name that parses as an IP address goes in as an IP SAN, because TLS clients match an IP host only against IP SANs, never against a DNS SAN spelling the same digits.
|
|
78
78
|
*/
|
|
79
79
|
export declare function mintLeaf(ca: CaMaterial, names: readonly string[], now: Readonly<Date>): LeafCert;
|
|
80
|
-
/** Mints each host's leaf once from the CA and reuses it, because a 2048-bit keypair
|
|
80
|
+
/** Mints each host's leaf once from the CA and reuses it, because generating a 2048-bit RSA keypair costs noticeable time and nothing about the certificate changes between sessions. */
|
|
81
81
|
export declare function createLeafCache(ca: CaMaterial, now: () => Date): (host: string) => LeafCert;
|
|
82
82
|
/**
|
|
83
83
|
* Persistence for the CA: generated once on the machine's first headroom start and reused forever after, because regenerating it would invalidate every child's `NODE_EXTRA_CA_CERTS` pointing at the old file. The real implementation writes the key with mode 0600; injected fakes keep it in memory.
|
|
@@ -89,7 +89,7 @@ export interface ConnectCertStore {
|
|
|
89
89
|
readonly writeCa: (ca: CaMaterial) => void;
|
|
90
90
|
}
|
|
91
91
|
/**
|
|
92
|
-
* Loads the persisted CA, generating and persisting one when no usable CA exists. A stored pair that node
|
|
92
|
+
* Loads the persisted CA, generating and persisting one when no usable CA exists. A stored pair that `node:crypto` cannot parse is treated as absent and replaced: a truncated write is the one failure mode a regeneration can actually repair, and every child that trusted the old file needs a new `NODE_EXTRA_CA_CERTS` anyway once its CA stops parsing.
|
|
93
93
|
*/
|
|
94
94
|
export declare function ensureCa(store: ConnectCertStore, generate: () => CaMaterial): CaMaterial;
|
|
95
95
|
/** Where one forwarded request goes, and how: the connect surface's own forwards go to the real upstream over TLS, while a local hop over plain HTTP is what tests redirect to. */
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** An IP address as the 4 or 16 octets an iPAddress GeneralName carries. */
|
|
2
|
+
export declare function ipBytes(address: string): Buffer;
|
|
3
|
+
/** A key pair in the PEM forms claude-use persists. */
|
|
4
|
+
export interface PemKeyPair {
|
|
5
|
+
readonly certPem: string;
|
|
6
|
+
readonly keyPem: string;
|
|
7
|
+
}
|
|
8
|
+
/** Generates a self-signed CA certificate, CA-only by basicConstraints and keyUsage, with a random serial. */
|
|
9
|
+
export declare function issueCaCertificate(commonName: string, notBefore: Readonly<Date>, notAfter: Readonly<Date>): PemKeyPair;
|
|
10
|
+
/** Issues a server-auth leaf under a CA: its own RSA key, the first name as the common name, every name in the subjectAltName (an IP address as an iPAddress entry, because TLS clients match an IP host only against those). */
|
|
11
|
+
export declare function issueLeafCertificate(ca: PemKeyPair, names: readonly string[], notBefore: Readonly<Date>, notAfter: Readonly<Date>): PemKeyPair;
|
|
@@ -46,6 +46,8 @@ export interface FarmFs {
|
|
|
46
46
|
readonly mkdirPrivate: (path: string) => void;
|
|
47
47
|
/** Writes a file's full contents as UTF-8 text readable only by its owner (mode 0600), atomically: a temporary sibling is written at that mode and renamed into place, so no reader ever sees a partial file or a wider mode. */
|
|
48
48
|
readonly writeFilePrivate: (path: string, contents: string) => void;
|
|
49
|
+
/** Appends UTF-8 text to a file readable only by its owner (mode 0600), creating it at that mode when it is missing and narrowing an existing wider mode. The file is opened in append mode, so the text always lands at the end however the file grew; a reader running at the same moment can still see a trailing partial write. It is the primitive an append-only log with one writer per file is built on. */
|
|
50
|
+
readonly appendFilePrivate: (path: string, contents: string) => void;
|
|
49
51
|
/** Creates a file only if it does not already exist, returning false when it does. This is the exclusive-create the identity lock depends on for its mutual exclusion — a read-then-write pair would race. */
|
|
50
52
|
readonly writeFileExclusive: (path: string, contents: string) => boolean;
|
|
51
53
|
/** A stable content hash of one file, or undefined when it is missing or is not a regular file. Used only to recognise farm data that has already been adopted into the canonical tree. */
|
package/dist/types/paths.d.ts
CHANGED
|
@@ -52,6 +52,12 @@ export interface LayoutPaths {
|
|
|
52
52
|
readonly frontdoorSessionsDir: string;
|
|
53
53
|
/** Path to the front door's log: one line per lifecycle event and routed failure, never a token. */
|
|
54
54
|
readonly frontdoorLogPath: string;
|
|
55
|
+
/** Directory holding the usage store, mode 0700: the request log and the per-identity snapshots. Metadata only, never content or credentials. */
|
|
56
|
+
readonly usageDir: string;
|
|
57
|
+
/** Directory of the append-only usage log: one `<YYYY-MM-DD>.<writer-pid>.jsonl` segment per writing process per UTC day. */
|
|
58
|
+
readonly usageLogDir: string;
|
|
59
|
+
/** Directory of the latest-state snapshots, one `<identity>.json` per identity: the stable file other tools read. */
|
|
60
|
+
readonly usageSnapshotsDir: string;
|
|
55
61
|
}
|
|
56
62
|
/**
|
|
57
63
|
* Resolves the current CLAUDE_USE_HOME root: the environment variable when set to a non-empty string, otherwise `~/.claude-use`. An empty string counts as unset, consistent with how this project treats empty-string environment variables elsewhere (see the ambient-credential guard).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-use",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"description": "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Joseph Mearman <joseph@mearman.co.uk>",
|
|
@@ -51,7 +51,6 @@
|
|
|
51
51
|
"@clack/prompts": "^1.8.0",
|
|
52
52
|
"commander": "^15.0.0",
|
|
53
53
|
"cosmiconfig": "^9.0.2",
|
|
54
|
-
"node-forge": "1.4.0",
|
|
55
54
|
"picomatch": "^4.0.7",
|
|
56
55
|
"undici": "7.29.1",
|
|
57
56
|
"zod": "^4.6.1"
|
|
@@ -64,7 +63,6 @@
|
|
|
64
63
|
"@semantic-release/git": "^11.0.1",
|
|
65
64
|
"@semantic-release/npm": "^13.1.5",
|
|
66
65
|
"@types/node": "^26.5.1",
|
|
67
|
-
"@types/node-forge": "1.3.14",
|
|
68
66
|
"@types/picomatch": "^4.0.3",
|
|
69
67
|
"@vitest/coverage-v8": "^4.1.11",
|
|
70
68
|
"conventional-changelog-conventionalcommits": "^10.4.0",
|