Repository navigation
Expand file tree
/
Copy pathscenario.ts
More file actions
109 lines (99 loc) · 3.17 KB
/
Copy pathscenario.ts
File metadata and controls
109 lines (99 loc) · 3.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import { z } from "zod";
import { featureId } from "./feature";
import { kebabId, revisionRefSchema } from "./spec";
export const actorKind = z.enum([
"user",
"agent",
"mcp-client",
"mcp-server",
"authorization-server",
"resource",
"idp",
]);
export type ActorKind = z.infer<typeof actorKind>;
export const actorSchema = z.object({
id: kebabId,
kind: actorKind,
name: z.string().min(1),
});
export type Actor = z.infer<typeof actorSchema>;
/** A revision reference plus the document's own heading or anchor. */
export const citationSchema = revisionRefSchema.extend({ section: z.string().min(1) });
export type Citation = z.infer<typeof citationSchema>;
/** A claim value: scalar, list of strings, or a nested map (for `act`). */
export type TokenValue = string | number | boolean | string[] | { [key: string]: TokenValue };
export const tokenValueSchema: z.ZodType<TokenValue> = z.lazy(() =>
z.union([
z.string(),
z.number(),
z.boolean(),
z.array(z.string()),
z.record(z.string(), tokenValueSchema),
]),
);
/** A decoded token as the message carries it; change marks are derived, never stored. */
export const tokenViewSchema = z.object({
name: z.string().min(1),
header: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional(),
claims: z.record(z.string(), tokenValueSchema),
note: z.string().min(1).optional(),
});
export type TokenView = z.infer<typeof tokenViewSchema>;
export const stepSchema = z.object({
id: kebabId,
from: kebabId,
to: kebabId,
message: z.string().min(1),
note: z.string().min(1).optional(),
cites: citationSchema,
token: tokenViewSchema.optional(),
});
export type Step = z.infer<typeof stepSchema>;
export const toggleKind = z.enum(["attack", "misconfiguration"]);
export type ToggleKind = z.infer<typeof toggleKind>;
export const toggleSchema = z.object({
id: kebabId,
name: z.string().min(1),
kind: toggleKind,
summary: z.string().min(1),
cites: citationSchema,
/** Baseline step id → the steps that take its place; an empty list removes the step. */
replaces: z.record(kebabId, z.array(stepSchema)),
violates: z.array(featureId).optional(),
vulnerability: kebabId.optional(),
});
export type Toggle = z.infer<typeof toggleSchema>;
function uniqueIds(
ctx: z.RefinementCtx,
items: { id: string }[],
path: (string | number)[],
what: string,
) {
const seen = new Set<string>();
for (const [i, item] of items.entries()) {
if (seen.has(item.id)) {
ctx.addIssue({
code: "custom",
path: [...path, i, "id"],
message: `${what} id "${item.id}" listed twice`,
});
}
seen.add(item.id);
}
}
export const scenarioSchema = z
.object({
id: kebabId,
title: z.string().min(1),
summary: z.string().min(1),
actors: z.array(actorSchema).min(2),
features: z.array(featureId),
steps: z.array(stepSchema).min(1),
toggles: z.array(toggleSchema).default([]),
})
.superRefine((scenario, ctx) => {
uniqueIds(ctx, scenario.actors, ["actors"], "actor");
uniqueIds(ctx, scenario.steps, ["steps"], "step");
uniqueIds(ctx, scenario.toggles, ["toggles"], "toggle");
});
export type Scenario = z.infer<typeof scenarioSchema>;