@zanii/certify 0.0.0-stage → 0.5.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.
Files changed (133) hide show
  1. package/LICENSE +159 -0
  2. package/README.md +112 -2
  3. package/dist/appliance-execution.d.ts +18 -0
  4. package/dist/appliance-execution.js +12 -0
  5. package/dist/appliance.d.ts +135 -0
  6. package/dist/appliance.js +253 -0
  7. package/dist/assurance-status.d.ts +58 -0
  8. package/dist/assurance-status.js +86 -0
  9. package/dist/audit-renewal.d.ts +39 -0
  10. package/dist/audit-renewal.js +62 -0
  11. package/dist/audit.d.ts +69 -0
  12. package/dist/audit.js +112 -0
  13. package/dist/beacon-client.d.ts +38 -0
  14. package/dist/beacon-client.js +52 -0
  15. package/dist/beacon.d.ts +84 -0
  16. package/dist/beacon.js +106 -0
  17. package/dist/bitstring-status.d.ts +56 -0
  18. package/dist/bitstring-status.js +135 -0
  19. package/dist/board.d.ts +218 -0
  20. package/dist/board.js +428 -0
  21. package/dist/bundle.d.ts +92 -0
  22. package/dist/bundle.js +109 -0
  23. package/dist/certificate.d.ts +161 -0
  24. package/dist/certificate.js +345 -0
  25. package/dist/cli-messages.json +148 -0
  26. package/dist/cli.d.ts +16 -0
  27. package/dist/cli.js +369 -0
  28. package/dist/current-issuer-vc.d.ts +20 -0
  29. package/dist/current-issuer-vc.js +60 -0
  30. package/dist/evidence-share.d.ts +89 -0
  31. package/dist/evidence-share.js +479 -0
  32. package/dist/federation.d.ts +103 -0
  33. package/dist/federation.js +240 -0
  34. package/dist/feed-snapshot.d.ts +97 -0
  35. package/dist/feed-snapshot.js +186 -0
  36. package/dist/fingerprint.d.ts +23 -0
  37. package/dist/fingerprint.js +48 -0
  38. package/dist/formats/status-v2.schema.json +31 -0
  39. package/dist/governance.d.ts +242 -0
  40. package/dist/governance.js +411 -0
  41. package/dist/identity.d.ts +79 -0
  42. package/dist/identity.js +146 -0
  43. package/dist/impartiality.d.ts +47 -0
  44. package/dist/impartiality.js +85 -0
  45. package/dist/index.d.ts +58 -0
  46. package/dist/index.js +59 -0
  47. package/dist/issuer-authorization.d.ts +4 -0
  48. package/dist/issuer-authorization.js +52 -0
  49. package/dist/issuer-resolution.d.ts +25 -0
  50. package/dist/issuer-resolution.js +89 -0
  51. package/dist/licence-owner-entry.d.ts +20 -0
  52. package/dist/licence-owner-entry.js +33 -0
  53. package/dist/licence-owner.d.ts +67 -0
  54. package/dist/licence-owner.js +180 -0
  55. package/dist/licence.d.ts +174 -0
  56. package/dist/licence.js +400 -0
  57. package/dist/live-evidence.d.ts +135 -0
  58. package/dist/live-evidence.js +210 -0
  59. package/dist/nitro-roots.d.ts +3 -0
  60. package/dist/nitro-roots.js +20 -0
  61. package/dist/nitro-x509.d.ts +16 -0
  62. package/dist/nitro-x509.js +141 -0
  63. package/dist/nitro.d.ts +74 -0
  64. package/dist/nitro.js +329 -0
  65. package/dist/normalization.d.ts +8 -0
  66. package/dist/normalization.js +10 -0
  67. package/dist/pq.d.ts +32 -0
  68. package/dist/pq.js +69 -0
  69. package/dist/profile.d.ts +20 -0
  70. package/dist/profile.js +161 -0
  71. package/dist/register.d.ts +79 -0
  72. package/dist/register.js +134 -0
  73. package/dist/rotation.d.ts +49 -0
  74. package/dist/rotation.js +124 -0
  75. package/dist/royalty-reconciliation.d.ts +24 -0
  76. package/dist/royalty-reconciliation.js +88 -0
  77. package/dist/runtime-client.d.ts +38 -0
  78. package/dist/runtime-client.js +185 -0
  79. package/dist/runtime-gateway.d.ts +24 -0
  80. package/dist/runtime-gateway.js +167 -0
  81. package/dist/runtime-knowledge.d.ts +28 -0
  82. package/dist/runtime-knowledge.js +75 -0
  83. package/dist/runtime-nitro.d.ts +30 -0
  84. package/dist/runtime-nitro.js +57 -0
  85. package/dist/runtime.d.ts +226 -0
  86. package/dist/runtime.js +254 -0
  87. package/dist/sandbox-delegation.d.ts +38 -0
  88. package/dist/sandbox-delegation.js +61 -0
  89. package/dist/schema-dialect.d.ts +1 -0
  90. package/dist/schema-dialect.js +97 -0
  91. package/dist/schemas/clause-map.schema.json +52 -0
  92. package/dist/schemas/profile.schema.json +80 -0
  93. package/dist/schemas/scenarios.schema.json +573 -0
  94. package/dist/schemas/suite.schema.json +138 -0
  95. package/dist/scheme-authority.d.ts +5 -0
  96. package/dist/scheme-authority.js +15 -0
  97. package/dist/scheme-entry.d.ts +25 -0
  98. package/dist/scheme-entry.js +85 -0
  99. package/dist/scheme-history.d.ts +5 -0
  100. package/dist/scheme-history.js +87 -0
  101. package/dist/scheme-log.d.ts +98 -0
  102. package/dist/scheme-log.js +266 -0
  103. package/dist/scheme-merkle.d.ts +2 -0
  104. package/dist/scheme-merkle.js +20 -0
  105. package/dist/scheme-text.d.ts +25 -0
  106. package/dist/scheme-text.js +46 -0
  107. package/dist/scheme-timing.d.ts +16 -0
  108. package/dist/scheme-timing.js +22 -0
  109. package/dist/scheme-transition.d.ts +47 -0
  110. package/dist/scheme-transition.js +101 -0
  111. package/dist/scoring.d.ts +35 -0
  112. package/dist/scoring.js +50 -0
  113. package/dist/shared.d.ts +16 -0
  114. package/dist/shared.js +20 -0
  115. package/dist/signer.d.ts +18 -0
  116. package/dist/signer.js +49 -0
  117. package/dist/signing-identity.d.ts +1 -0
  118. package/dist/signing-identity.js +11 -0
  119. package/dist/status.d.ts +113 -0
  120. package/dist/status.js +204 -0
  121. package/dist/succession.d.ts +21 -0
  122. package/dist/succession.js +67 -0
  123. package/dist/suite.d.ts +257 -0
  124. package/dist/suite.js +441 -0
  125. package/dist/vc.d.ts +29 -0
  126. package/dist/vc.js +96 -0
  127. package/dist/verification-bounds.d.ts +5 -0
  128. package/dist/verification-bounds.js +76 -0
  129. package/dist/verification-policy.d.ts +32 -0
  130. package/dist/verification-policy.js +85 -0
  131. package/dist/witness.d.ts +19 -0
  132. package/dist/witness.js +80 -0
  133. package/package.json +57 -3
