@simonsbs/keylore 1.0.0-rc4

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 (81) hide show
  1. package/.env.example +64 -0
  2. package/LICENSE +176 -0
  3. package/NOTICE +5 -0
  4. package/README.md +424 -0
  5. package/bin/keylore-http.js +3 -0
  6. package/bin/keylore-stdio.js +3 -0
  7. package/data/auth-clients.json +54 -0
  8. package/data/catalog.json +53 -0
  9. package/data/policies.json +25 -0
  10. package/dist/adapters/adapter-registry.js +143 -0
  11. package/dist/adapters/aws-secrets-manager-adapter.js +99 -0
  12. package/dist/adapters/command-runner.js +17 -0
  13. package/dist/adapters/env-secret-adapter.js +42 -0
  14. package/dist/adapters/gcp-secret-manager-adapter.js +129 -0
  15. package/dist/adapters/local-secret-adapter.js +54 -0
  16. package/dist/adapters/onepassword-secret-adapter.js +83 -0
  17. package/dist/adapters/reference-utils.js +44 -0
  18. package/dist/adapters/types.js +1 -0
  19. package/dist/adapters/vault-secret-adapter.js +103 -0
  20. package/dist/app.js +132 -0
  21. package/dist/cli/args.js +51 -0
  22. package/dist/cli/run.js +483 -0
  23. package/dist/cli.js +18 -0
  24. package/dist/config.js +295 -0
  25. package/dist/domain/types.js +967 -0
  26. package/dist/http/admin-ui.js +3010 -0
  27. package/dist/http/server.js +1210 -0
  28. package/dist/index.js +40 -0
  29. package/dist/mcp/create-server.js +388 -0
  30. package/dist/mcp/stdio.js +7 -0
  31. package/dist/repositories/credential-repository.js +109 -0
  32. package/dist/repositories/interfaces.js +1 -0
  33. package/dist/repositories/json-file.js +20 -0
  34. package/dist/repositories/pg-access-token-repository.js +118 -0
  35. package/dist/repositories/pg-approval-repository.js +157 -0
  36. package/dist/repositories/pg-audit-log.js +62 -0
  37. package/dist/repositories/pg-auth-client-repository.js +98 -0
  38. package/dist/repositories/pg-authorization-code-repository.js +95 -0
  39. package/dist/repositories/pg-break-glass-repository.js +174 -0
  40. package/dist/repositories/pg-credential-repository.js +163 -0
  41. package/dist/repositories/pg-oauth-client-assertion-repository.js +25 -0
  42. package/dist/repositories/pg-policy-repository.js +62 -0
  43. package/dist/repositories/pg-refresh-token-repository.js +125 -0
  44. package/dist/repositories/pg-rotation-run-repository.js +127 -0
  45. package/dist/repositories/pg-tenant-repository.js +56 -0
  46. package/dist/repositories/policy-repository.js +24 -0
  47. package/dist/runtime/sandbox-runner.js +114 -0
  48. package/dist/services/access-fingerprint.js +13 -0
  49. package/dist/services/approval-service.js +148 -0
  50. package/dist/services/audit-log.js +38 -0
  51. package/dist/services/auth-context.js +43 -0
  52. package/dist/services/auth-secrets.js +14 -0
  53. package/dist/services/auth-service.js +784 -0
  54. package/dist/services/backup-service.js +610 -0
  55. package/dist/services/break-glass-service.js +207 -0
  56. package/dist/services/broker-service.js +557 -0
  57. package/dist/services/core-mode-service.js +154 -0
  58. package/dist/services/egress-policy.js +119 -0
  59. package/dist/services/local-secret-store.js +119 -0
  60. package/dist/services/maintenance-service.js +99 -0
  61. package/dist/services/notification-service.js +83 -0
  62. package/dist/services/policy-engine.js +85 -0
  63. package/dist/services/rate-limit-service.js +80 -0
  64. package/dist/services/rotation-service.js +271 -0
  65. package/dist/services/telemetry.js +149 -0
  66. package/dist/services/tenant-service.js +127 -0
  67. package/dist/services/trace-export-service.js +126 -0
  68. package/dist/services/trace-service.js +87 -0
  69. package/dist/storage/bootstrap.js +68 -0
  70. package/dist/storage/database.js +39 -0
  71. package/dist/storage/in-memory-database.js +40 -0
  72. package/dist/storage/migrations.js +27 -0
  73. package/migrations/001_init.sql +49 -0
  74. package/migrations/002_phase2_auth.sql +53 -0
  75. package/migrations/003_v05_operations.sql +9 -0
  76. package/migrations/004_v07_security.sql +28 -0
  77. package/migrations/005_v08_reviews.sql +11 -0
  78. package/migrations/006_v09_auth_trace_rotation.sql +51 -0
  79. package/migrations/007_v010_multi_tenant.sql +32 -0
  80. package/migrations/008_v011_auth_tenant_ops.sql +95 -0
  81. package/package.json +78 -0
