@clipboard-health/clearance 1.0.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 +219 -0
- package/bin/ensure.js +3 -0
- package/bin/run.js +3 -0
- package/package.json +35 -0
- package/safehouse/clearance-only.sb +11 -0
- package/safehouse/clearance.env +9 -0
- package/safehouse/safehouse-clearance +22 -0
- package/src/allowlist.d.ts +5 -0
- package/src/allowlist.js +48 -0
- package/src/allowlist.js.map +1 -0
- package/src/cli.d.ts +2 -0
- package/src/cli.js +8 -0
- package/src/cli.js.map +1 -0
- package/src/ensureCli.d.ts +2 -0
- package/src/ensureCli.js +12 -0
- package/src/ensureCli.js.map +1 -0
- package/src/hostRule.d.ts +4 -0
- package/src/hostRule.js +62 -0
- package/src/hostRule.js.map +1 -0
- package/src/index.d.ts +36 -0
- package/src/index.js +509 -0
- package/src/index.js.map +1 -0
- package/src/launcher.d.ts +32 -0
- package/src/launcher.js +148 -0
- package/src/launcher.js.map +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# @clipboard-health/clearance
|
|
2
|
+
|
|
3
|
+
HTTP/HTTPS clearance for deny-by-default sandboxes.
|
|
4
|
+
|
|
5
|
+
The proxy ships with **zero compiled-in opinions** about which hosts to allow.
|
|
6
|
+
Bring your own list — either inline via env or by pointing at one or more
|
|
7
|
+
plain-text files. Teams typically check those files into their repo so
|
|
8
|
+
everyone shares the same baseline.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g @clipboard-health/clearance
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
This installs two binaries: `clearance` (the proxy) and `clearance-ensure`
|
|
17
|
+
(idempotent daemon launcher).
|
|
18
|
+
|
|
19
|
+
## Raw proxy usage
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
CLEARANCE_ALLOW_HOSTS=api.example.com clearance
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then point tools at `http://127.0.0.1:19999` with `HTTP_PROXY` and
|
|
26
|
+
`HTTPS_PROXY`.
|
|
27
|
+
|
|
28
|
+
By default the proxy listens on `127.0.0.1:19999`, allows destination port
|
|
29
|
+
`443`, and blocks private, loopback, link-local, multicast, documentation,
|
|
30
|
+
and other non-public IP ranges after DNS resolution.
|
|
31
|
+
|
|
32
|
+
If neither `CLEARANCE_ALLOW_HOSTS` nor `CLEARANCE_ALLOW_HOSTS_FILES` is
|
|
33
|
+
set, the proxy refuses to start.
|
|
34
|
+
|
|
35
|
+
### Allow-host files
|
|
36
|
+
|
|
37
|
+
A file is a plain-text list of hosts, one per line. Blank lines are ignored,
|
|
38
|
+
`#` introduces a comment to end-of-line, and trailing dots / wildcard
|
|
39
|
+
prefixes (`*.example.com`) are normalized.
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
# AI agents
|
|
43
|
+
api.openai.com
|
|
44
|
+
api.anthropic.com
|
|
45
|
+
|
|
46
|
+
# Source code
|
|
47
|
+
github.com
|
|
48
|
+
api.github.com
|
|
49
|
+
*.githubusercontent.com
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Point at one or more files via `CLEARANCE_ALLOW_HOSTS_FILES` using your
|
|
53
|
+
platform's PATH delimiter — `:` on macOS/Linux, `;` on Windows. The values
|
|
54
|
+
from `CLEARANCE_ALLOW_HOSTS`, all referenced files, and any duplicates
|
|
55
|
+
are concatenated and deduped.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
CLEARANCE_ALLOW_HOSTS_FILES="$REPO/clearance-allow-hosts:$HOME/.config/clearance/personal-hosts" \
|
|
59
|
+
clearance
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
This is how teams share a baseline: check a `team-allow-hosts` file into
|
|
63
|
+
your repo, and let individuals layer a personal file on top via the same
|
|
64
|
+
env var.
|
|
65
|
+
|
|
66
|
+
### Configuration reference
|
|
67
|
+
|
|
68
|
+
<!-- markdownlint-disable MD060 -->
|
|
69
|
+
|
|
70
|
+
| Variable | Default | Notes |
|
|
71
|
+
| ----------------------------- | ----------- | --------------------------------------------------------------------------------------------------------- |
|
|
72
|
+
| `CLEARANCE_ALLOW_HOSTS` | _(unset)_ | Comma- or whitespace-separated exact hosts and wildcard suffixes. |
|
|
73
|
+
| `CLEARANCE_ALLOW_HOSTS_FILES` | _(unset)_ | PATH-delimited paths (`:` macOS/Linux, `;` Windows) to plain-text host files (`#` comments, blank lines). |
|
|
74
|
+
| `CLEARANCE_ALLOW_PORTS` | `443` | Comma- or whitespace-separated TCP ports. |
|
|
75
|
+
| `CLEARANCE_LISTEN_HOST` | `127.0.0.1` | Bind host. |
|
|
76
|
+
| `CLEARANCE_PORT` | `19999` | Bind port. |
|
|
77
|
+
| `CLEARANCE_DNS_TTL_MS` | `60000` | In-process DNS cache TTL. |
|
|
78
|
+
| `CLEARANCE_IDLE_TIMEOUT_MS` | `120000` | Socket idle timeout. |
|
|
79
|
+
| `CLEARANCE_MAX_SOCKETS` | `1024` | Inbound connection cap and outbound HTTP agent cap. |
|
|
80
|
+
| `CLEARANCE_ALLOW_PRIVATE_IPS` | _(unset)_ | Set to `1` to disable private/non-public IP blocking for local testing. |
|
|
81
|
+
|
|
82
|
+
<!-- markdownlint-enable MD060 -->
|
|
83
|
+
|
|
84
|
+
## Managed proxy: `clearance-ensure`
|
|
85
|
+
|
|
86
|
+
`clearance-ensure` checks `127.0.0.1:19999`; if no proxy is listening it
|
|
87
|
+
spawns one detached using the same env vars and waits for it to bind. Logs
|
|
88
|
+
live at `${XDG_CACHE_HOME:-$HOME/.cache}/clearance/clearance.log`,
|
|
89
|
+
pid at `…/clearance.pid`.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
CLEARANCE_ALLOW_HOSTS_FILES="$REPO/clearance-allow-hosts" clearance-ensure
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
To stop or restart the managed proxy after editing your allow-host files:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
kill "$(cat "${XDG_CACHE_HOME:-$HOME/.cache}/clearance/clearance.pid")"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Safehouse integration (macOS)
|
|
102
|
+
|
|
103
|
+
Safehouse uses macOS sandbox profiles, so this section is for macOS hosts
|
|
104
|
+
only. Safehouse allows network access by default for agent compatibility.
|
|
105
|
+
To force a wrapped agent through this proxy, run the proxy outside
|
|
106
|
+
Safehouse, then append a Safehouse profile that denies direct remote
|
|
107
|
+
egress while leaving `localhost` open for `http://127.0.0.1:19999`.
|
|
108
|
+
|
|
109
|
+
The package ships a `safehouse-clearance` wrapper plus the matching
|
|
110
|
+
`clearance.env` and `clearance-only.sb` profile. After a global install,
|
|
111
|
+
the wrapper lives at `$(npm root -g)/@clipboard-health/clearance/safehouse/safehouse-clearance`.
|
|
112
|
+
It ensures the proxy is running, then `exec`s safehouse with the env file and
|
|
113
|
+
sandbox profile alongside it. It does **not** parse or rewrite your
|
|
114
|
+
arguments — anything you pass is forwarded verbatim, so put any safehouse
|
|
115
|
+
flags before the agent command:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
SAFEHOUSE_CLEARANCE="$(npm root -g)/@clipboard-health/clearance/safehouse/safehouse-clearance"
|
|
119
|
+
|
|
120
|
+
"$SAFEHOUSE_CLEARANCE" \
|
|
121
|
+
--enable=cloud-credentials --env-pass=AWS_PROFILE,AWS_REGION \
|
|
122
|
+
-- codex --dangerously-bypass-approvals-and-sandbox
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
For day-to-day agent use, set `CLEARANCE_ALLOW_HOSTS_FILES` to point at
|
|
126
|
+
your team's checked-in file (and optionally a personal file), then add
|
|
127
|
+
shell aliases:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
SAFEHOUSE_CLEARANCE="$(npm root -g)/@clipboard-health/clearance/safehouse/safehouse-clearance"
|
|
131
|
+
export CLEARANCE_ALLOW_HOSTS_FILES="$HOME/code/<your-repo>/clearance-allow-hosts:$HOME/.config/clearance/personal-allow-hosts"
|
|
132
|
+
|
|
133
|
+
alias codex-proxy="$SAFEHOUSE_CLEARANCE codex --dangerously-bypass-approvals-and-sandbox"
|
|
134
|
+
alias claude-proxy="$SAFEHOUSE_CLEARANCE claude --enable-auto-mode"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
For AWS SSO work, log in on the host first, then layer the safehouse cloud
|
|
138
|
+
credentials flag through the wrapper:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
aws sso login --profile <profile>
|
|
142
|
+
AWS_PROFILE=<profile> "$SAFEHOUSE_CLEARANCE" \
|
|
143
|
+
--enable=cloud-credentials --env-pass=AWS_PROFILE,AWS_REGION,AWS_SDK_LOAD_CONFIG,AWS_CONFIG_FILE,AWS_SHARED_CREDENTIALS_FILE,AWS_CA_BUNDLE \
|
|
144
|
+
-- codex --dangerously-bypass-approvals-and-sandbox
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Make sure your allow-host file includes the AWS endpoints you need
|
|
148
|
+
(typically `*.amazonaws.com`, `*.api.aws`, `*.awsapps.com`, `*.ecr.aws`).
|
|
149
|
+
|
|
150
|
+
### Manual setup
|
|
151
|
+
|
|
152
|
+
If you'd rather not depend on the bundled assets, you can build the same
|
|
153
|
+
setup by hand.
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
mkdir -p ~/.config/agent-safehouse
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
cat > ~/.config/agent-safehouse/clearance-only.sb <<'SB'
|
|
161
|
+
;; Force remote network egress through the local clearance while keeping
|
|
162
|
+
;; localhost available for local-only agent workflows.
|
|
163
|
+
(deny network-outbound
|
|
164
|
+
(remote ip "*:*")
|
|
165
|
+
(remote tcp "*:*")
|
|
166
|
+
(remote udp "*:*"))
|
|
167
|
+
|
|
168
|
+
(allow network-outbound
|
|
169
|
+
(remote ip "localhost:*")
|
|
170
|
+
(remote tcp "localhost:*")
|
|
171
|
+
(remote udp "localhost:*"))
|
|
172
|
+
SB
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
cat > ~/.config/agent-safehouse/clearance.env <<'ENV'
|
|
177
|
+
HTTP_PROXY="http://127.0.0.1:19999"
|
|
178
|
+
HTTPS_PROXY="http://127.0.0.1:19999"
|
|
179
|
+
ALL_PROXY="http://127.0.0.1:19999"
|
|
180
|
+
NO_PROXY="localhost,127.0.0.1,::1"
|
|
181
|
+
|
|
182
|
+
http_proxy="${HTTP_PROXY}"
|
|
183
|
+
https_proxy="${HTTPS_PROXY}"
|
|
184
|
+
all_proxy="${ALL_PROXY}"
|
|
185
|
+
no_proxy="${NO_PROXY}"
|
|
186
|
+
ENV
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Then run the wrapped command with Safehouse:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
safehouse \
|
|
193
|
+
--env="$HOME/.config/agent-safehouse/clearance.env" \
|
|
194
|
+
--append-profile="$HOME/.config/agent-safehouse/clearance-only.sb" \
|
|
195
|
+
-- <agent-command>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Do not pass API-key environment variables just for proxying. The env file
|
|
199
|
+
only supplies proxy settings; agents should continue to use their normal
|
|
200
|
+
auth/config stores.
|
|
201
|
+
|
|
202
|
+
Quick checks:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# Should use the proxy and usually return 401 without auth.
|
|
206
|
+
safehouse --env="$HOME/.config/agent-safehouse/clearance.env" \
|
|
207
|
+
--append-profile="$HOME/.config/agent-safehouse/clearance-only.sb" \
|
|
208
|
+
-- curl -I https://api.openai.com/v1/models
|
|
209
|
+
|
|
210
|
+
# Should fail because this bypasses the proxy and tries direct egress.
|
|
211
|
+
safehouse --env="$HOME/.config/agent-safehouse/clearance.env" \
|
|
212
|
+
--append-profile="$HOME/.config/agent-safehouse/clearance-only.sb" \
|
|
213
|
+
-- curl --noproxy '*' -I https://api.openai.com/v1/models
|
|
214
|
+
|
|
215
|
+
# Should be denied by the proxy unless example.com is in your allow-host list.
|
|
216
|
+
safehouse --env="$HOME/.config/agent-safehouse/clearance.env" \
|
|
217
|
+
--append-profile="$HOME/.config/agent-safehouse/clearance-only.sb" \
|
|
218
|
+
-- curl -I https://example.com
|
|
219
|
+
```
|
package/bin/ensure.js
ADDED
package/bin/run.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clipboard-health/clearance",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Localhost HTTP/HTTPS egress proxy with hostname allow-listing, plus a macOS Safehouse integration for sandboxed agent processes.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agent",
|
|
7
|
+
"allowlist",
|
|
8
|
+
"egress",
|
|
9
|
+
"proxy",
|
|
10
|
+
"safehouse",
|
|
11
|
+
"sandbox"
|
|
12
|
+
],
|
|
13
|
+
"bugs": "https://github.com/ClipboardHealth/core-utils/issues",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/ClipboardHealth/core-utils.git",
|
|
18
|
+
"directory": "packages/clearance"
|
|
19
|
+
},
|
|
20
|
+
"bin": {
|
|
21
|
+
"clearance": "./bin/run.js",
|
|
22
|
+
"clearance-ensure": "./bin/ensure.js"
|
|
23
|
+
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "./src/index.js",
|
|
26
|
+
"typings": "./src/index.d.ts",
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"tslib": "2.8.1"
|
|
32
|
+
},
|
|
33
|
+
"types": "./src/index.d.ts",
|
|
34
|
+
"module": "./src/index.js"
|
|
35
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
;; Force remote network egress through the local clearance while keeping
|
|
2
|
+
;; localhost available for local-only agent workflows.
|
|
3
|
+
(deny network-outbound
|
|
4
|
+
(remote ip "*:*")
|
|
5
|
+
(remote tcp "*:*")
|
|
6
|
+
(remote udp "*:*"))
|
|
7
|
+
|
|
8
|
+
(allow network-outbound
|
|
9
|
+
(remote ip "localhost:*")
|
|
10
|
+
(remote tcp "localhost:*")
|
|
11
|
+
(remote udp "localhost:*"))
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
source_path="${BASH_SOURCE[0]}"
|
|
5
|
+
while [ -h "$source_path" ]; do
|
|
6
|
+
source_dir="$(cd -P "$(dirname "$source_path")" >/dev/null 2>&1 && pwd)"
|
|
7
|
+
link_target="$(readlink "$source_path")"
|
|
8
|
+
case "$link_target" in
|
|
9
|
+
/*) source_path="$link_target" ;;
|
|
10
|
+
*) source_path="$source_dir/$link_target" ;;
|
|
11
|
+
esac
|
|
12
|
+
done
|
|
13
|
+
|
|
14
|
+
script_dir="$(cd -P "$(dirname "$source_path")" >/dev/null 2>&1 && pwd)"
|
|
15
|
+
package_dir="$(cd -P "$script_dir/.." >/dev/null 2>&1 && pwd)"
|
|
16
|
+
|
|
17
|
+
node "$package_dir/bin/ensure.js" >&2
|
|
18
|
+
|
|
19
|
+
exec safehouse \
|
|
20
|
+
--env="$script_dir/clearance.env" \
|
|
21
|
+
--append-profile="$script_dir/clearance-only.sb" \
|
|
22
|
+
"$@"
|
package/src/allowlist.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { delimiter as PATH_DELIMITER } from "node:path";
|
|
3
|
+
import { normalizeRule, parseList } from "./hostRule.js";
|
|
4
|
+
const COMMENT_PREFIX = "#";
|
|
5
|
+
export function resolveAllowlist(input) {
|
|
6
|
+
const readFile = input.readFile ?? defaultReadFile;
|
|
7
|
+
const fromEnv = parseList(input.env["CLEARANCE_ALLOW_HOSTS"]);
|
|
8
|
+
const fromFiles = parseFilesEnv(input.env["CLEARANCE_ALLOW_HOSTS_FILES"]).flatMap((path) => readHostsFile({ path, readFile }));
|
|
9
|
+
const normalized = [...fromEnv, ...fromFiles]
|
|
10
|
+
.map(normalizeRule)
|
|
11
|
+
.filter((rule) => rule !== undefined);
|
|
12
|
+
if (normalized.length === 0) {
|
|
13
|
+
throw new Error("Set CLEARANCE_ALLOW_HOSTS or CLEARANCE_ALLOW_HOSTS_FILES, e.g. CLEARANCE_ALLOW_HOSTS=api.example.com");
|
|
14
|
+
}
|
|
15
|
+
return [...new Set(normalized)];
|
|
16
|
+
}
|
|
17
|
+
function parseFilesEnv(value) {
|
|
18
|
+
if (value === undefined) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
return value
|
|
22
|
+
.split(PATH_DELIMITER)
|
|
23
|
+
.map((path) => path.trim())
|
|
24
|
+
.filter((path) => path.length > 0);
|
|
25
|
+
}
|
|
26
|
+
function readHostsFile(input) {
|
|
27
|
+
let content;
|
|
28
|
+
try {
|
|
29
|
+
content = input.readFile(input.path);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
33
|
+
throw new Error(`Failed to read CLEARANCE_ALLOW_HOSTS_FILES path ${JSON.stringify(input.path)}: ${message}`, { cause: error });
|
|
34
|
+
}
|
|
35
|
+
const hosts = [];
|
|
36
|
+
for (const rawLine of content.split("\n")) {
|
|
37
|
+
const [beforeComment = ""] = rawLine.split(COMMENT_PREFIX, 1);
|
|
38
|
+
const trimmed = beforeComment.trim();
|
|
39
|
+
if (trimmed.length > 0) {
|
|
40
|
+
hosts.push(trimmed);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return hosts;
|
|
44
|
+
}
|
|
45
|
+
function defaultReadFile(path) {
|
|
46
|
+
return readFileSync(path, "utf8");
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=allowlist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allowlist.js","sourceRoot":"","sources":["../../../../packages/clearance/src/allowlist.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,SAAS,IAAI,cAAc,EAAE,MAAM,WAAW,CAAC;AAExD,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAEzD,MAAM,cAAc,GAAG,GAAG,CAAC;AAO3B,MAAM,UAAU,gBAAgB,CAAC,KAA4B;IAC3D,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,eAAe,CAAC;IACnD,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CACzF,aAAa,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAClC,CAAC;IACF,MAAM,UAAU,GAAG,CAAC,GAAG,OAAO,EAAE,GAAG,SAAS,CAAC;SAC1C,GAAG,CAAC,aAAa,CAAC;SAClB,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;IAExD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CACb,sGAAsG,CACvG,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,SAAS,aAAa,CAAC,KAAyB;IAC9C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,KAAK;SACT,KAAK,CAAC,cAAc,CAAC;SACrB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACvC,CAAC;AAOD,SAAS,aAAa,CAAC,KAAyB;IAC9C,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACvC,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,IAAI,KAAK,CACb,mDAAmD,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,OAAO,EAAE,EAC3F,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1C,MAAM,CAAC,aAAa,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC;QAC9D,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,EAAE,CAAC;QACrC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,eAAe,CAAC,IAAY;IACnC,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACpC,CAAC"}
|
package/src/cli.d.ts
ADDED
package/src/cli.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { startClearanceFromEnv } from "./index.js";
|
|
3
|
+
startClearanceFromEnv({ env: process.env }).catch((error) => {
|
|
4
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5
|
+
process.stderr.write(`${message}\n`);
|
|
6
|
+
process.exitCode = 2;
|
|
7
|
+
});
|
|
8
|
+
//# sourceMappingURL=cli.js.map
|
package/src/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../../../packages/clearance/src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEnD,qBAAqB,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IACnE,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;IACrC,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
|
package/src/ensureCli.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { ensureClearance } from "./launcher.js";
|
|
3
|
+
ensureClearance({
|
|
4
|
+
logger: (message) => {
|
|
5
|
+
process.stderr.write(`${message}\n`);
|
|
6
|
+
},
|
|
7
|
+
}).catch((error) => {
|
|
8
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
9
|
+
process.stderr.write(`${message}\n`);
|
|
10
|
+
process.exit(2);
|
|
11
|
+
});
|
|
12
|
+
//# sourceMappingURL=ensureCli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ensureCli.js","sourceRoot":"","sources":["../../../../packages/clearance/src/ensureCli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,eAAe,CAAC;IACd,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE;QAClB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;IACvC,CAAC;CACF,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC1B,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function normalizeRule(rule: string): string | undefined;
|
|
2
|
+
export declare function normalizeRules(rules: readonly string[]): string[];
|
|
3
|
+
export declare function parseList(input: string | undefined): string[];
|
|
4
|
+
export declare function normalizeHost(host: string): string | undefined;
|
package/src/hostRule.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as net from "node:net";
|
|
2
|
+
import { domainToASCII } from "node:url";
|
|
3
|
+
export function normalizeRule(rule) {
|
|
4
|
+
const trimmed = rule.trim().toLowerCase().replace(/\.$/, "");
|
|
5
|
+
if (trimmed.length === 0) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
if (trimmed.startsWith("*.")) {
|
|
9
|
+
const suffix = normalizeHost(trimmed.slice(2));
|
|
10
|
+
if (suffix === undefined || net.isIP(suffix) !== 0) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
return `*.${suffix}`;
|
|
14
|
+
}
|
|
15
|
+
return normalizeHost(trimmed);
|
|
16
|
+
}
|
|
17
|
+
export function normalizeRules(rules) {
|
|
18
|
+
const normalizedRules = [];
|
|
19
|
+
for (const rule of rules) {
|
|
20
|
+
const normalizedRule = normalizeRule(rule);
|
|
21
|
+
if (normalizedRule !== undefined) {
|
|
22
|
+
normalizedRules.push(normalizedRule);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return [...new Set(normalizedRules)];
|
|
26
|
+
}
|
|
27
|
+
export function parseList(input) {
|
|
28
|
+
if (input === undefined) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
return input
|
|
32
|
+
.split(/[\s,]+/)
|
|
33
|
+
.map((item) => item.trim())
|
|
34
|
+
.filter((item) => item.length > 0);
|
|
35
|
+
}
|
|
36
|
+
export function normalizeHost(host) {
|
|
37
|
+
const trimmed = host.trim().toLowerCase().replace(/\.$/, "");
|
|
38
|
+
const isBracketed = trimmed.startsWith("[") && trimmed.endsWith("]");
|
|
39
|
+
const unbracketed = isBracketed ? trimmed.slice(1, -1) : trimmed;
|
|
40
|
+
if (unbracketed.length === 0 ||
|
|
41
|
+
unbracketed.includes("%") ||
|
|
42
|
+
unbracketed.includes("/") ||
|
|
43
|
+
/\s/.test(unbracketed)) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
if (net.isIP(unbracketed) !== 0) {
|
|
47
|
+
return unbracketed;
|
|
48
|
+
}
|
|
49
|
+
// Brackets are reserved for IP literals; bracketed non-IPs are malformed.
|
|
50
|
+
if (isBracketed) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
if (unbracketed.includes(":") || unbracketed.includes("@")) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
const ascii = domainToASCII(unbracketed);
|
|
57
|
+
if (ascii.length === 0 || ascii.includes("*")) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
return ascii;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=hostRule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hostRule.js","sourceRoot":"","sources":["../../../../packages/clearance/src/hostRule.ts"],"names":[],"mappings":"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"}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { LookupAddress } from "node:dns";
|
|
2
|
+
import * as http from "node:http";
|
|
3
|
+
export { resolveAllowlist, type ResolveAllowlistInput } from "./allowlist.js";
|
|
4
|
+
export { type ClearanceCheckInput, type ClearanceListenerCheck, type ClearanceSpawner, ensureClearance, type EnsureClearanceInput, type EnsureClearanceResult, isClearanceListening, spawnClearance, type SpawnClearanceInput, } from "./launcher.js";
|
|
5
|
+
export declare const CLEARANCE_PACKAGE_NAME = "@clipboard-health/clearance";
|
|
6
|
+
export interface ClearanceConfig {
|
|
7
|
+
allowedHosts: readonly string[];
|
|
8
|
+
allowedPorts: readonly number[];
|
|
9
|
+
dnsTtlMs: number;
|
|
10
|
+
idleTimeoutMs: number;
|
|
11
|
+
listenHost: string;
|
|
12
|
+
maxSockets: number;
|
|
13
|
+
port: number;
|
|
14
|
+
shouldBlockPrivateIps: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface ClearanceLogger {
|
|
17
|
+
info(message: string): void;
|
|
18
|
+
}
|
|
19
|
+
export type DnsLookup = (hostname: string) => Promise<readonly LookupAddress[]>;
|
|
20
|
+
export interface CreateClearanceServerOptions {
|
|
21
|
+
allowedHosts: readonly string[];
|
|
22
|
+
allowedPorts?: readonly number[];
|
|
23
|
+
dnsLookup?: DnsLookup;
|
|
24
|
+
dnsTtlMs?: number;
|
|
25
|
+
idleTimeoutMs?: number;
|
|
26
|
+
logger?: ClearanceLogger;
|
|
27
|
+
maxSockets?: number;
|
|
28
|
+
shouldBlockPrivateIps?: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface StartClearanceFromEnvInput {
|
|
31
|
+
env: NodeJS.ProcessEnv;
|
|
32
|
+
logger?: ClearanceLogger;
|
|
33
|
+
}
|
|
34
|
+
export declare function resolveClearanceConfig(env: NodeJS.ProcessEnv): ClearanceConfig;
|
|
35
|
+
export declare function createClearanceServer(options: CreateClearanceServerOptions): http.Server;
|
|
36
|
+
export declare function startClearanceFromEnv(input: StartClearanceFromEnvInput): Promise<http.Server>;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import * as dns from "node:dns/promises";
|
|
3
|
+
import * as http from "node:http";
|
|
4
|
+
import * as net from "node:net";
|
|
5
|
+
import { resolveAllowlist } from "./allowlist.js";
|
|
6
|
+
import { normalizeHost, normalizeRules, parseList } from "./hostRule.js";
|
|
7
|
+
export { resolveAllowlist } from "./allowlist.js";
|
|
8
|
+
export { ensureClearance, isClearanceListening, spawnClearance, } from "./launcher.js";
|
|
9
|
+
export const CLEARANCE_PACKAGE_NAME = "@clipboard-health/clearance";
|
|
10
|
+
const DEFAULT_LISTEN_HOST = "127.0.0.1";
|
|
11
|
+
const DEFAULT_PORT = 19_999;
|
|
12
|
+
const DEFAULT_ALLOWED_PORTS = [443];
|
|
13
|
+
const DEFAULT_DNS_TTL_MS = 60_000;
|
|
14
|
+
const DEFAULT_IDLE_TIMEOUT_MS = 120_000;
|
|
15
|
+
const DEFAULT_MAX_SOCKETS = 1024;
|
|
16
|
+
const SERVER_HEADERS_TIMEOUT_MS = 15_000;
|
|
17
|
+
const SERVER_KEEP_ALIVE_TIMEOUT_MS = 5000;
|
|
18
|
+
const NOOP_LOGGER = {
|
|
19
|
+
info: noop,
|
|
20
|
+
};
|
|
21
|
+
const HOP_BY_HOP_HEADERS = new Set([
|
|
22
|
+
"connection",
|
|
23
|
+
"keep-alive",
|
|
24
|
+
"proxy-authenticate",
|
|
25
|
+
"proxy-authorization",
|
|
26
|
+
"proxy-connection",
|
|
27
|
+
"te",
|
|
28
|
+
"trailer",
|
|
29
|
+
"transfer-encoding",
|
|
30
|
+
"upgrade",
|
|
31
|
+
]);
|
|
32
|
+
const PRIVATE_IPV4_RANGES = [
|
|
33
|
+
["0.0.0.0", 8],
|
|
34
|
+
["10.0.0.0", 8],
|
|
35
|
+
["100.64.0.0", 10],
|
|
36
|
+
["127.0.0.0", 8],
|
|
37
|
+
["169.254.0.0", 16],
|
|
38
|
+
["172.16.0.0", 12],
|
|
39
|
+
["192.0.0.0", 24],
|
|
40
|
+
["192.0.2.0", 24],
|
|
41
|
+
["192.168.0.0", 16],
|
|
42
|
+
["198.18.0.0", 15],
|
|
43
|
+
["198.51.100.0", 24],
|
|
44
|
+
["203.0.113.0", 24],
|
|
45
|
+
["224.0.0.0", 4],
|
|
46
|
+
["240.0.0.0", 4],
|
|
47
|
+
];
|
|
48
|
+
const PRIVATE_IPV6_RANGES = [
|
|
49
|
+
["::", 128],
|
|
50
|
+
["::1", 128],
|
|
51
|
+
["::", 96],
|
|
52
|
+
["::ffff:0:0", 96],
|
|
53
|
+
["64:ff9b::", 96],
|
|
54
|
+
["64:ff9b:1::", 48],
|
|
55
|
+
["100::", 64],
|
|
56
|
+
["2001::", 23],
|
|
57
|
+
["2001:db8::", 32],
|
|
58
|
+
["2002::", 16],
|
|
59
|
+
["fc00::", 7],
|
|
60
|
+
["fe80::", 10],
|
|
61
|
+
["ff00::", 8],
|
|
62
|
+
];
|
|
63
|
+
const PRIVATE_IPV4_BLOCK_LIST = createIpBlockList(PRIVATE_IPV4_RANGES, "ipv4");
|
|
64
|
+
const PRIVATE_IPV6_BLOCK_LIST = createIpBlockList(PRIVATE_IPV6_RANGES, "ipv6");
|
|
65
|
+
export function resolveClearanceConfig(env) {
|
|
66
|
+
const allowedHosts = resolveAllowlist({ env });
|
|
67
|
+
return {
|
|
68
|
+
allowedHosts,
|
|
69
|
+
allowedPorts: normalizeAllowedPorts(parseList(env["CLEARANCE_ALLOW_PORTS"] ?? DEFAULT_ALLOWED_PORTS.join(","))),
|
|
70
|
+
dnsTtlMs: parseIntegerEnv(env, "CLEARANCE_DNS_TTL_MS", DEFAULT_DNS_TTL_MS, 0, Number.MAX_SAFE_INTEGER),
|
|
71
|
+
idleTimeoutMs: parseIntegerEnv(env, "CLEARANCE_IDLE_TIMEOUT_MS", DEFAULT_IDLE_TIMEOUT_MS, 1, Number.MAX_SAFE_INTEGER),
|
|
72
|
+
listenHost: env["CLEARANCE_LISTEN_HOST"] ?? DEFAULT_LISTEN_HOST,
|
|
73
|
+
maxSockets: parseIntegerEnv(env, "CLEARANCE_MAX_SOCKETS", DEFAULT_MAX_SOCKETS, 1, Number.MAX_SAFE_INTEGER),
|
|
74
|
+
port: parseIntegerEnv(env, "CLEARANCE_PORT", DEFAULT_PORT, 1, 65_535),
|
|
75
|
+
shouldBlockPrivateIps: env["CLEARANCE_ALLOW_PRIVATE_IPS"] !== "1",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export function createClearanceServer(options) {
|
|
79
|
+
const state = createProxyState(options);
|
|
80
|
+
const server = http.createServer((req, res) => {
|
|
81
|
+
void handleHttpRequest({ req, res, state });
|
|
82
|
+
});
|
|
83
|
+
server.on("connect", (req, clientSocket, head) => {
|
|
84
|
+
/* v8 ignore next @preserve */
|
|
85
|
+
if (!isNetSocket(clientSocket)) {
|
|
86
|
+
socketReply(clientSocket, 400, "Bad Request", "Bad CONNECT socket\n");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
void handleConnect({ clientSocket, head, req, state });
|
|
90
|
+
});
|
|
91
|
+
server.on("clientError", (_error, socket) => {
|
|
92
|
+
socketReply(socket, 400, "Bad Request", "Bad Request\n");
|
|
93
|
+
});
|
|
94
|
+
server.headersTimeout = SERVER_HEADERS_TIMEOUT_MS;
|
|
95
|
+
server.keepAliveTimeout = SERVER_KEEP_ALIVE_TIMEOUT_MS;
|
|
96
|
+
server.maxConnections = state.maxSockets;
|
|
97
|
+
server.once("close", () => {
|
|
98
|
+
state.httpAgent.destroy();
|
|
99
|
+
});
|
|
100
|
+
return server;
|
|
101
|
+
}
|
|
102
|
+
export async function startClearanceFromEnv(input) {
|
|
103
|
+
const { env } = input;
|
|
104
|
+
const logger = input.logger ?? console;
|
|
105
|
+
const config = resolveClearanceConfig(env);
|
|
106
|
+
const server = createClearanceServer({
|
|
107
|
+
allowedHosts: config.allowedHosts,
|
|
108
|
+
allowedPorts: config.allowedPorts,
|
|
109
|
+
dnsTtlMs: config.dnsTtlMs,
|
|
110
|
+
idleTimeoutMs: config.idleTimeoutMs,
|
|
111
|
+
logger,
|
|
112
|
+
maxSockets: config.maxSockets,
|
|
113
|
+
shouldBlockPrivateIps: config.shouldBlockPrivateIps,
|
|
114
|
+
});
|
|
115
|
+
await listen(server, config);
|
|
116
|
+
logger.info(`clearance listening on http://${config.listenHost}:${config.port}`);
|
|
117
|
+
logger.info(`allowed hosts: ${config.allowedHosts.join(", ")}`);
|
|
118
|
+
logger.info(`allowed ports: ${config.allowedPorts.join(",")}`);
|
|
119
|
+
return server;
|
|
120
|
+
}
|
|
121
|
+
function createProxyState(options) {
|
|
122
|
+
const allowedHosts = normalizeRules(options.allowedHosts);
|
|
123
|
+
if (allowedHosts.length === 0) {
|
|
124
|
+
throw new Error("allowedHosts must include at least one valid host rule");
|
|
125
|
+
}
|
|
126
|
+
const maxSockets = options.maxSockets ?? DEFAULT_MAX_SOCKETS;
|
|
127
|
+
return {
|
|
128
|
+
allowedHosts,
|
|
129
|
+
allowedPorts: new Set(normalizeAllowedPorts(options.allowedPorts ?? DEFAULT_ALLOWED_PORTS)),
|
|
130
|
+
dnsCache: new Map(),
|
|
131
|
+
dnsLookup: options.dnsLookup ?? defaultDnsLookup,
|
|
132
|
+
dnsTtlMs: options.dnsTtlMs ?? DEFAULT_DNS_TTL_MS,
|
|
133
|
+
httpAgent: new http.Agent({ keepAlive: true, maxSockets }),
|
|
134
|
+
idleTimeoutMs: options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS,
|
|
135
|
+
logger: options.logger ?? NOOP_LOGGER,
|
|
136
|
+
maxSockets,
|
|
137
|
+
shouldBlockPrivateIps: options.shouldBlockPrivateIps ?? true,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
async function listen(server, config) {
|
|
141
|
+
await new Promise((resolve, reject) => {
|
|
142
|
+
function onError(error) {
|
|
143
|
+
server.off("listening", onListening);
|
|
144
|
+
reject(error);
|
|
145
|
+
}
|
|
146
|
+
function onListening() {
|
|
147
|
+
server.off("error", onError);
|
|
148
|
+
resolve();
|
|
149
|
+
}
|
|
150
|
+
server.once("error", onError);
|
|
151
|
+
server.once("listening", onListening);
|
|
152
|
+
server.listen(config.port, config.listenHost);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
async function handleConnect(input) {
|
|
156
|
+
const { clientSocket, head, req, state } = input;
|
|
157
|
+
/* v8 ignore next @preserve */
|
|
158
|
+
clientSocket.on("error", noop);
|
|
159
|
+
/* v8 ignore next @preserve */
|
|
160
|
+
clientSocket.setTimeout(state.idleTimeoutMs, () => {
|
|
161
|
+
clientSocket.destroy(new Error("idle timeout"));
|
|
162
|
+
});
|
|
163
|
+
const parsed = parseConnectTarget(String(req.url));
|
|
164
|
+
if (parsed === undefined) {
|
|
165
|
+
socketReply(clientSocket, 400, "Bad Request", "Bad CONNECT target\n");
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
let target;
|
|
169
|
+
try {
|
|
170
|
+
target = await authorize({ host: parsed.host, port: parsed.port, state });
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
const reason = errorMessage(error);
|
|
174
|
+
logDecision(state.logger, {
|
|
175
|
+
decision: "DENY",
|
|
176
|
+
hostname: parsed.host,
|
|
177
|
+
method: "CONNECT",
|
|
178
|
+
port: parsed.port,
|
|
179
|
+
reason,
|
|
180
|
+
});
|
|
181
|
+
socketReply(clientSocket, 403, "Forbidden", `${reason}\n`);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
let isEstablished = false;
|
|
185
|
+
const upstream = net.createConnection({
|
|
186
|
+
family: target.family,
|
|
187
|
+
host: target.address,
|
|
188
|
+
port: target.port,
|
|
189
|
+
});
|
|
190
|
+
upstream.setNoDelay(true);
|
|
191
|
+
/* v8 ignore next @preserve */
|
|
192
|
+
upstream.setTimeout(state.idleTimeoutMs, () => {
|
|
193
|
+
upstream.destroy(new Error("idle timeout"));
|
|
194
|
+
});
|
|
195
|
+
upstream.once("connect", () => {
|
|
196
|
+
isEstablished = true;
|
|
197
|
+
logDecision(state.logger, {
|
|
198
|
+
address: target.address,
|
|
199
|
+
decision: "ALLOW",
|
|
200
|
+
hostname: target.hostname,
|
|
201
|
+
method: "CONNECT",
|
|
202
|
+
port: target.port,
|
|
203
|
+
});
|
|
204
|
+
clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
|
|
205
|
+
if (head.length > 0) {
|
|
206
|
+
upstream.write(head);
|
|
207
|
+
}
|
|
208
|
+
upstream.pipe(clientSocket);
|
|
209
|
+
clientSocket.pipe(upstream);
|
|
210
|
+
});
|
|
211
|
+
upstream.once("error", (error) => {
|
|
212
|
+
/* v8 ignore else @preserve */
|
|
213
|
+
if (!isEstablished) {
|
|
214
|
+
socketReply(clientSocket, 502, "Bad Gateway", `${error.message}\n`);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
/* v8 ignore next @preserve */
|
|
218
|
+
clientSocket.destroy(error);
|
|
219
|
+
});
|
|
220
|
+
clientSocket.once("close", () => {
|
|
221
|
+
upstream.destroy();
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
async function handleHttpRequest(input) {
|
|
225
|
+
const { req, res, state } = input;
|
|
226
|
+
const rawUrl = String(req.url);
|
|
227
|
+
let url;
|
|
228
|
+
try {
|
|
229
|
+
url = new URL(rawUrl);
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
httpReply(res, 400, "HTTP proxy requests must use absolute URLs\n");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (url.protocol !== "http:") {
|
|
236
|
+
httpReply(res, 400, "Use CONNECT for HTTPS\n");
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const port = url.port.length > 0 ? Number(url.port) : 80;
|
|
240
|
+
const method = String(req.method);
|
|
241
|
+
let target;
|
|
242
|
+
try {
|
|
243
|
+
target = await authorize({ host: url.hostname, port, state });
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
const reason = errorMessage(error);
|
|
247
|
+
logDecision(state.logger, {
|
|
248
|
+
decision: "DENY",
|
|
249
|
+
hostname: url.hostname,
|
|
250
|
+
method,
|
|
251
|
+
port,
|
|
252
|
+
reason,
|
|
253
|
+
});
|
|
254
|
+
httpReply(res, 403, `${reason}\n`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const headers = stripHopByHopHeaders(req.headers);
|
|
258
|
+
headers.host = url.host;
|
|
259
|
+
const upstreamReq = http.request({
|
|
260
|
+
agent: state.httpAgent,
|
|
261
|
+
family: target.family,
|
|
262
|
+
headers,
|
|
263
|
+
hostname: target.address,
|
|
264
|
+
method: req.method,
|
|
265
|
+
path: `${url.pathname}${url.search}`,
|
|
266
|
+
port: target.port,
|
|
267
|
+
}, (upstreamRes) => {
|
|
268
|
+
logDecision(state.logger, {
|
|
269
|
+
address: target.address,
|
|
270
|
+
decision: "ALLOW",
|
|
271
|
+
hostname: target.hostname,
|
|
272
|
+
method,
|
|
273
|
+
port: target.port,
|
|
274
|
+
});
|
|
275
|
+
/* v8 ignore next @preserve */
|
|
276
|
+
const statusCode = upstreamRes.statusCode ?? 502;
|
|
277
|
+
res.writeHead(statusCode, stripHopByHopHeaders(upstreamRes.headers));
|
|
278
|
+
upstreamRes.pipe(res);
|
|
279
|
+
});
|
|
280
|
+
upstreamReq.setTimeout(state.idleTimeoutMs, () => {
|
|
281
|
+
upstreamReq.destroy(new Error("upstream timeout"));
|
|
282
|
+
});
|
|
283
|
+
/* v8 ignore next @preserve */
|
|
284
|
+
req.socket.setTimeout(state.idleTimeoutMs, () => {
|
|
285
|
+
upstreamReq.destroy(new Error("client idle timeout"));
|
|
286
|
+
});
|
|
287
|
+
req.once("end", () => {
|
|
288
|
+
req.socket.setTimeout(0);
|
|
289
|
+
});
|
|
290
|
+
upstreamReq.on("error", (error) => {
|
|
291
|
+
/* v8 ignore next @preserve */
|
|
292
|
+
if (res.headersSent) {
|
|
293
|
+
res.destroy(error);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
httpReply(res, 502, `${error.message}\n`);
|
|
297
|
+
});
|
|
298
|
+
upstreamReq.once("close", () => {
|
|
299
|
+
req.socket.setTimeout(0);
|
|
300
|
+
});
|
|
301
|
+
/* v8 ignore next @preserve */
|
|
302
|
+
req.once("aborted", () => {
|
|
303
|
+
upstreamReq.destroy(new Error("client aborted"));
|
|
304
|
+
});
|
|
305
|
+
res.once("close", () => {
|
|
306
|
+
if (!res.writableEnded) {
|
|
307
|
+
upstreamReq.destroy(new Error("client disconnected"));
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
req.pipe(upstreamReq);
|
|
311
|
+
}
|
|
312
|
+
async function authorize(input) {
|
|
313
|
+
const { host, port, state } = input;
|
|
314
|
+
const hostname = normalizeHost(host);
|
|
315
|
+
if (hostname === undefined) {
|
|
316
|
+
throw new Error("empty or invalid host");
|
|
317
|
+
}
|
|
318
|
+
if (!state.allowedPorts.has(port)) {
|
|
319
|
+
throw new Error(`port not allowed: ${port}`);
|
|
320
|
+
}
|
|
321
|
+
if (!hostAllowed(hostname, state.allowedHosts)) {
|
|
322
|
+
throw new Error(`host not allowed: ${hostname}`);
|
|
323
|
+
}
|
|
324
|
+
const resolved = await resolveHost({ hostname, state });
|
|
325
|
+
return { address: resolved.address, family: resolved.family, hostname, port };
|
|
326
|
+
}
|
|
327
|
+
async function resolveHost(input) {
|
|
328
|
+
const { hostname, state } = input;
|
|
329
|
+
const directIpFamily = net.isIP(hostname);
|
|
330
|
+
if (isIpFamily(directIpFamily)) {
|
|
331
|
+
if (state.shouldBlockPrivateIps && isPrivateIpAddress(hostname, directIpFamily)) {
|
|
332
|
+
throw new Error(`private IP blocked: ${hostname}`);
|
|
333
|
+
}
|
|
334
|
+
return { address: hostname, family: directIpFamily };
|
|
335
|
+
}
|
|
336
|
+
const now = Date.now();
|
|
337
|
+
const cached = state.dnsCache.get(hostname);
|
|
338
|
+
const cachedRecord = cached?.records[0];
|
|
339
|
+
if (cached !== undefined && cached.until > now && cachedRecord !== undefined) {
|
|
340
|
+
return cachedRecord;
|
|
341
|
+
}
|
|
342
|
+
if (cached !== undefined && cached.until <= now) {
|
|
343
|
+
state.dnsCache.delete(hostname);
|
|
344
|
+
}
|
|
345
|
+
const lookupRecords = await state.dnsLookup(hostname);
|
|
346
|
+
const records = lookupRecords
|
|
347
|
+
.filter(hasIpFamily)
|
|
348
|
+
.filter((record) => !(state.shouldBlockPrivateIps && isPrivateIpAddress(record.address, record.family)))
|
|
349
|
+
.toSorted((a, b) => a.family - b.family);
|
|
350
|
+
const [firstRecord] = records;
|
|
351
|
+
if (firstRecord === undefined) {
|
|
352
|
+
throw new Error(`no public address for ${hostname}`);
|
|
353
|
+
}
|
|
354
|
+
if (state.dnsTtlMs > 0) {
|
|
355
|
+
state.dnsCache.set(hostname, { records, until: now + state.dnsTtlMs });
|
|
356
|
+
}
|
|
357
|
+
return firstRecord;
|
|
358
|
+
}
|
|
359
|
+
async function defaultDnsLookup(hostname) {
|
|
360
|
+
return await dns.lookup(hostname, { all: true, verbatim: false });
|
|
361
|
+
}
|
|
362
|
+
function hostAllowed(hostname, allowedHosts) {
|
|
363
|
+
return allowedHosts.some((rule) => {
|
|
364
|
+
if (rule.startsWith("*.")) {
|
|
365
|
+
const suffix = rule.slice(1);
|
|
366
|
+
return hostname.endsWith(suffix) && hostname.length > suffix.length;
|
|
367
|
+
}
|
|
368
|
+
return hostname === rule;
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
function parseConnectTarget(input) {
|
|
372
|
+
const ipv6 = /^\[([^\]]+)]:(\d+)$/.exec(input);
|
|
373
|
+
if (ipv6 !== null) {
|
|
374
|
+
const [, host, rawPort] = ipv6;
|
|
375
|
+
const port = parsePort(rawPort);
|
|
376
|
+
/* v8 ignore next @preserve */
|
|
377
|
+
if (host === undefined || port === undefined) {
|
|
378
|
+
return undefined;
|
|
379
|
+
}
|
|
380
|
+
return { host, port };
|
|
381
|
+
}
|
|
382
|
+
const separatorIndex = input.lastIndexOf(":");
|
|
383
|
+
if (separatorIndex <= 0) {
|
|
384
|
+
return undefined;
|
|
385
|
+
}
|
|
386
|
+
const port = parsePort(input.slice(separatorIndex + 1));
|
|
387
|
+
if (port === undefined) {
|
|
388
|
+
return undefined;
|
|
389
|
+
}
|
|
390
|
+
return { host: input.slice(0, separatorIndex), port };
|
|
391
|
+
}
|
|
392
|
+
function stripHopByHopHeaders(headers) {
|
|
393
|
+
const blockedHeaders = new Set(HOP_BY_HOP_HEADERS);
|
|
394
|
+
for (const token of parseConnectionHeaderTokens(headers.connection)) {
|
|
395
|
+
blockedHeaders.add(token);
|
|
396
|
+
}
|
|
397
|
+
const out = {};
|
|
398
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
399
|
+
if (!blockedHeaders.has(key.toLowerCase()) && value !== undefined) {
|
|
400
|
+
out[key] = value;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return out;
|
|
404
|
+
}
|
|
405
|
+
function parseConnectionHeaderTokens(value) {
|
|
406
|
+
/* v8 ignore next @preserve */
|
|
407
|
+
if (value === undefined) {
|
|
408
|
+
return [];
|
|
409
|
+
}
|
|
410
|
+
/* v8 ignore next @preserve */
|
|
411
|
+
const values = Array.isArray(value) ? value : [String(value)];
|
|
412
|
+
return values
|
|
413
|
+
.flatMap((header) => header.split(","))
|
|
414
|
+
.map((token) => token.trim().toLowerCase())
|
|
415
|
+
.filter((token) => token.length > 0);
|
|
416
|
+
}
|
|
417
|
+
function normalizeAllowedPorts(rawPorts) {
|
|
418
|
+
const ports = rawPorts
|
|
419
|
+
.map((rawPort) => (typeof rawPort === "number" ? rawPort : Number(rawPort)))
|
|
420
|
+
.filter((port) => isValidPort(port));
|
|
421
|
+
if (ports.length === 0) {
|
|
422
|
+
throw new Error("CLEARANCE_ALLOW_PORTS must include at least one valid TCP port");
|
|
423
|
+
}
|
|
424
|
+
return [...new Set(ports)];
|
|
425
|
+
}
|
|
426
|
+
function parseIntegerEnv(env, name, fallback, minimum, maximum) {
|
|
427
|
+
const value = env[name];
|
|
428
|
+
if (value === undefined || value.trim().length === 0) {
|
|
429
|
+
return fallback;
|
|
430
|
+
}
|
|
431
|
+
const parsed = Number(value);
|
|
432
|
+
if (!Number.isInteger(parsed)) {
|
|
433
|
+
throw new TypeError(`${name} must be an integer`);
|
|
434
|
+
}
|
|
435
|
+
if (parsed < minimum || parsed > maximum) {
|
|
436
|
+
throw new Error(`${name} must be between ${minimum} and ${maximum}`);
|
|
437
|
+
}
|
|
438
|
+
return parsed;
|
|
439
|
+
}
|
|
440
|
+
function parsePort(rawPort) {
|
|
441
|
+
if (rawPort === undefined || rawPort.trim().length === 0) {
|
|
442
|
+
return undefined;
|
|
443
|
+
}
|
|
444
|
+
const port = Number(rawPort);
|
|
445
|
+
return isValidPort(port) ? port : undefined;
|
|
446
|
+
}
|
|
447
|
+
function isValidPort(port) {
|
|
448
|
+
return Number.isInteger(port) && port > 0 && port <= 65_535;
|
|
449
|
+
}
|
|
450
|
+
function isIpFamily(family) {
|
|
451
|
+
return family === 4 || family === 6;
|
|
452
|
+
}
|
|
453
|
+
function hasIpFamily(record) {
|
|
454
|
+
return isIpFamily(record.family);
|
|
455
|
+
}
|
|
456
|
+
function isPrivateIpAddress(ip, family) {
|
|
457
|
+
if (family === 4) {
|
|
458
|
+
return PRIVATE_IPV4_BLOCK_LIST.check(ip, "ipv4");
|
|
459
|
+
}
|
|
460
|
+
return PRIVATE_IPV6_BLOCK_LIST.check(ip, "ipv6");
|
|
461
|
+
}
|
|
462
|
+
function createIpBlockList(ranges, family) {
|
|
463
|
+
const blockList = new net.BlockList();
|
|
464
|
+
for (const [address, prefix] of ranges) {
|
|
465
|
+
blockList.addSubnet(address, prefix, family);
|
|
466
|
+
}
|
|
467
|
+
return blockList;
|
|
468
|
+
}
|
|
469
|
+
function httpReply(res, status, body) {
|
|
470
|
+
res.writeHead(status, {
|
|
471
|
+
connection: "close",
|
|
472
|
+
"content-length": Buffer.byteLength(body),
|
|
473
|
+
"content-type": "text/plain; charset=utf-8",
|
|
474
|
+
});
|
|
475
|
+
res.end(body);
|
|
476
|
+
}
|
|
477
|
+
function socketReply(socket, status, reason, body) {
|
|
478
|
+
/* v8 ignore next @preserve */
|
|
479
|
+
if (socket.destroyed) {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
socket.end(`HTTP/1.1 ${status} ${reason}\r\ncontent-type: text/plain; charset=utf-8\r\ncontent-length: ${Buffer.byteLength(body)}\r\nconnection: close\r\n\r\n${body}`);
|
|
483
|
+
}
|
|
484
|
+
function logDecision(logger, input) {
|
|
485
|
+
const parts = [
|
|
486
|
+
input.decision,
|
|
487
|
+
`method=${input.method}`,
|
|
488
|
+
`host=${input.hostname}`,
|
|
489
|
+
`port=${input.port}`,
|
|
490
|
+
];
|
|
491
|
+
if (input.address !== undefined) {
|
|
492
|
+
parts.push(`address=${input.address}`);
|
|
493
|
+
}
|
|
494
|
+
if (input.reason !== undefined) {
|
|
495
|
+
parts.push(`reason=${input.reason}`);
|
|
496
|
+
}
|
|
497
|
+
logger.info(parts.join(" "));
|
|
498
|
+
}
|
|
499
|
+
function errorMessage(error) {
|
|
500
|
+
/* v8 ignore next @preserve */
|
|
501
|
+
return error instanceof Error ? error.message : String(error);
|
|
502
|
+
}
|
|
503
|
+
function isNetSocket(socket) {
|
|
504
|
+
return socket instanceof net.Socket;
|
|
505
|
+
}
|
|
506
|
+
function noop() {
|
|
507
|
+
// Intentionally ignore optional proxy log and socket error events.
|
|
508
|
+
}
|
|
509
|
+
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/clearance/src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface ClearanceCheckInput {
|
|
2
|
+
host: string;
|
|
3
|
+
port: number;
|
|
4
|
+
}
|
|
5
|
+
export type ClearanceListenerCheck = (input: ClearanceCheckInput) => Promise<boolean>;
|
|
6
|
+
export interface SpawnClearanceInput {
|
|
7
|
+
args: readonly string[];
|
|
8
|
+
command: string;
|
|
9
|
+
env: NodeJS.ProcessEnv;
|
|
10
|
+
logPath: string;
|
|
11
|
+
}
|
|
12
|
+
export type ClearanceSpawner = (input: SpawnClearanceInput) => number;
|
|
13
|
+
export interface EnsureClearanceInput {
|
|
14
|
+
cacheDir?: string;
|
|
15
|
+
env?: NodeJS.ProcessEnv;
|
|
16
|
+
isListening?: ClearanceListenerCheck;
|
|
17
|
+
logger?: (message: string) => void;
|
|
18
|
+
pollIntervalMs?: number;
|
|
19
|
+
sleep?: (ms: number) => Promise<void>;
|
|
20
|
+
spawnDetached?: ClearanceSpawner;
|
|
21
|
+
timeoutMs?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface EnsureClearanceResult {
|
|
24
|
+
logPath?: string;
|
|
25
|
+
pid?: number;
|
|
26
|
+
pidPath?: string;
|
|
27
|
+
port: number;
|
|
28
|
+
status: "already-running" | "started";
|
|
29
|
+
}
|
|
30
|
+
export declare function isClearanceListening(input: ClearanceCheckInput): Promise<boolean>;
|
|
31
|
+
export declare function spawnClearance(input: SpawnClearanceInput): number;
|
|
32
|
+
export declare function ensureClearance(input?: EnsureClearanceInput): Promise<EnsureClearanceResult>;
|
package/src/launcher.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { closeSync, mkdirSync, openSync, writeFileSync } from "node:fs";
|
|
3
|
+
import * as net from "node:net";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join, resolve as resolvePath } from "node:path";
|
|
6
|
+
import { resolveAllowlist } from "./allowlist.js";
|
|
7
|
+
const CLEARANCE_HOST = "127.0.0.1";
|
|
8
|
+
const CLEARANCE_PORT = 19_999;
|
|
9
|
+
const CLEARANCE_START_TIMEOUT_MS = 3000;
|
|
10
|
+
const CLEARANCE_POLL_INTERVAL_MS = 100;
|
|
11
|
+
const FORWARDED_ENV_VARS = [
|
|
12
|
+
"CLEARANCE_ALLOW_HOSTS",
|
|
13
|
+
"CLEARANCE_ALLOW_HOSTS_FILES",
|
|
14
|
+
"CLEARANCE_ALLOW_PORTS",
|
|
15
|
+
"CLEARANCE_ALLOW_PRIVATE_IPS",
|
|
16
|
+
"CLEARANCE_DNS_TTL_MS",
|
|
17
|
+
"CLEARANCE_IDLE_TIMEOUT_MS",
|
|
18
|
+
"CLEARANCE_MAX_SOCKETS",
|
|
19
|
+
"HOME",
|
|
20
|
+
"PATH",
|
|
21
|
+
"XDG_CACHE_HOME",
|
|
22
|
+
];
|
|
23
|
+
// import.meta.dirname is `<package>/{src,dist}`; the proxy server bin lives at `<package>/bin/run.js`.
|
|
24
|
+
const PACKAGE_ROOT = resolvePath(import.meta.dirname, "..");
|
|
25
|
+
const CLEARANCE_BIN_PATH = resolvePath(PACKAGE_ROOT, "bin", "run.js");
|
|
26
|
+
export async function isClearanceListening(input) {
|
|
27
|
+
return await new Promise((resolve) => {
|
|
28
|
+
const socket = net.createConnection({ host: input.host, port: input.port });
|
|
29
|
+
socket.setTimeout(500);
|
|
30
|
+
socket.once("connect", () => {
|
|
31
|
+
socket.destroy();
|
|
32
|
+
resolve(true);
|
|
33
|
+
});
|
|
34
|
+
socket.once("error", () => {
|
|
35
|
+
socket.destroy();
|
|
36
|
+
resolve(false);
|
|
37
|
+
});
|
|
38
|
+
/* v8 ignore next 4 @preserve -- timeout is a defensive slow-network path; connection refused is the normal closed-port path */
|
|
39
|
+
socket.once("timeout", () => {
|
|
40
|
+
socket.destroy();
|
|
41
|
+
resolve(false);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export function spawnClearance(input) {
|
|
46
|
+
const logFile = openSync(input.logPath, "a");
|
|
47
|
+
try {
|
|
48
|
+
const child = spawn(input.command, input.args, {
|
|
49
|
+
detached: true,
|
|
50
|
+
env: input.env,
|
|
51
|
+
stdio: ["ignore", logFile, logFile],
|
|
52
|
+
});
|
|
53
|
+
child.unref();
|
|
54
|
+
/* v8 ignore next 3 @preserve -- Node assigns pid for spawned processes; this is a defensive guard */
|
|
55
|
+
if (child.pid === undefined) {
|
|
56
|
+
throw new Error("clearance process started without a pid");
|
|
57
|
+
}
|
|
58
|
+
return child.pid;
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
closeSync(logFile);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export async function ensureClearance(input = {}) {
|
|
65
|
+
const env = input.env ?? defaultProxyEnv();
|
|
66
|
+
/* v8 ignore next @preserve -- default listener is covered directly by isClearanceListening tests */
|
|
67
|
+
const isListening = input.isListening ?? isClearanceListening;
|
|
68
|
+
const logger = input.logger ?? noop;
|
|
69
|
+
if (await isListening({ host: CLEARANCE_HOST, port: CLEARANCE_PORT })) {
|
|
70
|
+
logger(`Clearance already listening on http://${CLEARANCE_HOST}:${CLEARANCE_PORT}`);
|
|
71
|
+
return { port: CLEARANCE_PORT, status: "already-running" };
|
|
72
|
+
}
|
|
73
|
+
// Fail fast with a readable message instead of a "did not start listening" timeout.
|
|
74
|
+
resolveAllowlist({ env });
|
|
75
|
+
const cacheDir = input.cacheDir ?? cacheDirFor(env);
|
|
76
|
+
const logPath = join(cacheDir, "clearance.log");
|
|
77
|
+
const pidPath = join(cacheDir, "clearance.pid");
|
|
78
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
79
|
+
/* v8 ignore next @preserve -- default spawner is covered directly by spawnClearance tests */
|
|
80
|
+
const spawnDetached = input.spawnDetached ?? spawnClearance;
|
|
81
|
+
const pid = spawnDetached({
|
|
82
|
+
args: [CLEARANCE_BIN_PATH],
|
|
83
|
+
command: process.execPath,
|
|
84
|
+
env: childEnv(env),
|
|
85
|
+
logPath,
|
|
86
|
+
});
|
|
87
|
+
writeFileSync(pidPath, `${pid}\n`);
|
|
88
|
+
const isReady = await waitForListening({
|
|
89
|
+
host: CLEARANCE_HOST,
|
|
90
|
+
isListening,
|
|
91
|
+
pollIntervalMs: input.pollIntervalMs ?? CLEARANCE_POLL_INTERVAL_MS,
|
|
92
|
+
port: CLEARANCE_PORT,
|
|
93
|
+
sleep: input.sleep ?? defaultSleep,
|
|
94
|
+
timeoutMs: input.timeoutMs ?? CLEARANCE_START_TIMEOUT_MS,
|
|
95
|
+
});
|
|
96
|
+
if (!isReady) {
|
|
97
|
+
throw new Error(`Clearance did not start listening on ${CLEARANCE_HOST}:${CLEARANCE_PORT}; check ${logPath}`);
|
|
98
|
+
}
|
|
99
|
+
logger(`Started clearance on http://${CLEARANCE_HOST}:${CLEARANCE_PORT} (pid ${pid}); logs: ${logPath}`);
|
|
100
|
+
return { logPath, pid, pidPath, port: CLEARANCE_PORT, status: "started" };
|
|
101
|
+
}
|
|
102
|
+
function noop() {
|
|
103
|
+
// No-op default for the optional logger hook.
|
|
104
|
+
}
|
|
105
|
+
function cacheDirFor(env) {
|
|
106
|
+
const xdgCacheHome = env["XDG_CACHE_HOME"];
|
|
107
|
+
if (xdgCacheHome !== undefined && xdgCacheHome.length > 0) {
|
|
108
|
+
return join(xdgCacheHome, "clearance");
|
|
109
|
+
}
|
|
110
|
+
const home = env["HOME"];
|
|
111
|
+
/* v8 ignore else @preserve -- tests use HOME/XDG_CACHE_HOME to avoid writing to the real home dir */
|
|
112
|
+
if (home !== undefined && home.length > 0) {
|
|
113
|
+
return join(home, ".cache", "clearance");
|
|
114
|
+
}
|
|
115
|
+
/* v8 ignore next @preserve -- tests pass HOME/XDG_CACHE_HOME to avoid writing to the real home dir */
|
|
116
|
+
return join(homedir(), ".cache", "clearance");
|
|
117
|
+
}
|
|
118
|
+
function childEnv(env) {
|
|
119
|
+
return {
|
|
120
|
+
...env,
|
|
121
|
+
CLEARANCE_LISTEN_HOST: CLEARANCE_HOST,
|
|
122
|
+
CLEARANCE_PORT: String(CLEARANCE_PORT),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function defaultProxyEnv() {
|
|
126
|
+
return Object.fromEntries(FORWARDED_ENV_VARS
|
|
127
|
+
// oxlint-disable-next-line node/no-process-env -- centralized env accessor for the launcher CLI
|
|
128
|
+
.map((name) => [name, process.env[name]])
|
|
129
|
+
.filter(([, value]) => value !== undefined));
|
|
130
|
+
}
|
|
131
|
+
async function waitForListening(input) {
|
|
132
|
+
const attempts = Math.max(1, Math.ceil(input.timeoutMs / input.pollIntervalMs));
|
|
133
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
134
|
+
// eslint-disable-next-line no-await-in-loop -- readiness polling is intentionally sequential
|
|
135
|
+
if (await input.isListening({ host: input.host, port: input.port })) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
// eslint-disable-next-line no-await-in-loop -- readiness polling is intentionally sequential
|
|
139
|
+
await input.sleep(input.pollIntervalMs);
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
async function defaultSleep(ms) {
|
|
144
|
+
await new Promise((resolve) => {
|
|
145
|
+
setTimeout(resolve, ms);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=launcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launcher.js","sourceRoot":"","sources":["../../../../packages/clearance/src/launcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,WAAW,CAAC;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,cAAc,GAAG,WAAW,CAAC;AACnC,MAAM,cAAc,GAAG,MAAM,CAAC;AAC9B,MAAM,0BAA0B,GAAG,IAAI,CAAC;AACxC,MAAM,0BAA0B,GAAG,GAAG,CAAC;AAEvC,MAAM,kBAAkB,GAAG;IACzB,uBAAuB;IACvB,6BAA6B;IAC7B,uBAAuB;IACvB,6BAA6B;IAC7B,sBAAsB;IACtB,2BAA2B;IAC3B,uBAAuB;IACvB,MAAM;IACN,MAAM;IACN,gBAAgB;CACR,CAAC;AAqCX,uGAAuG;AACvG,MAAM,YAAY,GAAG,WAAW,CAAC,OAAO,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AAC5D,MAAM,kBAAkB,GAAG,WAAW,CAAC,YAAY,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;AAEtE,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,KAA0B;IACnE,OAAO,MAAM,IAAI,OAAO,CAAU,CAAC,OAAO,EAAE,EAAE;QAC5C,MAAM,MAAM,GAAG,GAAG,CAAC,gBAAgB,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QAC5E,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACvB,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE;YAC1B,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;YACxB,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC,CAAC,CAAC;QACH,+HAA+H;QAC/H,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE;YAC1B,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAA0B;IACvD,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC7C,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE;YAC7C,QAAQ,EAAE,IAAI;YACd,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,KAAK,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC;SACpC,CAAC,CAAC;QACH,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,qGAAqG;QACrG,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;QAC7D,CAAC;QAED,OAAO,KAAK,CAAC,GAAG,CAAC;IACnB,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,OAAO,CAAC,CAAC;IACrB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,KAAK,GAAyB,EAAE;IAEhC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,eAAe,EAAE,CAAC;IAC3C,oGAAoG;IACpG,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,IAAI,oBAAoB,CAAC;IAC9D,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC;IAEpC,IAAI,MAAM,WAAW,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,EAAE,CAAC;QACtE,MAAM,CAAC,yCAAyC,cAAc,IAAI,cAAc,EAAE,CAAC,CAAC;QACpF,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC7D,CAAC;IAED,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;IAE1B,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC;IACpD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;IAChD,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzC,6FAA6F;IAC7F,MAAM,aAAa,GAAG,KAAK,CAAC,aAAa,IAAI,cAAc,CAAC;IAC5D,MAAM,GAAG,GAAG,aAAa,CAAC;QACxB,IAAI,EAAE,CAAC,kBAAkB,CAAC;QAC1B,OAAO,EAAE,OAAO,CAAC,QAAQ;QACzB,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC;QAClB,OAAO;KACR,CAAC,CAAC;IACH,aAAa,CAAC,OAAO,EAAE,GAAG,GAAG,IAAI,CAAC,CAAC;IAEnC,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC;QACrC,IAAI,EAAE,cAAc;QACpB,WAAW;QACX,cAAc,EAAE,KAAK,CAAC,cAAc,IAAI,0BAA0B;QAClE,IAAI,EAAE,cAAc;QACpB,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,YAAY;QAClC,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,0BAA0B;KACzD,CAAC,CAAC;IACH,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,wCAAwC,cAAc,IAAI,cAAc,WAAW,OAAO,EAAE,CAC7F,CAAC;IACJ,CAAC;IAED,MAAM,CACJ,+BAA+B,cAAc,IAAI,cAAc,SAAS,GAAG,YAAY,OAAO,EAAE,CACjG,CAAC;IACF,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AAC5E,CAAC;AAED,SAAS,IAAI;IACX,8CAA8C;AAChD,CAAC;AAED,SAAS,WAAW,CAAC,GAAsB;IACzC,MAAM,YAAY,GAAG,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC3C,IAAI,YAAY,KAAK,SAAS,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1D,OAAO,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,qGAAqG;IACrG,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;IAC3C,CAAC;IAED,sGAAsG;IACtG,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;AAChD,CAAC;AAED,SAAS,QAAQ,CAAC,GAAsB;IACtC,OAAO;QACL,GAAG,GAAG;QACN,qBAAqB,EAAE,cAAc;QACrC,cAAc,EAAE,MAAM,CAAC,cAAc,CAAC;KACvC,CAAC;AACJ,CAAC;AAED,SAAS,eAAe;IACtB,OAAO,MAAM,CAAC,WAAW,CACvB,kBAAkB;QAChB,gGAAgG;SAC/F,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAU,CAAC;SACjD,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAC9C,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,gBAAgB,CAAC,KAO/B;IACC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;IAChF,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,QAAQ,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QACvD,6FAA6F;QAC7F,IAAI,MAAM,KAAK,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YACpE,OAAO,IAAI,CAAC;QACd,CAAC;QACD,6FAA6F;QAC7F,MAAM,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAC1C,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,EAAU;IACpC,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QAClC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC"}
|