@@ -0,0 +1,226 @@
1
+ import { type Signer } from "./signer.ts";
2
+ export interface RuntimeCheck {
3
+ ok: boolean;
4
+ reasons: string[];
5
+ }
6
+ interface Signed {
7
+ v: 1;
8
+ signer: string;
9
+ sig: string;
10
+ }
11
+ export interface RuntimeRelease extends Signed {
12
+ type: "certify.runtime-release";
13
+ agent: string;
14
+ fingerprint: string;
15
+ image: string;
16
+ config: string;
17
+ tools: {
18
+ name: string;
19
+ digest: string;
20
+ }[];
21
+ knowledge: string | null;
22
+ retrieval: string | null;
23
+ model: {
24
+ id: string;
25
+ revision: string | null;
26
+ artifact: string | null;
27
+ };
28
+ permissions: string[];
29
+ egress: string[];
30
+ provenance: {
31
+ builder: string;
32
+ artifact: string;
33
+ workflow: string;
34
+ };
35
+ issued_at: string;
36
+ }
37
+ export interface RuntimePolicy {
38
+ v: 1;
39
+ type: "certify.runtime-policy";
40
+ platform: "nitro";
41
+ pcrs: Record<string, string>;
42
+ evidence_max_age_s: number;
43
+ challenge_ttl_s: number;
44
+ lease_ttl_s: number;
45
+ permissions: string[];
46
+ audiences: string[];
47
+ authorizers: string[];
48
+ max_uses: number;
49
+ require_model_artifact: boolean;
50
+ }
51
+ export interface RuntimeApproval extends Signed {
52
+ type: "certify.runtime-approval";
53
+ certificate: string;
54
+ release: string;
55
+ policy: string;
56
+ /** Human-reviewed artifact digest; not a cryptographic proof of a test/build relationship. */
57
+ qualification: string;
58
+ approved_by: string;
59
+ issued_at: string;
60
+ valid_until: string;
61
+ epoch: number;
62
+ }
63
+ export interface RuntimeProof extends Signed {
64
+ type: "certify.runtime-proof";
65
+ deployment: string;
66
+ certificate: string;
67
+ release: string;
68
+ challenge: string;
69
+ public_key: string;
70
+ attestation_sha256: string;
71
+ observed_at: string;
72
+ }
73
+ export interface RuntimeChallengeRequest extends Signed {
74
+ type: "certify.runtime-challenge-request";
75
+ deployment: string;
76
+ certificate: string;
77
+ release: string;
78
+ nonce: string;
79
+ issued_at: string;
80
+ }
81
+ export interface RuntimeLease extends Signed {
82
+ type: "certify.runtime-lease";
83
+ id: string;
84
+ certificate: string;
85
+ release: string;
86
+ policy: string;
87
+ deployment: string;
88
+ workload: string;
89
+ audience: string;
90
+ permission: string;
91
+ request: string;
92
+ epoch: number;
93
+ issued_at: string;
94
+ expires_at: string;
95
+ max_uses: number;
96
+ }
97
+ export interface RuntimeAction extends Signed {
98
+ type: "certify.runtime-action";
99
+ lease: string;
100
+ certificate: string;
101
+ deployment: string;
102
+ audience: string;
103
+ permission: string;
104
+ request: string;
105
+ nonce: string;
106
+ issued_at: string;
107
+ }
108
+ export interface RuntimeLeaseRequest extends Signed {
109
+ type: "certify.runtime-lease-request";
110
+ deployment: string;
111
+ certificate: string;
112
+ audience: string;
113
+ permission: string;
114
+ request: string;
115
+ nonce: string;
116
+ issued_at: string;
117
+ }
118
+ export interface RuntimeMandate extends Signed {
119
+ type: "certify.runtime-mandate";
120
+ deployment: string;
121
+ certificate: string;
122
+ workload: string;
123
+ audience: string;
124
+ permission: string;
125
+ request: string;
126
+ nonce: string;
127
+ issued_at: string;
128
+ expires_at: string;
129
+ }
130
+ export interface RuntimeTrustOptions {
131
+ signers: readonly string[];
132
+ now: string;
133
+ maxSkewSeconds?: number;
134
+ }
135
+ export interface RuntimeReleaseOptions extends RuntimeTrustOptions {
136
+ agent?: string;
137
+ fingerprint?: string;
138
+ hash?: string;
139
+ }
140
+ export interface RuntimeApprovalOptions extends RuntimeTrustOptions {
141
+ certificate: string;
142
+ release: string;
143
+ policy: string;
144
+ qualification?: string;
145
+ epoch?: number;
146
+ }
147
+ export interface RuntimeProofOptions extends RuntimeTrustOptions {
148
+ certificate: string;
149
+ release: string;
150
+ deployment: string;
151
+ challenge: string;
152
+ attestation_sha256: string;
153
+ maxAgeSeconds: number;
154
+ }
155
+ export interface RuntimeChallengeOptions extends RuntimeTrustOptions {
156
+ certificate: string;
157
+ release: string;
158
+ deployment: string;
159
+ maxAgeSeconds?: number;
160
+ }
161
+ export interface RuntimeLeaseOptions extends RuntimeTrustOptions {
162
+ certificate: string;
163
+ release: string;
164
+ policy: string;
165
+ deployment: string;
166
+ workload: string;
167
+ audience: string;
168
+ permission: string;
169
+ request: string;
170
+ epoch: number;
171
+ maxTtlSeconds?: number;
172
+ }
173
+ export interface RuntimeActionOptions extends RuntimeTrustOptions {
174
+ lease: string;
175
+ certificate: string;
176
+ deployment: string;
177
+ audience: string;
178
+ permission: string;
179
+ request: string;
180
+ maxAgeSeconds?: number;
181
+ }
182
+ export interface RuntimeLeaseRequestOptions extends RuntimeTrustOptions {
183
+ certificate: string;
184
+ deployment: string;
185
+ audience: string;
186
+ permission: string;
187
+ request: string;
188
+ maxAgeSeconds?: number;
189
+ }
190
+ export interface RuntimeMandateOptions extends RuntimeTrustOptions {
191
+ certificate: string;
192
+ deployment: string;
193
+ workload: string;
194
+ audience: string;
195
+ permission: string;
196
+ request: string;
197
+ maxTtlSeconds?: number;
198
+ }
199
+ type Input<T> = Omit<T, "v" | "type" | "signer" | "sig">;
200
+ export declare const buildRuntimeRelease: (fields: Input<RuntimeRelease>, signer: Signer) => Promise<RuntimeRelease>;
201
+ export declare const buildRuntimeApproval: (fields: Input<RuntimeApproval>, signer: Signer) => Promise<RuntimeApproval>;
202
+ export declare const buildRuntimeProof: (fields: Input<RuntimeProof>, signer: Signer) => Promise<RuntimeProof>;
203
+ export declare const buildRuntimeChallengeRequest: (fields: Input<RuntimeChallengeRequest>, signer: Signer) => Promise<RuntimeChallengeRequest>;
204
+ export declare const buildRuntimeLease: (fields: Input<RuntimeLease>, signer: Signer) => Promise<RuntimeLease>;
205
+ export declare const buildRuntimeAction: (fields: Input<RuntimeAction>, signer: Signer) => Promise<RuntimeAction>;
206
+ export declare const buildRuntimeLeaseRequest: (fields: Input<RuntimeLeaseRequest>, signer: Signer) => Promise<RuntimeLeaseRequest>;
207
+ export declare const buildRuntimeMandate: (fields: Input<RuntimeMandate>, signer: Signer) => Promise<RuntimeMandate>;
208
+ export declare function buildRuntimePolicy(fields: Omit<RuntimePolicy, "v" | "type">): RuntimePolicy;
209
+ export declare function verifyRuntimeRelease(doc: unknown, opts?: RuntimeReleaseOptions): RuntimeCheck;
210
+ export declare const verifyRuntimePolicy: (doc: unknown) => RuntimeCheck;
211
+ export declare function verifyRuntimeApproval(doc: unknown, opts?: RuntimeApprovalOptions): RuntimeCheck;
212
+ export declare function verifyRuntimeProof(doc: unknown, opts?: RuntimeProofOptions): RuntimeCheck;
213
+ export declare function verifyRuntimeChallengeRequest(doc: unknown, opts?: RuntimeChallengeOptions): RuntimeCheck;
214
+ export declare function verifyRuntimeLease(doc: unknown, opts?: RuntimeLeaseOptions): RuntimeCheck;
215
+ export declare function verifyRuntimeAction(doc: unknown, opts?: RuntimeActionOptions): RuntimeCheck;
216
+ export declare function verifyRuntimeLeaseRequest(doc: unknown, opts?: RuntimeLeaseRequestOptions): RuntimeCheck;
217
+ export declare function verifyRuntimeMandate(doc: unknown, opts?: RuntimeMandateOptions): RuntimeCheck;
218
+ /** Hashes identify validated wire bytes; they do not establish signer trust or hardware state. */
219
+ export declare function runtimeReleaseHash(doc: RuntimeRelease): string;
220
+ export declare function runtimePolicyHash(doc: RuntimePolicy): string;
221
+ export declare function runtimeRequestHash(request: {
222
+ method: string;
223
+ url: string;
224
+ body_sha256: string;
225
+ }): string;
226
+ export {};
@@ -0,0 +1,254 @@
1
+ // Runtime documents bind signed claims and request scope; hardware and live authorization are separate checks.
2
+ import { jcsHash, publicKeyFromDid } from "@zanii/core";
3
+ import { isDid as sharedDid, isInt, isIso as sharedIso, isObj, isSha as sharedSha, ms } from "./shared.js";
4
+ import { signatureOk, signerDid, signWith } from "./signer.js";
5
+ const keys = (v, names) => isObj(v) && Object.keys(v).length === names.split(" ").length && names.split(" ").every((key) => Object.hasOwn(v, key));
6
+ const isDid = (v) => sharedDid(v) && v.trim() === v && publicKeyFromDid(v) !== null;
7
+ const isSha = (v) => sharedSha(v) && v.trim() === v;
8
+ const isIso = (v) => sharedIso(v) && v.trim() === v;
9
+ const text = (v, max = 200) => typeof v === "string" && v.length > 0 && v.length <= max && v.isWellFormed() && !/[\u0000-\u001f\u007f]/.test(v);
10
+ const identifier = (v) => text(v, 128) && /^[A-Za-z0-9._:-]{1,128}$/.test(v);
11
+ const nonce = (v) => text(v, 128) && /^[A-Za-z0-9_-]{32,128}$/.test(v);
12
+ const digestOrNull = (v) => v === null || isSha(v);
13
+ const ordered = (v, max, valid) => Array.isArray(v) && v.length <= max && v.every((s, i) => valid(s) && (i === 0 || v[i - 1] < s));
14
+ /** Canonical ASCII HTTPS origins only; neither credentials nor a default port are hidden by normalization. */
15
+ const origin = (v) => {
16
+ if (typeof v !== "string" || v.length > 2048 || !/^[\x21-\x7e]+$/.test(v))
17
+ return false;
18
+ try {
19
+ const url = new URL(v);
20
+ return url.protocol === "https:" && v === url.origin && !url.username && !url.password
21
+ && (url.hostname.startsWith("[") || (url.hostname.length <= 253 && /^[a-z0-9_.-]+$/.test(url.hostname)));
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ };
27
+ const common = (d, purpose) => d.v === 1 && d.type === `certify.runtime-${purpose}` && isDid(d.signer) && typeof d.sig === "string" && d.sig.length === 136 && /^ed25519:[0-9a-f]{128}$/.test(d.sig);
28
+ const tools = (v) => Array.isArray(v) && v.length <= 100 && v.every((tool, i) => keys(tool, "name digest") && identifier(tool.name) && isSha(tool.digest) && (i === 0 || v[i - 1].name < tool.name));
29
+ function releaseShape(v) {
30
+ return keys(v, "v type agent fingerprint image config tools knowledge retrieval model permissions egress provenance issued_at signer sig") && common(v, "release")
31
+ && isDid(v.agent) && isSha(v.fingerprint) && isSha(v.image) && isSha(v.config) && digestOrNull(v.knowledge) && digestOrNull(v.retrieval)
32
+ && tools(v.tools)
33
+ && keys(v.model, "id revision artifact") && text(v.model.id) && (v.model.revision === null || text(v.model.revision)) && digestOrNull(v.model.artifact)
34
+ && ordered(v.permissions, 100, (s) => text(s, 128)) && ordered(v.egress, 32, origin)
35
+ && keys(v.provenance, "builder artifact workflow") && isDid(v.provenance.builder) && v.provenance.builder === v.signer && isSha(v.provenance.artifact) && v.provenance.artifact === v.image && text(v.provenance.workflow)
36
+ && isIso(v.issued_at);
37
+ }
38
+ function policyShape(v) {
39
+ return keys(v, "v type platform pcrs evidence_max_age_s challenge_ttl_s lease_ttl_s permissions audiences authorizers max_uses require_model_artifact") && v.v === 1 && v.type === "certify.runtime-policy" && v.platform === "nitro"
40
+ && isObj(v.pcrs) && Object.keys(v.pcrs).length >= 1 && Object.keys(v.pcrs).length <= 8 && typeof v.pcrs["0"] === "string" && /[1-9a-f]/.test(v.pcrs["0"])
41
+ && Object.entries(v.pcrs).every(([key, value]) => key.trim() === key && /^(?:[0-9]|[12][0-9]|3[01])$/.test(key) && typeof value === "string" && value.length === 96 && /^[0-9a-f]{96}$/.test(value))
42
+ && isInt(v.evidence_max_age_s, 15, 300) && isInt(v.challenge_ttl_s, 5, 120) && isInt(v.lease_ttl_s, 1, 60)
43
+ && ordered(v.permissions, 100, (s) => text(s, 128)) && ordered(v.audiences, 32, origin) && ordered(v.authorizers, 8, isDid) && v.authorizers.length > 0 && isInt(v.max_uses, 1, 1000) && typeof v.require_model_artifact === "boolean";
44
+ }
45
+ function approvalShape(v) {
46
+ return keys(v, "v type certificate release policy qualification approved_by issued_at valid_until epoch signer sig") && common(v, "approval")
47
+ && [v.certificate, v.release, v.policy, v.qualification].every(isSha) && isDid(v.approved_by) && isIso(v.issued_at) && isIso(v.valid_until) && ms(v.valid_until) > ms(v.issued_at) && isInt(v.epoch, 1, Number.MAX_SAFE_INTEGER);
48
+ }
49
+ function proofShape(v) {
50
+ return keys(v, "v type deployment certificate release challenge public_key attestation_sha256 observed_at signer sig") && common(v, "proof")
51
+ && identifier(v.deployment) && isSha(v.certificate) && isSha(v.release) && nonce(v.challenge) && isDid(v.public_key) && v.public_key === v.signer && isSha(v.attestation_sha256) && isIso(v.observed_at);
52
+ }
53
+ function challengeShape(v) {
54
+ return keys(v, "v type deployment certificate release nonce issued_at signer sig") && common(v, "challenge-request")
55
+ && identifier(v.deployment) && isSha(v.certificate) && isSha(v.release) && nonce(v.nonce) && isIso(v.issued_at);
56
+ }
57
+ function leaseShape(v) {
58
+ return keys(v, "v type id certificate release policy deployment workload audience permission request epoch issued_at expires_at max_uses signer sig") && common(v, "lease")
59
+ && identifier(v.id) && [v.certificate, v.release, v.policy, v.request].every(isSha) && identifier(v.deployment) && isDid(v.workload) && origin(v.audience) && text(v.permission, 128)
60
+ && isInt(v.epoch, 1, Number.MAX_SAFE_INTEGER) && isInt(v.max_uses, 1, 1000) && isIso(v.issued_at) && isIso(v.expires_at) && ms(v.expires_at) > ms(v.issued_at) && ms(v.expires_at) - ms(v.issued_at) <= 60_000;
61
+ }
62
+ function actionShape(v) {
63
+ return keys(v, "v type lease certificate deployment audience permission request nonce issued_at signer sig") && common(v, "action")
64
+ && identifier(v.lease) && isSha(v.certificate) && identifier(v.deployment) && origin(v.audience) && text(v.permission, 128) && isSha(v.request) && nonce(v.nonce) && isIso(v.issued_at);
65
+ }
66
+ function leaseRequestShape(v) {
67
+ return keys(v, "v type deployment certificate audience permission request nonce issued_at signer sig") && common(v, "lease-request")
68
+ && identifier(v.deployment) && isSha(v.certificate) && origin(v.audience) && text(v.permission, 128) && isSha(v.request) && nonce(v.nonce) && isIso(v.issued_at);
69
+ }
70
+ function mandateShape(v) {
71
+ return keys(v, "v type deployment certificate workload audience permission request nonce issued_at expires_at signer sig") && common(v, "mandate")
72
+ && identifier(v.deployment) && isSha(v.certificate) && isDid(v.workload) && v.workload !== v.signer && origin(v.audience) && text(v.permission, 128) && isSha(v.request) && nonce(v.nonce)
73
+ && isIso(v.issued_at) && isIso(v.expires_at) && ms(v.expires_at) > ms(v.issued_at) && ms(v.expires_at) - ms(v.issued_at) <= 60_000;
74
+ }
75
+ const signatures = { signer: "", sig: `ed25519:${"0".repeat(128)}` };
76
+ const copy = (value) => JSON.parse(JSON.stringify(value));
77
+ async function build(purpose, fields, signer, shape) {
78
+ if (!isObj(fields) || ["v", "type", "signer", "sig"].some((key) => Object.hasOwn(fields, key)))
79
+ throw new Error(`not a runtime ${purpose} input`);
80
+ const doc = { ...fields, v: 1, type: `certify.runtime-${purpose}`, ...signatures, signer: signerDid(signer) };
81
+ // Bound arrays before sorting or serializing attacker-supplied manifests.
82
+ for (const [key, limit] of [["tools", 100], ["permissions", 100], ["egress", 32]]) {
83
+ const value = doc[key];
84
+ if (Array.isArray(value) && value.length > limit)
85
+ throw new Error(`runtime ${key} exceeds ${limit} items`);
86
+ }
87
+ if (Array.isArray(doc.tools))
88
+ doc.tools = [...doc.tools].sort((a, b) => String(a?.name) < String(b?.name) ? -1 : String(a?.name) > String(b?.name) ? 1 : 0);
89
+ for (const key of ["permissions", "egress"])
90
+ if (key in doc && Array.isArray(doc[key]))
91
+ doc[key] = [...doc[key]].sort();
92
+ if (!shape(doc))
93
+ throw new Error(`not a runtime ${purpose} (v1)`);
94
+ const { sig: _, signer: __, ...unsigned } = copy(doc);
95
+ return await signWith(unsigned, signer);
96
+ }
97
+ export const buildRuntimeRelease = (fields, signer) => build("release", fields, signer, releaseShape);
98
+ export const buildRuntimeApproval = (fields, signer) => build("approval", fields, signer, approvalShape);
99
+ export const buildRuntimeProof = (fields, signer) => build("proof", fields, signer, proofShape);
100
+ export const buildRuntimeChallengeRequest = (fields, signer) => build("challenge-request", fields, signer, challengeShape);
101
+ export const buildRuntimeLease = (fields, signer) => build("lease", fields, signer, leaseShape);
102
+ export const buildRuntimeAction = (fields, signer) => build("action", fields, signer, actionShape);
103
+ export const buildRuntimeLeaseRequest = (fields, signer) => build("lease-request", fields, signer, leaseRequestShape);
104
+ export const buildRuntimeMandate = (fields, signer) => build("mandate", fields, signer, mandateShape);
105
+ export function buildRuntimePolicy(fields) {
106
+ if (!isObj(fields) || ["v", "type"].some((key) => Object.hasOwn(fields, key)))
107
+ throw new Error("not a runtime policy input");
108
+ const doc = { ...fields, v: 1, type: "certify.runtime-policy" };
109
+ for (const [key, limit] of [["permissions", 100], ["audiences", 32], ["authorizers", 8]]) {
110
+ if (Array.isArray(doc[key]) && doc[key].length <= limit)
111
+ doc[key] = [...doc[key]].sort();
112
+ }
113
+ if (!policyShape(doc))
114
+ throw new Error("not a runtime policy (v1)");
115
+ return copy(doc);
116
+ }
117
+ function trust(doc, opts) {
118
+ const reasons = [];
119
+ if (!Array.isArray(opts.signers) || opts.signers.length < 1 || opts.signers.length > 100 || !opts.signers.every(isDid))
120
+ reasons.push("no trusted runtime signers supplied");
121
+ else if (!opts.signers.includes(doc.signer))
122
+ reasons.push("runtime signer is not trusted");
123
+ if (!isIso(opts.now))
124
+ reasons.push("now must be an ISO UTC timestamp");
125
+ if (!isInt(opts.maxSkewSeconds ?? 5, 0, 5))
126
+ reasons.push("maxSkewSeconds must be from 0 to 5");
127
+ const { sig, ...unsigned } = doc;
128
+ if (!signatureOk(unsigned, sig, doc.signer))
129
+ reasons.push("bad runtime signature");
130
+ return reasons;
131
+ }
132
+ function expected(doc, opts, fields, optional = []) {
133
+ const reasons = [];
134
+ for (const [key, valid] of Object.entries(fields)) {
135
+ const value = opts[key];
136
+ if (value === undefined && optional.includes(key))
137
+ continue;
138
+ if (!valid(value))
139
+ reasons.push(`expected ${key} is required`);
140
+ else if (doc[key] !== value)
141
+ reasons.push(`runtime ${key} does not match`);
142
+ }
143
+ return reasons;
144
+ }
145
+ function time(at, opts, maxAge) {
146
+ if (ms(at) > ms(opts.now) + (opts.maxSkewSeconds ?? 5) * 1000)
147
+ return ["runtime timestamp is in the future"];
148
+ return maxAge !== undefined && ms(opts.now) - ms(at) >= maxAge * 1000 ? ["runtime evidence is stale"] : [];
149
+ }
150
+ const result = (reasons) => ({ ok: reasons.length === 0, reasons });
151
+ const invalid = (purpose) => result([`not a runtime ${purpose} (v1)`]);
152
+ const bindings = { certificate: isSha, release: isSha, deployment: identifier };
153
+ export function verifyRuntimeRelease(doc, opts = {}) {
154
+ if (!releaseShape(doc))
155
+ return invalid("release");
156
+ const reasons = [...trust(doc, opts), ...time(doc.issued_at, opts), ...expected(doc, opts, { agent: isDid, fingerprint: isSha }, ["agent", "fingerprint"])];
157
+ if (opts.hash !== undefined && (!isSha(opts.hash) || jcsHash(doc) !== opts.hash))
158
+ reasons.push("runtime release hash does not match");
159
+ return result(reasons);
160
+ }
161
+ export const verifyRuntimePolicy = (doc) => policyShape(doc) ? result([]) : invalid("policy");
162
+ export function verifyRuntimeApproval(doc, opts = {}) {
163
+ if (!approvalShape(doc))
164
+ return invalid("approval");
165
+ const reasons = [...trust(doc, opts), ...time(doc.issued_at, opts), ...expected(doc, opts, { certificate: isSha, release: isSha, policy: isSha, qualification: isSha, epoch: (v) => isInt(v, 1, Number.MAX_SAFE_INTEGER) }, ["qualification", "epoch"])];
166
+ if (ms(doc.valid_until) <= ms(opts.now))
167
+ reasons.push("runtime approval expired");
168
+ return result(reasons);
169
+ }
170
+ export function verifyRuntimeProof(doc, opts = {}) {
171
+ if (!proofShape(doc))
172
+ return invalid("proof");
173
+ const reasons = [...trust(doc, opts), ...expected(doc, opts, { ...bindings, challenge: nonce, attestation_sha256: isSha })];
174
+ if (!isInt(opts.maxAgeSeconds, 15, 300))
175
+ reasons.push("maxAgeSeconds must be from 15 to 300");
176
+ else
177
+ reasons.push(...time(doc.observed_at, opts, opts.maxAgeSeconds));
178
+ return result(reasons);
179
+ }
180
+ export function verifyRuntimeChallengeRequest(doc, opts = {}) {
181
+ if (!challengeShape(doc))
182
+ return invalid("challenge-request");
183
+ const reasons = [...trust(doc, opts), ...expected(doc, opts, bindings)];
184
+ if (!isInt(opts.maxAgeSeconds ?? 60, 1, 60))
185
+ reasons.push("maxAgeSeconds must be from 1 to 60");
186
+ else
187
+ reasons.push(...time(doc.issued_at, opts, opts.maxAgeSeconds ?? 60));
188
+ return result(reasons);
189
+ }
190
+ export function verifyRuntimeLease(doc, opts = {}) {
191
+ if (!leaseShape(doc))
192
+ return invalid("lease");
193
+ const reasons = [...trust(doc, opts), ...time(doc.issued_at, opts), ...expected(doc, opts, { ...bindings, policy: isSha, workload: isDid, audience: origin, permission: (v) => text(v, 128), request: isSha, epoch: (v) => isInt(v, 1, Number.MAX_SAFE_INTEGER) })];
194
+ if (!isInt(opts.maxTtlSeconds ?? 60, 1, 60) || ms(doc.expires_at) - ms(doc.issued_at) > (opts.maxTtlSeconds ?? 60) * 1000)
195
+ reasons.push("runtime lease exceeds the allowed lifetime");
196
+ if (ms(doc.expires_at) <= ms(opts.now))
197
+ reasons.push("runtime lease expired");
198
+ return result(reasons);
199
+ }
200
+ export function verifyRuntimeAction(doc, opts = {}) {
201
+ if (!actionShape(doc))
202
+ return invalid("action");
203
+ const reasons = [...trust(doc, opts), ...expected(doc, opts, { lease: identifier, certificate: isSha, deployment: identifier, audience: origin, permission: (v) => text(v, 128), request: isSha })];
204
+ if (!isInt(opts.maxAgeSeconds ?? 60, 1, 60))
205
+ reasons.push("maxAgeSeconds must be from 1 to 60");
206
+ else
207
+ reasons.push(...time(doc.issued_at, opts, opts.maxAgeSeconds ?? 60));
208
+ return result(reasons);
209
+ }
210
+ export function verifyRuntimeLeaseRequest(doc, opts = {}) {
211
+ if (!leaseRequestShape(doc))
212
+ return invalid("lease-request");
213
+ const reasons = [...trust(doc, opts), ...expected(doc, opts, { certificate: isSha, deployment: identifier, audience: origin, permission: (v) => text(v, 128), request: isSha })];
214
+ if (!isInt(opts.maxAgeSeconds ?? 60, 1, 60))
215
+ reasons.push("maxAgeSeconds must be from 1 to 60");
216
+ else
217
+ reasons.push(...time(doc.issued_at, opts, opts.maxAgeSeconds ?? 60));
218
+ return result(reasons);
219
+ }
220
+ export function verifyRuntimeMandate(doc, opts = {}) {
221
+ if (!mandateShape(doc))
222
+ return invalid("mandate");
223
+ const reasons = [...trust(doc, opts), ...time(doc.issued_at, opts), ...expected(doc, opts, { certificate: isSha, deployment: identifier, workload: isDid, audience: origin, permission: (v) => text(v, 128), request: isSha })];
224
+ if (!isInt(opts.maxTtlSeconds ?? 60, 1, 60) || ms(doc.expires_at) - ms(doc.issued_at) > (opts.maxTtlSeconds ?? 60) * 1000)
225
+ reasons.push("runtime mandate exceeds the allowed lifetime");
226
+ if (ms(doc.expires_at) <= ms(opts.now))
227
+ reasons.push("runtime mandate expired");
228
+ return result(reasons);
229
+ }
230
+ /** Hashes identify validated wire bytes; they do not establish signer trust or hardware state. */
231
+ export function runtimeReleaseHash(doc) {
232
+ if (!releaseShape(doc))
233
+ throw new Error("not a runtime release (v1)");
234
+ return jcsHash(doc);
235
+ }
236
+ export function runtimePolicyHash(doc) {
237
+ if (!policyShape(doc))
238
+ throw new Error("not a runtime policy (v1)");
239
+ return jcsHash(doc);
240
+ }
241
+ export function runtimeRequestHash(request) {
242
+ if (!keys(request, "method url body_sha256") || !text(request.method, 16) || !/^[A-Z]{1,16}$/.test(request.method) || !isSha(request.body_sha256) || typeof request.url !== "string" || request.url.length > 2048 || !/^[\x21-\x7e]+$/.test(request.url))
243
+ throw new Error("not a canonical runtime request");
244
+ let url;
245
+ try {
246
+ url = new URL(request.url);
247
+ }
248
+ catch {
249
+ throw new Error("not a canonical runtime request");
250
+ }
251
+ if (!origin(url.origin) || request.url !== url.href || request.url.includes("#") || url.username || url.password)
252
+ throw new Error("not a canonical runtime request");
253
+ return jcsHash({ v: 1, type: "certify.runtime-request", ...request });
254
+ }
@@ -0,0 +1,38 @@
1
+ import { type Signer } from "./signer.ts";
2
+ import type { Delegation } from "./suite.ts";
3
+ export interface SandboxDelegationPolicy {
4
+ v: 1;
5
+ type: "certify.sandbox-delegation";
6
+ run: string;
7
+ case_id: string;
8
+ agent: string;
9
+ issued_at: string;
10
+ expires_at: string;
11
+ chain_hash: string;
12
+ tools_hash: string;
13
+ certs_hash: string;
14
+ spend_limit: {
15
+ amount_minor: number;
16
+ currency: string;
17
+ };
18
+ signer: string;
19
+ sig: string;
20
+ }
21
+ export interface SandboxDelegationContext {
22
+ root: string;
23
+ run: string;
24
+ case_id: string;
25
+ agent: string;
26
+ now: string;
27
+ }
28
+ export declare function buildSandboxDelegationPolicy(d: Delegation, context: {
29
+ run: string;
30
+ case_id: string;
31
+ agent: string;
32
+ issued_at: string;
33
+ expires_at: string;
34
+ }, signer: Signer): Promise<SandboxDelegationPolicy>;
35
+ export declare function verifySandboxDelegationPolicy(value: unknown, d: Delegation, context: SandboxDelegationContext): {
36
+ ok: boolean;
37
+ reasons: string[];
38
+ };
@@ -0,0 +1,61 @@
1
+ // Lab test authority only: this envelope never grants production resource access.
2
+ import { jcsHash } from "@zanii/core";
3
+ import { isDid, isInt, isIso, isObj, isSha, ms } from "./shared.js";
4
+ import { signWith, signatureOk, signerDid } from "./signer.js";
5
+ const id = (v) => typeof v === "string" && /^[A-Za-z0-9._:-]{1,128}$/.test(v);
6
+ const scope = (v) => typeof v === "string" && v.length <= 128 && /^(\*|[a-z0-9_-]+(\.[a-z0-9_-]+)*(\.\*)?)$/.test(v);
7
+ const keys = (v, required, optional = []) => isObj(v) && required.every((key) => Object.hasOwn(v, key)) && Object.keys(v).every((key) => required.includes(key) || optional.includes(key));
8
+ const amount = (v) => keys(v, ["amount_minor", "currency"]) && isInt(v.amount_minor, 0, Number.MAX_SAFE_INTEGER) && typeof v.currency === "string" && /^[A-Z]{3}$/.test(v.currency);
9
+ function hashes(d) {
10
+ if (!isObj(d) || !Array.isArray(d.chain) || d.chain.length < 1 || d.chain.length > 8 || !Array.isArray(d.tools) || d.tools.length > 64
11
+ || !Array.isArray(d.certs) || d.certs.length !== d.chain.length)
12
+ throw new Error("sandbox delegation needs a bounded signed chain");
13
+ for (const link of d.chain)
14
+ if (!keys(link, ["agent", "scopes", "spend_limit"]) || !id(link.agent) || !Array.isArray(link.scopes) || !link.scopes.length || link.scopes.length > 64 || !link.scopes.every(scope) || !amount(link.spend_limit))
15
+ throw new Error("invalid sandbox delegation link");
16
+ if (new Set(d.chain.map((link) => link.spend_limit.currency)).size !== 1)
17
+ throw new Error("sandbox delegation currency must be consistent");
18
+ for (const tool of d.tools)
19
+ if (!keys(tool, ["name", "scope"], ["amount", "currency"]) || typeof tool.name !== "string" || !/^[A-Za-z0-9_-]{1,64}$/.test(tool.name) || !scope(tool.scope)
20
+ || [tool.amount, tool.currency].some((v) => v !== undefined && (typeof v !== "string" || !/^[A-Za-z0-9_]{1,64}$/.test(v))))
21
+ throw new Error("invalid sandbox delegation tool");
22
+ for (const cert of d.certs)
23
+ if (!keys(cert, ["v", "issuer", "subject", "scopes", "exp", "sig"]) || cert.v !== 1 || !isDid(cert.issuer) || !isDid(cert.subject) || !isIso(cert.exp)
24
+ || !Array.isArray(cert.scopes) || !cert.scopes.length || cert.scopes.length > 64 || !cert.scopes.every(scope) || typeof cert.sig !== "string" || !/^ed25519:[0-9a-f]{128}$/.test(cert.sig))
25
+ throw new Error("invalid sandbox delegation certificate");
26
+ return { chain_hash: jcsHash(d.chain), tools_hash: jcsHash(d.tools), certs_hash: jcsHash(d.certs), spend_limit: {
27
+ amount_minor: Math.min(...d.chain.map((link) => link.spend_limit.amount_minor)), currency: d.chain[0].spend_limit.currency
28
+ } };
29
+ }
30
+ function shape(value) {
31
+ return keys(value, ["v", "type", "run", "case_id", "agent", "issued_at", "expires_at", "chain_hash", "tools_hash", "certs_hash", "spend_limit", "signer", "sig"])
32
+ && value.v === 1 && value.type === "certify.sandbox-delegation" && id(value.run) && id(value.case_id) && isDid(value.agent) && isDid(value.signer)
33
+ && isIso(value.issued_at) && isIso(value.expires_at) && ms(value.expires_at) > ms(value.issued_at) && ms(value.expires_at) - ms(value.issued_at) <= 3600000
34
+ && [value.chain_hash, value.tools_hash, value.certs_hash].every(isSha) && amount(value.spend_limit) && typeof value.sig === "string" && /^ed25519:[0-9a-f]{128}$/.test(value.sig);
35
+ }
36
+ export async function buildSandboxDelegationPolicy(d, context, signer) {
37
+ const fields = { v: 1, type: "certify.sandbox-delegation", ...context, ...hashes(d) };
38
+ const candidate = { ...fields, signer: signerDid(signer), sig: `ed25519:${"0".repeat(128)}` };
39
+ if (!shape(candidate) || d.certs[0].issuer !== candidate.signer || d.certs.at(-1).subject !== context.agent)
40
+ throw new Error("sandbox policy signer or context does not match its chain");
41
+ return await signWith(fields, signer);
42
+ }
43
+ export function verifySandboxDelegationPolicy(value, d, context) {
44
+ try {
45
+ if (!shape(value) || !isDid(context.root) || !id(context.run) || !id(context.case_id) || !isDid(context.agent) || !isIso(context.now))
46
+ return { ok: false, reasons: ["invalid sandbox delegation or expected context"] };
47
+ const reasons = [], expected = hashes(d), { sig, ...unsigned } = value;
48
+ if (value.signer !== context.root || d.certs[0].issuer !== context.root || !signatureOk(unsigned, sig, context.root))
49
+ reasons.push("sandbox policy is not signed by the expected run root");
50
+ if (value.run !== context.run || value.case_id !== context.case_id || value.agent !== context.agent || d.certs.at(-1).subject !== context.agent)
51
+ reasons.push("sandbox run, case or tested agent does not match");
52
+ if (ms(value.issued_at) > ms(context.now) || ms(value.expires_at) <= ms(context.now))
53
+ reasons.push("sandbox policy is not current");
54
+ if (value.chain_hash !== expected.chain_hash || value.tools_hash !== expected.tools_hash || value.certs_hash !== expected.certs_hash || jcsHash(value.spend_limit) !== jcsHash(expected.spend_limit))
55
+ reasons.push("sandbox signed budget or delegation changed");
56
+ return { ok: reasons.length === 0, reasons };
57
+ }
58
+ catch {
59
+ return { ok: false, reasons: ["invalid sandbox delegation or expected context"] };
60
+ }
61
+ }
@@ -0,0 +1 @@
1
+ export declare function schemaDialectErrors(schema: unknown): string[];