@@ -0,0 +1,967 @@
1
+ import * as z from "zod/v4";
2
+ export const scopeTierSchema = z.enum(["read_only", "read_write", "admin"]);
3
+ export const sensitivitySchema = z.enum(["low", "moderate", "high", "critical"]);
4
+ export const credentialStatusSchema = z.enum(["active", "disabled"]);
5
+ export const bindingAdapterSchema = z.enum([
6
+ "local",
7
+ "env",
8
+ "vault",
9
+ "1password",
10
+ "aws_secrets_manager",
11
+ "gcp_secret_manager",
12
+ ]);
13
+ export const authTypeSchema = z.enum(["bearer", "api_key"]);
14
+ export const operationSchema = z.enum(["http.get", "http.post"]);
15
+ export const runtimeModeSchema = z.enum(["proxy", "sandbox_injection"]);
16
+ export const principalRoleSchema = z.enum([
17
+ "admin",
18
+ "auth_admin",
19
+ "operator",
20
+ "maintenance_operator",
21
+ "backup_operator",
22
+ "breakglass_operator",
23
+ "auditor",
24
+ "approver",
25
+ "consumer",
26
+ ]);
27
+ export const accessScopeSchema = z.enum([
28
+ "catalog:read",
29
+ "catalog:write",
30
+ "admin:read",
31
+ "admin:write",
32
+ "auth:read",
33
+ "auth:write",
34
+ "broker:use",
35
+ "sandbox:run",
36
+ "audit:read",
37
+ "approval:read",
38
+ "approval:review",
39
+ "system:read",
40
+ "system:write",
41
+ "backup:read",
42
+ "backup:write",
43
+ "breakglass:request",
44
+ "breakglass:read",
45
+ "breakglass:review",
46
+ "mcp:use",
47
+ ]);
48
+ export const authClientStatusSchema = z.enum(["active", "disabled"]);
49
+ export const authClientAuthMethodSchema = z.enum([
50
+ "client_secret_basic",
51
+ "client_secret_post",
52
+ "private_key_jwt",
53
+ "none",
54
+ ]);
55
+ export const authGrantTypeSchema = z.enum([
56
+ "client_credentials",
57
+ "authorization_code",
58
+ "refresh_token",
59
+ ]);
60
+ export const tenantIdSchema = z.string().min(1).max(128);
61
+ export const tenantStatusSchema = z.enum(["active", "disabled"]);
62
+ export const approvalStatusSchema = z.enum(["pending", "approved", "denied", "expired"]);
63
+ export const accessModeSchema = z.enum(["live", "dry_run", "simulation"]);
64
+ export const accessTokenStatusSchema = z.enum(["active", "revoked"]);
65
+ export const authCodeChallengeMethodSchema = z.enum(["S256"]);
66
+ export const breakGlassStatusSchema = z.enum(["pending", "active", "denied", "expired", "revoked"]);
67
+ export const reviewDecisionSchema = z.enum(["approved", "denied"]);
68
+ export const rotationRunStatusSchema = z.enum([
69
+ "pending",
70
+ "in_progress",
71
+ "completed",
72
+ "failed",
73
+ "cancelled",
74
+ ]);
75
+ export const rotationRunSourceSchema = z.enum([
76
+ "manual",
77
+ "catalog_expiry",
78
+ "secret_expiry",
79
+ "secret_rotation_window",
80
+ ]);
81
+ export const publicJwkSchema = z
82
+ .object({
83
+ kty: z.string().min(1),
84
+ kid: z.string().min(1).optional(),
85
+ alg: z.string().min(1).optional(),
86
+ use: z.string().min(1).optional(),
87
+ n: z.string().min(1).optional(),
88
+ e: z.string().min(1).optional(),
89
+ crv: z.string().min(1).optional(),
90
+ x: z.string().min(1).optional(),
91
+ y: z.string().min(1).optional(),
92
+ })
93
+ .passthrough();
94
+ export const credentialBindingSchema = z.object({
95
+ adapter: bindingAdapterSchema,
96
+ ref: z.string().min(1),
97
+ authType: authTypeSchema,
98
+ headerName: z.string().min(1).default("Authorization"),
99
+ headerPrefix: z.string().default("Bearer "),
100
+ injectionEnvName: z.string().regex(/^[A-Z_][A-Z0-9_]*$/).optional(),
101
+ });
102
+ export const credentialRecordSchema = z.object({
103
+ id: z.string().min(1),
104
+ tenantId: tenantIdSchema.default("default"),
105
+ displayName: z.string().min(1),
106
+ service: z.string().min(1),
107
+ owner: z.string().min(1),
108
+ scopeTier: scopeTierSchema,
109
+ sensitivity: sensitivitySchema,
110
+ allowedDomains: z.array(z.string().min(1)).min(1),
111
+ permittedOperations: z.array(operationSchema).min(1),
112
+ expiresAt: z.string().datetime().nullable(),
113
+ rotationPolicy: z.string().min(1),
114
+ lastValidatedAt: z.string().datetime().nullable(),
115
+ selectionNotes: z.string().min(1),
116
+ binding: credentialBindingSchema,
117
+ tags: z.array(z.string().min(1)).default([]),
118
+ status: credentialStatusSchema.default("active"),
119
+ });
120
+ export const credentialSummarySchema = credentialRecordSchema.omit({
121
+ binding: true,
122
+ });
123
+ export const catalogFileSchema = z.object({
124
+ version: z.number().int().positive(),
125
+ credentials: z.array(credentialRecordSchema),
126
+ });
127
+ export const policyRuleSchema = z.object({
128
+ id: z.string().min(1),
129
+ tenantId: tenantIdSchema.default("default"),
130
+ effect: z.enum(["allow", "deny", "approval"]),
131
+ description: z.string().min(1),
132
+ principals: z.array(z.string().min(1)).min(1),
133
+ principalRoles: z.array(principalRoleSchema).optional(),
134
+ credentialIds: z.array(z.string().min(1)).optional(),
135
+ services: z.array(z.string().min(1)).optional(),
136
+ operations: z.array(z.union([operationSchema, z.literal("*")])).min(1),
137
+ domainPatterns: z.array(z.string().min(1)).min(1),
138
+ environments: z.array(z.string().min(1)).optional(),
139
+ });
140
+ export const policyFileSchema = z.object({
141
+ version: z.number().int().positive(),
142
+ rules: z.array(policyRuleSchema),
143
+ });
144
+ export const auditEventSchema = z.object({
145
+ eventId: z.string().uuid(),
146
+ occurredAt: z.string().datetime(),
147
+ tenantId: tenantIdSchema.default("default"),
148
+ type: z.enum([
149
+ "catalog.search",
150
+ "catalog.read",
151
+ "catalog.write",
152
+ "authz.decision",
153
+ "credential.use",
154
+ "approval.request",
155
+ "approval.review",
156
+ "breakglass.request",
157
+ "breakglass.review",
158
+ "breakglass.use",
159
+ "auth.client",
160
+ "auth.token",
161
+ "runtime.exec",
162
+ "adapter.health",
163
+ "notification.delivery",
164
+ "trace.export",
165
+ "rotation.run",
166
+ "system.backup",
167
+ ]),
168
+ action: z.string().min(1),
169
+ outcome: z.enum(["allowed", "denied", "success", "error"]),
170
+ principal: z.string().min(1),
171
+ correlationId: z.string().uuid(),
172
+ metadata: z.record(z.string(), z.unknown()),
173
+ });
174
+ export const httpResultSchema = z.object({
175
+ status: z.number().int(),
176
+ contentType: z.string().nullable(),
177
+ bodyPreview: z.string(),
178
+ bodyTruncated: z.boolean(),
179
+ });
180
+ export const accessDecisionSchema = z.object({
181
+ decision: z.enum(["allowed", "denied", "approval_required"]),
182
+ mode: accessModeSchema,
183
+ reason: z.string(),
184
+ correlationId: z.string().uuid(),
185
+ credential: credentialSummarySchema.optional(),
186
+ ruleId: z.string().optional(),
187
+ httpResult: httpResultSchema.optional(),
188
+ approvalRequestId: z.string().uuid().optional(),
189
+ });
190
+ export const catalogSearchOutputSchema = z.object({
191
+ results: z.array(credentialSummarySchema),
192
+ count: z.number().int().min(0),
193
+ });
194
+ export const catalogGetOutputSchema = z.object({
195
+ result: credentialSummarySchema.nullable(),
196
+ });
197
+ export const auditRecentOutputSchema = z.object({
198
+ events: z.array(auditEventSchema),
199
+ });
200
+ export const catalogSearchInputSchema = z.object({
201
+ query: z.string().trim().optional(),
202
+ service: z.string().trim().optional(),
203
+ owner: z.string().trim().optional(),
204
+ scopeTier: scopeTierSchema.optional(),
205
+ sensitivity: sensitivitySchema.optional(),
206
+ status: credentialStatusSchema.optional(),
207
+ tag: z.string().trim().optional(),
208
+ limit: z.coerce.number().int().min(1).max(50).default(10),
209
+ });
210
+ export const createCredentialInputSchema = credentialRecordSchema;
211
+ const secretLikeSelectionNotesPattern = /(gh[pousr]_[A-Za-z0-9_]+|github_pat_[A-Za-z0-9_]+|sk-[A-Za-z0-9_-]+|AKIA[0-9A-Z]{16})/;
212
+ const vagueSelectionNotesPattern = /^(use when needed|general use|general purpose|for api|api token|token for api|default token|main token)$/i;
213
+ export const coreCredentialCreateInputSchema = z
214
+ .object({
215
+ credentialId: z.string().min(1),
216
+ tenantId: tenantIdSchema.default("default"),
217
+ displayName: z.string().min(1),
218
+ service: z.string().min(1),
219
+ owner: z.string().default("local"),
220
+ scopeTier: scopeTierSchema.default("read_only"),
221
+ sensitivity: sensitivitySchema.default("high"),
222
+ allowedDomains: z.array(z.string().min(1)).min(1),
223
+ permittedOperations: z.array(operationSchema).min(1).default(["http.get"]),
224
+ selectionNotes: z.string().min(1),
225
+ rotationPolicy: z.string().default("Managed locally"),
226
+ tags: z.array(z.string().min(1)).default([]),
227
+ status: credentialStatusSchema.default("active"),
228
+ expiresAt: z.string().datetime().nullable().default(null),
229
+ authType: authTypeSchema.default("bearer"),
230
+ headerName: z.string().min(1).default("Authorization"),
231
+ headerPrefix: z.string().default("Bearer "),
232
+ injectionEnvName: z.string().regex(/^[A-Z_][A-Z0-9_]*$/).optional(),
233
+ secretSource: z.discriminatedUnion("adapter", [
234
+ z.object({
235
+ adapter: z.literal("local"),
236
+ secretValue: z.string().min(1),
237
+ }),
238
+ z.object({
239
+ adapter: z.literal("env"),
240
+ ref: z.string().min(1),
241
+ }),
242
+ ]),
243
+ })
244
+ .superRefine((value, ctx) => {
245
+ if (value.authType === "bearer" &&
246
+ value.headerName.toLowerCase() === "authorization" &&
247
+ !value.headerPrefix.trim()) {
248
+ ctx.addIssue({
249
+ code: z.ZodIssueCode.custom,
250
+ path: ["headerPrefix"],
251
+ message: "Bearer credentials should include a non-empty header prefix.",
252
+ });
253
+ }
254
+ const selectionNotes = value.selectionNotes.trim();
255
+ if (selectionNotes.length < 16) {
256
+ ctx.addIssue({
257
+ code: z.ZodIssueCode.custom,
258
+ path: ["selectionNotes"],
259
+ message: "Selection notes must explain when the agent should use this credential in more detail.",
260
+ });
261
+ }
262
+ if (vagueSelectionNotesPattern.test(selectionNotes)) {
263
+ ctx.addIssue({
264
+ code: z.ZodIssueCode.custom,
265
+ path: ["selectionNotes"],
266
+ message: "Selection notes are too vague. Describe the target service, intended use, and what the agent should avoid.",
267
+ });
268
+ }
269
+ if (secretLikeSelectionNotesPattern.test(selectionNotes)) {
270
+ ctx.addIssue({
271
+ code: z.ZodIssueCode.custom,
272
+ path: ["selectionNotes"],
273
+ message: "Selection notes must not contain token-like secret material.",
274
+ });
275
+ }
276
+ if (value.permittedOperations.includes("http.post") && value.scopeTier === "read_only") {
277
+ ctx.addIssue({
278
+ code: z.ZodIssueCode.custom,
279
+ path: ["scopeTier"],
280
+ message: "Credentials that allow http.post cannot use the read_only scope tier.",
281
+ });
282
+ }
283
+ });
284
+ export const coreCredentialCreateOutputSchema = z.object({
285
+ credential: credentialSummarySchema,
286
+ });
287
+ export const coreCredentialContextUpdateInputSchema = z
288
+ .object({
289
+ displayName: z.string().min(1).optional(),
290
+ service: z.string().min(1).optional(),
291
+ scopeTier: scopeTierSchema.optional(),
292
+ sensitivity: sensitivitySchema.optional(),
293
+ allowedDomains: z.array(z.string().min(1)).min(1).optional(),
294
+ permittedOperations: z.array(operationSchema).min(1).optional(),
295
+ selectionNotes: z.string().min(1).optional(),
296
+ tags: z.array(z.string().min(1)).optional(),
297
+ status: credentialStatusSchema.optional(),
298
+ })
299
+ .refine((value) => Object.keys(value).length > 0, {
300
+ message: "At least one context field must be provided.",
301
+ })
302
+ .superRefine((value, ctx) => {
303
+ if (value.selectionNotes !== undefined) {
304
+ const selectionNotes = value.selectionNotes.trim();
305
+ if (selectionNotes.length < 16) {
306
+ ctx.addIssue({
307
+ code: z.ZodIssueCode.custom,
308
+ path: ["selectionNotes"],
309
+ message: "Selection notes must explain when the agent should use this credential in more detail.",
310
+ });
311
+ }
312
+ if (vagueSelectionNotesPattern.test(selectionNotes)) {
313
+ ctx.addIssue({
314
+ code: z.ZodIssueCode.custom,
315
+ path: ["selectionNotes"],
316
+ message: "Selection notes are too vague. Describe the target service, intended use, and what the agent should avoid.",
317
+ });
318
+ }
319
+ if (secretLikeSelectionNotesPattern.test(selectionNotes)) {
320
+ ctx.addIssue({
321
+ code: z.ZodIssueCode.custom,
322
+ path: ["selectionNotes"],
323
+ message: "Selection notes must not contain token-like secret material.",
324
+ });
325
+ }
326
+ }
327
+ const scopeTier = value.scopeTier;
328
+ const permittedOperations = value.permittedOperations;
329
+ if (scopeTier === "read_only" && permittedOperations?.includes("http.post")) {
330
+ ctx.addIssue({
331
+ code: z.ZodIssueCode.custom,
332
+ path: ["scopeTier"],
333
+ message: "Credentials that allow http.post cannot use the read_only scope tier.",
334
+ });
335
+ }
336
+ });
337
+ export const updateCredentialInputSchema = credentialRecordSchema
338
+ .omit({ id: true, tenantId: true })
339
+ .partial()
340
+ .refine((value) => Object.keys(value).length > 0, {
341
+ message: "At least one field must be provided.",
342
+ });
343
+ export const accessRequestInputSchema = z.object({
344
+ credentialId: z.string().min(1),
345
+ operation: operationSchema,
346
+ targetUrl: z.string().url(),
347
+ headers: z.record(z.string(), z.string()).optional(),
348
+ payload: z.string().max(20_000).optional(),
349
+ approvalId: z.string().uuid().optional(),
350
+ breakGlassId: z.string().uuid().optional(),
351
+ dryRun: z.boolean().optional(),
352
+ });
353
+ export const runtimeExecutionInputSchema = z.object({
354
+ credentialId: z.string().min(1),
355
+ command: z.string().min(1),
356
+ args: z.array(z.string()).max(32).default([]),
357
+ secretEnvName: z.string().regex(/^[A-Z_][A-Z0-9_]*$/).optional(),
358
+ env: z.record(z.string().regex(/^[A-Z_][A-Z0-9_]*$/), z.string()).optional(),
359
+ timeoutMs: z.number().int().min(100).max(60000).optional(),
360
+ });
361
+ export const runtimeExecutionResultSchema = z.object({
362
+ mode: z.literal("sandbox_injection"),
363
+ command: z.string(),
364
+ args: z.array(z.string()),
365
+ exitCode: z.number().int(),
366
+ timedOut: z.boolean(),
367
+ durationMs: z.number().int().min(0),
368
+ stdoutPreview: z.string(),
369
+ stderrPreview: z.string(),
370
+ outputTruncated: z.boolean(),
371
+ });
372
+ export const authClientSeedSchema = z
373
+ .object({
374
+ clientId: z.string().min(1),
375
+ tenantId: tenantIdSchema.default("default"),
376
+ displayName: z.string().min(1),
377
+ secretRef: z.string().min(1).optional(),
378
+ tokenEndpointAuthMethod: authClientAuthMethodSchema.default("client_secret_basic"),
379
+ grantTypes: z.array(authGrantTypeSchema).min(1).default(["client_credentials"]),
380
+ redirectUris: z.array(z.string().url()).default([]),
381
+ jwks: z.array(publicJwkSchema).min(1).optional(),
382
+ roles: z.array(principalRoleSchema).min(1),
383
+ allowedScopes: z.array(accessScopeSchema).min(1),
384
+ status: authClientStatusSchema.default("active"),
385
+ })
386
+ .superRefine((value, ctx) => {
387
+ const supportsAuthCode = value.grantTypes.includes("authorization_code");
388
+ if (supportsAuthCode && value.redirectUris.length === 0) {
389
+ ctx.addIssue({
390
+ code: z.ZodIssueCode.custom,
391
+ path: ["redirectUris"],
392
+ message: "authorization_code clients require at least one redirect URI.",
393
+ });
394
+ }
395
+ if (value.tokenEndpointAuthMethod === "none") {
396
+ if (!supportsAuthCode) {
397
+ ctx.addIssue({
398
+ code: z.ZodIssueCode.custom,
399
+ path: ["grantTypes"],
400
+ message: "Public clients must support authorization_code.",
401
+ });
402
+ }
403
+ if (value.grantTypes.includes("client_credentials")) {
404
+ ctx.addIssue({
405
+ code: z.ZodIssueCode.custom,
406
+ path: ["grantTypes"],
407
+ message: "Public clients cannot use client_credentials.",
408
+ });
409
+ }
410
+ if (value.secretRef) {
411
+ ctx.addIssue({
412
+ code: z.ZodIssueCode.custom,
413
+ path: ["secretRef"],
414
+ message: "Public clients must not define secretRef.",
415
+ });
416
+ }
417
+ return;
418
+ }
419
+ if (value.tokenEndpointAuthMethod === "private_key_jwt") {
420
+ if (!value.jwks?.length) {
421
+ ctx.addIssue({
422
+ code: z.ZodIssueCode.custom,
423
+ path: ["jwks"],
424
+ message: "private_key_jwt clients require at least one public JWK.",
425
+ });
426
+ }
427
+ if (value.secretRef) {
428
+ ctx.addIssue({
429
+ code: z.ZodIssueCode.custom,
430
+ path: ["secretRef"],
431
+ message: "private_key_jwt clients must not define secretRef.",
432
+ });
433
+ }
434
+ return;
435
+ }
436
+ if (!value.secretRef) {
437
+ ctx.addIssue({
438
+ code: z.ZodIssueCode.custom,
439
+ path: ["secretRef"],
440
+ message: "client_secret auth clients require secretRef.",
441
+ });
442
+ }
443
+ });
444
+ export const authClientSeedFileSchema = z.object({
445
+ version: z.number().int().positive(),
446
+ clients: z.array(authClientSeedSchema),
447
+ });
448
+ export const authClientRecordSchema = z.object({
449
+ clientId: z.string().min(1),
450
+ tenantId: tenantIdSchema.default("default"),
451
+ displayName: z.string().min(1),
452
+ roles: z.array(principalRoleSchema).min(1),
453
+ allowedScopes: z.array(accessScopeSchema).min(1),
454
+ status: authClientStatusSchema,
455
+ tokenEndpointAuthMethod: authClientAuthMethodSchema.default("client_secret_basic"),
456
+ grantTypes: z.array(authGrantTypeSchema).min(1).default(["client_credentials"]),
457
+ redirectUris: z.array(z.string().url()).default([]),
458
+ jwks: z.array(publicJwkSchema).default([]),
459
+ });
460
+ export const authClientCreateInputSchema = z
461
+ .object({
462
+ clientId: z.string().min(1),
463
+ tenantId: tenantIdSchema.default("default"),
464
+ displayName: z.string().min(1),
465
+ roles: z.array(principalRoleSchema).min(1),
466
+ allowedScopes: z.array(accessScopeSchema).min(1),
467
+ clientSecret: z.string().min(16).optional(),
468
+ status: authClientStatusSchema.default("active"),
469
+ tokenEndpointAuthMethod: authClientAuthMethodSchema.default("client_secret_basic"),
470
+ grantTypes: z.array(authGrantTypeSchema).min(1).default(["client_credentials"]),
471
+ redirectUris: z.array(z.string().url()).default([]),
472
+ jwks: z.array(publicJwkSchema).min(1).optional(),
473
+ })
474
+ .superRefine((value, ctx) => {
475
+ const supportsAuthCode = value.grantTypes.includes("authorization_code");
476
+ if (supportsAuthCode && value.redirectUris.length === 0) {
477
+ ctx.addIssue({
478
+ code: z.ZodIssueCode.custom,
479
+ path: ["redirectUris"],
480
+ message: "authorization_code clients require at least one redirect URI.",
481
+ });
482
+ }
483
+ if (value.tokenEndpointAuthMethod === "none") {
484
+ if (!supportsAuthCode) {
485
+ ctx.addIssue({
486
+ code: z.ZodIssueCode.custom,
487
+ path: ["grantTypes"],
488
+ message: "Public clients must support authorization_code.",
489
+ });
490
+ }
491
+ if (value.grantTypes.includes("client_credentials")) {
492
+ ctx.addIssue({
493
+ code: z.ZodIssueCode.custom,
494
+ path: ["grantTypes"],
495
+ message: "Public clients cannot use client_credentials.",
496
+ });
497
+ }
498
+ if (value.clientSecret) {
499
+ ctx.addIssue({
500
+ code: z.ZodIssueCode.custom,
501
+ path: ["clientSecret"],
502
+ message: "Public clients do not use shared secrets.",
503
+ });
504
+ }
505
+ if (value.jwks?.length) {
506
+ ctx.addIssue({
507
+ code: z.ZodIssueCode.custom,
508
+ path: ["jwks"],
509
+ message: "Public clients do not use client assertion keys.",
510
+ });
511
+ }
512
+ return;
513
+ }
514
+ if (value.tokenEndpointAuthMethod !== "private_key_jwt") {
515
+ return;
516
+ }
517
+ if (!value.jwks?.length) {
518
+ ctx.addIssue({
519
+ code: z.ZodIssueCode.custom,
520
+ path: ["jwks"],
521
+ message: "private_key_jwt clients require at least one public JWK.",
522
+ });
523
+ }
524
+ if (value.clientSecret) {
525
+ ctx.addIssue({
526
+ code: z.ZodIssueCode.custom,
527
+ path: ["clientSecret"],
528
+ message: "private_key_jwt clients do not use shared secrets.",
529
+ });
530
+ }
531
+ });
532
+ export const authClientUpdateInputSchema = z
533
+ .object({
534
+ displayName: z.string().min(1).optional(),
535
+ roles: z.array(principalRoleSchema).min(1).optional(),
536
+ allowedScopes: z.array(accessScopeSchema).min(1).optional(),
537
+ status: authClientStatusSchema.optional(),
538
+ tokenEndpointAuthMethod: authClientAuthMethodSchema.optional(),
539
+ grantTypes: z.array(authGrantTypeSchema).min(1).optional(),
540
+ redirectUris: z.array(z.string().url()).min(1).optional(),
541
+ jwks: z.array(publicJwkSchema).min(1).optional(),
542
+ })
543
+ .refine((value) => Object.keys(value).length > 0, {
544
+ message: "At least one field must be provided.",
545
+ });
546
+ export const authClientRotateSecretInputSchema = z.object({
547
+ clientSecret: z.string().min(16).optional(),
548
+ });
549
+ export const authClientSecretOutputSchema = z.object({
550
+ client: authClientRecordSchema,
551
+ clientSecret: z.string().min(16).optional(),
552
+ });
553
+ export const tokenIssueInputSchema = z.object({
554
+ clientId: z.string().min(1),
555
+ clientSecret: z.string().min(1).optional(),
556
+ grantType: authGrantTypeSchema,
557
+ scope: z.array(accessScopeSchema).optional(),
558
+ resource: z.string().url().optional(),
559
+ code: z.string().min(1).optional(),
560
+ codeVerifier: z.string().min(43).max(128).optional(),
561
+ redirectUri: z.string().url().optional(),
562
+ refreshToken: z.string().min(1).optional(),
563
+ clientAssertionType: z
564
+ .literal("urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
565
+ .optional(),
566
+ clientAssertion: z.string().min(1).optional(),
567
+ }).superRefine((value, ctx) => {
568
+ if (value.grantType === "authorization_code") {
569
+ if (!value.code) {
570
+ ctx.addIssue({
571
+ code: z.ZodIssueCode.custom,
572
+ path: ["code"],
573
+ message: "authorization_code requires code.",
574
+ });
575
+ }
576
+ if (!value.codeVerifier) {
577
+ ctx.addIssue({
578
+ code: z.ZodIssueCode.custom,
579
+ path: ["codeVerifier"],
580
+ message: "authorization_code requires codeVerifier.",
581
+ });
582
+ }
583
+ if (!value.redirectUri) {
584
+ ctx.addIssue({
585
+ code: z.ZodIssueCode.custom,
586
+ path: ["redirectUri"],
587
+ message: "authorization_code requires redirectUri.",
588
+ });
589
+ }
590
+ }
591
+ if (value.grantType === "refresh_token" && !value.refreshToken) {
592
+ ctx.addIssue({
593
+ code: z.ZodIssueCode.custom,
594
+ path: ["refreshToken"],
595
+ message: "refresh_token requires refreshToken.",
596
+ });
597
+ }
598
+ });
599
+ export const tokenIssueOutputSchema = z.object({
600
+ access_token: z.string().min(1),
601
+ token_type: z.literal("Bearer"),
602
+ expires_in: z.number().int().positive(),
603
+ scope: z.string().min(1),
604
+ refresh_token: z.string().min(1).optional(),
605
+ });
606
+ export const authContextSchema = z.object({
607
+ principal: z.string().min(1),
608
+ clientId: z.string().min(1),
609
+ tenantId: tenantIdSchema.optional(),
610
+ roles: z.array(principalRoleSchema).min(1),
611
+ scopes: z.array(accessScopeSchema).min(1),
612
+ resource: z.string().url().optional(),
613
+ });
614
+ export const approvalRequestSchema = z.object({
615
+ id: z.string().uuid(),
616
+ tenantId: tenantIdSchema.default("default"),
617
+ createdAt: z.string().datetime(),
618
+ expiresAt: z.string().datetime(),
619
+ status: approvalStatusSchema,
620
+ requestedBy: z.string().min(1),
621
+ requestedRoles: z.array(principalRoleSchema).min(1),
622
+ credentialId: z.string().min(1),
623
+ operation: operationSchema,
624
+ targetUrl: z.string().url(),
625
+ targetHost: z.string().min(1),
626
+ reason: z.string().min(1),
627
+ ruleId: z.string().optional(),
628
+ correlationId: z.string().uuid(),
629
+ fingerprint: z.string().min(1),
630
+ requiredApprovals: z.number().int().min(1),
631
+ approvalCount: z.number().int().min(0),
632
+ denialCount: z.number().int().min(0),
633
+ reviews: z.array(z.object({
634
+ reviewId: z.string().uuid(),
635
+ reviewedAt: z.string().datetime(),
636
+ reviewedBy: z.string().min(1),
637
+ decision: reviewDecisionSchema,
638
+ note: z.string().max(1000).optional(),
639
+ })).default([]),
640
+ reviewedBy: z.string().optional(),
641
+ reviewedAt: z.string().datetime().optional(),
642
+ reviewNote: z.string().optional(),
643
+ });
644
+ export const approvalReviewInputSchema = z.object({
645
+ note: z.string().max(1000).optional(),
646
+ });
647
+ export const approvalListOutputSchema = z.object({
648
+ approvals: z.array(approvalRequestSchema),
649
+ });
650
+ export const breakGlassRequestSchema = z.object({
651
+ id: z.string().uuid(),
652
+ tenantId: tenantIdSchema.default("default"),
653
+ createdAt: z.string().datetime(),
654
+ expiresAt: z.string().datetime(),
655
+ status: breakGlassStatusSchema,
656
+ requestedBy: z.string().min(1),
657
+ requestedRoles: z.array(principalRoleSchema).min(1),
658
+ credentialId: z.string().min(1),
659
+ operation: operationSchema,
660
+ targetUrl: z.string().url(),
661
+ targetHost: z.string().min(1),
662
+ justification: z.string().min(12).max(2000),
663
+ requestedDurationSeconds: z.number().int().min(60),
664
+ correlationId: z.string().uuid(),
665
+ fingerprint: z.string().min(1),
666
+ requiredApprovals: z.number().int().min(1),
667
+ approvalCount: z.number().int().min(0),
668
+ denialCount: z.number().int().min(0),
669
+ reviews: z.array(z.object({
670
+ reviewId: z.string().uuid(),
671
+ reviewedAt: z.string().datetime(),
672
+ reviewedBy: z.string().min(1),
673
+ decision: reviewDecisionSchema,
674
+ note: z.string().max(1000).optional(),
675
+ })).default([]),
676
+ reviewedBy: z.string().optional(),
677
+ reviewedAt: z.string().datetime().optional(),
678
+ reviewNote: z.string().max(1000).optional(),
679
+ revokedBy: z.string().optional(),
680
+ revokedAt: z.string().datetime().optional(),
681
+ revokeNote: z.string().max(1000).optional(),
682
+ });
683
+ export const breakGlassRequestInputSchema = z.object({
684
+ credentialId: z.string().min(1),
685
+ operation: operationSchema,
686
+ targetUrl: z.string().url(),
687
+ justification: z.string().min(12).max(2000),
688
+ requestedDurationSeconds: z.number().int().min(60).max(86400).optional(),
689
+ });
690
+ export const breakGlassReviewInputSchema = z.object({
691
+ note: z.string().max(1000).optional(),
692
+ });
693
+ export const breakGlassListOutputSchema = z.object({
694
+ requests: z.array(breakGlassRequestSchema),
695
+ });
696
+ export const authClientListOutputSchema = z.object({
697
+ clients: z.array(authClientRecordSchema),
698
+ });
699
+ export const accessTokenRecordSchema = z.object({
700
+ tokenId: z.string().uuid(),
701
+ clientId: z.string().min(1),
702
+ tenantId: tenantIdSchema.default("default"),
703
+ subject: z.string().min(1),
704
+ scopes: z.array(accessScopeSchema).min(1),
705
+ roles: z.array(principalRoleSchema).min(1),
706
+ resource: z.string().url().optional(),
707
+ expiresAt: z.string().datetime(),
708
+ status: accessTokenStatusSchema,
709
+ createdAt: z.string().datetime(),
710
+ lastUsedAt: z.string().datetime().optional(),
711
+ });
712
+ export const accessTokenListOutputSchema = z.object({
713
+ tokens: z.array(accessTokenRecordSchema),
714
+ });
715
+ export const accessTokenRevokeOutputSchema = z.object({
716
+ token: accessTokenRecordSchema.nullable(),
717
+ });
718
+ export const refreshTokenRecordSchema = z.object({
719
+ refreshTokenId: z.string().uuid(),
720
+ clientId: z.string().min(1),
721
+ tenantId: tenantIdSchema.default("default"),
722
+ subject: z.string().min(1),
723
+ scopes: z.array(accessScopeSchema).min(1),
724
+ roles: z.array(principalRoleSchema).min(1),
725
+ resource: z.string().url().optional(),
726
+ expiresAt: z.string().datetime(),
727
+ status: accessTokenStatusSchema,
728
+ createdAt: z.string().datetime(),
729
+ lastUsedAt: z.string().datetime().optional(),
730
+ });
731
+ export const refreshTokenListOutputSchema = z.object({
732
+ tokens: z.array(refreshTokenRecordSchema),
733
+ });
734
+ export const refreshTokenRevokeOutputSchema = z.object({
735
+ token: refreshTokenRecordSchema.nullable(),
736
+ });
737
+ export const authTokenListQuerySchema = z.object({
738
+ clientId: z.string().min(1).optional(),
739
+ status: accessTokenStatusSchema.optional(),
740
+ });
741
+ export const authorizationRequestInputSchema = z.object({
742
+ clientId: z.string().min(1),
743
+ redirectUri: z.string().url(),
744
+ scope: z.array(accessScopeSchema).optional(),
745
+ resource: z.string().url().optional(),
746
+ codeChallenge: z.string().min(43).max(128),
747
+ codeChallengeMethod: authCodeChallengeMethodSchema.default("S256"),
748
+ state: z.string().max(512).optional(),
749
+ });
750
+ export const authorizationRequestOutputSchema = z.object({
751
+ code: z.string().min(1),
752
+ clientId: z.string().min(1),
753
+ tenantId: tenantIdSchema,
754
+ subject: z.string().min(1),
755
+ redirectUri: z.string().url(),
756
+ expiresIn: z.number().int().positive(),
757
+ scope: z.string().min(1),
758
+ state: z.string().max(512).optional(),
759
+ });
760
+ export const tenantRecordSchema = z.object({
761
+ tenantId: tenantIdSchema,
762
+ displayName: z.string().min(1),
763
+ description: z.string().max(2000).optional(),
764
+ status: tenantStatusSchema.default("active"),
765
+ createdAt: z.string().datetime(),
766
+ updatedAt: z.string().datetime(),
767
+ });
768
+ export const tenantCreateInputSchema = z.object({
769
+ tenantId: tenantIdSchema,
770
+ displayName: z.string().min(1),
771
+ description: z.string().max(2000).optional(),
772
+ status: tenantStatusSchema.default("active"),
773
+ });
774
+ export const tenantUpdateInputSchema = z
775
+ .object({
776
+ displayName: z.string().min(1).optional(),
777
+ description: z.string().max(2000).optional(),
778
+ status: tenantStatusSchema.optional(),
779
+ })
780
+ .refine((value) => Object.keys(value).length > 0, {
781
+ message: "At least one field must be provided.",
782
+ });
783
+ export const tenantSummarySchema = tenantRecordSchema.extend({
784
+ credentialCount: z.number().int().min(0),
785
+ authClientCount: z.number().int().min(0),
786
+ activeTokenCount: z.number().int().min(0),
787
+ });
788
+ export const tenantListOutputSchema = z.object({
789
+ tenants: z.array(tenantSummarySchema),
790
+ });
791
+ export const tenantGetOutputSchema = z.object({
792
+ tenant: tenantSummarySchema.nullable(),
793
+ });
794
+ export const tenantBootstrapClientInputSchema = z
795
+ .object({
796
+ clientId: z.string().min(1),
797
+ displayName: z.string().min(1),
798
+ roles: z.array(principalRoleSchema).min(1),
799
+ allowedScopes: z.array(accessScopeSchema).min(1),
800
+ clientSecret: z.string().min(16).optional(),
801
+ status: authClientStatusSchema.default("active"),
802
+ tokenEndpointAuthMethod: authClientAuthMethodSchema.default("client_secret_basic"),
803
+ grantTypes: z.array(authGrantTypeSchema).min(1).default(["client_credentials"]),
804
+ redirectUris: z.array(z.string().url()).default([]),
805
+ jwks: z.array(publicJwkSchema).min(1).optional(),
806
+ })
807
+ .superRefine((value, ctx) => {
808
+ authClientCreateInputSchema.safeParse({
809
+ ...value,
810
+ tenantId: "default",
811
+ }).error?.issues.forEach((issue) => {
812
+ ctx.addIssue({
813
+ ...issue,
814
+ path: issue.path.filter((segment) => segment !== "tenantId"),
815
+ });
816
+ });
817
+ });
818
+ export const tenantBootstrapInputSchema = z.object({
819
+ tenant: tenantCreateInputSchema,
820
+ authClients: z.array(tenantBootstrapClientInputSchema).default([]),
821
+ });
822
+ export const tenantBootstrapOutputSchema = z.object({
823
+ tenant: tenantSummarySchema,
824
+ clients: z.array(authClientSecretOutputSchema),
825
+ });
826
+ export const secretInspectionSchema = z.object({
827
+ adapter: bindingAdapterSchema,
828
+ ref: z.string().min(1),
829
+ status: z.enum(["ok", "warning", "error"]),
830
+ resolved: z.boolean(),
831
+ version: z.string().optional(),
832
+ createdAt: z.string().datetime().optional(),
833
+ updatedAt: z.string().datetime().optional(),
834
+ expiresAt: z.string().datetime().optional(),
835
+ nextRotationAt: z.string().datetime().optional(),
836
+ state: z.string().optional(),
837
+ rotationEnabled: z.boolean().optional(),
838
+ notes: z.array(z.string()).default([]),
839
+ error: z.string().optional(),
840
+ });
841
+ export const credentialStatusReportSchema = z.object({
842
+ credential: credentialSummarySchema,
843
+ runtimeMode: runtimeModeSchema,
844
+ catalogExpiresAt: z.string().datetime().nullable(),
845
+ daysUntilCatalogExpiry: z.number().int().nullable(),
846
+ inspection: secretInspectionSchema,
847
+ });
848
+ export const credentialStatusReportListOutputSchema = z.object({
849
+ reports: z.array(credentialStatusReportSchema),
850
+ });
851
+ export const adapterHealthSchema = z.object({
852
+ adapter: bindingAdapterSchema,
853
+ available: z.boolean(),
854
+ status: z.enum(["ok", "warning", "error"]),
855
+ details: z.string(),
856
+ });
857
+ export const adapterHealthListOutputSchema = z.object({
858
+ adapters: z.array(adapterHealthSchema),
859
+ });
860
+ export const maintenanceTaskResultSchema = z.object({
861
+ approvalsExpired: z.number().int().min(0),
862
+ breakGlassExpired: z.number().int().min(0),
863
+ accessTokensExpired: z.number().int().min(0),
864
+ refreshTokensExpired: z.number().int().min(0),
865
+ rateLimitBucketsDeleted: z.number().int().min(0),
866
+ authorizationCodesExpired: z.number().int().min(0),
867
+ oauthClientAssertionsExpired: z.number().int().min(0),
868
+ });
869
+ export const maintenanceStatusSchema = z.object({
870
+ enabled: z.boolean(),
871
+ intervalMs: z.number().int().min(0),
872
+ running: z.boolean(),
873
+ lastRunAt: z.string().datetime().optional(),
874
+ lastSuccessAt: z.string().datetime().optional(),
875
+ lastDurationMs: z.number().int().min(0).optional(),
876
+ consecutiveFailures: z.number().int().min(0),
877
+ lastError: z.string().optional(),
878
+ lastResult: maintenanceTaskResultSchema.optional(),
879
+ });
880
+ export const maintenanceStatusOutputSchema = z.object({
881
+ maintenance: maintenanceStatusSchema,
882
+ });
883
+ export const traceSpanSchema = z.object({
884
+ spanId: z.string().uuid(),
885
+ traceId: z.string().min(1).max(128),
886
+ parentSpanId: z.string().uuid().optional(),
887
+ name: z.string().min(1),
888
+ startedAt: z.string().datetime(),
889
+ endedAt: z.string().datetime(),
890
+ durationMs: z.number().int().min(0),
891
+ status: z.enum(["ok", "error"]),
892
+ attributes: z.record(z.string(), z.unknown()),
893
+ });
894
+ export const traceListOutputSchema = z.object({
895
+ traceId: z.string().min(1).max(128).optional(),
896
+ traces: z.array(traceSpanSchema),
897
+ });
898
+ export const traceExportStatusSchema = z.object({
899
+ enabled: z.boolean(),
900
+ endpoint: z.string().url().optional(),
901
+ pendingSpans: z.number().int().min(0),
902
+ lastFlushAt: z.string().datetime().optional(),
903
+ lastError: z.string().optional(),
904
+ consecutiveFailures: z.number().int().min(0),
905
+ lastBatchSize: z.number().int().min(0).optional(),
906
+ running: z.boolean(),
907
+ });
908
+ export const traceExportStatusOutputSchema = z.object({
909
+ exporter: traceExportStatusSchema,
910
+ });
911
+ export const rotationRunSchema = z.object({
912
+ id: z.string().uuid(),
913
+ tenantId: tenantIdSchema.default("default"),
914
+ credentialId: z.string().min(1),
915
+ status: rotationRunStatusSchema,
916
+ source: rotationRunSourceSchema,
917
+ reason: z.string().min(1),
918
+ dueAt: z.string().datetime().optional(),
919
+ plannedAt: z.string().datetime(),
920
+ startedAt: z.string().datetime().optional(),
921
+ completedAt: z.string().datetime().optional(),
922
+ plannedBy: z.string().min(1),
923
+ updatedBy: z.string().min(1),
924
+ note: z.string().max(2000).optional(),
925
+ targetRef: z.string().min(1).optional(),
926
+ resultNote: z.string().max(2000).optional(),
927
+ });
928
+ export const rotationRunListOutputSchema = z.object({
929
+ rotations: z.array(rotationRunSchema),
930
+ });
931
+ export const rotationPlanInputSchema = z.object({
932
+ horizonDays: z.number().int().min(1).max(365).default(14),
933
+ credentialIds: z.array(z.string().min(1)).max(100).optional(),
934
+ });
935
+ export const rotationCreateInputSchema = z.object({
936
+ credentialId: z.string().min(1),
937
+ reason: z.string().min(8).max(2000),
938
+ dueAt: z.string().datetime().optional(),
939
+ note: z.string().max(2000).optional(),
940
+ });
941
+ export const rotationTransitionInputSchema = z.object({
942
+ note: z.string().max(2000).optional(),
943
+ });
944
+ export const rotationCompleteInputSchema = z.object({
945
+ note: z.string().max(2000).optional(),
946
+ targetRef: z.string().min(1).optional(),
947
+ expiresAt: z.string().datetime().nullable().optional(),
948
+ lastValidatedAt: z.string().datetime().optional(),
949
+ });
950
+ export const backupSummarySchema = z.object({
951
+ format: z.literal("keylore-logical-backup"),
952
+ version: z.number().int().positive(),
953
+ sourceVersion: z.string().min(1),
954
+ createdAt: z.string().datetime(),
955
+ tenants: z.number().int().min(0),
956
+ credentials: z.number().int().min(0),
957
+ authClients: z.number().int().min(0),
958
+ accessTokens: z.number().int().min(0),
959
+ refreshTokens: z.number().int().min(0),
960
+ approvals: z.number().int().min(0),
961
+ breakGlassRequests: z.number().int().min(0),
962
+ rotationRuns: z.number().int().min(0),
963
+ auditEvents: z.number().int().min(0),
964
+ });
965
+ export const backupInspectOutputSchema = z.object({
966
+ backup: backupSummarySchema,
967
+ });