aquaman-proxy 0.14.0 → 0.15.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 +28 -5
- package/dist/broker-scope.d.ts +89 -0
- package/dist/broker-scope.d.ts.map +1 -0
- package/dist/broker-scope.js +186 -0
- package/dist/broker-scope.js.map +1 -0
- package/dist/cli/index.js +374 -35
- package/dist/cli/index.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/types.d.ts +14 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/utils/config.d.ts +9 -0
- package/dist/core/utils/config.d.ts.map +1 -1
- package/dist/core/utils/config.js +29 -1
- package/dist/core/utils/config.js.map +1 -1
- package/dist/core/utils/index.d.ts +1 -1
- package/dist/core/utils/index.d.ts.map +1 -1
- package/dist/core/utils/index.js +1 -1
- package/dist/core/utils/index.js.map +1 -1
- package/dist/daemon.d.ts +36 -0
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +136 -15
- package/dist/daemon.js.map +1 -1
- package/dist/hermes/config-writer.d.ts +19 -0
- package/dist/hermes/config-writer.d.ts.map +1 -1
- package/dist/hermes/config-writer.js +51 -1
- package/dist/hermes/config-writer.js.map +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -5
- package/dist/index.js.map +1 -1
- package/dist/migration/openclaw-migrator.d.ts.map +1 -1
- package/dist/migration/openclaw-migrator.js +7 -6
- package/dist/migration/openclaw-migrator.js.map +1 -1
- package/dist/openclaw/channel-routing.d.ts +81 -0
- package/dist/openclaw/channel-routing.d.ts.map +1 -0
- package/dist/openclaw/channel-routing.js +152 -0
- package/dist/openclaw/channel-routing.js.map +1 -0
- package/dist/openclaw/integration.d.ts +18 -0
- package/dist/openclaw/integration.d.ts.map +1 -1
- package/dist/openclaw/integration.js +32 -0
- package/dist/openclaw/integration.js.map +1 -1
- package/dist/openclaw/secretref.d.ts +35 -6
- package/dist/openclaw/secretref.d.ts.map +1 -1
- package/dist/openclaw/secretref.js +73 -8
- package/dist/openclaw/secretref.js.map +1 -1
- package/dist/request-policy.d.ts +7 -0
- package/dist/request-policy.d.ts.map +1 -1
- package/dist/request-policy.js +31 -2
- package/dist/request-policy.js.map +1 -1
- package/dist/service-registry.d.ts +8 -0
- package/dist/service-registry.d.ts.map +1 -1
- package/dist/service-registry.js +11 -4
- package/dist/service-registry.js.map +1 -1
- package/package.json +20 -6
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Agent / OpenClaw / Coding Agent Aquaman Proxy
|
|
|
17
17
|
│ │ ~/.aquaman/ │ basic / oauth │
|
|
18
18
|
│ No credentials. │ proxy.sock │ │
|
|
19
19
|
│ No open ports. │ (chmod 0o600) │ │
|
|
20
|
-
│
|
|
20
|
+
│ No keys to read. │ │ │
|
|
21
21
|
└──────────────────────┘ └──┬──────────┬────────┘
|
|
22
22
|
│ │
|
|
23
23
|
│ ▼
|
|
@@ -57,29 +57,52 @@ aquaman daemon & # start the proxy on ~/.aquaman/proxy.sock
|
|
|
57
57
|
|
|
58
58
|
Four layers of protection:
|
|
59
59
|
|
|
60
|
-
- **Process isolation**: credentials in a separate address space,
|
|
60
|
+
- **Process isolation**: credentials live in a separate address space, reached over a Unix socket (`chmod 0o600`) or a token-gated loopback listener (see below)
|
|
61
61
|
- **Service allowlisting**: `proxiedServices` controls which APIs the agent can reach
|
|
62
62
|
- **Request policies**: method + path rules per service, checked *before* credential injection ([details in the root README](https://github.com/tech4242/aquaman#request-policies))
|
|
63
|
+
- **Broker scope** (v0.15.0+): the one endpoint that returns a credential *value* serves only refs you declared, and never runs on an OpenClaw-hosted proxy
|
|
63
64
|
- **Audit trail**: SHA-256 hash-chained logs of every credential use
|
|
64
65
|
|
|
66
|
+
### Transports and access control
|
|
67
|
+
|
|
68
|
+
| Path | Transport | Access control |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| Coding agents, any client that can dial a socket | Unix socket `~/.aquaman/proxy.sock` | File permissions (`0600`): only processes running as you |
|
|
71
|
+
| Hermes (v0.13.0+), OpenClaw model and Telegram traffic (v0.15.0+) | Loopback TCP `127.0.0.1:<port>` | Per-install token, constant-time check, loopback bind |
|
|
72
|
+
|
|
73
|
+
Hermes and OpenClaw each build their own HTTP client and can't dial a socket, so they use the listener. Everything else uses the socket.
|
|
74
|
+
|
|
75
|
+
The token is a capability to reach the local proxy, not a credential. Generated per install, stored in `~/.aquaman/config.yaml` (`0600`), sent by the host as its provider api key. The proxy checks it, strips it, injects your real key.
|
|
76
|
+
|
|
77
|
+
The Telegram Bot API has no auth header, so there the token travels in the `/bot<TOKEN>` path segment instead. The proxy accepts it there, strips it before the policy check and the request log, and puts the real bot token back at the same position, which is what keeps `/file/bot<TOKEN>/<path>` downloads working.
|
|
78
|
+
|
|
79
|
+
Trade-off: any local process can reach a loopback port, including other users, where the socket's `0600` shuts them out. The token is the gate there, so the listener stays off until `aquaman hermes setup` or `aquaman openclaw setup` turns it on.
|
|
80
|
+
|
|
65
81
|
**Bring your own vault.** Aquaman has no house vault. It injects credentials from the secret store you already run: Keychain, 1Password, HashiCorp Vault, Bitwarden, KeePassXC, systemd-creds, or encrypted-file. No new store to adopt, no migration.
|
|
66
82
|
|
|
67
83
|
## Broker endpoint (v0.12.0+)
|
|
68
84
|
|
|
69
|
-
`POST /broker/resolve`
|
|
85
|
+
`POST /broker/resolve` is the one endpoint that returns a credential *value*. `aquaman-coder` uses it to materialize credentials per tool call, as does the Hermes secret source. Body:
|
|
70
86
|
|
|
71
87
|
```json
|
|
72
88
|
{"service":"anthropic","key":"api_key","ttl_seconds":60}
|
|
73
89
|
```
|
|
74
90
|
|
|
75
|
-
Response: `{"value":"...","expires_at":"2026-05-20T12:34:56Z"}`. Validates service/key names against safe regexes; 4 KB body cap
|
|
91
|
+
Response: `{"value":"...","expires_at":"2026-05-20T12:34:56Z"}`. Validates service/key names against safe regexes; 4 KB body cap.
|
|
92
|
+
|
|
93
|
+
Scoped since v0.15.0, because it hands out values rather than injecting them:
|
|
94
|
+
|
|
95
|
+
- Served by `aquaman daemon` only. A proxy started by the OpenClaw plugin answers `404 broker_disabled`: that path never materializes a credential.
|
|
96
|
+
- Only refs you declared resolve: the `env` refs in `~/.aquaman/projects.yaml`, plus anything added with `aquaman broker allow`. Everything else is `404 broker_ref_not_declared`, decided before the vault is consulted, so a refusal reveals nothing about what you store.
|
|
97
|
+
- Over the loopback listener the LLM provider keys are never materialized, declared or not (`404 broker_ref_isolated`). They stay on the proxy path.
|
|
98
|
+
- Declaring a ref is an explicit opt-in to handing that value to processes running as you. Refusals and resolves are both audited.
|
|
76
99
|
|
|
77
100
|
## Documentation
|
|
78
101
|
|
|
79
102
|
- **[Root README](https://github.com/tech4242/aquaman#readme)**: value prop, three-path Quick Start, security model
|
|
80
103
|
- **[`docs/PACKAGES.md`](../../docs/PACKAGES.md)**: package boundary policy
|
|
81
104
|
- **[`docs/compliance/`](../../docs/compliance/)**: MITRE ATLAS + NIST SP 800-53 mappings
|
|
82
|
-
- **[`
|
|
105
|
+
- **[`AGENTS.md`](../../AGENTS.md)**: architecture notes
|
|
83
106
|
|
|
84
107
|
## License
|
|
85
108
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential-broker scoping (v0.15.0).
|
|
3
|
+
*
|
|
4
|
+
* `POST /broker/resolve` is the one endpoint where the proxy hands a
|
|
5
|
+
* credential VALUE to its caller instead of injecting it on egress. Through
|
|
6
|
+
* v0.14.x it served any service/key in the vault, in every mode, to any
|
|
7
|
+
* process that could reach the socket — including an OpenClaw agent's exec
|
|
8
|
+
* tool (`curl --unix-socket ~/.aquaman/proxy.sock ...`), which is the ClawScan
|
|
9
|
+
* finding against aquaman-plugin 0.14.x. The rules now:
|
|
10
|
+
*
|
|
11
|
+
* 1. OpenClaw-hosted proxies (`openclaw plugin-mode`, `openclaw start`) are
|
|
12
|
+
* built without a scope, so the broker is off: the OpenClaw path never
|
|
13
|
+
* materializes a credential.
|
|
14
|
+
* 2. `aquaman daemon` materializes only refs the user DECLARED for
|
|
15
|
+
* materialization: the env refs in projects.yaml (coding agents) plus
|
|
16
|
+
* `broker.allowedRefs` in config.yaml (e.g. Hermes secret-source
|
|
17
|
+
* bindings). Everything else is refused.
|
|
18
|
+
* 3. Over the loopback listener, the LLM-provider services Hermes reaches
|
|
19
|
+
* through the proxy are never materialized, declared or not. They stay
|
|
20
|
+
* on the proxy path. This is the same two-tier rule the Hermes client
|
|
21
|
+
* enforces, but checked server-side.
|
|
22
|
+
*
|
|
23
|
+
* The scope is checked before any vault lookup, so a refusal says nothing
|
|
24
|
+
* about what the vault holds. Declared refs are re-read when projects.yaml or
|
|
25
|
+
* config.yaml changes (mtime-cached), so `aquaman coder project add` and
|
|
26
|
+
* `aquaman broker allow` take effect without restarting the daemon.
|
|
27
|
+
*
|
|
28
|
+
* Threat model, honestly stated: every process running as the same user can
|
|
29
|
+
* reach the socket and can edit ~/.aquaman/*. Declaring a ref is an explicit
|
|
30
|
+
* opt-in to handing that credential to same-user processes. The broker scope
|
|
31
|
+
* keeps undeclared credentials, and the ones the proxy isolates for agent
|
|
32
|
+
* hosts, out of reach. It is not a boundary between processes of one user.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseAquamanRef(ref: string): {
|
|
35
|
+
service: string;
|
|
36
|
+
key: string;
|
|
37
|
+
} | null;
|
|
38
|
+
export declare function formatAquamanRef(service: string, key: string): string;
|
|
39
|
+
/** Default location of the coding-agent project map (honors AQUAMAN_CONFIG_DIR). */
|
|
40
|
+
export declare function defaultProjectsPath(): string;
|
|
41
|
+
export interface DeclaredRefsResult {
|
|
42
|
+
/** Set of `aquaman://service/key` strings. */
|
|
43
|
+
refs: Set<string>;
|
|
44
|
+
/** Parse problem, if the file exists but can't be read as a project map. Fails closed. */
|
|
45
|
+
error?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Collect every `aquaman://` ref declared in a projects.yaml env map. A
|
|
49
|
+
* missing file declares nothing. A malformed file declares nothing and
|
|
50
|
+
* reports why, so the broker fails closed instead of guessing.
|
|
51
|
+
*/
|
|
52
|
+
export declare function loadProjectRefs(projectsPath?: string): DeclaredRefsResult;
|
|
53
|
+
/**
|
|
54
|
+
* Read `broker.allowedRefs` straight from a config.yaml file (not via
|
|
55
|
+
* loadConfig, which would also apply env overrides and defaults). Missing
|
|
56
|
+
* file or key declares nothing. A malformed file declares nothing and says why.
|
|
57
|
+
*/
|
|
58
|
+
export declare function loadConfigAllowedRefs(configPath: string): DeclaredRefsResult;
|
|
59
|
+
export type BrokerCaller = 'uds' | 'loopback';
|
|
60
|
+
export type BrokerDecision = {
|
|
61
|
+
allowed: true;
|
|
62
|
+
} | {
|
|
63
|
+
allowed: false;
|
|
64
|
+
code: 'broker_ref_not_declared' | 'broker_ref_isolated';
|
|
65
|
+
reason: string;
|
|
66
|
+
fix: string;
|
|
67
|
+
};
|
|
68
|
+
export interface BrokerScopeOptions {
|
|
69
|
+
/** projects.yaml to read declared refs from. Default: `<configDir>/projects.yaml`. */
|
|
70
|
+
projectsPath?: string;
|
|
71
|
+
/** config.yaml whose `broker.allowedRefs` are declared refs (re-read on change). */
|
|
72
|
+
configPath?: string;
|
|
73
|
+
/** Additional static declared refs. Invalid entries are ignored. */
|
|
74
|
+
allowedRefs?: readonly string[];
|
|
75
|
+
/** Services never materialized over the loopback listener (the Hermes LLM-provider tier). */
|
|
76
|
+
loopbackDeniedServices?: readonly string[];
|
|
77
|
+
}
|
|
78
|
+
export interface BrokerScope {
|
|
79
|
+
check(service: string, key: string, caller: BrokerCaller): BrokerDecision;
|
|
80
|
+
/** Every currently declared ref, with where it came from. For `aquaman broker list` and startup logs. */
|
|
81
|
+
declared(): {
|
|
82
|
+
ref: string;
|
|
83
|
+
source: 'projects.yaml' | 'config.yaml';
|
|
84
|
+
}[];
|
|
85
|
+
/** Parse problems with the declaring files, if any (the broker fails closed for their refs). */
|
|
86
|
+
declarationError(): string | undefined;
|
|
87
|
+
}
|
|
88
|
+
export declare function createBrokerScope(opts?: BrokerScopeOptions): BrokerScope;
|
|
89
|
+
//# sourceMappingURL=broker-scope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broker-scope.d.ts","sourceRoot":"","sources":["../src/broker-scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAUH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAGpF;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAErE;AAED,oFAAoF;AACpF,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,MAAM,WAAW,kBAAkB;IACjC,8CAA8C;IAC9C,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAClB,0FAA0F;IAC1F,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,YAAY,GAAE,MAA8B,GAAG,kBAAkB,CA4BhG;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,kBAAkB,CAoB5E;AAqBD,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,UAAU,CAAC;AAE9C,MAAM,MAAM,cAAc,GACtB;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GACjB;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,yBAAyB,GAAG,qBAAqB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7G,MAAM,WAAW,kBAAkB;IACjC,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,6FAA6F;IAC7F,sBAAsB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5C;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,cAAc,CAAC;IAC1E,yGAAyG;IACzG,QAAQ,IAAI;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,eAAe,GAAG,aAAa,CAAA;KAAE,EAAE,CAAC;IACvE,gGAAgG;IAChG,gBAAgB,IAAI,MAAM,GAAG,SAAS,CAAC;CACxC;AAED,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,kBAAuB,GAAG,WAAW,CAmD5E"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential-broker scoping (v0.15.0).
|
|
3
|
+
*
|
|
4
|
+
* `POST /broker/resolve` is the one endpoint where the proxy hands a
|
|
5
|
+
* credential VALUE to its caller instead of injecting it on egress. Through
|
|
6
|
+
* v0.14.x it served any service/key in the vault, in every mode, to any
|
|
7
|
+
* process that could reach the socket — including an OpenClaw agent's exec
|
|
8
|
+
* tool (`curl --unix-socket ~/.aquaman/proxy.sock ...`), which is the ClawScan
|
|
9
|
+
* finding against aquaman-plugin 0.14.x. The rules now:
|
|
10
|
+
*
|
|
11
|
+
* 1. OpenClaw-hosted proxies (`openclaw plugin-mode`, `openclaw start`) are
|
|
12
|
+
* built without a scope, so the broker is off: the OpenClaw path never
|
|
13
|
+
* materializes a credential.
|
|
14
|
+
* 2. `aquaman daemon` materializes only refs the user DECLARED for
|
|
15
|
+
* materialization: the env refs in projects.yaml (coding agents) plus
|
|
16
|
+
* `broker.allowedRefs` in config.yaml (e.g. Hermes secret-source
|
|
17
|
+
* bindings). Everything else is refused.
|
|
18
|
+
* 3. Over the loopback listener, the LLM-provider services Hermes reaches
|
|
19
|
+
* through the proxy are never materialized, declared or not. They stay
|
|
20
|
+
* on the proxy path. This is the same two-tier rule the Hermes client
|
|
21
|
+
* enforces, but checked server-side.
|
|
22
|
+
*
|
|
23
|
+
* The scope is checked before any vault lookup, so a refusal says nothing
|
|
24
|
+
* about what the vault holds. Declared refs are re-read when projects.yaml or
|
|
25
|
+
* config.yaml changes (mtime-cached), so `aquaman coder project add` and
|
|
26
|
+
* `aquaman broker allow` take effect without restarting the daemon.
|
|
27
|
+
*
|
|
28
|
+
* Threat model, honestly stated: every process running as the same user can
|
|
29
|
+
* reach the socket and can edit ~/.aquaman/*. Declaring a ref is an explicit
|
|
30
|
+
* opt-in to handing that credential to same-user processes. The broker scope
|
|
31
|
+
* keeps undeclared credentials, and the ones the proxy isolates for agent
|
|
32
|
+
* hosts, out of reach. It is not a boundary between processes of one user.
|
|
33
|
+
*/
|
|
34
|
+
import * as fs from 'node:fs';
|
|
35
|
+
import * as path from 'node:path';
|
|
36
|
+
import { parse as parseYaml } from 'yaml';
|
|
37
|
+
import { getConfigDir } from './core/utils/config.js';
|
|
38
|
+
/** `aquaman://service/key` — same grammar as the daemon's service/key validation. */
|
|
39
|
+
const AQUAMAN_REF = /^aquaman:\/\/([a-z0-9][a-z0-9._-]*)\/([a-zA-Z0-9][a-zA-Z0-9._-]*)$/;
|
|
40
|
+
export function parseAquamanRef(ref) {
|
|
41
|
+
const m = AQUAMAN_REF.exec(ref);
|
|
42
|
+
return m ? { service: m[1], key: m[2] } : null;
|
|
43
|
+
}
|
|
44
|
+
export function formatAquamanRef(service, key) {
|
|
45
|
+
return `aquaman://${service}/${key}`;
|
|
46
|
+
}
|
|
47
|
+
/** Default location of the coding-agent project map (honors AQUAMAN_CONFIG_DIR). */
|
|
48
|
+
export function defaultProjectsPath() {
|
|
49
|
+
return path.join(getConfigDir(), 'projects.yaml');
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Collect every `aquaman://` ref declared in a projects.yaml env map. A
|
|
53
|
+
* missing file declares nothing. A malformed file declares nothing and
|
|
54
|
+
* reports why, so the broker fails closed instead of guessing.
|
|
55
|
+
*/
|
|
56
|
+
export function loadProjectRefs(projectsPath = defaultProjectsPath()) {
|
|
57
|
+
let raw;
|
|
58
|
+
try {
|
|
59
|
+
raw = fs.readFileSync(projectsPath, 'utf-8');
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
if (err?.code === 'ENOENT')
|
|
63
|
+
return { refs: new Set() };
|
|
64
|
+
return { refs: new Set(), error: `cannot read ${projectsPath}: ${err?.message ?? err}` };
|
|
65
|
+
}
|
|
66
|
+
let parsed;
|
|
67
|
+
try {
|
|
68
|
+
parsed = parseYaml(raw);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
return { refs: new Set(), error: `cannot parse ${projectsPath}: ${err?.message ?? err}` };
|
|
72
|
+
}
|
|
73
|
+
const refs = new Set();
|
|
74
|
+
const projects = parsed && typeof parsed === 'object' ? parsed.projects : undefined;
|
|
75
|
+
if (!projects || typeof projects !== 'object')
|
|
76
|
+
return { refs };
|
|
77
|
+
for (const cfg of Object.values(projects)) {
|
|
78
|
+
const env = cfg && typeof cfg === 'object' ? cfg.env : undefined;
|
|
79
|
+
if (!env || typeof env !== 'object')
|
|
80
|
+
continue;
|
|
81
|
+
for (const ref of Object.values(env)) {
|
|
82
|
+
if (typeof ref === 'string' && parseAquamanRef(ref))
|
|
83
|
+
refs.add(ref);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { refs };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Read `broker.allowedRefs` straight from a config.yaml file (not via
|
|
90
|
+
* loadConfig, which would also apply env overrides and defaults). Missing
|
|
91
|
+
* file or key declares nothing. A malformed file declares nothing and says why.
|
|
92
|
+
*/
|
|
93
|
+
export function loadConfigAllowedRefs(configPath) {
|
|
94
|
+
let raw;
|
|
95
|
+
try {
|
|
96
|
+
raw = fs.readFileSync(configPath, 'utf-8');
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
if (err?.code === 'ENOENT')
|
|
100
|
+
return { refs: new Set() };
|
|
101
|
+
return { refs: new Set(), error: `cannot read ${configPath}: ${err?.message ?? err}` };
|
|
102
|
+
}
|
|
103
|
+
let parsed;
|
|
104
|
+
try {
|
|
105
|
+
parsed = parseYaml(raw);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
return { refs: new Set(), error: `cannot parse ${configPath}: ${err?.message ?? err}` };
|
|
109
|
+
}
|
|
110
|
+
const list = parsed?.broker?.allowedRefs;
|
|
111
|
+
const refs = new Set();
|
|
112
|
+
if (Array.isArray(list)) {
|
|
113
|
+
for (const ref of list)
|
|
114
|
+
if (typeof ref === 'string' && parseAquamanRef(ref))
|
|
115
|
+
refs.add(ref);
|
|
116
|
+
}
|
|
117
|
+
return { refs };
|
|
118
|
+
}
|
|
119
|
+
/** Re-load a declared-refs file only when its mtime changes. */
|
|
120
|
+
function mtimeCached(file, load) {
|
|
121
|
+
let cachedMtimeMs; // undefined = never loaded; null = file absent
|
|
122
|
+
let cached = { refs: new Set() };
|
|
123
|
+
return () => {
|
|
124
|
+
let mtimeMs;
|
|
125
|
+
try {
|
|
126
|
+
mtimeMs = fs.statSync(file).mtimeMs;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
mtimeMs = null;
|
|
130
|
+
}
|
|
131
|
+
if (mtimeMs !== cachedMtimeMs) {
|
|
132
|
+
cached = mtimeMs === null ? { refs: new Set() } : load(file);
|
|
133
|
+
cachedMtimeMs = mtimeMs;
|
|
134
|
+
}
|
|
135
|
+
return cached;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
export function createBrokerScope(opts = {}) {
|
|
139
|
+
const projectRefs = mtimeCached(opts.projectsPath ?? defaultProjectsPath(), loadProjectRefs);
|
|
140
|
+
const configFileRefs = opts.configPath ? mtimeCached(opts.configPath, loadConfigAllowedRefs) : () => ({ refs: new Set() });
|
|
141
|
+
const staticRefs = new Set((opts.allowedRefs ?? []).filter(r => typeof r === 'string' && parseAquamanRef(r)));
|
|
142
|
+
const loopbackDenied = new Set(opts.loopbackDeniedServices ?? []);
|
|
143
|
+
const configRefs = () => {
|
|
144
|
+
const fromFile = configFileRefs();
|
|
145
|
+
return { refs: new Set([...staticRefs, ...fromFile.refs]), error: fromFile.error };
|
|
146
|
+
};
|
|
147
|
+
const errors = () => {
|
|
148
|
+
const e = [projectRefs().error, configRefs().error].filter(Boolean);
|
|
149
|
+
return e.length ? e.join('; ') : undefined;
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
check(service, key, caller) {
|
|
153
|
+
const ref = formatAquamanRef(service, key);
|
|
154
|
+
if (caller === 'loopback' && loopbackDenied.has(service)) {
|
|
155
|
+
return {
|
|
156
|
+
allowed: false,
|
|
157
|
+
code: 'broker_ref_isolated',
|
|
158
|
+
reason: `${ref} is not materialized over the loopback listener: ${service} keys stay process-isolated on the proxy path`,
|
|
159
|
+
fix: `Keep ${service} on the proxy path (base URL + placeholder key in ~/.hermes/.env) and remove the binding from your Hermes secrets config`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
if (projectRefs().refs.has(ref) || configRefs().refs.has(ref))
|
|
163
|
+
return { allowed: true };
|
|
164
|
+
const err = errors();
|
|
165
|
+
const parseNote = err ? ` (${err} — fix it to restore its refs)` : '';
|
|
166
|
+
return {
|
|
167
|
+
allowed: false,
|
|
168
|
+
code: 'broker_ref_not_declared',
|
|
169
|
+
reason: `${ref} is not declared for materialization${parseNote}`,
|
|
170
|
+
fix: `Declare it for a coding-agent project (aquaman coder project add <name> --path <dir> --env VAR=${ref}) or allow it explicitly (aquaman broker allow ${ref})`,
|
|
171
|
+
};
|
|
172
|
+
},
|
|
173
|
+
declared() {
|
|
174
|
+
const out = [];
|
|
175
|
+
for (const ref of [...projectRefs().refs].sort())
|
|
176
|
+
out.push({ ref, source: 'projects.yaml' });
|
|
177
|
+
for (const ref of [...configRefs().refs].sort())
|
|
178
|
+
out.push({ ref, source: 'config.yaml' });
|
|
179
|
+
return out;
|
|
180
|
+
},
|
|
181
|
+
declarationError() {
|
|
182
|
+
return errors();
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=broker-scope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broker-scope.js","sourceRoot":"","sources":["../src/broker-scope.ts"],"names":[],"mappings":"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"}
|