@melra/policy-core 0.3.0-alpha.10 → 0.3.0-alpha.11
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 +19 -3
- package/dist/credentials.d.ts +29 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +112 -0
- package/dist/credentials.js.map +1 -0
- package/dist/credentials.test.d.ts +2 -0
- package/dist/credentials.test.d.ts.map +1 -0
- package/dist/credentials.test.js +103 -0
- package/dist/credentials.test.js.map +1 -0
- package/dist/index.d.ts +78 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +155 -27
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +173 -0
- package/dist/index.test.js.map +1 -1
- package/dist/network-policy.d.ts +28 -0
- package/dist/network-policy.d.ts.map +1 -0
- package/dist/network-policy.js +121 -0
- package/dist/network-policy.js.map +1 -0
- package/dist/network-policy.test.d.ts +2 -0
- package/dist/network-policy.test.d.ts.map +1 -0
- package/dist/network-policy.test.js +39 -0
- package/dist/network-policy.test.js.map +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -30,12 +30,22 @@ never reaches a runtime.
|
|
|
30
30
|
- Terminal commands must be allowlisted by basename. Shell interpreters and
|
|
31
31
|
`sudo`/`su` are denied unconditionally. `git` counts as a read only for a small
|
|
32
32
|
read-only subcommand set; `npm`, `npx`, and `pnpm` are high-risk mutations.
|
|
33
|
-
- Browser destinations default to `allowedDomains: ["*"]` with localhost
|
|
34
|
-
because the allowlist is a narrowing control, not the safety boundary
|
|
35
|
-
|
|
33
|
+
- Browser and HTTP destinations default to `allowedDomains: ["*"]` with localhost
|
|
34
|
+
allowed, because the allowlist is a narrowing control, not the safety boundary
|
|
35
|
+
— `assertSafeDestination`, exported from this package, is what blocks private
|
|
36
|
+
and metadata destinations, and it holds even with `allowedDomains: ["*"]`.
|
|
36
37
|
- `classifyOperation` is the single place effect and risk are decided. An action
|
|
37
38
|
that is not classified there is mis-classified, not unclassified.
|
|
38
39
|
|
|
40
|
+
## The destination boundary
|
|
41
|
+
|
|
42
|
+
`assertSafeDestination` lives here rather than in a runtime so every adapter that
|
|
43
|
+
opens a socket shares one implementation. It rejects non-`http(s)` protocols and
|
|
44
|
+
URL credentials, resolves DNS first and checks **every** answer, and blocks
|
|
45
|
+
private, loopback, link-local, and cloud-metadata ranges in IPv4, IPv6, and
|
|
46
|
+
IPv4-mapped IPv6 form. It returns the address it checked so the caller can pin
|
|
47
|
+
the socket to it instead of letting the name resolve a second time.
|
|
48
|
+
|
|
39
49
|
## Capability grants
|
|
40
50
|
|
|
41
51
|
`policy.capabilities` is authority; everything above is rules. It is empty by
|
|
@@ -72,6 +82,12 @@ rules it was not written for.
|
|
|
72
82
|
caller's own `forbiddenEffects` and `constraints` are honoured, because those are
|
|
73
83
|
the caller bounding its own task rather than a guardrail MELRA imposes.
|
|
74
84
|
|
|
85
|
+
`mode` is `developer` or `enforced` and is not about a request at all: it records
|
|
86
|
+
how this kernel is deployed, and therefore what it refuses about its own setup
|
|
87
|
+
rather than about a caller. `deploymentMode` parses it from the flag, the
|
|
88
|
+
environment, and the policy file through one function, so a typo fails loudly
|
|
89
|
+
instead of leaving a machine permissive with a config that claims otherwise.
|
|
90
|
+
|
|
75
91
|
Requires Node.js 22 or newer. Full documentation:
|
|
76
92
|
[github.com/XAGI-Lab/melra](https://github.com/XAGI-Lab/melra)
|
|
77
93
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CredentialDefinition } from "@melra/protocol";
|
|
2
|
+
export interface BrokeredHeaders {
|
|
3
|
+
/** Headers to merge into the outgoing request, lowercased. */
|
|
4
|
+
headers: Record<string, string>;
|
|
5
|
+
/** Names of the credentials that were injected — never their values. */
|
|
6
|
+
used: string[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Holds the secrets so the agent does not have to.
|
|
10
|
+
*
|
|
11
|
+
* An adapter asks for the headers one operation may carry and gets back either
|
|
12
|
+
* a header it can send or nothing at all. It never receives the secret as a
|
|
13
|
+
* value it could log, return, or persist, and the broker never writes one down:
|
|
14
|
+
* every call re-reads the source, so rotating a key takes effect on the next
|
|
15
|
+
* request rather than on the next restart.
|
|
16
|
+
*/
|
|
17
|
+
export declare class CredentialBroker {
|
|
18
|
+
private readonly definitions;
|
|
19
|
+
private readonly environment;
|
|
20
|
+
constructor(definitions?: Record<string, CredentialDefinition>, environment?: NodeJS.ProcessEnv);
|
|
21
|
+
/** True when nothing is configured, so a caller can skip the await. */
|
|
22
|
+
get empty(): boolean;
|
|
23
|
+
headersFor(request: {
|
|
24
|
+
host: string;
|
|
25
|
+
capability: string;
|
|
26
|
+
target: string;
|
|
27
|
+
}): Promise<BrokeredHeaders>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAE5D,MAAM,WAAW,eAAe;IAC9B,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,wEAAwE;IACxE,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AA4DD;;;;;;;;GAQG;AACH,qBAAa,gBAAgB;IAEzB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,WAAW;gBADX,WAAW,GAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAM,EACtD,WAAW,GAAE,MAAM,CAAC,UAAwB;IAG/D,uEAAuE;IACvE,IAAI,KAAK,IAAI,OAAO,CAEnB;IAEK,UAAU,CAAC,OAAO,EAAE;QACxB,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,MAAM,CAAC;KAChB,GAAG,OAAO,CAAC,eAAe,CAAC;CAkC7B"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Copyright 2026 XAGI Labs Private Limited
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { constants } from "node:fs";
|
|
4
|
+
import { lstat, open } from "node:fs/promises";
|
|
5
|
+
function patternMatches(pattern, value) {
|
|
6
|
+
if (pattern === "*")
|
|
7
|
+
return true;
|
|
8
|
+
const escaped = pattern
|
|
9
|
+
.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
10
|
+
.replaceAll("\\*", ".*");
|
|
11
|
+
return new RegExp(`^${escaped}$`).test(value);
|
|
12
|
+
}
|
|
13
|
+
function errorCode(error) {
|
|
14
|
+
return error !== null && typeof error === "object" && "code" in error
|
|
15
|
+
? String(error.code)
|
|
16
|
+
: undefined;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Read a secret out of a file the same way the payload key is read: refuse a
|
|
20
|
+
* symlink, refuse anything but a regular file, and refuse a mode any other user
|
|
21
|
+
* can read. A secret in a world-readable file is one the agent could have read
|
|
22
|
+
* for itself, which would make the whole broker decorative.
|
|
23
|
+
*/
|
|
24
|
+
async function readSecretFile(path) {
|
|
25
|
+
const before = await lstat(path);
|
|
26
|
+
if (before.isSymbolicLink()) {
|
|
27
|
+
throw new Error("credential_file_must_not_be_symlink");
|
|
28
|
+
}
|
|
29
|
+
let handle;
|
|
30
|
+
try {
|
|
31
|
+
handle = await open(path, constants.O_RDONLY |
|
|
32
|
+
(process.platform === "win32" ? 0 : constants.O_NOFOLLOW));
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (errorCode(error) === "ELOOP") {
|
|
36
|
+
throw new Error("credential_file_must_not_be_symlink");
|
|
37
|
+
}
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const metadata = await handle.stat();
|
|
42
|
+
if (!metadata.isFile() ||
|
|
43
|
+
metadata.dev !== before.dev ||
|
|
44
|
+
metadata.ino !== before.ino) {
|
|
45
|
+
throw new Error("credential_file_not_regular_file");
|
|
46
|
+
}
|
|
47
|
+
if (process.platform !== "win32" && (metadata.mode & 0o077) !== 0) {
|
|
48
|
+
throw new Error("credential_file_permissions_too_open");
|
|
49
|
+
}
|
|
50
|
+
const secret = (await handle.readFile("utf8")).trim();
|
|
51
|
+
if (secret === "")
|
|
52
|
+
throw new Error("credential_file_empty");
|
|
53
|
+
return secret;
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
await handle.close();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Holds the secrets so the agent does not have to.
|
|
61
|
+
*
|
|
62
|
+
* An adapter asks for the headers one operation may carry and gets back either
|
|
63
|
+
* a header it can send or nothing at all. It never receives the secret as a
|
|
64
|
+
* value it could log, return, or persist, and the broker never writes one down:
|
|
65
|
+
* every call re-reads the source, so rotating a key takes effect on the next
|
|
66
|
+
* request rather than on the next restart.
|
|
67
|
+
*/
|
|
68
|
+
export class CredentialBroker {
|
|
69
|
+
definitions;
|
|
70
|
+
environment;
|
|
71
|
+
constructor(definitions = {}, environment = process.env) {
|
|
72
|
+
this.definitions = definitions;
|
|
73
|
+
this.environment = environment;
|
|
74
|
+
}
|
|
75
|
+
/** True when nothing is configured, so a caller can skip the await. */
|
|
76
|
+
get empty() {
|
|
77
|
+
return Object.keys(this.definitions).length === 0;
|
|
78
|
+
}
|
|
79
|
+
async headersFor(request) {
|
|
80
|
+
const headers = {};
|
|
81
|
+
const used = [];
|
|
82
|
+
const operation = `${request.capability}:${request.target}`;
|
|
83
|
+
const host = request.host.toLowerCase();
|
|
84
|
+
for (const [name, definition] of Object.entries(this.definitions)) {
|
|
85
|
+
if (!definition.hosts.some((pattern) => patternMatches(pattern.toLowerCase(), host))) {
|
|
86
|
+
// Not this credential's host. The request still goes out, just
|
|
87
|
+
// unauthenticated — which is what stops a URL the caller chose from
|
|
88
|
+
// walking somebody's bearer token to an origin it was never issued for.
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (!patternMatches(definition.capability, operation)) {
|
|
92
|
+
// Refused before the source is touched. The credential could perform
|
|
93
|
+
// this call; the delegation did not say it may, and that gap is the
|
|
94
|
+
// entire difference between holding a credential and holding authority.
|
|
95
|
+
throw new Error(`credential_capability_not_covered:${name}`);
|
|
96
|
+
}
|
|
97
|
+
const secret = "env" in definition.source
|
|
98
|
+
? this.environment[definition.source.env]
|
|
99
|
+
: await readSecretFile(definition.source.file);
|
|
100
|
+
if (secret === undefined || secret === "") {
|
|
101
|
+
throw new Error(`credential_source_missing:${name}`);
|
|
102
|
+
}
|
|
103
|
+
headers[definition.inject.header.toLowerCase()] =
|
|
104
|
+
definition.inject.scheme === undefined
|
|
105
|
+
? secret
|
|
106
|
+
: `${definition.inject.scheme} ${secret}`;
|
|
107
|
+
used.push(name);
|
|
108
|
+
}
|
|
109
|
+
return { headers, used };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.test.d.ts","sourceRoot":"","sources":["../src/credentials.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Copyright 2026 XAGI Labs Private Limited
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { afterAll, describe, expect, it } from "vitest";
|
|
7
|
+
import { CredentialsSchema } from "@melra/protocol";
|
|
8
|
+
import { CredentialBroker } from "./credentials.js";
|
|
9
|
+
const roots = [];
|
|
10
|
+
afterAll(async () => {
|
|
11
|
+
for (const root of roots)
|
|
12
|
+
await rm(root, { recursive: true, force: true });
|
|
13
|
+
});
|
|
14
|
+
async function secretFile(mode) {
|
|
15
|
+
const root = await mkdtemp(join(tmpdir(), "melra-credentials-"));
|
|
16
|
+
roots.push(root);
|
|
17
|
+
const path = join(root, "stripe.key");
|
|
18
|
+
await writeFile(path, "sk_live_from_a_file\n");
|
|
19
|
+
await chmod(path, mode);
|
|
20
|
+
return path;
|
|
21
|
+
}
|
|
22
|
+
const stripe = {
|
|
23
|
+
stripe: {
|
|
24
|
+
source: { env: "MELRA_TEST_STRIPE_KEY" },
|
|
25
|
+
inject: { header: "Authorization", scheme: "Bearer" },
|
|
26
|
+
hosts: ["api.stripe.com"],
|
|
27
|
+
capability: "http.post:https://api.stripe.com/v1/refunds*",
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
const broker = (definitions = stripe, environment = { MELRA_TEST_STRIPE_KEY: "sk_live_secret" }) => new CredentialBroker(CredentialsSchema.parse(definitions), environment);
|
|
31
|
+
describe("credential broker", () => {
|
|
32
|
+
it("injects the secret for an operation the delegation covers", async () => {
|
|
33
|
+
const { headers, used } = await broker().headersFor({
|
|
34
|
+
host: "api.stripe.com",
|
|
35
|
+
capability: "http.post",
|
|
36
|
+
target: "https://api.stripe.com/v1/refunds",
|
|
37
|
+
});
|
|
38
|
+
expect(headers.authorization).toBe("Bearer sk_live_secret");
|
|
39
|
+
// The names are what a caller may see. The values are what it may not.
|
|
40
|
+
expect(used).toEqual(["stripe"]);
|
|
41
|
+
});
|
|
42
|
+
it("sends a request to another host unauthenticated rather than refusing it", async () => {
|
|
43
|
+
const { headers, used } = await broker().headersFor({
|
|
44
|
+
host: "api.example.com",
|
|
45
|
+
capability: "http.post",
|
|
46
|
+
target: "https://api.example.com/v1/refunds",
|
|
47
|
+
});
|
|
48
|
+
expect(headers).toEqual({});
|
|
49
|
+
expect(used).toEqual([]);
|
|
50
|
+
});
|
|
51
|
+
it("refuses an operation outside the delegation without reading the secret", async () => {
|
|
52
|
+
// The source points at a file that does not exist. A `credential_source_*`
|
|
53
|
+
// or `ENOENT` here would mean the broker read first and checked second —
|
|
54
|
+
// the refusal has to land before the plaintext is anywhere near the process.
|
|
55
|
+
const refusing = broker({
|
|
56
|
+
stripe: {
|
|
57
|
+
...stripe.stripe,
|
|
58
|
+
source: { file: join(tmpdir(), "melra-nonexistent-credential") },
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
await expect(refusing.headersFor({
|
|
62
|
+
host: "api.stripe.com",
|
|
63
|
+
capability: "http.delete",
|
|
64
|
+
target: "https://api.stripe.com/v1/customers/cus_1",
|
|
65
|
+
})).rejects.toThrow("credential_capability_not_covered:stripe");
|
|
66
|
+
});
|
|
67
|
+
it("reads a mode-0600 file and refuses a permissive one", async () => {
|
|
68
|
+
const path = await secretFile(0o600);
|
|
69
|
+
const request = {
|
|
70
|
+
host: "api.stripe.com",
|
|
71
|
+
capability: "http.post",
|
|
72
|
+
target: "https://api.stripe.com/v1/refunds",
|
|
73
|
+
};
|
|
74
|
+
const definitions = {
|
|
75
|
+
stripe: { ...stripe.stripe, source: { file: path } },
|
|
76
|
+
};
|
|
77
|
+
const { headers } = await broker(definitions).headersFor(request);
|
|
78
|
+
expect(headers.authorization).toBe("Bearer sk_live_from_a_file");
|
|
79
|
+
await chmod(path, 0o644);
|
|
80
|
+
const permissive = broker(definitions).headersFor(request);
|
|
81
|
+
if (process.platform === "win32") {
|
|
82
|
+
// Windows does not carry the mode bits this checks, so the guard cannot
|
|
83
|
+
// fire there. Asserting it would only test the platform.
|
|
84
|
+
await expect(permissive).resolves.toBeDefined();
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
await expect(permissive).rejects.toThrow("credential_file_permissions_too_open");
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
it("fails loudly when the source names nothing", async () => {
|
|
91
|
+
await expect(broker(stripe, {}).headersFor({
|
|
92
|
+
host: "api.stripe.com",
|
|
93
|
+
capability: "http.post",
|
|
94
|
+
target: "https://api.stripe.com/v1/refunds",
|
|
95
|
+
})).rejects.toThrow("credential_source_missing:stripe");
|
|
96
|
+
});
|
|
97
|
+
it("rejects an unknown field in a definition", () => {
|
|
98
|
+
expect(() => CredentialsSchema.parse({
|
|
99
|
+
stripe: { ...stripe.stripe, allowRedirects: true },
|
|
100
|
+
})).toThrow();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
//# sourceMappingURL=credentials.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.test.js","sourceRoot":"","sources":["../src/credentials.test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ApprovalChallenge, ApprovalResponse, CapabilityGrant, CapabilityTrait, Effect, EvidencePredicate, Identity, Operation, PolicyDecision, Risk, TaskRequest } from "@melra/protocol";
|
|
1
|
+
import type { ApprovalChallenge, ApprovalResponse, CapabilityGrant, CapabilityTrait, CredentialDefinition, DeploymentMode, Effect, EvidencePredicate, HttpOperation, Identity, Operation, PolicyDecision, Risk, TaskRequest } from "@melra/protocol";
|
|
2
2
|
export interface LocalPolicy {
|
|
3
3
|
version: string;
|
|
4
4
|
workspaceRoot: string;
|
|
@@ -53,6 +53,16 @@ export interface LocalPolicy {
|
|
|
53
53
|
* holding authority over one directory rather than over the whole workspace.
|
|
54
54
|
*/
|
|
55
55
|
capabilities: CapabilityGrant[];
|
|
56
|
+
/**
|
|
57
|
+
* Secrets the kernel injects on the caller's behalf, keyed by a name the
|
|
58
|
+
* caller may see in a receipt. Empty by default.
|
|
59
|
+
*
|
|
60
|
+
* The agent never holds one. It names an operation; if a definition's `hosts`
|
|
61
|
+
* cover the destination *and* its `capability` covers the operation, the
|
|
62
|
+
* kernel reads the secret at send time and puts it on the wire. See
|
|
63
|
+
* `CredentialBroker` for why those two lists behave differently on a miss.
|
|
64
|
+
*/
|
|
65
|
+
credentials: Record<string, CredentialDefinition>;
|
|
56
66
|
/**
|
|
57
67
|
* Stops a task from re-running an operation whose target keeps failing.
|
|
58
68
|
*
|
|
@@ -79,10 +89,32 @@ export interface LocalPolicy {
|
|
|
79
89
|
* derives it from another setting.
|
|
80
90
|
*/
|
|
81
91
|
unhinged: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* How this kernel is deployed, and therefore what it refuses about its own
|
|
94
|
+
* setup rather than about a caller's request.
|
|
95
|
+
*
|
|
96
|
+
* `developer` is the default and changes nothing: MELRA governs the effects it
|
|
97
|
+
* is asked for, and the operator accepts that the harness may have another way
|
|
98
|
+
* to the same disk. `enforced` is the operator asserting there is no other way
|
|
99
|
+
* — the harness is sandboxed, holds no privileged secrets, and reaches systems
|
|
100
|
+
* only through here. MELRA cannot verify that assertion, so what it does
|
|
101
|
+
* instead is remove every door it owns that would contradict it: no
|
|
102
|
+
* `--unsafe-local`, no bind outside loopback, no client registering itself.
|
|
103
|
+
*
|
|
104
|
+
* Named on every receipt, because "was this effect governed by the only door
|
|
105
|
+
* or by one of several" is not something an auditor should have to infer.
|
|
106
|
+
*/
|
|
107
|
+
mode: DeploymentMode;
|
|
82
108
|
}
|
|
83
109
|
export interface PolicyEvaluation {
|
|
84
110
|
decision: PolicyDecision;
|
|
85
111
|
challenge?: ApprovalChallenge;
|
|
112
|
+
/**
|
|
113
|
+
* The grant that authorised this, when `policy.capabilities` is non-empty.
|
|
114
|
+
* What the commit meters against — absent means no grants are configured and
|
|
115
|
+
* there is nothing to draw down.
|
|
116
|
+
*/
|
|
117
|
+
grantId?: string;
|
|
86
118
|
}
|
|
87
119
|
export declare function normalizeCommandName(command: string): string;
|
|
88
120
|
/**
|
|
@@ -103,6 +135,7 @@ export declare function normalizeCommandName(command: string): string;
|
|
|
103
135
|
* network at all, not "no network except the browser".
|
|
104
136
|
*/
|
|
105
137
|
export type { CapabilityTrait } from "@melra/protocol";
|
|
138
|
+
export { CredentialBroker, type BrokeredHeaders, } from "./credentials.js";
|
|
106
139
|
/**
|
|
107
140
|
* Classify a terminal command by what it does, not just by its name.
|
|
108
141
|
*
|
|
@@ -122,6 +155,8 @@ export declare function classifyOperation(operation: Operation): {
|
|
|
122
155
|
capability: string;
|
|
123
156
|
target: string;
|
|
124
157
|
traits: CapabilityTrait[];
|
|
158
|
+
/** What the caller declared this operation moves, when it declared one. */
|
|
159
|
+
spend?: DeclaredSpend;
|
|
125
160
|
};
|
|
126
161
|
/**
|
|
127
162
|
* Evidence a mutation must satisfy when the caller declared none.
|
|
@@ -140,16 +175,51 @@ export declare function classifyOperation(operation: Operation): {
|
|
|
140
175
|
*/
|
|
141
176
|
export declare function defaultEvidenceFor(operation: Operation): EvidencePredicate[];
|
|
142
177
|
/**
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
178
|
+
* What a grant has already drawn down, read from wherever commits are counted.
|
|
179
|
+
*
|
|
180
|
+
* A lookup rather than a number because policy is evaluated more than once per
|
|
181
|
+
* task and a grant may be metered by any of several bounds; the caller that
|
|
182
|
+
* owns the durable count decides how to answer.
|
|
146
183
|
*/
|
|
147
|
-
|
|
184
|
+
/**
|
|
185
|
+
* What an operation declared it moves. Taken from the schema that defines it
|
|
186
|
+
* rather than restated, so a field added there cannot go unnoticed here.
|
|
187
|
+
*/
|
|
188
|
+
export type DeclaredSpend = NonNullable<HttpOperation["spend"]>;
|
|
189
|
+
export type CapabilityUsageReader = (grantId: string) => {
|
|
190
|
+
/** Operations committed against this grant, ever. */
|
|
191
|
+
operations: number;
|
|
192
|
+
/** Declared amounts committed against it in the last 24 hours. */
|
|
193
|
+
amountToday: number;
|
|
194
|
+
};
|
|
195
|
+
/** What the issued grants say about one effect. */
|
|
196
|
+
export interface CapabilityCheck {
|
|
197
|
+
/** Why they do not cover it, or `undefined` if they do. */
|
|
198
|
+
refusal?: string;
|
|
199
|
+
/** The grant it will be metered against, when a grant was needed at all. */
|
|
200
|
+
grantId?: string;
|
|
201
|
+
}
|
|
202
|
+
type ClassifiedForGrant = {
|
|
148
203
|
effect: Effect;
|
|
149
204
|
capability: string;
|
|
150
205
|
target: string;
|
|
151
|
-
|
|
152
|
-
|
|
206
|
+
spend?: DeclaredSpend;
|
|
207
|
+
};
|
|
208
|
+
/** Milliseconds in the rolling window `provider.dailyMax` is measured over. */
|
|
209
|
+
export declare const CAPABILITY_DAY_MS: number;
|
|
210
|
+
/**
|
|
211
|
+
* Which issued grant covers this effect, or why none does.
|
|
212
|
+
*
|
|
213
|
+
* An empty grant list is not a closed world — it means the operator has not
|
|
214
|
+
* issued grants at all, and the rest of policy decides on its own.
|
|
215
|
+
*/
|
|
216
|
+
export declare function capabilityCheck(classified: ClassifiedForGrant, identity: Identity, policy: LocalPolicy, usage?: CapabilityUsageReader): CapabilityCheck;
|
|
217
|
+
/**
|
|
218
|
+
* Why the issued capabilities do not cover this effect, or `undefined` if they
|
|
219
|
+
* do. `capabilityCheck` also reports which grant covered it.
|
|
220
|
+
*/
|
|
221
|
+
export declare function capabilityRefusal(classified: ClassifiedForGrant, identity: Identity, policy: LocalPolicy, usage?: CapabilityUsageReader): string | undefined;
|
|
222
|
+
export declare function evaluatePolicy(taskId: string, request: TaskRequest, policy: LocalPolicy, usage?: CapabilityUsageReader): PolicyEvaluation;
|
|
153
223
|
export declare function digestOperation(taskId: string, operation: Operation): string;
|
|
154
224
|
export declare function validateApproval(challenge: ApprovalChallenge | undefined, response: ApprovalResponse | undefined): {
|
|
155
225
|
ok: true;
|
|
@@ -157,4 +227,5 @@ export declare function validateApproval(challenge: ApprovalChallenge | undefine
|
|
|
157
227
|
ok: false;
|
|
158
228
|
reason: string;
|
|
159
229
|
};
|
|
230
|
+
export * from "./network-policy.js";
|
|
160
231
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,MAAM,EACN,iBAAiB,EACjB,QAAQ,EACR,SAAS,EACT,cAAc,EACd,IAAI,EACJ,WAAW,EACZ,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|