approval-md 0.3.0 → 0.4.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 +83 -914
- package/SPEC.md +98 -13
- package/cli.js +66 -7
- package/dist/src/adapters/smtp.d.ts +18 -0
- package/dist/src/adapters/smtp.js +24 -3
- package/dist/src/adapters/smtp.js.map +1 -1
- package/dist/src/channels/contract.d.ts +19 -0
- package/dist/src/channels/contract.js +105 -1
- package/dist/src/channels/contract.js.map +1 -1
- package/dist/src/channels/relay-server.d.ts +90 -0
- package/dist/src/channels/relay-server.js +295 -0
- package/dist/src/channels/relay-server.js.map +1 -0
- package/dist/src/channels/relay.d.ts +273 -0
- package/dist/src/channels/relay.js +564 -0
- package/dist/src/channels/relay.js.map +1 -0
- package/dist/src/channels/tagging.js +6 -1
- package/dist/src/channels/tagging.js.map +1 -1
- package/dist/src/channels/telegram-webhook.d.ts +273 -0
- package/dist/src/channels/telegram-webhook.js +536 -0
- package/dist/src/channels/telegram-webhook.js.map +1 -0
- package/dist/src/channels/telegram.d.ts +271 -1
- package/dist/src/channels/telegram.js +403 -45
- package/dist/src/channels/telegram.js.map +1 -1
- package/dist/src/cli/amend.js +350 -4
- package/dist/src/cli/amend.js.map +1 -1
- package/dist/src/cli/attest.js +43 -6
- package/dist/src/cli/attest.js.map +1 -1
- package/dist/src/cli/channel-relay.d.ts +108 -0
- package/dist/src/cli/channel-relay.js +334 -0
- package/dist/src/cli/channel-relay.js.map +1 -0
- package/dist/src/cli/channel-telegram-webhook.d.ts +273 -0
- package/dist/src/cli/channel-telegram-webhook.js +709 -0
- package/dist/src/cli/channel-telegram-webhook.js.map +1 -0
- package/dist/src/cli/channel-telegram.d.ts +272 -26
- package/dist/src/cli/channel-telegram.js +478 -41
- package/dist/src/cli/channel-telegram.js.map +1 -1
- package/dist/src/cli/channel.d.ts +1 -1
- package/dist/src/cli/channel.js +5 -1
- package/dist/src/cli/channel.js.map +1 -1
- package/dist/src/cli/daemon.js +37 -5
- package/dist/src/cli/daemon.js.map +1 -1
- package/dist/src/cli/doctor.d.ts +63 -0
- package/dist/src/cli/doctor.js +856 -111
- package/dist/src/cli/doctor.js.map +1 -1
- package/dist/src/cli/execute.d.ts +17 -1
- package/dist/src/cli/execute.js +341 -20
- package/dist/src/cli/execute.js.map +1 -1
- package/dist/src/cli/exit-codes.d.ts +12 -0
- package/dist/src/cli/exit-codes.js +13 -0
- package/dist/src/cli/exit-codes.js.map +1 -1
- package/dist/src/cli/gate.d.ts +2 -0
- package/dist/src/cli/gate.js +233 -2
- package/dist/src/cli/gate.js.map +1 -1
- package/dist/src/cli/help.d.ts +9 -1
- package/dist/src/cli/help.js +246 -38
- package/dist/src/cli/help.js.map +1 -1
- package/dist/src/cli/hook.d.ts +371 -3
- package/dist/src/cli/hook.js +1926 -44
- package/dist/src/cli/hook.js.map +1 -1
- package/dist/src/cli/init.js +16 -7
- package/dist/src/cli/init.js.map +1 -1
- package/dist/src/cli/instructions.js +24 -3
- package/dist/src/cli/instructions.js.map +1 -1
- package/dist/src/cli/log-anchor.d.ts +55 -0
- package/dist/src/cli/log-anchor.js +199 -60
- package/dist/src/cli/log-anchor.js.map +1 -1
- package/dist/src/cli/main.d.ts +0 -12
- package/dist/src/cli/main.js +51 -0
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/cli/muse.d.ts +2 -0
- package/dist/src/cli/muse.js +62 -0
- package/dist/src/cli/muse.js.map +1 -0
- package/dist/src/cli/payload.js +79 -1
- package/dist/src/cli/payload.js.map +1 -1
- package/dist/src/cli/scaffold.js +6 -0
- package/dist/src/cli/scaffold.js.map +1 -1
- package/dist/src/cli/serve.d.ts +53 -0
- package/dist/src/cli/serve.js +270 -0
- package/dist/src/cli/serve.js.map +1 -0
- package/dist/src/cli/up.js +21 -2
- package/dist/src/cli/up.js.map +1 -1
- package/dist/src/cli/verb-registry.d.ts +2 -0
- package/dist/src/cli/verb-registry.js +337 -39
- package/dist/src/cli/verb-registry.js.map +1 -1
- package/dist/src/cli/wordmark.d.ts +2 -2
- package/dist/src/cli/wordmark.js +2 -2
- package/dist/src/core/attest-bootstrap.d.ts +95 -0
- package/dist/src/core/attest-bootstrap.js +135 -0
- package/dist/src/core/attest-bootstrap.js.map +1 -0
- package/dist/src/core/attest.d.ts +20 -3
- package/dist/src/core/attest.js +25 -0
- package/dist/src/core/attest.js.map +1 -1
- package/dist/src/core/audit.d.ts +55 -3
- package/dist/src/core/audit.js +52 -5
- package/dist/src/core/audit.js.map +1 -1
- package/dist/src/core/channel-lease.d.ts +322 -0
- package/dist/src/core/channel-lease.js +663 -0
- package/dist/src/core/channel-lease.js.map +1 -0
- package/dist/src/core/command-class.d.ts +51 -0
- package/dist/src/core/command-class.js +760 -8
- package/dist/src/core/command-class.js.map +1 -1
- package/dist/src/core/daemon-host.d.ts +178 -0
- package/dist/src/core/daemon-host.js +228 -0
- package/dist/src/core/daemon-host.js.map +1 -0
- package/dist/src/core/daemon-identity.d.ts +238 -0
- package/dist/src/core/daemon-identity.js +252 -0
- package/dist/src/core/daemon-identity.js.map +1 -0
- package/dist/src/core/gate.d.ts +306 -5
- package/dist/src/core/gate.js +772 -24
- package/dist/src/core/gate.js.map +1 -1
- package/dist/src/core/harness-version.d.ts +128 -4
- package/dist/src/core/harness-version.js +179 -12
- package/dist/src/core/harness-version.js.map +1 -1
- package/dist/src/core/harness-wait.d.ts +82 -3
- package/dist/src/core/harness-wait.js +89 -3
- package/dist/src/core/harness-wait.js.map +1 -1
- package/dist/src/core/log-subscribe.d.ts +21 -0
- package/dist/src/core/log-subscribe.js +16 -6
- package/dist/src/core/log-subscribe.js.map +1 -1
- package/dist/src/core/log-write-layer.d.ts +55 -0
- package/dist/src/core/log-write-layer.js +91 -0
- package/dist/src/core/log-write-layer.js.map +1 -0
- package/dist/src/core/log.d.ts +50 -21
- package/dist/src/core/log.js +197 -19
- package/dist/src/core/log.js.map +1 -1
- package/dist/src/core/payload-census.d.ts +51 -0
- package/dist/src/core/payload-census.js +125 -2
- package/dist/src/core/payload-census.js.map +1 -1
- package/dist/src/core/payload-store.d.ts +31 -0
- package/dist/src/core/payload-store.js +113 -8
- package/dist/src/core/payload-store.js.map +1 -1
- package/dist/src/core/payload.d.ts +18 -3
- package/dist/src/core/payload.js +20 -4
- package/dist/src/core/payload.js.map +1 -1
- package/dist/src/core/policy-diff.d.ts +10 -0
- package/dist/src/core/policy-diff.js +10 -2
- package/dist/src/core/policy-diff.js.map +1 -1
- package/dist/src/core/policy-explain.d.ts +7 -1
- package/dist/src/core/policy-explain.js +16 -1
- package/dist/src/core/policy-explain.js.map +1 -1
- package/dist/src/core/policy-load.d.ts +24 -0
- package/dist/src/core/policy-load.js.map +1 -1
- package/dist/src/core/policy-match.d.ts +38 -0
- package/dist/src/core/policy-match.js +35 -0
- package/dist/src/core/policy-match.js.map +1 -1
- package/dist/src/core/policy-proposal.d.ts +23 -0
- package/dist/src/core/policy-proposal.js +8 -2
- package/dist/src/core/policy-proposal.js.map +1 -1
- package/dist/src/core/read-scope.d.ts +10 -0
- package/dist/src/core/read-scope.js +4 -0
- package/dist/src/core/read-scope.js.map +1 -1
- package/dist/src/core/run-payload.d.ts +160 -0
- package/dist/src/core/run-payload.js +304 -0
- package/dist/src/core/run-payload.js.map +1 -0
- package/dist/src/core/sender-identity.d.ts +35 -10
- package/dist/src/core/sender-identity.js +48 -11
- package/dist/src/core/sender-identity.js.map +1 -1
- package/dist/src/core/state.d.ts +52 -1
- package/dist/src/core/state.js +52 -6
- package/dist/src/core/state.js.map +1 -1
- package/dist/src/core/telegram-config.d.ts +79 -0
- package/dist/src/core/telegram-config.js +108 -0
- package/dist/src/core/telegram-config.js.map +1 -1
- package/dist/src/core/verify.d.ts +23 -1
- package/dist/src/core/verify.js +20 -2
- package/dist/src/core/verify.js.map +1 -1
- package/dist/src/daemon/audit.d.ts +80 -5
- package/dist/src/daemon/audit.js +126 -6
- package/dist/src/daemon/audit.js.map +1 -1
- package/dist/src/daemon/daemon.d.ts +174 -2
- package/dist/src/daemon/daemon.js +298 -7
- package/dist/src/daemon/daemon.js.map +1 -1
- package/dist/src/daemon/git-evidence.d.ts +2 -2
- package/dist/src/daemon/git-evidence.js +1 -1
- package/dist/src/daemon/projection.d.ts +13 -2
- package/dist/src/daemon/projection.js +39 -6
- package/dist/src/daemon/projection.js.map +1 -1
- package/dist/src/mcp/server.d.ts +39 -3
- package/dist/src/mcp/server.js +22 -3
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/muse/server.d.ts +16 -0
- package/dist/src/muse/server.js +238 -0
- package/dist/src/muse/server.js.map +1 -0
- package/dist/src/serve/archive.d.ts +158 -0
- package/dist/src/serve/archive.js +354 -0
- package/dist/src/serve/archive.js.map +1 -0
- package/dist/src/serve/arguments.d.ts +93 -0
- package/dist/src/serve/arguments.js +228 -0
- package/dist/src/serve/arguments.js.map +1 -0
- package/dist/src/serve/credentials.d.ts +91 -0
- package/dist/src/serve/credentials.js +143 -0
- package/dist/src/serve/credentials.js.map +1 -0
- package/dist/src/serve/follow.d.ts +86 -0
- package/dist/src/serve/follow.js +101 -0
- package/dist/src/serve/follow.js.map +1 -0
- package/dist/src/serve/hook-thread.d.ts +134 -0
- package/dist/src/serve/hook-thread.js +333 -0
- package/dist/src/serve/hook-thread.js.map +1 -0
- package/dist/src/serve/hook-worker.d.ts +134 -0
- package/dist/src/serve/hook-worker.js +173 -0
- package/dist/src/serve/hook-worker.js.map +1 -0
- package/dist/src/serve/server.d.ts +412 -0
- package/dist/src/serve/server.js +1251 -0
- package/dist/src/serve/server.js.map +1 -0
- package/docs/README-extended.md +766 -0
- package/docs/cli-reference.md +1633 -43
- package/package.json +4 -2
- package/schema/event.schema.json +59 -2
- package/schema/fixtures/event/invalid/daemon-id-malformed.json +15 -0
- package/schema/fixtures/event/invalid/execution-started-truncated-policy-sha256.json +18 -0
- package/schema/fixtures/event/invalid/harness-cap-at-margin.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-fractional.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-negative.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-string.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-without-harness-execution.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-zero.json +16 -0
- package/schema/fixtures/event/valid/approval-requested-harness-cap.json +18 -0
- package/schema/fixtures/event/valid/daemon-identity-declared.json +17 -0
- package/schema/fixtures/event/valid/daemon-identity.json +15 -0
- package/schema/fixtures/event/valid/execution-started-policy-sha256.json +18 -0
- package/schema/fixtures/event/valid/harness-kind-hermes.json +23 -0
- package/schema/fixtures/policy/invalid/daemons-malformed-id.json +10 -0
- package/schema/fixtures/policy/valid/daemons-allowlist.json +19 -0
- package/schema/fixtures/policy-md/invalid/schema-invalid-agent-may-request.md +21 -0
- package/schema/fixtures/policy-md/valid/agent-may-request.md +24 -0
- package/schema/policy.schema.json +50 -6
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `approval channel relay` — gestures an operator's authenticated relay
|
|
3
|
+
* carries (SPEC.md §10.3, APRV-455).
|
|
4
|
+
*
|
|
5
|
+
* The third arrival of a human gesture, beside a Telegram tap and a terminal.
|
|
6
|
+
* An operator's control plane holds an EdgeOS session for the person, shows
|
|
7
|
+
* them a request or their policy, and posts what they did here: the gesture,
|
|
8
|
+
* the EdgeOS `/humans/me` id of the session that made it, the hash or the key
|
|
9
|
+
* it answers, and a nonce. `channels/relay-server.ts` authenticates the post
|
|
10
|
+
* and `channels/relay.ts` turns it into a log event through the one function a
|
|
11
|
+
* Telegram tap goes through. This file resolves configuration, refuses what it
|
|
12
|
+
* must refuse, binds a port and reports; it decides nothing.
|
|
13
|
+
*
|
|
14
|
+
* ## The refusals that are the point
|
|
15
|
+
*
|
|
16
|
+
* 1. **The secret comes from the launch environment, never from the tree.**
|
|
17
|
+
* `APPROVAL_RELAY_SECRET`, refused when unset, when shorter than a value a
|
|
18
|
+
* host would generate, and when it holds a character a header cannot carry
|
|
19
|
+
* unambiguously (SPEC.md §11.1 invariant 7). It is a conventional name,
|
|
20
|
+
* beside `APPROVAL_TG_WEBHOOK_SECRET`, for that variable's reason: the
|
|
21
|
+
* policy decides WHETHER a channel exists, never the value that
|
|
22
|
+
* authenticates it, and a second declaration would be a second place for
|
|
23
|
+
* one fact to be wrong. Under the `APPROVAL_` prefix it is withheld from
|
|
24
|
+
* every child `approval run` spawns (`core/child-env.ts`), and the hook
|
|
25
|
+
* classifier holds this command line `policy.core`, so an agent session
|
|
26
|
+
* cannot start a relay with a secret of its own.
|
|
27
|
+
* 2. **No human identity.** This verb takes no `--as` and reads no
|
|
28
|
+
* `APPROVAL_HUMAN`. Every gesture is attributed to the account the attested
|
|
29
|
+
* policy maps, or refused `sender-unmapped`.
|
|
30
|
+
* 3. **A post without the secret decides nothing and appends nothing.**
|
|
31
|
+
*
|
|
32
|
+
* ## Why there is no transport lease
|
|
33
|
+
*
|
|
34
|
+
* A Telegram bot admits one receiver, so its transports take a lease. A relay
|
|
35
|
+
* has no such constraint: two relays on one gate would each record through
|
|
36
|
+
* compare-and-append, the gate would answer a duplicate decision
|
|
37
|
+
* `already-decided`, and the nonce ledger is a directory of exclusively linked files that
|
|
38
|
+
* refuses a replay whichever relay it reaches. A second relay on the SAME port
|
|
39
|
+
* fails to bind and says so.
|
|
40
|
+
*
|
|
41
|
+
* ## Where TLS is
|
|
42
|
+
*
|
|
43
|
+
* Not here, exactly as for `approval serve` and the webhook. Loopback by
|
|
44
|
+
* default, behind whatever the operator puts in front of it; a routable bind
|
|
45
|
+
* takes two flags and prints a banner, because the secret travels in a header.
|
|
46
|
+
*/
|
|
47
|
+
import type { Streams } from "./main.js";
|
|
48
|
+
/** The environment variable the shared secret is read from. */
|
|
49
|
+
export declare const RELAY_SECRET_ENV = "APPROVAL_RELAY_SECRET";
|
|
50
|
+
/** The shortest secret this verb starts with: `serve`'s floor, for its reason. */
|
|
51
|
+
export declare const RELAY_MIN_SECRET_LENGTH = 24;
|
|
52
|
+
/**
|
|
53
|
+
* Why the relay could not start. A closed union, distinct per repair (SPEC.md
|
|
54
|
+
* §11.1 invariant 6). Its per-request refusals are `RELAY_REFUSAL_CODES`.
|
|
55
|
+
*/
|
|
56
|
+
export declare const RELAY_START_REFUSAL_CODES: readonly [
|
|
57
|
+
/** `APPROVAL_RELAY_SECRET` is unset or empty. */
|
|
58
|
+
"relay-secret-missing",
|
|
59
|
+
/** It is shorter than {@link RELAY_MIN_SECRET_LENGTH}. */
|
|
60
|
+
"relay-secret-weak",
|
|
61
|
+
/** It holds a character outside A-Z a-z 0-9 _ -, or is longer than 256. */
|
|
62
|
+
"relay-secret-charset",
|
|
63
|
+
/** `--proposer` is not an `agent:` id. */
|
|
64
|
+
"relay-proposer-invalid",
|
|
65
|
+
/** The bind flags do not name a usable loopback (or explicitly widened) address. */
|
|
66
|
+
"relay-bind-invalid"];
|
|
67
|
+
export type RelayStartRefusalCode = (typeof RELAY_START_REFUSAL_CODES)[number];
|
|
68
|
+
export interface RelaySetup {
|
|
69
|
+
logPath: string;
|
|
70
|
+
policy: {
|
|
71
|
+
dir?: string;
|
|
72
|
+
file?: string;
|
|
73
|
+
};
|
|
74
|
+
secret: string;
|
|
75
|
+
host: string;
|
|
76
|
+
port: number;
|
|
77
|
+
proposer: string;
|
|
78
|
+
json: boolean;
|
|
79
|
+
}
|
|
80
|
+
export type RelayPreparation = {
|
|
81
|
+
ok: true;
|
|
82
|
+
setup: RelaySetup;
|
|
83
|
+
} | {
|
|
84
|
+
ok: false;
|
|
85
|
+
code: RelayStartRefusalCode;
|
|
86
|
+
message: string;
|
|
87
|
+
};
|
|
88
|
+
export interface RelayRequest {
|
|
89
|
+
logPath: string;
|
|
90
|
+
policy: {
|
|
91
|
+
dir?: string;
|
|
92
|
+
file?: string;
|
|
93
|
+
};
|
|
94
|
+
listen: string | null;
|
|
95
|
+
port: string | null;
|
|
96
|
+
allowNonLoopback: boolean;
|
|
97
|
+
proposer: string | null;
|
|
98
|
+
json: boolean;
|
|
99
|
+
env?: NodeJS.ProcessEnv;
|
|
100
|
+
}
|
|
101
|
+
/** Everything that can fail without binding a port. Prints nothing. */
|
|
102
|
+
export declare function prepareRelay(request: RelayRequest): RelayPreparation;
|
|
103
|
+
/** The banner a routable bind prints, every time, on stderr. */
|
|
104
|
+
export declare function relayNonLoopbackBanner(host: string, port: number): string;
|
|
105
|
+
/** Run the relay until a signal stops it. */
|
|
106
|
+
export declare function runRelay(setup: RelaySetup, streams: Streams): Promise<number>;
|
|
107
|
+
/** `approval channel relay [flags]`. */
|
|
108
|
+
export declare function commandChannelRelay(argv: string[], streams: Streams, cwd: string): Promise<number>;
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `approval channel relay` — gestures an operator's authenticated relay
|
|
3
|
+
* carries (SPEC.md §10.3, APRV-455).
|
|
4
|
+
*
|
|
5
|
+
* The third arrival of a human gesture, beside a Telegram tap and a terminal.
|
|
6
|
+
* An operator's control plane holds an EdgeOS session for the person, shows
|
|
7
|
+
* them a request or their policy, and posts what they did here: the gesture,
|
|
8
|
+
* the EdgeOS `/humans/me` id of the session that made it, the hash or the key
|
|
9
|
+
* it answers, and a nonce. `channels/relay-server.ts` authenticates the post
|
|
10
|
+
* and `channels/relay.ts` turns it into a log event through the one function a
|
|
11
|
+
* Telegram tap goes through. This file resolves configuration, refuses what it
|
|
12
|
+
* must refuse, binds a port and reports; it decides nothing.
|
|
13
|
+
*
|
|
14
|
+
* ## The refusals that are the point
|
|
15
|
+
*
|
|
16
|
+
* 1. **The secret comes from the launch environment, never from the tree.**
|
|
17
|
+
* `APPROVAL_RELAY_SECRET`, refused when unset, when shorter than a value a
|
|
18
|
+
* host would generate, and when it holds a character a header cannot carry
|
|
19
|
+
* unambiguously (SPEC.md §11.1 invariant 7). It is a conventional name,
|
|
20
|
+
* beside `APPROVAL_TG_WEBHOOK_SECRET`, for that variable's reason: the
|
|
21
|
+
* policy decides WHETHER a channel exists, never the value that
|
|
22
|
+
* authenticates it, and a second declaration would be a second place for
|
|
23
|
+
* one fact to be wrong. Under the `APPROVAL_` prefix it is withheld from
|
|
24
|
+
* every child `approval run` spawns (`core/child-env.ts`), and the hook
|
|
25
|
+
* classifier holds this command line `policy.core`, so an agent session
|
|
26
|
+
* cannot start a relay with a secret of its own.
|
|
27
|
+
* 2. **No human identity.** This verb takes no `--as` and reads no
|
|
28
|
+
* `APPROVAL_HUMAN`. Every gesture is attributed to the account the attested
|
|
29
|
+
* policy maps, or refused `sender-unmapped`.
|
|
30
|
+
* 3. **A post without the secret decides nothing and appends nothing.**
|
|
31
|
+
*
|
|
32
|
+
* ## Why there is no transport lease
|
|
33
|
+
*
|
|
34
|
+
* A Telegram bot admits one receiver, so its transports take a lease. A relay
|
|
35
|
+
* has no such constraint: two relays on one gate would each record through
|
|
36
|
+
* compare-and-append, the gate would answer a duplicate decision
|
|
37
|
+
* `already-decided`, and the nonce ledger is a directory of exclusively linked files that
|
|
38
|
+
* refuses a replay whichever relay it reaches. A second relay on the SAME port
|
|
39
|
+
* fails to bind and says so.
|
|
40
|
+
*
|
|
41
|
+
* ## Where TLS is
|
|
42
|
+
*
|
|
43
|
+
* Not here, exactly as for `approval serve` and the webhook. Loopback by
|
|
44
|
+
* default, behind whatever the operator puts in front of it; a routable bind
|
|
45
|
+
* takes two flags and prints a banner, because the secret travels in a header.
|
|
46
|
+
*/
|
|
47
|
+
import { isAbsolute, join, resolve as resolvePathSegments } from "node:path";
|
|
48
|
+
import { RELAY_DEFAULT_HOST, RELAY_DEFAULT_PORT, RELAY_DEFAULT_PROPOSER, RELAY_PATH, RELAY_SECRET_HEADER, fileNonceLedger, } from "../channels/relay.js";
|
|
49
|
+
import { serveRelay } from "../channels/relay-server.js";
|
|
50
|
+
import { channelLeaseDirFor } from "../core/channel-lease.js";
|
|
51
|
+
import { readGatePolicy } from "../core/gate.js";
|
|
52
|
+
import { mapsSendersFor } from "../core/sender-identity.js";
|
|
53
|
+
import { readVerifiedRecords } from "../core/state.js";
|
|
54
|
+
import { boolFlag, parseFlags, stringFlag } from "./args.js";
|
|
55
|
+
import { EXIT_INTEGRITY, EXIT_IO, EXIT_OK, EXIT_TORN_TAIL, EXIT_USAGE } from "./exit-codes.js";
|
|
56
|
+
import { CHANNEL_RELAY_HELP } from "./help.js";
|
|
57
|
+
import { isLoopbackHost, parseListen } from "./mcp.js";
|
|
58
|
+
import { DEFAULT_LOG_PATH, resolvePath } from "./paths.js";
|
|
59
|
+
import { usageErrorText } from "./usage.js";
|
|
60
|
+
/** The environment variable the shared secret is read from. */
|
|
61
|
+
export const RELAY_SECRET_ENV = "APPROVAL_RELAY_SECRET";
|
|
62
|
+
/** The shortest secret this verb starts with: `serve`'s floor, for its reason. */
|
|
63
|
+
export const RELAY_MIN_SECRET_LENGTH = 24;
|
|
64
|
+
/** What a header carries unambiguously, and what `openssl rand -hex` produces. */
|
|
65
|
+
const SECRET_CHARSET = /^[A-Za-z0-9_-]{1,256}$/u;
|
|
66
|
+
/**
|
|
67
|
+
* A proposer is an `agent:` id. The relay is a machine carrying a proposal the
|
|
68
|
+
* control plane rendered, and recording it under a `human:` id would say a
|
|
69
|
+
* person proposed something nobody typed (APRV-455 refuter L1).
|
|
70
|
+
*/
|
|
71
|
+
const PROPOSER_PATTERN = /^agent:[^\s]+$/u;
|
|
72
|
+
const RELAY_FLAGS = {
|
|
73
|
+
"--listen": "string",
|
|
74
|
+
"--port": "string",
|
|
75
|
+
"--allow-non-loopback": "boolean",
|
|
76
|
+
"--log": "string",
|
|
77
|
+
"--policy": "string",
|
|
78
|
+
"--dir": "string",
|
|
79
|
+
"--proposer": "string",
|
|
80
|
+
"--json": "boolean",
|
|
81
|
+
"--help": "boolean",
|
|
82
|
+
"-h": "boolean",
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Why the relay could not start. A closed union, distinct per repair (SPEC.md
|
|
86
|
+
* §11.1 invariant 6). Its per-request refusals are `RELAY_REFUSAL_CODES`.
|
|
87
|
+
*/
|
|
88
|
+
export const RELAY_START_REFUSAL_CODES = [
|
|
89
|
+
/** `APPROVAL_RELAY_SECRET` is unset or empty. */
|
|
90
|
+
"relay-secret-missing",
|
|
91
|
+
/** It is shorter than {@link RELAY_MIN_SECRET_LENGTH}. */
|
|
92
|
+
"relay-secret-weak",
|
|
93
|
+
/** It holds a character outside A-Z a-z 0-9 _ -, or is longer than 256. */
|
|
94
|
+
"relay-secret-charset",
|
|
95
|
+
/** `--proposer` is not an `agent:` id. */
|
|
96
|
+
"relay-proposer-invalid",
|
|
97
|
+
/** The bind flags do not name a usable loopback (or explicitly widened) address. */
|
|
98
|
+
"relay-bind-invalid",
|
|
99
|
+
];
|
|
100
|
+
/** Everything that can fail without binding a port. Prints nothing. */
|
|
101
|
+
export function prepareRelay(request) {
|
|
102
|
+
const environment = request.env ?? process.env;
|
|
103
|
+
const raw = environment[RELAY_SECRET_ENV];
|
|
104
|
+
const secret = raw === undefined ? "" : raw.trim();
|
|
105
|
+
if (secret.length === 0) {
|
|
106
|
+
return {
|
|
107
|
+
ok: false,
|
|
108
|
+
code: "relay-secret-missing",
|
|
109
|
+
message: `no ${RELAY_SECRET_ENV} in the environment. The relay's only authentication is this shared secret, carried in ${RELAY_SECRET_HEADER}; without one, any post that looked like a gesture would be one. Generate it (\`openssl rand -hex 32\`), give the same value to the control plane, and set it in the environment this process is launched from, never in a file in the tree`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (!SECRET_CHARSET.test(secret)) {
|
|
113
|
+
return {
|
|
114
|
+
ok: false,
|
|
115
|
+
code: "relay-secret-charset",
|
|
116
|
+
message: `${RELAY_SECRET_ENV} holds a value outside 1 to 256 characters of A-Z, a-z, 0-9, underscore and hyphen, which a header cannot be relied on to carry byte for byte. Its value is not shown here and is in no message this process writes`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (secret.length < RELAY_MIN_SECRET_LENGTH) {
|
|
120
|
+
return {
|
|
121
|
+
ok: false,
|
|
122
|
+
code: "relay-secret-weak",
|
|
123
|
+
message: `${RELAY_SECRET_ENV} is shorter than ${String(RELAY_MIN_SECRET_LENGTH)} characters. Generate it (\`openssl rand -hex 32\`) rather than choosing it: it is the whole of what separates the operator's relay from anyone who can reach this port`,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const proposer = request.proposer ?? RELAY_DEFAULT_PROPOSER;
|
|
127
|
+
if (!PROPOSER_PATTERN.test(proposer)) {
|
|
128
|
+
return {
|
|
129
|
+
ok: false,
|
|
130
|
+
code: "relay-proposer-invalid",
|
|
131
|
+
message: `--proposer ${JSON.stringify(proposer)} is not an agent:<id> actor. A proposal is recorded under it, and the relay is a machine carrying what the control plane rendered: a human: id would say a person proposed something nobody typed`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (request.listen !== null && request.port !== null) {
|
|
135
|
+
return {
|
|
136
|
+
ok: false,
|
|
137
|
+
code: "relay-bind-invalid",
|
|
138
|
+
message: "--listen and --port name the same thing; pass one. --listen <[host:]port> is the only way to bind a non-loopback interface",
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
let host = RELAY_DEFAULT_HOST;
|
|
142
|
+
let port = RELAY_DEFAULT_PORT;
|
|
143
|
+
if (request.listen !== null) {
|
|
144
|
+
const bind = parseListen(request.listen);
|
|
145
|
+
if (!bind.ok)
|
|
146
|
+
return { ok: false, code: "relay-bind-invalid", message: bind.message };
|
|
147
|
+
host = bind.host;
|
|
148
|
+
port = bind.port;
|
|
149
|
+
}
|
|
150
|
+
else if (request.port !== null) {
|
|
151
|
+
if (!/^\d+$/u.test(request.port.trim())) {
|
|
152
|
+
return {
|
|
153
|
+
ok: false,
|
|
154
|
+
code: "relay-bind-invalid",
|
|
155
|
+
message: `--port expects a whole number, got ${JSON.stringify(request.port)}`,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
port = Number(request.port.trim());
|
|
159
|
+
}
|
|
160
|
+
if (port < 1 || port > 65535) {
|
|
161
|
+
return {
|
|
162
|
+
ok: false,
|
|
163
|
+
code: "relay-bind-invalid",
|
|
164
|
+
message: `the bind port ${String(port)} is outside 1..65535. Port 0 is refused too: the control plane posts to a fixed address, and an ephemeral port is one nobody would find`,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
if (!isLoopbackHost(host) && !request.allowNonLoopback) {
|
|
168
|
+
return {
|
|
169
|
+
ok: false,
|
|
170
|
+
code: "relay-bind-invalid",
|
|
171
|
+
message: `--listen ${JSON.stringify(host)} is not the loopback interface, and this process speaks plain HTTP: the relay secret arrives in a header and a cleartext hop hands it to whoever is on it. Add --allow-non-loopback if you meant it and TLS is terminated in front of this process`,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
ok: true,
|
|
176
|
+
setup: {
|
|
177
|
+
logPath: request.logPath,
|
|
178
|
+
policy: request.policy,
|
|
179
|
+
secret,
|
|
180
|
+
host,
|
|
181
|
+
port,
|
|
182
|
+
proposer,
|
|
183
|
+
json: request.json,
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function usageError(streams, json, code, message) {
|
|
188
|
+
if (json)
|
|
189
|
+
streams.err(`${JSON.stringify({ error: { code, message } })}\n`);
|
|
190
|
+
else
|
|
191
|
+
streams.err(usageErrorText(message, CHANNEL_RELAY_HELP));
|
|
192
|
+
return EXIT_USAGE;
|
|
193
|
+
}
|
|
194
|
+
function failure(streams, json, code, message, exit) {
|
|
195
|
+
if (json)
|
|
196
|
+
streams.err(`${JSON.stringify({ error: { code, message } })}\n`);
|
|
197
|
+
else
|
|
198
|
+
streams.err(`approval: ${message}\n`);
|
|
199
|
+
return exit;
|
|
200
|
+
}
|
|
201
|
+
/** The banner a routable bind prints, every time, on stderr. */
|
|
202
|
+
export function relayNonLoopbackBanner(host, port) {
|
|
203
|
+
return [
|
|
204
|
+
"",
|
|
205
|
+
" !! the approval relay IS BOUND TO A NON-LOOPBACK INTERFACE !!",
|
|
206
|
+
` ${host}:${String(port)} is reachable by anyone who can route to it, and this`,
|
|
207
|
+
" process speaks PLAIN HTTP. The relay secret travels in a header, and a",
|
|
208
|
+
" cleartext hop hands it to whoever is on it. Terminate TLS in front of this",
|
|
209
|
+
" process, or give it a loopback bind.",
|
|
210
|
+
"",
|
|
211
|
+
"",
|
|
212
|
+
].join("\n");
|
|
213
|
+
}
|
|
214
|
+
/** Run the relay until a signal stops it. */
|
|
215
|
+
export async function runRelay(setup, streams) {
|
|
216
|
+
const json = setup.json;
|
|
217
|
+
// The log must be readable before anything binds: an operator who pointed
|
|
218
|
+
// at the wrong store learns it here, not from the first refused gesture.
|
|
219
|
+
const read = readVerifiedRecords(setup.logPath);
|
|
220
|
+
if (!read.ok) {
|
|
221
|
+
const exit = read.code === "log-torn-tail" ? EXIT_TORN_TAIL : read.code === "log-unreadable" ? EXIT_IO : EXIT_INTEGRITY;
|
|
222
|
+
return failure(streams, json, read.code, read.message, exit);
|
|
223
|
+
}
|
|
224
|
+
// Not a refusal: a policy that maps no EdgeOS account is a legitimate state
|
|
225
|
+
// (the operator has not mapped the resident yet), and the relay refuses
|
|
226
|
+
// every gesture `sender-unmapped` until it does. Said once, at start.
|
|
227
|
+
const load = readGatePolicy({ policy: setup.policy });
|
|
228
|
+
if (!load.ok || !mapsSendersFor(load.policy.approvers, "edgeos")) {
|
|
229
|
+
streams.err(`approval: relay: the policy ${load.ok ? "maps no edgeos sender" : `does not load (${load.code})`}, so every attest, decline, grant and reject will be refused sender-unmapped until an attested policy maps the person's EdgeOS id under approvers.<id>.senders.edgeos\n`);
|
|
230
|
+
}
|
|
231
|
+
const report = (line, text) => {
|
|
232
|
+
if (json)
|
|
233
|
+
streams.out(`${JSON.stringify(line)}\n`);
|
|
234
|
+
else
|
|
235
|
+
streams.out(`${text}\n`);
|
|
236
|
+
};
|
|
237
|
+
let handle;
|
|
238
|
+
try {
|
|
239
|
+
handle = await serveRelay({
|
|
240
|
+
logPath: setup.logPath,
|
|
241
|
+
secret: setup.secret,
|
|
242
|
+
ledger: fileNonceLedger(join(channelLeaseDirFor(setup.logPath), "relay-nonces")),
|
|
243
|
+
host: setup.host,
|
|
244
|
+
port: setup.port,
|
|
245
|
+
gateOptions: { policy: setup.policy },
|
|
246
|
+
proposer: setup.proposer,
|
|
247
|
+
log: (message) => streams.err(`${message}\n`),
|
|
248
|
+
// What reached the gate, by code: never the body, the sender id or the
|
|
249
|
+
// secret. The record in the log is where the attribution lives.
|
|
250
|
+
afterGesture: (response) => {
|
|
251
|
+
const body = response.body;
|
|
252
|
+
const gesture = String(body["gesture"] ?? "?");
|
|
253
|
+
if (body["ok"] === true) {
|
|
254
|
+
report({
|
|
255
|
+
event: "relay_gesture",
|
|
256
|
+
gesture,
|
|
257
|
+
ok: true,
|
|
258
|
+
...(body["seq"] === undefined ? {} : { seq: body["seq"] }),
|
|
259
|
+
...(body["event"] === undefined ? {} : { record: body["event"] }),
|
|
260
|
+
...(body["actor"] === undefined ? {} : { actor: body["actor"] }),
|
|
261
|
+
}, `relay ${gesture}: recorded seq ${String(body["seq"])}${body["actor"] === undefined ? "" : ` by ${String(body["actor"])}`}`);
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
const refusal = body["refusal"];
|
|
265
|
+
const code = String(refusal?.code ?? "?");
|
|
266
|
+
report({ event: "relay_gesture", gesture, ok: false, code }, `relay ${gesture}: refused (${code})`);
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
catch (cause) {
|
|
272
|
+
return failure(streams, json, "io", `the relay could not bind ${setup.host}:${String(setup.port)}: ${cause instanceof Error ? cause.message : String(cause)}`, EXIT_IO);
|
|
273
|
+
}
|
|
274
|
+
if (!isLoopbackHost(handle.host))
|
|
275
|
+
streams.err(relayNonLoopbackBanner(handle.host, handle.port));
|
|
276
|
+
if (json) {
|
|
277
|
+
streams.out(`${JSON.stringify({ event: "relay_started", host: handle.host, port: handle.port, path: RELAY_PATH, proposer: setup.proposer })}\n`);
|
|
278
|
+
}
|
|
279
|
+
streams.err(`approval: relay bound http://${handle.host}:${String(handle.port)}${RELAY_PATH}. Every post must carry ${RELAY_SECRET_HEADER}; gestures are attributed to the EdgeOS account the attested policy maps, never to this process. TLS is your proxy's. Press Ctrl-C to stop.\n`);
|
|
280
|
+
return await new Promise((settle) => {
|
|
281
|
+
let stopping = false;
|
|
282
|
+
const stop = () => {
|
|
283
|
+
if (stopping)
|
|
284
|
+
return;
|
|
285
|
+
stopping = true;
|
|
286
|
+
void (async () => {
|
|
287
|
+
await handle.close();
|
|
288
|
+
if (json)
|
|
289
|
+
streams.out(`${JSON.stringify({ event: "stopped", relay: handle.stats() })}\n`);
|
|
290
|
+
process.off("SIGINT", stop);
|
|
291
|
+
process.off("SIGTERM", stop);
|
|
292
|
+
settle(EXIT_OK);
|
|
293
|
+
})();
|
|
294
|
+
};
|
|
295
|
+
process.on("SIGINT", stop);
|
|
296
|
+
process.on("SIGTERM", stop);
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
function absolute(value, cwd) {
|
|
300
|
+
return isAbsolute(value) ? value : resolvePathSegments(cwd, value);
|
|
301
|
+
}
|
|
302
|
+
/** `approval channel relay [flags]`. */
|
|
303
|
+
export async function commandChannelRelay(argv, streams, cwd) {
|
|
304
|
+
const json = argv.includes("--json");
|
|
305
|
+
const parsed = parseFlags(argv, RELAY_FLAGS);
|
|
306
|
+
if (!parsed.ok)
|
|
307
|
+
return usageError(streams, json, "usage", parsed.message);
|
|
308
|
+
if (boolFlag(parsed.flags, "--help") || boolFlag(parsed.flags, "-h")) {
|
|
309
|
+
streams.out(`${CHANNEL_RELAY_HELP}\n`);
|
|
310
|
+
return EXIT_OK;
|
|
311
|
+
}
|
|
312
|
+
const extra = parsed.positionals[0];
|
|
313
|
+
if (extra !== undefined)
|
|
314
|
+
return usageError(streams, json, "usage", `unexpected argument ${JSON.stringify(extra)}`);
|
|
315
|
+
const flags = parsed.flags;
|
|
316
|
+
const policyFlag = stringFlag(flags, "--policy");
|
|
317
|
+
const dirFlag = stringFlag(flags, "--dir");
|
|
318
|
+
const policy = policyFlag !== null
|
|
319
|
+
? { file: absolute(policyFlag, cwd) }
|
|
320
|
+
: { dir: dirFlag === null ? cwd : absolute(dirFlag, cwd) };
|
|
321
|
+
const prepared = prepareRelay({
|
|
322
|
+
logPath: resolvePath(stringFlag(flags, "--log"), DEFAULT_LOG_PATH, cwd),
|
|
323
|
+
policy,
|
|
324
|
+
listen: stringFlag(flags, "--listen"),
|
|
325
|
+
port: stringFlag(flags, "--port"),
|
|
326
|
+
allowNonLoopback: boolFlag(flags, "--allow-non-loopback"),
|
|
327
|
+
proposer: stringFlag(flags, "--proposer"),
|
|
328
|
+
json,
|
|
329
|
+
});
|
|
330
|
+
if (!prepared.ok)
|
|
331
|
+
return usageError(streams, json, prepared.code, prepared.message);
|
|
332
|
+
return await runRelay(prepared.setup, streams);
|
|
333
|
+
}
|
|
334
|
+
//# sourceMappingURL=channel-relay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-relay.js","sourceRoot":"","sources":["../../../src/cli/channel-relay.ts"],"names":[],"mappings":"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"}
|