@lllanyard/proxy 0.0.0-stage → 0.1.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/LICENSE.md +91 -0
- package/README.md +10 -2
- package/dist/egress.d.ts +26 -0
- package/dist/egress.js +105 -0
- package/dist/egress.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/netguard.d.ts +4 -0
- package/dist/netguard.js +65 -0
- package/dist/netguard.js.map +1 -0
- package/dist/providers.d.ts +4 -0
- package/dist/providers.js +12 -0
- package/dist/providers.js.map +1 -0
- package/dist/ratelimit.d.ts +29 -0
- package/dist/ratelimit.js +68 -0
- package/dist/ratelimit.js.map +1 -0
- package/dist/serve-options.d.ts +11 -0
- package/dist/serve-options.js +13 -0
- package/dist/serve-options.js.map +1 -0
- package/dist/server.d.ts +27 -0
- package/dist/server.js +151 -0
- package/dist/server.js.map +1 -0
- package/package.json +37 -4
package/LICENSE.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved.
|
|
2
|
+
"Business Source License" is a trademark of MariaDB Corporation Ab.
|
|
3
|
+
|
|
4
|
+
Parameters
|
|
5
|
+
|
|
6
|
+
Licensor: Ethan Askew T/A LLLanyard
|
|
7
|
+
Licensed Work: LLLanyard (all versions). The Licensed Work is (c) 2026
|
|
8
|
+
Ethan Askew T/A LLLanyard.
|
|
9
|
+
Additional Use Grant: You may make production use of the Licensed Work only if
|
|
10
|
+
BOTH of the following are true:
|
|
11
|
+
|
|
12
|
+
(1) Integrity. You use the Licensed Work's license-key
|
|
13
|
+
verification and usage limits as published. You do not
|
|
14
|
+
remove, disable, bypass, weaken, forge or tamper with any
|
|
15
|
+
license-key check, signature check, or limit on keys,
|
|
16
|
+
agents, users, seats, tiers or features, and you do not
|
|
17
|
+
help anyone else do so, whether by modifying the Licensed
|
|
18
|
+
Work, by providing modified copies, or by distributing
|
|
19
|
+
license keys or tools that produce them.
|
|
20
|
+
|
|
21
|
+
(2) Entitlement. Your use is either
|
|
22
|
+
(a) within the free-tier limits of the version you are
|
|
23
|
+
using (the limits defined in packages/core/src/limits.ts
|
|
24
|
+
of that version, and without the shared or remote
|
|
25
|
+
serving, dashboard or other features that version
|
|
26
|
+
reserves for paid tiers); or
|
|
27
|
+
(b) covered by a valid, unexpired paid license key
|
|
28
|
+
issued to you by the Licensor, used within its terms
|
|
29
|
+
(including its tier and seat count).
|
|
30
|
+
|
|
31
|
+
In addition, you may not offer the Licensed Work, or a
|
|
32
|
+
product derived from it, to third parties on a hosted,
|
|
33
|
+
managed or embedded basis where it competes with paid
|
|
34
|
+
versions of the Licensed Work offered by the Licensor.
|
|
35
|
+
Using the Licensed Work for the internal purposes of your
|
|
36
|
+
own organization is not such an offering.
|
|
37
|
+
|
|
38
|
+
Non-production use (evaluation, testing, development,
|
|
39
|
+
research) is permitted by the Terms below without
|
|
40
|
+
restriction as to limits.
|
|
41
|
+
Change Date: Four years from the date the Licensed Work is published.
|
|
42
|
+
Change License: Apache License, Version 2.0
|
|
43
|
+
|
|
44
|
+
For information about alternative licensing arrangements for the Licensed Work,
|
|
45
|
+
or to buy a paid license key, please contact Ethan Askew T/A LLLanyard
|
|
46
|
+
(licensing contact: sales@lllanyard.co.uk).
|
|
47
|
+
|
|
48
|
+
Notice
|
|
49
|
+
|
|
50
|
+
Business Source License 1.1
|
|
51
|
+
|
|
52
|
+
Terms
|
|
53
|
+
|
|
54
|
+
The Licensor hereby grants you the right to copy, modify, create derivative
|
|
55
|
+
works, redistribute, and make non-production use of the Licensed Work. The
|
|
56
|
+
Licensor may make an Additional Use Grant, above, permitting limited production use.
|
|
57
|
+
|
|
58
|
+
Effective on the Change Date, or the fourth anniversary of the first publicly
|
|
59
|
+
available distribution of a specific version of the Licensed Work under this
|
|
60
|
+
License, whichever comes first, the Licensor hereby grants you rights under
|
|
61
|
+
the terms of the Change License, and the rights granted in the paragraph
|
|
62
|
+
above terminate.
|
|
63
|
+
|
|
64
|
+
If your use of the Licensed Work does not comply with the requirements
|
|
65
|
+
currently in effect as described in this License, you must purchase a
|
|
66
|
+
commercial license from the Licensor, its affiliated entities, or authorized
|
|
67
|
+
resellers, or you must refrain from using the Licensed Work.
|
|
68
|
+
|
|
69
|
+
All copies of the original and modified Licensed Work, and derivative works
|
|
70
|
+
of the Licensed Work, are subject to this License. This License applies
|
|
71
|
+
separately for each version of the Licensed Work and the Change Date may vary
|
|
72
|
+
for each version of the Licensed Work released by Licensor.
|
|
73
|
+
|
|
74
|
+
You must conspicuously display this License on each original or modified copy
|
|
75
|
+
of the Licensed Work. If you receive the Licensed Work in original or
|
|
76
|
+
modified form from a third party, the terms and conditions set forth in this
|
|
77
|
+
License apply to your use of that work.
|
|
78
|
+
|
|
79
|
+
Any use of the Licensed Work in violation of this License will automatically
|
|
80
|
+
terminate your rights under this License for the current and all other
|
|
81
|
+
versions of the Licensed Work.
|
|
82
|
+
|
|
83
|
+
This License does not grant you any right in any trademark or logo of
|
|
84
|
+
Licensor or its affiliates (provided that you may use a trademark or logo of
|
|
85
|
+
Licensor as expressly required by this License).
|
|
86
|
+
|
|
87
|
+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
|
|
88
|
+
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
|
|
89
|
+
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
|
|
90
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
|
|
91
|
+
TITLE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @lllanyard/proxy
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
LLLanyard proxy: swaps placeholder tokens for real API keys on the way out.
|
|
4
|
+
|
|
5
|
+
This is an internal part of LLLanyard. Install the command-line tool instead:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
npm install -g @lllanyard/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
See https://lllanyard.app and the [main README](https://github.com/EthAsk/LLLanyard#readme). Licensed under the Business Source License 1.1 (see LICENSE.md).
|
package/dist/egress.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { safeLookup } from './netguard.js';
|
|
2
|
+
export declare function hostAllowed(host: string, allow: string[]): boolean;
|
|
3
|
+
export interface EgressOptions {
|
|
4
|
+
allow: string[];
|
|
5
|
+
ports?: number[];
|
|
6
|
+
log?: (rec: Record<string, unknown>) => void;
|
|
7
|
+
label?: string;
|
|
8
|
+
lookup?: typeof safeLookup;
|
|
9
|
+
unixSocket?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface EgressProxy {
|
|
12
|
+
env: Record<string, string>;
|
|
13
|
+
port: number;
|
|
14
|
+
password: string;
|
|
15
|
+
socketPath?: string;
|
|
16
|
+
close: () => void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A tiny CONNECT-only forward proxy on loopback. A tool started with HTTPS_PROXY pointing here can only open
|
|
20
|
+
* HTTPS tunnels to allowlisted hosts, and only to public IP addresses. It needs a per-run password so other local
|
|
21
|
+
* programs can't borrow it.
|
|
22
|
+
*
|
|
23
|
+
* LIMIT: this is best-effort. Tools that honour proxy settings are constrained; a deliberately malicious tool can
|
|
24
|
+
* ignore them. Hard enforcement needs an OS sandbox or container (see README).
|
|
25
|
+
*/
|
|
26
|
+
export declare function startEgressProxy(opts: EgressOptions): Promise<EgressProxy>;
|
package/dist/egress.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { chmodSync } from 'node:fs';
|
|
3
|
+
import http from 'node:http';
|
|
4
|
+
import net from 'node:net';
|
|
5
|
+
import { safeLookup } from './netguard.js';
|
|
6
|
+
export function hostAllowed(host, allow) {
|
|
7
|
+
const h = host.toLowerCase().replace(/\.$/, '');
|
|
8
|
+
return allow.some((a) => (a.startsWith('*.') ? h.length > a.length - 1 && h.endsWith(a.slice(1)) : h === a));
|
|
9
|
+
}
|
|
10
|
+
// Same variables as core's proxyEnv(); duplicated so this package keeps no runtime dependency on core.
|
|
11
|
+
function proxyEnv(url) {
|
|
12
|
+
const env = {};
|
|
13
|
+
for (const k of ['HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'http_proxy', 'https_proxy', 'all_proxy'])
|
|
14
|
+
env[k] = url;
|
|
15
|
+
env.NO_PROXY = '';
|
|
16
|
+
env.no_proxy = '';
|
|
17
|
+
env.NODE_USE_ENV_PROXY = '1';
|
|
18
|
+
return env;
|
|
19
|
+
}
|
|
20
|
+
const digest = (s) => createHash('sha256').update(s).digest();
|
|
21
|
+
/**
|
|
22
|
+
* A tiny CONNECT-only forward proxy on loopback. A tool started with HTTPS_PROXY pointing here can only open
|
|
23
|
+
* HTTPS tunnels to allowlisted hosts, and only to public IP addresses. It needs a per-run password so other local
|
|
24
|
+
* programs can't borrow it.
|
|
25
|
+
*
|
|
26
|
+
* LIMIT: this is best-effort. Tools that honour proxy settings are constrained; a deliberately malicious tool can
|
|
27
|
+
* ignore them. Hard enforcement needs an OS sandbox or container (see README).
|
|
28
|
+
*/
|
|
29
|
+
export function startEgressProxy(opts) {
|
|
30
|
+
const ports = new Set(opts.ports ?? [443]);
|
|
31
|
+
const log = opts.log ?? (() => { });
|
|
32
|
+
const lookup = opts.lookup ?? safeLookup;
|
|
33
|
+
const secret = randomBytes(18).toString('base64url');
|
|
34
|
+
const wantAuth = digest('Basic ' + Buffer.from(`lly:${secret}`).toString('base64'));
|
|
35
|
+
const sockets = new Set();
|
|
36
|
+
const note = (decision, host, port, reason) => log({ ts: new Date().toISOString(), event: 'egress', tool: opts.label, decision, host, port, ...(reason ? { reason } : {}) });
|
|
37
|
+
const server = http.createServer((req, res) => {
|
|
38
|
+
note('deny', String(req.headers.host ?? ''), 80, 'plain-http');
|
|
39
|
+
res.writeHead(403, { 'content-type': 'text/plain' });
|
|
40
|
+
res.end('LLLanyard egress: only HTTPS tunnels to approved hosts are allowed.\n');
|
|
41
|
+
});
|
|
42
|
+
server.on('connect', (req, client, head) => {
|
|
43
|
+
sockets.add(client);
|
|
44
|
+
client.once('close', () => sockets.delete(client));
|
|
45
|
+
client.on('error', () => client.destroy());
|
|
46
|
+
const reply = (code, msg) => { client.end(`HTTP/1.1 ${code} ${msg}\r\nConnection: close\r\n\r\n`); };
|
|
47
|
+
const given = digest(String(req.headers['proxy-authorization'] ?? ''));
|
|
48
|
+
if (!timingSafeEqual(given, wantAuth))
|
|
49
|
+
return reply(407, 'Proxy Authentication Required');
|
|
50
|
+
const m = /^(?:\[([^\]]+)\]|([^:]+)):(\d{1,5})$/.exec(req.url ?? '');
|
|
51
|
+
if (!m)
|
|
52
|
+
return reply(400, 'Bad Request');
|
|
53
|
+
const host = (m[1] ?? m[2]).toLowerCase();
|
|
54
|
+
const port = Number(m[3]);
|
|
55
|
+
if (net.isIP(host)) {
|
|
56
|
+
note('deny', host, port, 'ip-literal');
|
|
57
|
+
return reply(403, 'Forbidden');
|
|
58
|
+
}
|
|
59
|
+
if (!ports.has(port)) {
|
|
60
|
+
note('deny', host, port, 'port');
|
|
61
|
+
return reply(403, 'Forbidden');
|
|
62
|
+
}
|
|
63
|
+
if (!hostAllowed(host, opts.allow)) {
|
|
64
|
+
note('deny', host, port, 'host-not-allowed');
|
|
65
|
+
return reply(403, 'Forbidden');
|
|
66
|
+
}
|
|
67
|
+
const upstream = net.connect({ host, port, lookup: lookup });
|
|
68
|
+
sockets.add(upstream);
|
|
69
|
+
upstream.once('close', () => sockets.delete(upstream));
|
|
70
|
+
upstream.setTimeout(300_000, () => upstream.destroy());
|
|
71
|
+
upstream.on('error', () => { if (!client.destroyed && !client.writableEnded)
|
|
72
|
+
reply(502, 'Bad Gateway'); upstream.destroy(); });
|
|
73
|
+
upstream.once('connect', () => {
|
|
74
|
+
note('allow', host, port);
|
|
75
|
+
client.write('HTTP/1.1 200 Connection Established\r\n\r\n');
|
|
76
|
+
if (head.length)
|
|
77
|
+
upstream.write(head);
|
|
78
|
+
upstream.pipe(client);
|
|
79
|
+
client.pipe(upstream);
|
|
80
|
+
client.once('close', () => upstream.destroy());
|
|
81
|
+
upstream.once('close', () => client.destroy());
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
const close = () => { server.close(); for (const s of sockets)
|
|
85
|
+
s.destroy(); };
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
server.once('error', reject);
|
|
88
|
+
if (opts.unixSocket) {
|
|
89
|
+
const socketPath = opts.unixSocket;
|
|
90
|
+
server.listen(socketPath, () => {
|
|
91
|
+
try {
|
|
92
|
+
chmodSync(socketPath, 0o600);
|
|
93
|
+
}
|
|
94
|
+
catch { /* best effort; the directory is private */ }
|
|
95
|
+
resolve({ env: {}, port: 0, password: secret, socketPath, close });
|
|
96
|
+
});
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
server.listen(0, '127.0.0.1', () => {
|
|
100
|
+
const port = server.address().port;
|
|
101
|
+
resolve({ env: proxyEnv(`http://lly:${secret}@127.0.0.1:${port}`), port, password: secret, close });
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=egress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"egress.js","sourceRoot":"","sources":["../src/egress.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { createProxy } from './server.js';
|
|
2
|
+
export { fromConfig } from './providers.js';
|
|
3
|
+
export { isPublicAddress } from './netguard.js';
|
|
4
|
+
export { startEgressProxy, hostAllowed } from './egress.js';
|
|
5
|
+
export { AuthGuard } from './ratelimit.js';
|
|
6
|
+
export { checkServeOptions } from './serve-options.js';
|
|
7
|
+
export type { ServeOptions } from './serve-options.js';
|
|
8
|
+
export type { EgressProxy, EgressOptions } from './egress.js';
|
|
9
|
+
export type { ProxyDeps, ProviderDef, ProviderSource } from './server.js';
|
|
10
|
+
import { type ServeOptions } from './serve-options.js';
|
|
11
|
+
/** Starts the proxy. By default loopback only; a non-loopback host needs a Pro license, TLS and --allow-host. */
|
|
12
|
+
export declare function startProxy(opts?: ServeOptions): Promise<{
|
|
13
|
+
port: number;
|
|
14
|
+
close: () => void;
|
|
15
|
+
}>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { AgentStore, DEFAULT_PORT, LicenseStore, createUsageLog, KeyringStore, ProviderStore, allProviders, } from '@lllanyard/core';
|
|
2
|
+
import { createProxy } from './server.js';
|
|
3
|
+
import { fromConfig } from './providers.js';
|
|
4
|
+
export { createProxy } from './server.js';
|
|
5
|
+
export { fromConfig } from './providers.js';
|
|
6
|
+
export { isPublicAddress } from './netguard.js';
|
|
7
|
+
export { startEgressProxy, hostAllowed } from './egress.js';
|
|
8
|
+
export { AuthGuard } from './ratelimit.js';
|
|
9
|
+
export { checkServeOptions } from './serve-options.js';
|
|
10
|
+
import { checkServeOptions } from './serve-options.js';
|
|
11
|
+
/** Starts the proxy. By default loopback only; a non-loopback host needs a Pro license, TLS and --allow-host. */
|
|
12
|
+
export function startProxy(opts = {}) {
|
|
13
|
+
const host = opts.host ?? '127.0.0.1';
|
|
14
|
+
checkServeOptions(opts, new LicenseStore().status().tier);
|
|
15
|
+
const port = opts.port ?? Number(process.env.LLLANYARD_PORT ?? DEFAULT_PORT);
|
|
16
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
17
|
+
throw new Error('Invalid port');
|
|
18
|
+
const agents = new AgentStore();
|
|
19
|
+
const providerStore = new ProviderStore();
|
|
20
|
+
const build = () => {
|
|
21
|
+
const m = new Map();
|
|
22
|
+
const cfgs = allProviders(providerStore);
|
|
23
|
+
for (const [id, c] of Object.entries(cfgs))
|
|
24
|
+
m.set(id, fromConfig(c));
|
|
25
|
+
return m;
|
|
26
|
+
};
|
|
27
|
+
let verify = agents.verifier();
|
|
28
|
+
let providers = build();
|
|
29
|
+
// Picks up new/revoked agents and provider changes within ~10s without a keychain read per request.
|
|
30
|
+
const timer = setInterval(() => {
|
|
31
|
+
try {
|
|
32
|
+
verify = agents.verifier();
|
|
33
|
+
providers = build();
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
process.stderr.write(`refresh failed: ${e.message}\n`);
|
|
37
|
+
}
|
|
38
|
+
}, 10_000);
|
|
39
|
+
timer.unref();
|
|
40
|
+
const usage = createUsageLog();
|
|
41
|
+
const server = createProxy({
|
|
42
|
+
store: new KeyringStore(),
|
|
43
|
+
log: (r) => { process.stdout.write(JSON.stringify(r) + '\n'); usage.write(r); },
|
|
44
|
+
verify: (t) => verify(t),
|
|
45
|
+
providers: (id) => providers.get(id),
|
|
46
|
+
rpm: Number(process.env.LLLANYARD_RPM ?? 120),
|
|
47
|
+
allowOrigin: process.env.LLLANYARD_ALLOW_ORIGIN === '1',
|
|
48
|
+
tls: opts.tls, allowedHosts: opts.allowedHosts,
|
|
49
|
+
});
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
server.once('error', (e) => reject(new Error(e.code === 'EADDRINUSE' ? `Port ${port} is already in use (set --port or LLLANYARD_PORT)` : e.message)));
|
|
52
|
+
server.listen(port, host, () => resolve({ port, close: () => { clearInterval(timer); server.close(); } }));
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** True only for globally routable addresses. Loopback, private, link-local (cloud metadata), CGNAT etc. are false. */
|
|
2
|
+
export declare function isPublicAddress(addr: string): boolean;
|
|
3
|
+
/** dns.lookup replacement that refuses to connect to non-public addresses (SSRF / DNS-rebinding defence). */
|
|
4
|
+
export declare function safeLookup(hostname: string, options: any, callback: any): void;
|
package/dist/netguard.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import dns from 'node:dns';
|
|
2
|
+
import { isIP } from 'node:net';
|
|
3
|
+
/** True only for globally routable addresses. Loopback, private, link-local (cloud metadata), CGNAT etc. are false. */
|
|
4
|
+
export function isPublicAddress(addr) {
|
|
5
|
+
const ip = addr.replace(/^\[|\]$/g, '').toLowerCase();
|
|
6
|
+
const fam = isIP(ip);
|
|
7
|
+
if (fam === 4) {
|
|
8
|
+
const [a, b] = ip.split('.').map(Number);
|
|
9
|
+
if (a === 0 || a === 10 || a === 127)
|
|
10
|
+
return false;
|
|
11
|
+
if (a === 169 && b === 254)
|
|
12
|
+
return false;
|
|
13
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
14
|
+
return false;
|
|
15
|
+
if (a === 192 && b === 168)
|
|
16
|
+
return false;
|
|
17
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
18
|
+
return false;
|
|
19
|
+
if (a >= 224)
|
|
20
|
+
return false; // multicast + reserved
|
|
21
|
+
if (a === 192 && b === 0)
|
|
22
|
+
return false;
|
|
23
|
+
if (a === 198 && (b === 18 || b === 19))
|
|
24
|
+
return false;
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
if (fam === 6) {
|
|
28
|
+
if (ip === '::' || ip === '::1')
|
|
29
|
+
return false;
|
|
30
|
+
const mapped = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(ip);
|
|
31
|
+
if (mapped)
|
|
32
|
+
return isPublicAddress(mapped[1]);
|
|
33
|
+
const hex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(ip);
|
|
34
|
+
if (hex) {
|
|
35
|
+
const hi = parseInt(hex[1], 16), lo = parseInt(hex[2], 16);
|
|
36
|
+
return isPublicAddress(`${hi >> 8}.${hi & 255}.${lo >> 8}.${lo & 255}`);
|
|
37
|
+
}
|
|
38
|
+
const first = parseInt(ip.split(':')[0] || '0', 16);
|
|
39
|
+
if ((first & 0xfe00) === 0xfc00)
|
|
40
|
+
return false; // fc00::/7 unique local
|
|
41
|
+
if ((first & 0xffc0) === 0xfe80)
|
|
42
|
+
return false; // fe80::/10 link-local
|
|
43
|
+
if ((first & 0xff00) === 0xff00)
|
|
44
|
+
return false; // multicast
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
/** dns.lookup replacement that refuses to connect to non-public addresses (SSRF / DNS-rebinding defence). */
|
|
50
|
+
export function safeLookup(hostname, options, callback) {
|
|
51
|
+
const opts = typeof options === 'function' ? {} : options;
|
|
52
|
+
const cb = typeof options === 'function' ? options : callback;
|
|
53
|
+
dns.lookup(hostname, opts, (err, address, family) => {
|
|
54
|
+
if (err)
|
|
55
|
+
return cb(err, address, family);
|
|
56
|
+
const list = Array.isArray(address) ? address : [{ address }];
|
|
57
|
+
if (!list.length || list.some((a) => !isPublicAddress(a.address))) {
|
|
58
|
+
const e = new Error(`Blocked: ${hostname} resolves to a non-public address`);
|
|
59
|
+
e.code = 'EBLOCKED';
|
|
60
|
+
return cb(e, address, family);
|
|
61
|
+
}
|
|
62
|
+
cb(null, address, family);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=netguard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"netguard.js","sourceRoot":"","sources":["../src/netguard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Turns a stored provider config into the proxy's runtime definition. */
|
|
2
|
+
export function fromConfig(c) {
|
|
3
|
+
const a = c.auth;
|
|
4
|
+
return {
|
|
5
|
+
upstream: c.upstream,
|
|
6
|
+
allowLocal: c.allowLocal,
|
|
7
|
+
inject: a.type === 'bearer'
|
|
8
|
+
? (h, k) => { h['authorization'] = `Bearer ${k}`; }
|
|
9
|
+
: (h, k) => { h[a.name.toLowerCase()] = (a.prefix ?? '') + k; },
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=providers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"AAGA,0EAA0E;AAC1E,MAAM,UAAU,UAAU,CAAC,CAAiB;IAC1C,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;IACjB,OAAO;QACL,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,MAAM,EAAE,CAAC,CAAC,IAAI,KAAK,QAAQ;YACzB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,eAAe,CAAC,GAAG,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;YACnD,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KAClE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type Verdict = 'ok' | 'limited' | 'killed';
|
|
2
|
+
/**
|
|
3
|
+
* Fixed-window limiter with a kill switch: exceed `limit` requests/minute and you get 429s;
|
|
4
|
+
* exceed limit x killMultiplier and the agent is blocked outright for killMs (or until restart).
|
|
5
|
+
*/
|
|
6
|
+
export declare class RateLimiter {
|
|
7
|
+
private limit;
|
|
8
|
+
private opts;
|
|
9
|
+
private windows;
|
|
10
|
+
private killed;
|
|
11
|
+
constructor(limit: number, opts?: {
|
|
12
|
+
killMultiplier?: number;
|
|
13
|
+
killMs?: number;
|
|
14
|
+
now?: () => number;
|
|
15
|
+
});
|
|
16
|
+
hit(id: string): Verdict;
|
|
17
|
+
}
|
|
18
|
+
/** Locks out a remote address after repeated failed logins, so tokens can't be guessed at network speed. */
|
|
19
|
+
export declare class AuthGuard {
|
|
20
|
+
private max;
|
|
21
|
+
private windowMs;
|
|
22
|
+
private lockMs;
|
|
23
|
+
private now;
|
|
24
|
+
private m;
|
|
25
|
+
constructor(max?: number, windowMs?: number, lockMs?: number, now?: () => number);
|
|
26
|
+
blocked(ip: string): boolean;
|
|
27
|
+
fail(ip: string): void;
|
|
28
|
+
ok(ip: string): void;
|
|
29
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixed-window limiter with a kill switch: exceed `limit` requests/minute and you get 429s;
|
|
3
|
+
* exceed limit x killMultiplier and the agent is blocked outright for killMs (or until restart).
|
|
4
|
+
*/
|
|
5
|
+
export class RateLimiter {
|
|
6
|
+
limit;
|
|
7
|
+
opts;
|
|
8
|
+
windows = new Map();
|
|
9
|
+
killed = new Map();
|
|
10
|
+
constructor(limit, opts = {}) {
|
|
11
|
+
this.limit = limit;
|
|
12
|
+
this.opts = opts;
|
|
13
|
+
}
|
|
14
|
+
hit(id) {
|
|
15
|
+
const now = (this.opts.now ?? Date.now)();
|
|
16
|
+
const until = this.killed.get(id);
|
|
17
|
+
if (until !== undefined) {
|
|
18
|
+
if (now < until)
|
|
19
|
+
return 'killed';
|
|
20
|
+
this.killed.delete(id);
|
|
21
|
+
}
|
|
22
|
+
let w = this.windows.get(id);
|
|
23
|
+
if (!w || now - w.start >= 60_000) {
|
|
24
|
+
w = { start: now, n: 0 };
|
|
25
|
+
this.windows.set(id, w);
|
|
26
|
+
}
|
|
27
|
+
w.n++;
|
|
28
|
+
if (w.n > this.limit * (this.opts.killMultiplier ?? 3)) {
|
|
29
|
+
this.killed.set(id, now + (this.opts.killMs ?? 15 * 60_000));
|
|
30
|
+
return 'killed';
|
|
31
|
+
}
|
|
32
|
+
return w.n > this.limit ? 'limited' : 'ok';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** Locks out a remote address after repeated failed logins, so tokens can't be guessed at network speed. */
|
|
36
|
+
export class AuthGuard {
|
|
37
|
+
max;
|
|
38
|
+
windowMs;
|
|
39
|
+
lockMs;
|
|
40
|
+
now;
|
|
41
|
+
m = new Map();
|
|
42
|
+
constructor(max = 10, windowMs = 10 * 60_000, lockMs = 15 * 60_000, now = Date.now) {
|
|
43
|
+
this.max = max;
|
|
44
|
+
this.windowMs = windowMs;
|
|
45
|
+
this.lockMs = lockMs;
|
|
46
|
+
this.now = now;
|
|
47
|
+
}
|
|
48
|
+
blocked(ip) { const e = this.m.get(ip); return !!e && e.lockedUntil > this.now(); }
|
|
49
|
+
fail(ip) {
|
|
50
|
+
const t = this.now();
|
|
51
|
+
if (this.m.size > 10_000) {
|
|
52
|
+
for (const [k, v] of this.m)
|
|
53
|
+
if (v.lockedUntil <= t && t - v.start > this.windowMs)
|
|
54
|
+
this.m.delete(k);
|
|
55
|
+
if (this.m.size > 10_000)
|
|
56
|
+
this.m.clear();
|
|
57
|
+
}
|
|
58
|
+
let e = this.m.get(ip);
|
|
59
|
+
if (!e || t - e.start > this.windowMs) {
|
|
60
|
+
e = { n: 0, start: t, lockedUntil: 0 };
|
|
61
|
+
this.m.set(ip, e);
|
|
62
|
+
}
|
|
63
|
+
if (++e.n >= this.max)
|
|
64
|
+
e.lockedUntil = t + this.lockMs;
|
|
65
|
+
}
|
|
66
|
+
ok(ip) { this.m.delete(ip); }
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=ratelimit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ratelimit.js","sourceRoot":"","sources":["../src/ratelimit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ServeOptions {
|
|
2
|
+
port?: number;
|
|
3
|
+
host?: string;
|
|
4
|
+
tls?: {
|
|
5
|
+
cert: string | Buffer;
|
|
6
|
+
key: string | Buffer;
|
|
7
|
+
};
|
|
8
|
+
allowedHosts?: string[];
|
|
9
|
+
}
|
|
10
|
+
/** Rules for exposing the proxy beyond this machine. Pure so it can be tested; startProxy enforces it. */
|
|
11
|
+
export declare function checkServeOptions(o: ServeOptions, tier: 'free' | 'pro' | 'team'): void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const LOOPBACK = new Set(['127.0.0.1', '::1', 'localhost']);
|
|
2
|
+
/** Rules for exposing the proxy beyond this machine. Pure so it can be tested; startProxy enforces it. */
|
|
3
|
+
export function checkServeOptions(o, tier) {
|
|
4
|
+
if (!o.host || LOOPBACK.has(o.host))
|
|
5
|
+
return;
|
|
6
|
+
if (tier === 'free')
|
|
7
|
+
throw new Error('Sharing the proxy beyond this computer is a Pro feature (lllanyard license activate).');
|
|
8
|
+
if (!o.tls)
|
|
9
|
+
throw new Error('A shared proxy must use TLS: pass --tls-cert and --tls-key. Plain HTTP would expose agent tokens on the network.');
|
|
10
|
+
if (!o.allowedHosts?.length)
|
|
11
|
+
throw new Error('Pass --allow-host <name> with the DNS name your team uses to reach this server.');
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=serve-options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve-options.js","sourceRoot":"","sources":["../src/serve-options.ts"],"names":[],"mappings":"AAAA,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC;AAI5D,0GAA0G;AAC1G,MAAM,UAAU,iBAAiB,CAAC,CAAe,EAAE,IAA6B;IAC9E,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;QAAE,OAAO;IAC5C,IAAI,IAAI,KAAK,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,uFAAuF,CAAC,CAAC;IAC9H,IAAI,CAAC,CAAC,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,kHAAkH,CAAC,CAAC;IAChJ,IAAI,CAAC,CAAC,CAAC,YAAY,EAAE,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,iFAAiF,CAAC,CAAC;AAClI,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import https from 'node:https';
|
|
3
|
+
import type { AgentIdentity, SecureStore } from '@lllanyard/core';
|
|
4
|
+
import { AuthGuard } from './ratelimit.js';
|
|
5
|
+
export interface ProviderDef {
|
|
6
|
+
upstream: string;
|
|
7
|
+
inject(headers: http.OutgoingHttpHeaders, key: string): void;
|
|
8
|
+
allowLocal?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export type ProviderSource = Record<string, ProviderDef> | ((id: string) => ProviderDef | undefined);
|
|
11
|
+
export interface ProxyDeps {
|
|
12
|
+
store: Pick<SecureStore, 'get'>;
|
|
13
|
+
verify: (token: string) => AgentIdentity | null;
|
|
14
|
+
providers: ProviderSource;
|
|
15
|
+
rpm?: number;
|
|
16
|
+
allowOrigin?: boolean;
|
|
17
|
+
keyCacheMs?: number;
|
|
18
|
+
log?: (rec: Record<string, unknown>) => void;
|
|
19
|
+
now?: () => number;
|
|
20
|
+
allowedHosts?: string[];
|
|
21
|
+
tls?: {
|
|
22
|
+
cert: string | Buffer;
|
|
23
|
+
key: string | Buffer;
|
|
24
|
+
};
|
|
25
|
+
authGuard?: AuthGuard;
|
|
26
|
+
}
|
|
27
|
+
export declare function createProxy(deps: ProxyDeps): http.Server | https.Server;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import https from 'node:https';
|
|
3
|
+
import { isIP } from 'node:net';
|
|
4
|
+
import { isPublicAddress, safeLookup } from './netguard.js';
|
|
5
|
+
import { AuthGuard, RateLimiter } from './ratelimit.js';
|
|
6
|
+
const DROP_REQ = new Set([
|
|
7
|
+
'host', 'connection', 'keep-alive', 'proxy-authorization', 'proxy-connection', 'te', 'trailer',
|
|
8
|
+
'transfer-encoding', 'upgrade', 'expect', 'authorization', 'x-api-key', 'cookie',
|
|
9
|
+
]);
|
|
10
|
+
const DROP_RES = new Set(['connection', 'keep-alive', 'transfer-encoding', 'proxy-authenticate', 'upgrade', 'set-cookie']);
|
|
11
|
+
export function createProxy(deps) {
|
|
12
|
+
const lookupProvider = (id) => {
|
|
13
|
+
const src = deps.providers;
|
|
14
|
+
if (typeof src === 'function')
|
|
15
|
+
return src(id);
|
|
16
|
+
return Object.prototype.hasOwnProperty.call(src, id) ? src[id] : undefined; // "constructor" etc. are not providers
|
|
17
|
+
};
|
|
18
|
+
const now = deps.now ?? Date.now;
|
|
19
|
+
const limiter = new RateLimiter(deps.rpm ?? 120, { now });
|
|
20
|
+
const log = deps.log ?? ((r) => process.stdout.write(JSON.stringify(r) + '\n'));
|
|
21
|
+
const cache = new Map();
|
|
22
|
+
const guard = deps.authGuard ?? new AuthGuard(10, 10 * 60_000, 15 * 60_000, now);
|
|
23
|
+
const extraHosts = new Set((deps.allowedHosts ?? []).map((h) => h.toLowerCase()));
|
|
24
|
+
const keyFor = (provider, client) => {
|
|
25
|
+
const ck = `${provider}:${client}`;
|
|
26
|
+
const hit = cache.get(ck);
|
|
27
|
+
if (hit && hit.exp > now())
|
|
28
|
+
return hit.key;
|
|
29
|
+
const key = deps.store.get(provider, client);
|
|
30
|
+
if (key)
|
|
31
|
+
cache.set(ck, { key, exp: now() + (deps.keyCacheMs ?? 30_000) });
|
|
32
|
+
else
|
|
33
|
+
cache.delete(ck);
|
|
34
|
+
return key;
|
|
35
|
+
};
|
|
36
|
+
const send = (res, code, msg) => {
|
|
37
|
+
if (res.headersSent) {
|
|
38
|
+
res.destroy();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const body = JSON.stringify({ error: msg });
|
|
42
|
+
res.writeHead(code, { 'content-type': 'application/json', 'content-length': Buffer.byteLength(body) });
|
|
43
|
+
res.end(body);
|
|
44
|
+
};
|
|
45
|
+
const handler = (req, res) => {
|
|
46
|
+
const started = now();
|
|
47
|
+
const ip = req.socket.remoteAddress ?? 'unknown';
|
|
48
|
+
// Defence against DNS rebinding and drive-by web pages: only loopback Host values, no browser Origin.
|
|
49
|
+
const port = req.socket.localPort;
|
|
50
|
+
const host = String(req.headers.host ?? '').toLowerCase();
|
|
51
|
+
if (![`127.0.0.1:${port}`, `localhost:${port}`, `[::1]:${port}`].includes(host) && !extraHosts.has(host) && !extraHosts.has(host.replace(/:\d+$/, '')))
|
|
52
|
+
return send(res, 403, 'Forbidden host');
|
|
53
|
+
if (req.headers.origin && !deps.allowOrigin)
|
|
54
|
+
return send(res, 403, 'Browser-origin requests are not allowed');
|
|
55
|
+
const m = /^\/([A-Za-z0-9-]+)(.*)$/s.exec(req.url ?? '/');
|
|
56
|
+
if (!m)
|
|
57
|
+
return send(res, 404, 'Not found');
|
|
58
|
+
const provider = m[1].toLowerCase();
|
|
59
|
+
let rest = m[2] ?? '';
|
|
60
|
+
const def = lookupProvider(provider);
|
|
61
|
+
if (!def)
|
|
62
|
+
return send(res, 404, 'Unknown provider');
|
|
63
|
+
if (rest === '')
|
|
64
|
+
rest = '/';
|
|
65
|
+
else if (rest.startsWith('?'))
|
|
66
|
+
rest = '/' + rest;
|
|
67
|
+
else if (!rest.startsWith('/'))
|
|
68
|
+
return send(res, 404, 'Not found'); // blocks "/openai@evil.com"
|
|
69
|
+
// Agent authentication: the placeholder token arrives as the agent's "API key".
|
|
70
|
+
const bearer = /^Bearer\s+(.+)$/i.exec(String(req.headers.authorization ?? ''))?.[1];
|
|
71
|
+
const xkey = typeof req.headers['x-api-key'] === 'string' ? req.headers['x-api-key'] : undefined;
|
|
72
|
+
const token = bearer ?? xkey;
|
|
73
|
+
if (guard.blocked(ip)) {
|
|
74
|
+
log({ ts: new Date().toISOString(), ip, provider, status: 429, reason: 'auth-lockout' });
|
|
75
|
+
res.setHeader('retry-after', '900');
|
|
76
|
+
return send(res, 429, 'Too many failed attempts');
|
|
77
|
+
}
|
|
78
|
+
const agent = token ? deps.verify(token) : null;
|
|
79
|
+
if (!agent) {
|
|
80
|
+
guard.fail(ip);
|
|
81
|
+
log({ ts: new Date().toISOString(), ip, provider, status: 401, reason: 'bad-token' });
|
|
82
|
+
return send(res, 401, 'Invalid or missing agent token');
|
|
83
|
+
}
|
|
84
|
+
guard.ok(ip);
|
|
85
|
+
// Least privilege: an agent created with --providers may only reach those.
|
|
86
|
+
if (agent.providers && !agent.providers.includes(provider)) {
|
|
87
|
+
log({ ts: new Date().toISOString(), agent: agent.name, provider, status: 403, reason: 'provider-not-allowed' });
|
|
88
|
+
return send(res, 403, 'This agent is not allowed to use that provider');
|
|
89
|
+
}
|
|
90
|
+
const verdict = limiter.hit(agent.name);
|
|
91
|
+
if (verdict !== 'ok') {
|
|
92
|
+
log({ ts: new Date().toISOString(), agent: agent.name, provider, status: 429, killed: verdict === 'killed' });
|
|
93
|
+
res.setHeader('retry-after', '60');
|
|
94
|
+
return send(res, 429, verdict === 'killed' ? 'Agent blocked by kill switch' : 'Rate limit exceeded');
|
|
95
|
+
}
|
|
96
|
+
let key;
|
|
97
|
+
try {
|
|
98
|
+
key = keyFor(provider, agent.client);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return send(res, 503, 'Key store unavailable');
|
|
102
|
+
}
|
|
103
|
+
if (!key)
|
|
104
|
+
return send(res, 503, `No ${provider} key stored for client "${agent.client}"`);
|
|
105
|
+
// Never let the request path change the destination host.
|
|
106
|
+
let target;
|
|
107
|
+
try {
|
|
108
|
+
target = new URL(def.upstream + rest);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return send(res, 400, 'Bad request path');
|
|
112
|
+
}
|
|
113
|
+
if (target.origin !== new URL(def.upstream).origin)
|
|
114
|
+
return send(res, 400, 'Bad request path');
|
|
115
|
+
// SSRF guard: literal private/loopback IPs are refused here; hostnames are checked at connect time (safeLookup).
|
|
116
|
+
const hostname = target.hostname.replace(/^\[|\]$/g, '');
|
|
117
|
+
if (!def.allowLocal && isIP(hostname) && !isPublicAddress(hostname))
|
|
118
|
+
return send(res, 403, 'Upstream address not allowed');
|
|
119
|
+
const headers = {};
|
|
120
|
+
for (const [k, v] of Object.entries(req.headers))
|
|
121
|
+
if (!DROP_REQ.has(k) && v !== undefined)
|
|
122
|
+
headers[k] = v;
|
|
123
|
+
def.inject(headers, key);
|
|
124
|
+
let up;
|
|
125
|
+
res.on('close', () => {
|
|
126
|
+
log({ ts: new Date().toISOString(), agent: agent.name, client: agent.client, ip, provider, method: req.method,
|
|
127
|
+
path: target.pathname, status: res.statusCode, ms: now() - started }); // no query string, no bodies
|
|
128
|
+
up?.destroy();
|
|
129
|
+
});
|
|
130
|
+
// Redirects are never followed, so the key can't be forwarded to a third party.
|
|
131
|
+
const lib = target.protocol === 'https:' ? https : http;
|
|
132
|
+
try {
|
|
133
|
+
up = lib.request(target, { method: req.method, headers, timeout: 300_000, ...(def.allowLocal ? {} : { lookup: safeLookup }) }, (ures) => {
|
|
134
|
+
const rh = {};
|
|
135
|
+
for (const [k, v] of Object.entries(ures.headers))
|
|
136
|
+
if (!DROP_RES.has(k) && v !== undefined)
|
|
137
|
+
rh[k] = v;
|
|
138
|
+
res.writeHead(ures.statusCode ?? 502, rh);
|
|
139
|
+
ures.pipe(res); // streams SSE untouched
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return send(res, 502, 'Upstream request failed');
|
|
144
|
+
}
|
|
145
|
+
up.on('timeout', () => up.destroy(new Error('upstream timeout')));
|
|
146
|
+
up.on('error', () => send(res, 502, 'Upstream request failed'));
|
|
147
|
+
req.pipe(up);
|
|
148
|
+
};
|
|
149
|
+
return deps.tls ? https.createServer({ ...deps.tls, minVersion: 'TLSv1.2' }, handler) : http.createServer(handler);
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,KAAK,MAAM,YAAY,CAAC;AAE/B,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAUxD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC;IACvB,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,IAAI,EAAE,SAAS;IAC9F,mBAAmB,EAAE,SAAS,EAAE,QAAQ,EAAE,eAAe,EAAE,WAAW,EAAE,QAAQ;CACjF,CAAC,CAAC;AACH,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;AAgB3H,MAAM,UAAU,WAAW,CAAC,IAAe;IACzC,MAAM,cAAc,GAAG,CAAC,EAAU,EAA2B,EAAE;QAC7D,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC;QAC3B,IAAI,OAAO,GAAG,KAAK,UAAU;YAAE,OAAO,GAAG,CAAC,EAAE,CAAC,CAAC;QAC9C,OAAO,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,uCAAuC;IACrH,CAAC,CAAC;IACF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACjC,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IAChF,MAAM,KAAK,GAAG,IAAI,GAAG,EAAwC,CAAC;IAC9D,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,SAAS,CAAC,EAAE,EAAE,EAAE,GAAG,MAAM,EAAE,EAAE,GAAG,MAAM,EAAE,GAAG,CAAC,CAAC;IACjF,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAElF,MAAM,MAAM,GAAG,CAAC,QAAgB,EAAE,MAAc,EAAiB,EAAE;QACjE,MAAM,EAAE,GAAG,GAAG,QAAQ,IAAI,MAAM,EAAE,CAAC;QACnC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC1B,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,GAAG,GAAG,EAAE;YAAE,OAAO,GAAG,CAAC,GAAG,CAAC;QAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC7C,IAAI,GAAG;YAAE,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,UAAU,IAAI,MAAM,CAAC,EAAE,CAAC,CAAC;;YACrE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACtB,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,CAAC,GAAwB,EAAE,IAAY,EAAE,GAAW,EAAE,EAAE;QACnE,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;YAAC,GAAG,CAAC,OAAO,EAAE,CAAC;YAAC,OAAO;QAAC,CAAC;QAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;QAC5C,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACvG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,CAAC,GAAyB,EAAE,GAAwB,EAAE,EAAE;QACtE,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC;QACtB,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,SAAS,CAAC;QAEjD,sGAAsG;QACtG,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC;QAClC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QAC1D,IAAI,CAAC,CAAC,aAAa,IAAI,EAAE,EAAE,aAAa,IAAI,EAAE,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,gBAAgB,CAAC,CAAC;QAChM,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,yCAAyC,CAAC,CAAC;QAE9G,MAAM,CAAC,GAAG,0BAA0B,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC;QAC1D,IAAI,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;QAC3C,MAAM,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,CAAC;QACrC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;QACrC,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACpD,IAAI,IAAI,KAAK,EAAE;YAAE,IAAI,GAAG,GAAG,CAAC;aACvB,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC;aAC5C,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC,4BAA4B;QAEhG,gFAAgF;QAChF,MAAM,MAAM,GAAG,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACrF,MAAM,IAAI,GAAG,OAAO,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACjG,MAAM,KAAK,GAAG,MAAM,IAAI,IAAI,CAAC;QAC7B,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;YACtB,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC;YACzF,GAAG,CAAC,SAAS,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;YACpC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,0BAA0B,CAAC,CAAC;QACpD,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAChD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACf,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;YACtF,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,gCAAgC,CAAC,CAAC;QAC1D,CAAC;QACD,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAEb,2EAA2E;QAC3E,IAAI,KAAK,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3D,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC,CAAC;YAChH,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,gDAAgD,CAAC,CAAC;QAC1E,CAAC;QAED,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,KAAK,QAAQ,EAAE,CAAC,CAAC;YAC9G,GAAG,CAAC,SAAS,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;YACnC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,8BAA8B,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC;QACvG,CAAC;QAED,IAAI,GAAkB,CAAC;QACvB,IAAI,CAAC;YAAC,GAAG,GAAG,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,uBAAuB,CAAC,CAAC;QAAC,CAAC;QACvG,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,QAAQ,2BAA2B,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;QAE1F,0DAA0D;QAC1D,IAAI,MAAW,CAAC;QAChB,IAAI,CAAC;YAAC,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,kBAAkB,CAAC,CAAC;QAAC,CAAC;QACnG,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,kBAAkB,CAAC,CAAC;QAE9F,iHAAiH;QACjH,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QACzD,IAAI,CAAC,GAAG,CAAC,UAAU,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,8BAA8B,CAAC,CAAC;QAE3H,MAAM,OAAO,GAA6B,EAAE,CAAC;QAC7C,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,SAAS;gBAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAC1G,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAEzB,IAAI,EAAkC,CAAC;QACvC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACnB,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM;gBACvG,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,UAAU,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC,6BAA6B;YAC1G,EAAE,EAAE,OAAO,EAAE,CAAC;QAChB,CAAC,CAAC,CAAC;QAEH,gFAAgF;QAChF,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;QACxD,IAAI,CAAC;YACH,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE;gBACtI,MAAM,EAAE,GAA6B,EAAE,CAAC;gBACxC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;oBAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,SAAS;wBAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;gBACtG,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;gBAC1C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,wBAAwB;YAC1C,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,yBAAyB,CAAC,CAAC;QAAC,CAAC;QAC7D,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC;QAClE,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,yBAAyB,CAAC,CAAC,CAAC;QAChE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,CAAC,CAAC;IACF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;AACrH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lllanyard/proxy",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "LLLanyard proxy: swaps placeholder tokens for real API keys on the way out.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@lllanyard/core": "^0.1.0"
|
|
19
|
+
},
|
|
20
|
+
"license": "BUSL-1.1",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=22"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/EthAsk/LLLanyard.git",
|
|
27
|
+
"directory": "packages/proxy"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://lllanyard.app",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/EthAsk/LLLanyard/issues"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"prepack": "cp ../../LICENSE.md ."
|
|
38
|
+
}
|
|
39
|
+
}
|