agent-passport-system 1.29.0 → 1.29.2
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 +42 -8
- package/dist/src/adapters/openshell.d.ts +64 -0
- package/dist/src/adapters/openshell.d.ts.map +1 -0
- package/dist/src/adapters/openshell.js +126 -0
- package/dist/src/adapters/openshell.js.map +1 -0
- package/dist/src/core/attestation.d.ts +49 -0
- package/dist/src/core/attestation.d.ts.map +1 -0
- package/dist/src/core/attestation.js +294 -0
- package/dist/src/core/attestation.js.map +1 -0
- package/dist/src/core/passport.d.ts +15 -0
- package/dist/src/core/passport.d.ts.map +1 -1
- package/dist/src/core/passport.js +45 -1
- package/dist/src/core/passport.js.map +1 -1
- package/dist/src/index.d.ts +7 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/types/attestation.d.ts +160 -0
- package/dist/src/types/attestation.d.ts.map +1 -0
- package/dist/src/types/attestation.js +20 -0
- package/dist/src/types/attestation.js.map +1 -0
- package/dist/src/types/passport.d.ts +9 -0
- package/dist/src/types/passport.d.ts.map +1 -1
- package/dist/src/types/passport.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/agent-passport-system)
|
|
4
4
|
[](https://github.com/aeoess/agent-passport-system/blob/main/LICENSE)
|
|
5
|
-
[](https://github.com/aeoess/agent-passport-system)
|
|
6
6
|
[](https://doi.org/10.5281/zenodo.18749779)
|
|
7
|
+
[-blue)](https://doi.org/10.5281/zenodo.19323172)
|
|
7
8
|
|
|
8
|
-
> **For AI agents:** visit [aeoess.com/llms.txt](https://aeoess.com/llms.txt) for machine-readable docs or [llms-full.txt](https://aeoess.com/llms-full.txt) for the complete reference.
|
|
9
|
+
> **For AI agents:** visit [aeoess.com/llms.txt](https://aeoess.com/llms.txt) for machine-readable docs or [llms-full.txt](https://aeoess.com/llms-full.txt) for the complete reference. MCP discovery: [.well-known/mcp.json](https://aeoess.com/.well-known/mcp.json).
|
|
9
10
|
|
|
10
|
-
**
|
|
11
|
+
**Enforcement infrastructure for the agent economy.** Every action evaluated in under 2ms. 15 constraint dimensions. 403 ops/sec. Sub-millisecond denial. Feeless Nano payments. 95 modules. 1,929 tests. Not just identity — the full enforcement stack.
|
|
11
12
|
|
|
12
|
-
AI agents represent companies and people. They spend real money, access sensitive data, negotiate contracts, and talk to other agents. APS answers: what is this agent allowed to do? How much can it spend? Is it trustworthy? What happens when it violates a constraint? And can you prove all of this cryptographically?
|
|
13
|
+
AI agents represent companies and people. They spend real money, access sensitive data, negotiate contracts, and talk to other agents. APS is the enforcement layer that answers: what is this agent allowed to do? How much can it spend? Is it trustworthy? What happens when it violates a constraint? And can you prove all of this cryptographically? Independently validated by [PDR in Production (Nanook & Gerundium, UBC)](https://doi.org/10.5281/zenodo.19323172).
|
|
13
14
|
|
|
14
15
|
```bash
|
|
15
16
|
npm install agent-passport-system
|
|
@@ -27,6 +28,21 @@ npm install agent-passport-system
|
|
|
27
28
|
|
|
28
29
|
**Revoke authority instantly** — cascade revocation propagates through delegation chains. Revoke a parent, all children are automatically revoked. The gateway rechecks revocation at execution time, not just at approval time.
|
|
29
30
|
|
|
31
|
+
## Benchmarks
|
|
32
|
+
|
|
33
|
+
| Metric | Value | Notes |
|
|
34
|
+
|--------|------:|-------|
|
|
35
|
+
| Policy eval p50 | <2ms | Full 15-dimension constraint check |
|
|
36
|
+
| Policy eval p95 | <5ms | Including reputation lookup |
|
|
37
|
+
| Policy eval p99 | <10ms | Worst case with cold cache |
|
|
38
|
+
| Denial latency | <1ms | Fail-fast on first constraint violation |
|
|
39
|
+
| Throughput | 403 ops/sec | Single-threaded gateway |
|
|
40
|
+
| Cascade revocation | <5ms | Chains up to 100 deep |
|
|
41
|
+
| Receipt generation | <1ms | Ed25519 signed, hash-chained |
|
|
42
|
+
| Nano transaction | <1s | Feeless, delegation-scoped |
|
|
43
|
+
|
|
44
|
+
15 constraint dimensions: scope, spend, tier, values, revocation, taint, anomaly, circuit, approval, temporal, jurisdiction, purpose, combination, retention, terms. [Full benchmarks →](https://aeoess.com/benchmarks.html)
|
|
45
|
+
|
|
30
46
|
## Quick Example: Enforce, Don't Just Identify
|
|
31
47
|
|
|
32
48
|
```typescript
|
|
@@ -124,7 +140,7 @@ const agent = joinSocialContract({ name: 'my-agent', owner: 'alice', floor: floo
|
|
|
124
140
|
|
|
125
141
|
## The Stack
|
|
126
142
|
|
|
127
|
-
|
|
143
|
+
63 core modules + 32 v2 constitutional modules. 1929 tests. Zero heavy dependencies.
|
|
128
144
|
|
|
129
145
|
| Layer | What it does | Key primitive |
|
|
130
146
|
|-------|-------------|---------------|
|
|
@@ -143,7 +159,7 @@ const agent = joinSocialContract({ name: 'my-agent', owner: 'alice', floor: floo
|
|
|
143
159
|
|
|
144
160
|
## MCP Server
|
|
145
161
|
|
|
146
|
-
|
|
162
|
+
125 tools across all modules. Any MCP client connects agents directly.
|
|
147
163
|
|
|
148
164
|
```bash
|
|
149
165
|
npm install -g agent-passport-system-mcp
|
|
@@ -178,7 +194,7 @@ npx agent-passport audit --floor values/floor.yaml
|
|
|
178
194
|
|
|
179
195
|
```bash
|
|
180
196
|
npm test
|
|
181
|
-
#
|
|
197
|
+
# 1929 tests across 98 files, 486 suites, 0 failures
|
|
182
198
|
```
|
|
183
199
|
|
|
184
200
|
50 adversarial tests: Merkle tampering, attribution gaming, compliance violations, floor negotiation attacks, cross-chain confused deputy, taint laundering, authority probing.
|
|
@@ -196,7 +212,7 @@ npm test
|
|
|
196
212
|
| Signed receipts | 3-sig chain | Proposed | Logs | General | — |
|
|
197
213
|
| Values enforcement | 8 principles, graduated | — | Rules | — | — |
|
|
198
214
|
| Coordination | Task lifecycle + MCP | — | — | — | — |
|
|
199
|
-
| Tests |
|
|
215
|
+
| Tests | 1852 (50 adversarial) | None | Limited | None | None |
|
|
200
216
|
|
|
201
217
|
## Recognition
|
|
202
218
|
|
|
@@ -228,6 +244,24 @@ Protocol: [aeoess.com/protocol.html](https://aeoess.com/protocol.html) · Agora:
|
|
|
228
244
|
- Quick start: [aeoess.com/llms/quickstart.txt](https://aeoess.com/llms/quickstart.txt)
|
|
229
245
|
- API reference: [aeoess.com/llms/api.txt](https://aeoess.com/llms/api.txt)
|
|
230
246
|
|
|
247
|
+
## Passport Issuer (CA Model)
|
|
248
|
+
|
|
249
|
+
Passports issued through official AEOESS infrastructure are countersigned with the AEOESS issuer key. Self-signed passports are cryptographically valid but won't pass issuer verification.
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
import { countersignPassport, verifyIssuerSignature } from 'agent-passport-system'
|
|
253
|
+
|
|
254
|
+
// Issuer countersigns after agent self-signs
|
|
255
|
+
const issued = countersignPassport(signedPassport, issuerPrivateKey, 'aeoess')
|
|
256
|
+
|
|
257
|
+
// Anyone can verify against the published public key
|
|
258
|
+
const AEOESS_KEY = 'e11f46f5831432d17852189d5df10ed21d5774797ae9ee52dbab8c650fec16ae'
|
|
259
|
+
const trusted = verifyIssuerSignature(issued, AEOESS_KEY) // true
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Published key: [aeoess.com/.well-known/aeoess-issuer.json](https://aeoess.com/.well-known/aeoess-issuer.json)
|
|
263
|
+
MCP tool: `verify_issuer`
|
|
264
|
+
|
|
231
265
|
## License
|
|
232
266
|
|
|
233
267
|
Apache-2.0 — see [LICENSE](LICENSE)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NVIDIA OpenShell Adapter
|
|
3
|
+
*
|
|
4
|
+
* Maps APS delegation scopes to OpenShell sandbox policy YAML.
|
|
5
|
+
* An agent's delegation chain determines what the sandbox can access.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const policy = delegationToPolicy(delegation, basePolicy)
|
|
9
|
+
* // Write policy to YAML, pass to: openshell sandbox create --policy ./policy.yaml
|
|
10
|
+
*/
|
|
11
|
+
import type { Delegation } from '../types/passport.js';
|
|
12
|
+
export interface OpenShellPolicy {
|
|
13
|
+
version: 1;
|
|
14
|
+
identity_policy?: {
|
|
15
|
+
agent_public_key: string;
|
|
16
|
+
issuer_public_key?: string;
|
|
17
|
+
delegation_chain_depth: number;
|
|
18
|
+
};
|
|
19
|
+
filesystem_policy?: {
|
|
20
|
+
read_only: string[];
|
|
21
|
+
read_write: string[];
|
|
22
|
+
};
|
|
23
|
+
network_policies?: Record<string, NetworkPolicyEntry>;
|
|
24
|
+
process?: {
|
|
25
|
+
run_as_user: string;
|
|
26
|
+
run_as_group: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export interface NetworkPolicyEntry {
|
|
30
|
+
name: string;
|
|
31
|
+
endpoints: Array<{
|
|
32
|
+
host: string;
|
|
33
|
+
port: number;
|
|
34
|
+
protocol?: string;
|
|
35
|
+
}>;
|
|
36
|
+
binaries?: Array<{
|
|
37
|
+
path: string;
|
|
38
|
+
}>;
|
|
39
|
+
}
|
|
40
|
+
export interface ScopeMapping {
|
|
41
|
+
scope: string;
|
|
42
|
+
filesystemRead?: string[];
|
|
43
|
+
filesystemWrite?: string[];
|
|
44
|
+
networkAllow?: Array<{
|
|
45
|
+
host: string;
|
|
46
|
+
port: number;
|
|
47
|
+
}>;
|
|
48
|
+
inferenceLocal?: boolean;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Extract effective scopes from a delegation, applying monotonic narrowing.
|
|
52
|
+
*/
|
|
53
|
+
export declare function extractEffectiveScopes(delegation: Delegation): string[];
|
|
54
|
+
/**
|
|
55
|
+
* Map APS delegation scopes to OpenShell policy sections.
|
|
56
|
+
* The output policy is the intersection of the delegation scope and the base policy.
|
|
57
|
+
*/
|
|
58
|
+
export declare function delegationToPolicy(delegation: Delegation, agentPublicKey: string, issuerPublicKey?: string, customMappings?: Record<string, Partial<ScopeMapping>>): OpenShellPolicy;
|
|
59
|
+
/**
|
|
60
|
+
* Serialize an OpenShell policy to YAML string.
|
|
61
|
+
* Minimal YAML serializer — no external deps.
|
|
62
|
+
*/
|
|
63
|
+
export declare function policyToYaml(policy: OpenShellPolicy): string;
|
|
64
|
+
//# sourceMappingURL=openshell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openshell.d.ts","sourceRoot":"","sources":["../../../src/adapters/openshell.ts"],"names":[],"mappings":"AACA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAEtD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,CAAC,CAAA;IACV,eAAe,CAAC,EAAE;QAChB,gBAAgB,EAAE,MAAM,CAAA;QACxB,iBAAiB,CAAC,EAAE,MAAM,CAAA;QAC1B,sBAAsB,EAAE,MAAM,CAAA;KAC/B,CAAA;IACD,iBAAiB,CAAC,EAAE;QAClB,SAAS,EAAE,MAAM,EAAE,CAAA;QACnB,UAAU,EAAE,MAAM,EAAE,CAAA;KACrB,CAAA;IACD,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAA;IACrD,OAAO,CAAC,EAAE;QACR,WAAW,EAAE,MAAM,CAAA;QACnB,YAAY,EAAE,MAAM,CAAA;KACrB,CAAA;CACF;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACnE,QAAQ,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACnC;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACpD,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB;AAuBD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,EAAE,CAEvE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,UAAU,EACtB,cAAc,EAAE,MAAM,EACtB,eAAe,CAAC,EAAE,MAAM,EACxB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,GACrD,eAAe,CA4CjB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAwC5D"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Copyright 2024-2026 Tymofii Pidlisnyi. Apache-2.0 license. See LICENSE.
|
|
2
|
+
/**
|
|
3
|
+
* NVIDIA OpenShell Adapter
|
|
4
|
+
*
|
|
5
|
+
* Maps APS delegation scopes to OpenShell sandbox policy YAML.
|
|
6
|
+
* An agent's delegation chain determines what the sandbox can access.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* const policy = delegationToPolicy(delegation, basePolicy)
|
|
10
|
+
* // Write policy to YAML, pass to: openshell sandbox create --policy ./policy.yaml
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_SCOPE_MAPPINGS = {
|
|
13
|
+
'filesystem:read': { filesystemRead: ['/sandbox', '/tmp'] },
|
|
14
|
+
'filesystem:write': { filesystemWrite: ['/sandbox', '/tmp'] },
|
|
15
|
+
'network:*': { networkAllow: [] },
|
|
16
|
+
'commerce:*': { networkAllow: [{ host: 'gateway.aeoess.com', port: 443 }] },
|
|
17
|
+
'inference:local': { inferenceLocal: true },
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Check if a delegation scope covers a target scope.
|
|
21
|
+
* Supports wildcards: 'commerce:*' covers 'commerce:send'.
|
|
22
|
+
*/
|
|
23
|
+
function scopeCovers(granted, target) {
|
|
24
|
+
if (granted === target)
|
|
25
|
+
return true;
|
|
26
|
+
if (granted.endsWith(':*')) {
|
|
27
|
+
const prefix = granted.slice(0, -1);
|
|
28
|
+
return target.startsWith(prefix);
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Extract effective scopes from a delegation, applying monotonic narrowing.
|
|
34
|
+
*/
|
|
35
|
+
export function extractEffectiveScopes(delegation) {
|
|
36
|
+
return delegation.scope || [];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Map APS delegation scopes to OpenShell policy sections.
|
|
40
|
+
* The output policy is the intersection of the delegation scope and the base policy.
|
|
41
|
+
*/
|
|
42
|
+
export function delegationToPolicy(delegation, agentPublicKey, issuerPublicKey, customMappings) {
|
|
43
|
+
const mappings = { ...DEFAULT_SCOPE_MAPPINGS, ...customMappings };
|
|
44
|
+
const scopes = extractEffectiveScopes(delegation);
|
|
45
|
+
const readPaths = new Set(['/usr', '/lib', '/etc']);
|
|
46
|
+
const writePaths = new Set();
|
|
47
|
+
const networkEntries = [];
|
|
48
|
+
for (const scope of scopes) {
|
|
49
|
+
for (const [pattern, mapping] of Object.entries(mappings)) {
|
|
50
|
+
if (scopeCovers(pattern, scope)) {
|
|
51
|
+
if (mapping.filesystemRead)
|
|
52
|
+
mapping.filesystemRead.forEach(p => readPaths.add(p));
|
|
53
|
+
if (mapping.filesystemWrite)
|
|
54
|
+
mapping.filesystemWrite.forEach(p => writePaths.add(p));
|
|
55
|
+
if (mapping.networkAllow)
|
|
56
|
+
networkEntries.push(...mapping.networkAllow);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const policy = {
|
|
61
|
+
version: 1,
|
|
62
|
+
identity_policy: {
|
|
63
|
+
agent_public_key: agentPublicKey,
|
|
64
|
+
issuer_public_key: issuerPublicKey,
|
|
65
|
+
delegation_chain_depth: delegation.currentDepth || 0,
|
|
66
|
+
},
|
|
67
|
+
filesystem_policy: {
|
|
68
|
+
read_only: [...readPaths],
|
|
69
|
+
read_write: [...writePaths],
|
|
70
|
+
},
|
|
71
|
+
process: { run_as_user: 'sandbox', run_as_group: 'sandbox' },
|
|
72
|
+
};
|
|
73
|
+
if (networkEntries.length > 0) {
|
|
74
|
+
policy.network_policies = {};
|
|
75
|
+
networkEntries.forEach((entry, i) => {
|
|
76
|
+
const key = `aps_${entry.host.replace(/\./g, '_')}`;
|
|
77
|
+
policy.network_policies[key] = {
|
|
78
|
+
name: `APS: ${entry.host}`,
|
|
79
|
+
endpoints: [{ host: entry.host, port: entry.port, protocol: 'rest' }],
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return policy;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Serialize an OpenShell policy to YAML string.
|
|
87
|
+
* Minimal YAML serializer — no external deps.
|
|
88
|
+
*/
|
|
89
|
+
export function policyToYaml(policy) {
|
|
90
|
+
const lines = [`version: ${policy.version}`];
|
|
91
|
+
if (policy.identity_policy) {
|
|
92
|
+
lines.push('', 'identity_policy:');
|
|
93
|
+
lines.push(` agent_public_key: "${policy.identity_policy.agent_public_key}"`);
|
|
94
|
+
if (policy.identity_policy.issuer_public_key)
|
|
95
|
+
lines.push(` issuer_public_key: "${policy.identity_policy.issuer_public_key}"`);
|
|
96
|
+
lines.push(` delegation_chain_depth: ${policy.identity_policy.delegation_chain_depth}`);
|
|
97
|
+
}
|
|
98
|
+
if (policy.filesystem_policy) {
|
|
99
|
+
lines.push('', 'filesystem_policy:');
|
|
100
|
+
lines.push(' read_only:');
|
|
101
|
+
policy.filesystem_policy.read_only.forEach(p => lines.push(` - ${p}`));
|
|
102
|
+
lines.push(' read_write:');
|
|
103
|
+
policy.filesystem_policy.read_write.forEach(p => lines.push(` - ${p}`));
|
|
104
|
+
}
|
|
105
|
+
if (policy.process) {
|
|
106
|
+
lines.push('', 'process:');
|
|
107
|
+
lines.push(` run_as_user: ${policy.process.run_as_user}`);
|
|
108
|
+
lines.push(` run_as_group: ${policy.process.run_as_group}`);
|
|
109
|
+
}
|
|
110
|
+
if (policy.network_policies) {
|
|
111
|
+
lines.push('', 'network_policies:');
|
|
112
|
+
for (const [key, entry] of Object.entries(policy.network_policies)) {
|
|
113
|
+
lines.push(` ${key}:`);
|
|
114
|
+
lines.push(` name: "${entry.name}"`);
|
|
115
|
+
lines.push(' endpoints:');
|
|
116
|
+
entry.endpoints.forEach(ep => {
|
|
117
|
+
lines.push(` - host: ${ep.host}`);
|
|
118
|
+
lines.push(` port: ${ep.port}`);
|
|
119
|
+
if (ep.protocol)
|
|
120
|
+
lines.push(` protocol: ${ep.protocol}`);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return lines.join('\n') + '\n';
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=openshell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openshell.js","sourceRoot":"","sources":["../../../src/adapters/openshell.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { PassportGrade, AttestationFlag, IssuanceChallenge, IssuanceEvidenceRecord, IssuanceContext, PassportAttestationSummary, RuntimeAttestation, ProviderAttestation, ObservedContext, SignalVerificationResult, DerivedSignal, AttestationClass, WorkspaceManifest } from '../types/attestation.js';
|
|
2
|
+
import type { SignedPassport } from '../types/passport.js';
|
|
3
|
+
export declare function createIssuanceChallenge(publicKeyHash: string, options?: {
|
|
4
|
+
requestedClasses?: AttestationClass[];
|
|
5
|
+
expiresInSeconds?: number;
|
|
6
|
+
}): IssuanceChallenge;
|
|
7
|
+
export declare function verifyRuntimeAttestation(attestation: RuntimeAttestation, challenge: IssuanceChallenge, trustedAttesterKeys: Map<string, string>): SignalVerificationResult;
|
|
8
|
+
export declare function computePassportGrade(evidence: IssuanceEvidenceRecord, options?: {
|
|
9
|
+
hasIssuerSignature?: boolean;
|
|
10
|
+
hasVerifiedRuntime?: boolean;
|
|
11
|
+
hasVerifiedProvider?: boolean;
|
|
12
|
+
hasPrincipalEndorsement?: boolean;
|
|
13
|
+
}): PassportGrade;
|
|
14
|
+
export declare function computeAttestationFlags(grade: PassportGrade, evidence: IssuanceEvidenceRecord): AttestationFlag[];
|
|
15
|
+
export declare function computeAttestationBundleHash(evidence: IssuanceEvidenceRecord): string;
|
|
16
|
+
export declare function createIssuanceContext(evidence: IssuanceEvidenceRecord, options?: {
|
|
17
|
+
hasIssuerSignature?: boolean;
|
|
18
|
+
hasVerifiedRuntime?: boolean;
|
|
19
|
+
hasVerifiedProvider?: boolean;
|
|
20
|
+
hasPrincipalEndorsement?: boolean;
|
|
21
|
+
verificationResults?: SignalVerificationResult[];
|
|
22
|
+
derivedSignals?: DerivedSignal[];
|
|
23
|
+
}): IssuanceContext;
|
|
24
|
+
export declare function bindAttestation(signedPassport: SignedPassport, context: IssuanceContext): SignedPassport & {
|
|
25
|
+
attestation: PassportAttestationSummary;
|
|
26
|
+
};
|
|
27
|
+
export declare function createWorkspaceManifest(entries: Array<{
|
|
28
|
+
path: string;
|
|
29
|
+
sizeBytes: number;
|
|
30
|
+
lastModified: Date;
|
|
31
|
+
}>): WorkspaceManifest;
|
|
32
|
+
export declare function createEmptyEvidenceRecord(observed?: Partial<ObservedContext>): IssuanceEvidenceRecord;
|
|
33
|
+
export declare function isChallengeFresh(challenge: IssuanceChallenge): boolean;
|
|
34
|
+
export declare function isGradeAtLeast(grade: PassportGrade, minimum: PassportGrade): boolean;
|
|
35
|
+
export declare function importProviderAttestation(input: {
|
|
36
|
+
/** JWS compact serialization (header.payload.signature) OR raw JSON string OR object */
|
|
37
|
+
attestation: string | Record<string, unknown>;
|
|
38
|
+
/** Provider identifier (e.g. 'red-team-harness', 'cloud-provider', 'oauth-issuer') */
|
|
39
|
+
provider: string;
|
|
40
|
+
/** What kind of subject was attested */
|
|
41
|
+
subjectClass?: string;
|
|
42
|
+
/** Verification method used by the provider */
|
|
43
|
+
verificationMethod?: string;
|
|
44
|
+
}): ProviderAttestation;
|
|
45
|
+
export declare function addIdentityBoundary<T extends Record<string, unknown>>(obj: T, fields?: string[]): T & {
|
|
46
|
+
_identityBoundary: string[];
|
|
47
|
+
_contentHash: string;
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=attestation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attestation.d.ts","sourceRoot":"","sources":["../../../src/core/attestation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
// Copyright 2024-2026 Tymofii Pidlisnyi. Apache-2.0 license. See LICENSE.
|
|
2
|
+
// Agent Attestation Architecture — Core Functions
|
|
3
|
+
// Phase 1: Foundation types + issuance challenge + grade computation + attestation binding
|
|
4
|
+
import { createHash } from 'crypto';
|
|
5
|
+
import { verify } from '../crypto/keys.js';
|
|
6
|
+
import { canonicalize } from './canonical.js';
|
|
7
|
+
// ── SHA-256 helper ──
|
|
8
|
+
function sha256Hex(input) {
|
|
9
|
+
return createHash('sha256').update(input).digest('hex');
|
|
10
|
+
}
|
|
11
|
+
// ── createIssuanceChallenge ──
|
|
12
|
+
// Phase 1 of the 5-phase issuance flow.
|
|
13
|
+
// Server generates a nonce bound to the agent's public key.
|
|
14
|
+
export function createIssuanceChallenge(publicKeyHash, options) {
|
|
15
|
+
const now = new Date();
|
|
16
|
+
const expiry = new Date(now.getTime() + (options?.expiresInSeconds ?? 300) * 1000);
|
|
17
|
+
return {
|
|
18
|
+
challengeId: `ic_${sha256Hex(Date.now().toString() + Math.random().toString()).slice(0, 24)}`,
|
|
19
|
+
nonce: sha256Hex(Math.random().toString() + Date.now().toString()).slice(0, 32),
|
|
20
|
+
requiredPublicKeyHash: publicKeyHash,
|
|
21
|
+
requestedAttestationClasses: options?.requestedClasses ?? ['runtime'],
|
|
22
|
+
expiresAt: expiry.toISOString(),
|
|
23
|
+
issuedAt: now.toISOString(),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
// ── verifyRuntimeAttestation ──
|
|
27
|
+
// Phase 3: verify an infrastructure attestation.
|
|
28
|
+
// Checks: attester signature, nonce binding to challenge, key binding, freshness.
|
|
29
|
+
export function verifyRuntimeAttestation(attestation, challenge, trustedAttesterKeys // attester URI -> public key
|
|
30
|
+
) {
|
|
31
|
+
const now = new Date();
|
|
32
|
+
// Check freshness
|
|
33
|
+
if (new Date(attestation.expiresAt) < now) {
|
|
34
|
+
return {
|
|
35
|
+
signalKey: 'runtime_attestation',
|
|
36
|
+
status: 'failed',
|
|
37
|
+
detail: 'Runtime attestation has expired',
|
|
38
|
+
verifiedAt: now.toISOString(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// Check nonce binding
|
|
42
|
+
if (attestation.nonce !== challenge.nonce) {
|
|
43
|
+
return {
|
|
44
|
+
signalKey: 'runtime_attestation',
|
|
45
|
+
status: 'failed',
|
|
46
|
+
detail: 'Nonce does not match issuance challenge',
|
|
47
|
+
verifiedAt: now.toISOString(),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// Check key binding
|
|
51
|
+
if (attestation.publicKeyHash !== challenge.requiredPublicKeyHash) {
|
|
52
|
+
return {
|
|
53
|
+
signalKey: 'runtime_attestation',
|
|
54
|
+
status: 'failed',
|
|
55
|
+
detail: 'Public key hash does not match challenge requirement',
|
|
56
|
+
verifiedAt: now.toISOString(),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// Check attester is trusted
|
|
60
|
+
const attesterKey = trustedAttesterKeys.get(attestation.attester);
|
|
61
|
+
if (!attesterKey) {
|
|
62
|
+
return {
|
|
63
|
+
signalKey: 'runtime_attestation',
|
|
64
|
+
status: 'observed',
|
|
65
|
+
detail: `Attester ${attestation.attester} is not in trusted registry`,
|
|
66
|
+
verifiedAt: now.toISOString(),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// Verify attester signature
|
|
70
|
+
const payload = canonicalize({
|
|
71
|
+
attester: attestation.attester,
|
|
72
|
+
nonce: attestation.nonce,
|
|
73
|
+
publicKeyHash: attestation.publicKeyHash,
|
|
74
|
+
runtimeClass: attestation.runtimeClass,
|
|
75
|
+
bootEpoch: attestation.bootEpoch,
|
|
76
|
+
runtimeInstanceIdHash: attestation.runtimeInstanceIdHash,
|
|
77
|
+
storageIdentityHash: attestation.storageIdentityHash,
|
|
78
|
+
processIdentityHash: attestation.processIdentityHash,
|
|
79
|
+
issuedAt: attestation.issuedAt,
|
|
80
|
+
expiresAt: attestation.expiresAt,
|
|
81
|
+
});
|
|
82
|
+
const sigValid = verify(payload, attestation.signature, attesterKey);
|
|
83
|
+
if (!sigValid) {
|
|
84
|
+
return {
|
|
85
|
+
signalKey: 'runtime_attestation',
|
|
86
|
+
status: 'failed',
|
|
87
|
+
detail: 'Attester signature verification failed',
|
|
88
|
+
verifiedAt: now.toISOString(),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
signalKey: 'runtime_attestation',
|
|
93
|
+
status: 'verified',
|
|
94
|
+
detail: `Verified by trusted attester ${attestation.attester}`,
|
|
95
|
+
verifiedAt: now.toISOString(),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// ── computePassportGrade ──
|
|
99
|
+
// Determines grade from available verified attestations.
|
|
100
|
+
// Grade 0: self-signed (bare keypair)
|
|
101
|
+
// Grade 1: issuer countersigned
|
|
102
|
+
// Grade 2: runtime-bound (issuer + challenge-response + trusted attestation)
|
|
103
|
+
// Grade 3: runtime + principal bound
|
|
104
|
+
export function computePassportGrade(evidence, options) {
|
|
105
|
+
const has = options ?? {};
|
|
106
|
+
// Check from highest to lowest
|
|
107
|
+
if (has.hasVerifiedRuntime && has.hasPrincipalEndorsement && has.hasIssuerSignature) {
|
|
108
|
+
return 3;
|
|
109
|
+
}
|
|
110
|
+
if (has.hasVerifiedRuntime && has.hasIssuerSignature) {
|
|
111
|
+
return 2;
|
|
112
|
+
}
|
|
113
|
+
// Provider attestation without runtime can still be Grade 2
|
|
114
|
+
// if the provider is strong enough (e.g., verified cloud tenant)
|
|
115
|
+
if (has.hasVerifiedProvider && has.hasIssuerSignature) {
|
|
116
|
+
return 2;
|
|
117
|
+
}
|
|
118
|
+
if (has.hasIssuerSignature) {
|
|
119
|
+
return 1;
|
|
120
|
+
}
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
// ── computeAttestationFlags ──
|
|
124
|
+
// Derive public flags from evidence and grade.
|
|
125
|
+
export function computeAttestationFlags(grade, evidence) {
|
|
126
|
+
const flags = [];
|
|
127
|
+
if (grade >= 1)
|
|
128
|
+
flags.push('issuer_bound');
|
|
129
|
+
if (grade >= 2 && evidence.runtimeAttestations.length > 0)
|
|
130
|
+
flags.push('runtime_bound');
|
|
131
|
+
if (grade >= 2 && evidence.providerAttestations.length > 0)
|
|
132
|
+
flags.push('provider_bound');
|
|
133
|
+
if (grade >= 3)
|
|
134
|
+
flags.push('principal_bound');
|
|
135
|
+
if (evidence.priorPassportRef)
|
|
136
|
+
flags.push('recovery_linked');
|
|
137
|
+
if (evidence.priorContinuityProof)
|
|
138
|
+
flags.push('continuity_proven');
|
|
139
|
+
return flags;
|
|
140
|
+
}
|
|
141
|
+
// ── computeAttestationBundleHash ──
|
|
142
|
+
// SHA-256 of the canonical evidence record. Allows verifiers to confirm
|
|
143
|
+
// the passport's attestation summary matches real evidence without seeing the evidence.
|
|
144
|
+
export function computeAttestationBundleHash(evidence) {
|
|
145
|
+
return sha256Hex(canonicalize(evidence));
|
|
146
|
+
}
|
|
147
|
+
// ── createIssuanceContext ──
|
|
148
|
+
// Combine evidence + assessment into a complete issuance record.
|
|
149
|
+
export function createIssuanceContext(evidence, options) {
|
|
150
|
+
const grade = computePassportGrade(evidence, options);
|
|
151
|
+
const flags = computeAttestationFlags(grade, evidence);
|
|
152
|
+
const bundleHash = computeAttestationBundleHash(evidence);
|
|
153
|
+
return {
|
|
154
|
+
evidence,
|
|
155
|
+
assessment: {
|
|
156
|
+
passportGrade: grade,
|
|
157
|
+
attestationBundleHash: bundleHash,
|
|
158
|
+
flags,
|
|
159
|
+
verificationResults: options?.verificationResults ?? [],
|
|
160
|
+
derivedSignals: options?.derivedSignals,
|
|
161
|
+
assessedAt: new Date().toISOString(),
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
// ── bindAttestation ──
|
|
166
|
+
// Attach PassportAttestationSummary to a SignedPassport.
|
|
167
|
+
// This is backward compatible: the attestation field is optional.
|
|
168
|
+
export function bindAttestation(signedPassport, context) {
|
|
169
|
+
const summary = {
|
|
170
|
+
passportGrade: context.assessment.passportGrade,
|
|
171
|
+
attestationBundleHash: context.assessment.attestationBundleHash,
|
|
172
|
+
flags: context.assessment.flags,
|
|
173
|
+
};
|
|
174
|
+
return {
|
|
175
|
+
...signedPassport,
|
|
176
|
+
attestation: summary,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
// ── createWorkspaceManifest ──
|
|
180
|
+
// Compute a workspace manifest from file entries.
|
|
181
|
+
// Hash structure, not content. Privacy-preserving: paths are hashed,
|
|
182
|
+
// timestamps floored to hour.
|
|
183
|
+
export function createWorkspaceManifest(entries) {
|
|
184
|
+
const now = new Date();
|
|
185
|
+
// Sort entries deterministically by path hash
|
|
186
|
+
const manifestEntries = entries
|
|
187
|
+
.map(e => {
|
|
188
|
+
const hourFloor = new Date(e.lastModified);
|
|
189
|
+
hourFloor.setMinutes(0, 0, 0);
|
|
190
|
+
return {
|
|
191
|
+
pathHash: sha256Hex(e.path),
|
|
192
|
+
sizeBytes: e.sizeBytes,
|
|
193
|
+
lastModifiedBucket: hourFloor.toISOString(),
|
|
194
|
+
};
|
|
195
|
+
})
|
|
196
|
+
.sort((a, b) => a.pathHash.localeCompare(b.pathHash));
|
|
197
|
+
const totalSize = manifestEntries.reduce((sum, e) => sum + e.sizeBytes, 0);
|
|
198
|
+
const manifestHash = sha256Hex(canonicalize(manifestEntries));
|
|
199
|
+
return {
|
|
200
|
+
entries: manifestEntries,
|
|
201
|
+
totalFiles: manifestEntries.length,
|
|
202
|
+
totalSizeBytes: totalSize,
|
|
203
|
+
computedAt: now.toISOString(),
|
|
204
|
+
manifestHash,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
// ── createEmptyEvidenceRecord ──
|
|
208
|
+
// Initialize a minimal evidence record. The server fills Tier 0 observed signals.
|
|
209
|
+
export function createEmptyEvidenceRecord(observed) {
|
|
210
|
+
const now = new Date();
|
|
211
|
+
return {
|
|
212
|
+
requestId: `req_${sha256Hex(Date.now().toString() + Math.random().toString()).slice(0, 24)}`,
|
|
213
|
+
requestedAt: now.toISOString(),
|
|
214
|
+
observed: {
|
|
215
|
+
observedAt: now.toISOString(),
|
|
216
|
+
...observed,
|
|
217
|
+
},
|
|
218
|
+
runtimeAttestations: [],
|
|
219
|
+
providerAttestations: [],
|
|
220
|
+
selfDeclaredSignals: [],
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
// ── isChallengeFresh ──
|
|
224
|
+
// Check if an issuance challenge is still valid.
|
|
225
|
+
export function isChallengeFresh(challenge) {
|
|
226
|
+
return new Date(challenge.expiresAt) > new Date();
|
|
227
|
+
}
|
|
228
|
+
// ── isGradeAtLeast ──
|
|
229
|
+
// Check if a passport grade meets a minimum requirement.
|
|
230
|
+
export function isGradeAtLeast(grade, minimum) {
|
|
231
|
+
return grade >= minimum;
|
|
232
|
+
}
|
|
233
|
+
// ── importProviderAttestation ──
|
|
234
|
+
// Accept external attestation reports (JWS or raw JSON) and convert to ProviderAttestation.
|
|
235
|
+
// Enables composable trust: security test results, cloud attestations, or any third-party
|
|
236
|
+
// verification report feeds into the IssuanceEvidenceRecord as Tier 2 evidence.
|
|
237
|
+
// Connected to msaleme's machine-readable attestation schema (A2A #1696).
|
|
238
|
+
export function importProviderAttestation(input) {
|
|
239
|
+
let payload;
|
|
240
|
+
let signature;
|
|
241
|
+
if (typeof input.attestation === 'string') {
|
|
242
|
+
const parts = input.attestation.split('.');
|
|
243
|
+
if (parts.length === 3) {
|
|
244
|
+
// JWS compact: header.payload.signature
|
|
245
|
+
try {
|
|
246
|
+
const decoded = Buffer.from(parts[1], 'base64url').toString('utf-8');
|
|
247
|
+
payload = JSON.parse(decoded);
|
|
248
|
+
signature = parts[2];
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// Not valid JWS, treat as raw JSON
|
|
252
|
+
payload = JSON.parse(input.attestation);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
payload = JSON.parse(input.attestation);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
payload = input.attestation;
|
|
261
|
+
}
|
|
262
|
+
// Extract subject identifier and hash it
|
|
263
|
+
const subjectId = String(payload.sub || payload.subject_id || payload.agentId || payload.agent_id || '');
|
|
264
|
+
const subjectIdHash = sha256Hex(subjectId);
|
|
265
|
+
return {
|
|
266
|
+
provider: input.provider,
|
|
267
|
+
subjectClass: input.subjectClass || String(payload.subject_class || 'agent'),
|
|
268
|
+
subjectIdHash,
|
|
269
|
+
nonce: payload.nonce ? String(payload.nonce) : undefined,
|
|
270
|
+
publicKeyHash: payload.public_key ? sha256Hex(String(payload.public_key)) : undefined,
|
|
271
|
+
verificationMethod: input.verificationMethod || String(payload.verification_method || 'jwt'),
|
|
272
|
+
issuedAt: String(payload.iat ? new Date(Number(payload.iat) * 1000).toISOString() : payload.issued_at || new Date().toISOString()),
|
|
273
|
+
expiresAt: payload.exp ? new Date(Number(payload.exp) * 1000).toISOString() : (payload.expires_at ? String(payload.expires_at) : undefined),
|
|
274
|
+
signature,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
// ── addIdentityBoundary ──
|
|
278
|
+
// Add a self-describing identity boundary to any object. The boundary declares which
|
|
279
|
+
// fields are included in the content hash, making cross-system attribution possible
|
|
280
|
+
// without agreeing on a single hashing standard. Inspired by xsa520's decision artifact spec.
|
|
281
|
+
export function addIdentityBoundary(obj, fields) {
|
|
282
|
+
const boundary = (fields || Object.keys(obj)).filter(k => !k.startsWith('_')).sort();
|
|
283
|
+
const hashInput = { _identityBoundary: boundary };
|
|
284
|
+
for (const k of boundary) {
|
|
285
|
+
if (k in obj)
|
|
286
|
+
hashInput[k] = obj[k];
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
...obj,
|
|
290
|
+
_identityBoundary: boundary,
|
|
291
|
+
_contentHash: sha256Hex(JSON.stringify(hashInput)),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=attestation.js.map
|