@highflame/policy 2.1.41 → 2.1.42
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/_schemas/ai_gateway/context.json +60 -0
- package/_schemas/ai_gateway/schema.cedarschema +15 -0
- package/_schemas/guardrails/context.json +60 -0
- package/_schemas/guardrails/schema.cedarschema +15 -0
- package/_schemas/overwatch/context.json +60 -0
- package/_schemas/overwatch/schema.cedarschema +15 -0
- package/_schemas/sentry/context.json +48 -0
- package/_schemas/sentry/schema.cedarschema +12 -0
- package/dist/aarm-annotation.d.ts +120 -0
- package/dist/aarm-annotation.js +494 -0
- package/dist/aarm-annotations.gen.js +1 -1
- package/dist/ai_gateway-context.gen.d.ts +2 -0
- package/dist/ai_gateway-context.gen.js +2 -0
- package/dist/guardrails-context.gen.d.ts +2 -0
- package/dist/guardrails-context.gen.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/overwatch-context.gen.d.ts +2 -0
- package/dist/overwatch-context.gen.js +2 -0
- package/dist/sentry-context.gen.d.ts +2 -0
- package/dist/sentry-context.gen.js +2 -0
- package/dist/service-schemas.gen.d.ts +4 -4
- package/dist/service-schemas.gen.js +95 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.js +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AARM-Aware Annotation Parser (TypeScript)
|
|
3
|
+
*
|
|
4
|
+
* Runtime layer over the generated AARM annotation registry
|
|
5
|
+
* (aarm-annotations.gen.ts). Ported from packages/go/aarm_annotation.go to
|
|
6
|
+
* give Studio's policy editor the SAME parse + validation rules Shield runs
|
|
7
|
+
* at policy sync time (Go's ValidateAARMAnnotations). The two
|
|
8
|
+
* implementations share a test corpus — see aarm-annotation.test.ts and its
|
|
9
|
+
* Go twin aarm_annotation_test.go.
|
|
10
|
+
*
|
|
11
|
+
* Takes the `Record<string, string>` of a policy's @-annotations and
|
|
12
|
+
* produces:
|
|
13
|
+
*
|
|
14
|
+
* - typed directives Studio (and any TS consumer) can switch on
|
|
15
|
+
* (StepUpRequiredDirective, DeferBelowConfidenceDirective, ...) and
|
|
16
|
+
* - a list of typed validation errors (AARMAnnotationError).
|
|
17
|
+
*
|
|
18
|
+
* Three encoding shapes are accepted, matching the value_encoding block in
|
|
19
|
+
* schemas/annotations.json:
|
|
20
|
+
*
|
|
21
|
+
* 1. Marker: `@defer_on_conflict("")` → no params
|
|
22
|
+
* 2. Single-param positional: `@defer_below_confidence("0.7")` → bare value
|
|
23
|
+
* binds to the single required-positional parameter declared in the
|
|
24
|
+
* registry. Fails closed if no such parameter exists.
|
|
25
|
+
* 3. Multi-param key=value: `@step_up_required("role=finance_lead,timeout_seconds=3600")`
|
|
26
|
+
* parameters are comma-separated key=value pairs. Unknown keys error;
|
|
27
|
+
* missing required parameters error.
|
|
28
|
+
*
|
|
29
|
+
* Generic Cedar annotations (@id, @name, @severity, @tags, arbitrary custom
|
|
30
|
+
* KV pairs) are NOT parsed here — those live in annotations.ts and remain
|
|
31
|
+
* free-form. AARM annotations are a strict subset whose key MUST appear in
|
|
32
|
+
* AARM_ANNOTATION_BY_KEY.
|
|
33
|
+
*
|
|
34
|
+
* Validation is fail-closed (AARM R3/R4): a malformed AARM annotation never
|
|
35
|
+
* silently degrades to a no-op — the caller is told exactly which
|
|
36
|
+
* annotation, which parameter, and what was wrong, so Admin can reject the
|
|
37
|
+
* policy at deploy time and Studio's lint can surface it inline.
|
|
38
|
+
*/
|
|
39
|
+
import { AARM_ANNOTATION_BY_KEY, } from './aarm-annotations.gen.js';
|
|
40
|
+
/** True iff at least one AARM directive was parsed. */
|
|
41
|
+
export function hasAnyAARMDirective(d) {
|
|
42
|
+
if (!d)
|
|
43
|
+
return false;
|
|
44
|
+
return (d.stepUpRequired !== undefined ||
|
|
45
|
+
d.deferOnConflict !== undefined ||
|
|
46
|
+
d.deferBelowConfidence !== undefined ||
|
|
47
|
+
d.deferUntilContext !== undefined);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Structured error for a malformed AARM annotation. Mirrors Go's
|
|
51
|
+
* AARMAnnotationError. Studio's lint surfaces these inline; Admin serializes
|
|
52
|
+
* them in the policy-create rejection.
|
|
53
|
+
*/
|
|
54
|
+
export class AARMAnnotationError extends Error {
|
|
55
|
+
key;
|
|
56
|
+
parameter;
|
|
57
|
+
rawValue;
|
|
58
|
+
reason;
|
|
59
|
+
constructor(init) {
|
|
60
|
+
const key = init.key;
|
|
61
|
+
const parameter = init.parameter ?? '';
|
|
62
|
+
const rawValue = init.rawValue ?? '';
|
|
63
|
+
const reason = init.reason;
|
|
64
|
+
// Message format matches Go's AARMAnnotationError.Error().
|
|
65
|
+
const message = parameter !== ''
|
|
66
|
+
? `AARM annotation "${key}" parameter "${parameter}": ${reason} (got "${rawValue}")`
|
|
67
|
+
: `AARM annotation "${key}": ${reason} (got "${rawValue}")`;
|
|
68
|
+
super(message);
|
|
69
|
+
this.name = 'AARMAnnotationError';
|
|
70
|
+
this.key = key;
|
|
71
|
+
this.parameter = parameter;
|
|
72
|
+
this.rawValue = rawValue;
|
|
73
|
+
this.reason = reason;
|
|
74
|
+
Object.setPrototypeOf(this, AARMAnnotationError.prototype);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** True iff err is an AARMAnnotationError. */
|
|
78
|
+
export function isAARMAnnotationError(err) {
|
|
79
|
+
return err instanceof AARMAnnotationError;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Walks the raw annotation map and extracts every AARM-recognized annotation
|
|
83
|
+
* into typed directives. Non-AARM keys are ignored (handled by the generic
|
|
84
|
+
* annotation surface in annotations.ts).
|
|
85
|
+
*
|
|
86
|
+
* Errors are COLLECTED, not fail-fast, so a single call surfaces every
|
|
87
|
+
* offending annotation at once — Studio shows all squiggles in one pass.
|
|
88
|
+
* Keys are processed in sorted order for stable error reporting.
|
|
89
|
+
*/
|
|
90
|
+
export function parseAARMAnnotations(raw) {
|
|
91
|
+
const directives = {};
|
|
92
|
+
const errors = [];
|
|
93
|
+
if (!raw) {
|
|
94
|
+
return { directives, errors };
|
|
95
|
+
}
|
|
96
|
+
const keys = Object.keys(raw).sort();
|
|
97
|
+
for (const key of keys) {
|
|
98
|
+
const def = AARM_ANNOTATION_BY_KEY[key];
|
|
99
|
+
if (!def) {
|
|
100
|
+
continue; // not an AARM-recognized annotation; ignore
|
|
101
|
+
}
|
|
102
|
+
const value = raw[key];
|
|
103
|
+
const parsed = parseAARMAnnotationParams(def, value);
|
|
104
|
+
if (parsed.error) {
|
|
105
|
+
errors.push(parsed.error);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const params = parsed.params;
|
|
109
|
+
switch (def.key) {
|
|
110
|
+
case 'step_up_required': {
|
|
111
|
+
const r = buildStepUpRequired(def, params, value);
|
|
112
|
+
if (r.error)
|
|
113
|
+
errors.push(r.error);
|
|
114
|
+
else
|
|
115
|
+
directives.stepUpRequired = r.directive;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case 'defer_on_conflict':
|
|
119
|
+
directives.deferOnConflict = {};
|
|
120
|
+
break;
|
|
121
|
+
case 'defer_below_confidence': {
|
|
122
|
+
const r = buildDeferBelowConfidence(def, params, value);
|
|
123
|
+
if (r.error)
|
|
124
|
+
errors.push(r.error);
|
|
125
|
+
else
|
|
126
|
+
directives.deferBelowConfidence = r.directive;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case 'defer_until_context': {
|
|
130
|
+
const r = buildDeferUntilContext(def, params, value);
|
|
131
|
+
if (r.error)
|
|
132
|
+
errors.push(r.error);
|
|
133
|
+
else
|
|
134
|
+
directives.deferUntilContext = r.directive;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
default:
|
|
138
|
+
// Registry entry exists but no typed extractor is wired here — a
|
|
139
|
+
// programming error in highflame-policy. Fail closed at runtime.
|
|
140
|
+
errors.push(new AARMAnnotationError({
|
|
141
|
+
key,
|
|
142
|
+
rawValue: value,
|
|
143
|
+
reason: 'registry entry exists but no typed extractor is wired in ' +
|
|
144
|
+
'aarm-annotation.ts; this is a programming error in highflame-policy',
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return { directives, errors };
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Validate-only variant: returns the error list without the typed
|
|
152
|
+
* directives. Suitable for Admin's policy-create endpoint, which only needs
|
|
153
|
+
* the yes/no verdict + error list.
|
|
154
|
+
*/
|
|
155
|
+
export function validateAARMAnnotations(raw) {
|
|
156
|
+
return parseAARMAnnotations(raw).errors;
|
|
157
|
+
}
|
|
158
|
+
/** True iff key is a platform-recognized AARM annotation. */
|
|
159
|
+
export function isAARMAnnotationKey(key) {
|
|
160
|
+
return Object.prototype.hasOwnProperty.call(AARM_ANNOTATION_BY_KEY, key);
|
|
161
|
+
}
|
|
162
|
+
// Hard cap on a raw annotation value's length, enforced before any regex runs.
|
|
163
|
+
// AARM annotation values are short by construction — a role + timeout, a
|
|
164
|
+
// threshold, a dotted context-field path; the longest legitimate value is well
|
|
165
|
+
// under 100 chars. Bounding length here keeps every regex below (including the
|
|
166
|
+
// backtracking float matcher and the registry-sourced `defer_until_context`
|
|
167
|
+
// pattern) in trivially-linear territory, closing a ReDoS vector that would
|
|
168
|
+
// otherwise be reachable from policy-author input — this parser runs in Studio
|
|
169
|
+
// lint AND Admin's policy-create endpoint. (Go's RE2 + strconv are linear and
|
|
170
|
+
// don't share this hazard, so the cap is a TS-specific guard.)
|
|
171
|
+
const MAX_AARM_VALUE_LEN = 512;
|
|
172
|
+
// Compiled-pattern cache for registry-sourced regexes (defer_until_context's
|
|
173
|
+
// `field` pattern). Registry patterns are build-time, bounded, and immutable
|
|
174
|
+
// for the process lifetime, so compile once and reuse — parity with Go's
|
|
175
|
+
// compileRegistryPattern cache, and avoids recompiling on every parse call.
|
|
176
|
+
const patternCache = new Map();
|
|
177
|
+
/**
|
|
178
|
+
* Normalizes a raw Cedar annotation value into a `name → string-value` map,
|
|
179
|
+
* honoring the three encoding shapes. Per-parameter coercion + bounds happen
|
|
180
|
+
* in the per-directive builders.
|
|
181
|
+
*/
|
|
182
|
+
function parseAARMAnnotationParams(def, raw) {
|
|
183
|
+
const values = {};
|
|
184
|
+
// The public type is Record<string,string>, but at runtime a value can be
|
|
185
|
+
// non-string — e.g. `{"step_up_required": null}` from a deserialized policy,
|
|
186
|
+
// or a JS caller. Guard before any string op so a malformed value yields a
|
|
187
|
+
// structured error instead of a TypeError that crashes the validating
|
|
188
|
+
// request (fail-closed, consistent with the rest of the parser).
|
|
189
|
+
if (typeof raw !== 'string') {
|
|
190
|
+
return {
|
|
191
|
+
params: {},
|
|
192
|
+
error: new AARMAnnotationError({
|
|
193
|
+
key: def.key,
|
|
194
|
+
rawValue: raw === undefined ? 'undefined' : raw === null ? 'null' : String(raw),
|
|
195
|
+
reason: 'annotation value must be a string',
|
|
196
|
+
}),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (raw.length > MAX_AARM_VALUE_LEN) {
|
|
200
|
+
return {
|
|
201
|
+
params: {},
|
|
202
|
+
error: new AARMAnnotationError({
|
|
203
|
+
key: def.key,
|
|
204
|
+
// Truncate so the error itself doesn't echo the oversized value.
|
|
205
|
+
rawValue: `${raw.slice(0, 64)}…`,
|
|
206
|
+
reason: `annotation value exceeds the maximum length of ${MAX_AARM_VALUE_LEN} characters`,
|
|
207
|
+
}),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
// Marker annotation — Cedar requires a string value, so authors pass
|
|
211
|
+
// anything; PRESENCE is the signal.
|
|
212
|
+
if (def.parameters.length === 0) {
|
|
213
|
+
return { params: values };
|
|
214
|
+
}
|
|
215
|
+
if (raw.includes('=')) {
|
|
216
|
+
// Multi-param k=v (also covers a single named param).
|
|
217
|
+
for (const segment of raw.split(',')) {
|
|
218
|
+
const seg = segment.trim();
|
|
219
|
+
if (seg === '')
|
|
220
|
+
continue;
|
|
221
|
+
const eq = seg.indexOf('=');
|
|
222
|
+
if (eq <= 0 || eq === seg.length - 1) {
|
|
223
|
+
return {
|
|
224
|
+
params: {},
|
|
225
|
+
error: new AARMAnnotationError({
|
|
226
|
+
key: def.key,
|
|
227
|
+
rawValue: raw,
|
|
228
|
+
reason: `malformed key=value pair "${seg}"`,
|
|
229
|
+
}),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const k = seg.slice(0, eq).trim();
|
|
233
|
+
const v = seg.slice(eq + 1).trim();
|
|
234
|
+
if (!hasParameter(def, k)) {
|
|
235
|
+
return {
|
|
236
|
+
params: {},
|
|
237
|
+
error: new AARMAnnotationError({
|
|
238
|
+
key: def.key,
|
|
239
|
+
parameter: k,
|
|
240
|
+
rawValue: v,
|
|
241
|
+
reason: `unknown parameter; allowed: ${parameterNames(def).join(', ')}`,
|
|
242
|
+
}),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
values[k] = v;
|
|
246
|
+
}
|
|
247
|
+
return { params: values };
|
|
248
|
+
}
|
|
249
|
+
// Single-positional shorthand. Bind the whole value to the positional param.
|
|
250
|
+
const pos = positionalParameter(def);
|
|
251
|
+
if (!pos) {
|
|
252
|
+
return {
|
|
253
|
+
params: {},
|
|
254
|
+
error: new AARMAnnotationError({
|
|
255
|
+
key: def.key,
|
|
256
|
+
rawValue: raw,
|
|
257
|
+
reason: 'bare value provided but the annotation has no positional parameter; ' +
|
|
258
|
+
'use the key=value form',
|
|
259
|
+
}),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
values[pos.name] = raw.trim();
|
|
263
|
+
return { params: values };
|
|
264
|
+
}
|
|
265
|
+
function buildStepUpRequired(def, params, raw) {
|
|
266
|
+
const role = requireStringParam(def, params, 'role', raw);
|
|
267
|
+
if ('error' in role)
|
|
268
|
+
return { error: role.error };
|
|
269
|
+
const timeoutDef = mustLookupParam(def, 'timeout_seconds');
|
|
270
|
+
const defaultTimeout = timeoutDef.default && timeoutDef.default.kind === 'int' ? timeoutDef.default.value : 0;
|
|
271
|
+
const timeout = optionalIntParam(def, params, 'timeout_seconds', defaultTimeout, raw);
|
|
272
|
+
if ('error' in timeout)
|
|
273
|
+
return { error: timeout.error };
|
|
274
|
+
const bounds = checkIntBounds(def, 'timeout_seconds', timeout.value, timeoutDef, raw);
|
|
275
|
+
if (bounds)
|
|
276
|
+
return { error: bounds };
|
|
277
|
+
return { directive: { role: role.value, timeoutSeconds: timeout.value } };
|
|
278
|
+
}
|
|
279
|
+
function buildDeferBelowConfidence(def, params, raw) {
|
|
280
|
+
const thresholdDef = mustLookupParam(def, 'threshold');
|
|
281
|
+
const threshold = requireFloatParam(def, params, 'threshold', raw);
|
|
282
|
+
if ('error' in threshold)
|
|
283
|
+
return { error: threshold.error };
|
|
284
|
+
const bounds = checkFloatBounds(def, 'threshold', threshold.value, thresholdDef, raw);
|
|
285
|
+
if (bounds)
|
|
286
|
+
return { error: bounds };
|
|
287
|
+
return { directive: { threshold: threshold.value } };
|
|
288
|
+
}
|
|
289
|
+
function buildDeferUntilContext(def, params, raw) {
|
|
290
|
+
const field = requireStringParam(def, params, 'field', raw);
|
|
291
|
+
if ('error' in field)
|
|
292
|
+
return { error: field.error };
|
|
293
|
+
const fieldDef = mustLookupParam(def, 'field');
|
|
294
|
+
if (fieldDef.pattern !== '') {
|
|
295
|
+
let re = patternCache.get(fieldDef.pattern);
|
|
296
|
+
if (!re) {
|
|
297
|
+
try {
|
|
298
|
+
re = new RegExp(fieldDef.pattern);
|
|
299
|
+
}
|
|
300
|
+
catch (e) {
|
|
301
|
+
return {
|
|
302
|
+
error: new AARMAnnotationError({
|
|
303
|
+
key: def.key,
|
|
304
|
+
parameter: 'field',
|
|
305
|
+
rawValue: field.value,
|
|
306
|
+
reason: `registry pattern "${fieldDef.pattern}" does not compile: ${String(e)} (registry bug)`,
|
|
307
|
+
}),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
patternCache.set(fieldDef.pattern, re);
|
|
311
|
+
}
|
|
312
|
+
if (!re.test(field.value)) {
|
|
313
|
+
return {
|
|
314
|
+
error: new AARMAnnotationError({
|
|
315
|
+
key: def.key,
|
|
316
|
+
parameter: 'field',
|
|
317
|
+
rawValue: field.value,
|
|
318
|
+
reason: `value does not match required pattern "${fieldDef.pattern}" (Cedar context-attribute path)`,
|
|
319
|
+
}),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return { directive: { field: field.value } };
|
|
324
|
+
}
|
|
325
|
+
// Go's strconv.ParseFloat accepts NaN/Inf as valid float SYNTAX; we reject
|
|
326
|
+
// them as non-finite (parity with requireFloatParam's finite-number gate).
|
|
327
|
+
const NON_FINITE_RE = /^[+-]?(nan|inf|infinity)$/i;
|
|
328
|
+
const FLOAT_RE = /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/;
|
|
329
|
+
const INT_RE = /^[+-]?\d+$/;
|
|
330
|
+
function requireStringParam(def, params, name, raw) {
|
|
331
|
+
const v = params[name];
|
|
332
|
+
if (v === undefined || v === '') {
|
|
333
|
+
return {
|
|
334
|
+
error: new AARMAnnotationError({
|
|
335
|
+
key: def.key,
|
|
336
|
+
parameter: name,
|
|
337
|
+
rawValue: raw,
|
|
338
|
+
reason: 'required parameter is missing',
|
|
339
|
+
}),
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
return { value: v };
|
|
343
|
+
}
|
|
344
|
+
function requireFloatParam(def, params, name, raw) {
|
|
345
|
+
const v = params[name];
|
|
346
|
+
if (v === undefined || v === '') {
|
|
347
|
+
return {
|
|
348
|
+
error: new AARMAnnotationError({
|
|
349
|
+
key: def.key,
|
|
350
|
+
parameter: name,
|
|
351
|
+
rawValue: raw,
|
|
352
|
+
reason: 'required parameter is missing',
|
|
353
|
+
}),
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
// AARM R3/R4 fail-closed: NaN/Inf parse as "valid float syntax" in Go and
|
|
357
|
+
// would slip past bounds checks (NaN comparisons are uniformly false),
|
|
358
|
+
// letting Shield emit a directive that never fires. Reject up front.
|
|
359
|
+
if (NON_FINITE_RE.test(v)) {
|
|
360
|
+
return {
|
|
361
|
+
error: new AARMAnnotationError({
|
|
362
|
+
key: def.key,
|
|
363
|
+
parameter: name,
|
|
364
|
+
rawValue: v,
|
|
365
|
+
reason: 'value must be a finite number (NaN, +Inf, -Inf are rejected)',
|
|
366
|
+
}),
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
if (!FLOAT_RE.test(v)) {
|
|
370
|
+
return {
|
|
371
|
+
error: new AARMAnnotationError({
|
|
372
|
+
key: def.key,
|
|
373
|
+
parameter: name,
|
|
374
|
+
rawValue: v,
|
|
375
|
+
reason: 'value is not a valid float',
|
|
376
|
+
}),
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
const f = Number(v);
|
|
380
|
+
if (!Number.isFinite(f)) {
|
|
381
|
+
return {
|
|
382
|
+
error: new AARMAnnotationError({
|
|
383
|
+
key: def.key,
|
|
384
|
+
parameter: name,
|
|
385
|
+
rawValue: v,
|
|
386
|
+
reason: 'value must be a finite number (NaN, +Inf, -Inf are rejected)',
|
|
387
|
+
}),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
return { value: f };
|
|
391
|
+
}
|
|
392
|
+
function optionalIntParam(def, params, name, defaultValue, raw) {
|
|
393
|
+
const v = params[name];
|
|
394
|
+
if (v === undefined || v === '') {
|
|
395
|
+
return { value: defaultValue };
|
|
396
|
+
}
|
|
397
|
+
if (!INT_RE.test(v)) {
|
|
398
|
+
return {
|
|
399
|
+
error: new AARMAnnotationError({
|
|
400
|
+
key: def.key,
|
|
401
|
+
parameter: name,
|
|
402
|
+
rawValue: v,
|
|
403
|
+
reason: 'value is not a valid int',
|
|
404
|
+
}),
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
return { value: Number(v) };
|
|
408
|
+
}
|
|
409
|
+
function checkFloatBounds(def, name, value, pdef, raw) {
|
|
410
|
+
// Belt-and-braces with requireFloatParam: a non-finite value reaching here
|
|
411
|
+
// (via a future code path that skips it) must still be rejected.
|
|
412
|
+
if (!Number.isFinite(value)) {
|
|
413
|
+
return new AARMAnnotationError({
|
|
414
|
+
key: def.key,
|
|
415
|
+
parameter: name,
|
|
416
|
+
rawValue: raw,
|
|
417
|
+
reason: 'value must be a finite number (NaN, +Inf, -Inf are rejected)',
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
if (pdef.min !== null) {
|
|
421
|
+
const min = pdef.min.value;
|
|
422
|
+
if (value < min) {
|
|
423
|
+
return new AARMAnnotationError({
|
|
424
|
+
key: def.key,
|
|
425
|
+
parameter: name,
|
|
426
|
+
rawValue: raw,
|
|
427
|
+
reason: `value ${value} is below the minimum ${min}`,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (pdef.max !== null) {
|
|
432
|
+
const max = pdef.max.value;
|
|
433
|
+
if (value > max) {
|
|
434
|
+
return new AARMAnnotationError({
|
|
435
|
+
key: def.key,
|
|
436
|
+
parameter: name,
|
|
437
|
+
rawValue: raw,
|
|
438
|
+
reason: `value ${value} is above the maximum ${max}`,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return undefined;
|
|
443
|
+
}
|
|
444
|
+
function checkIntBounds(def, name, value, pdef, raw) {
|
|
445
|
+
if (pdef.min !== null) {
|
|
446
|
+
const min = pdef.min.value;
|
|
447
|
+
if (value < min) {
|
|
448
|
+
return new AARMAnnotationError({
|
|
449
|
+
key: def.key,
|
|
450
|
+
parameter: name,
|
|
451
|
+
rawValue: raw,
|
|
452
|
+
reason: `value ${value} is below the minimum ${min}`,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (pdef.max !== null) {
|
|
457
|
+
const max = pdef.max.value;
|
|
458
|
+
if (value > max) {
|
|
459
|
+
return new AARMAnnotationError({
|
|
460
|
+
key: def.key,
|
|
461
|
+
parameter: name,
|
|
462
|
+
rawValue: raw,
|
|
463
|
+
reason: `value ${value} is above the maximum ${max}`,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return undefined;
|
|
468
|
+
}
|
|
469
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
470
|
+
// Registry-introspection helpers
|
|
471
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
472
|
+
function hasParameter(def, name) {
|
|
473
|
+
return def.parameters.some((p) => p.name === name);
|
|
474
|
+
}
|
|
475
|
+
function parameterNames(def) {
|
|
476
|
+
return def.parameters.map((p) => p.name);
|
|
477
|
+
}
|
|
478
|
+
function positionalParameter(def) {
|
|
479
|
+
return def.parameters.find((p) => p.positional);
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Returns the named parameter def; throws if absent. Used only for
|
|
483
|
+
* parameters the builder REQUIRES the registry to declare — a missing entry
|
|
484
|
+
* means aarm-annotation.ts drifted from schemas/annotations.json, and a loud
|
|
485
|
+
* throw surfaces it at the test boundary rather than a later undefined-read.
|
|
486
|
+
*/
|
|
487
|
+
function mustLookupParam(def, name) {
|
|
488
|
+
const p = def.parameters.find((x) => x.name === name);
|
|
489
|
+
if (!p) {
|
|
490
|
+
throw new Error(`aarm-annotation.ts: registry entry for "${def.key}" does not declare ` +
|
|
491
|
+
`parameter "${name}" (schemas/annotations.json drift)`);
|
|
492
|
+
}
|
|
493
|
+
return p;
|
|
494
|
+
}
|
|
@@ -66,7 +66,7 @@ export const AARM_ANNOTATIONS = [
|
|
|
66
66
|
},
|
|
67
67
|
{
|
|
68
68
|
key: 'step_up_required',
|
|
69
|
-
description: 'Suspend the action pending human approval from an approver with the named role. AARM R4 STEP_UP decision: action does not execute until POST /
|
|
69
|
+
description: 'Suspend the action pending human approval from an approver with the named role. AARM R4 STEP_UP decision: Shield issues an OpenID CIBA bc-authorize challenge and the action does not execute until an approver carrying the role resolves it (POST /oauth2/bc-authorize/{auth_req_id}/approve via AuthN), OR timeout_seconds elapses (fail-closed: timeout DENYs the action, never permits).',
|
|
70
70
|
aarmRequirement: 'R4',
|
|
71
71
|
promotesCapability: 'CAP-ENF-004',
|
|
72
72
|
decisionEffect: 'step_up',
|
|
@@ -30,7 +30,9 @@ export declare const AiGatewayContextKey: {
|
|
|
30
30
|
readonly PiiDetected: "pii_detected";
|
|
31
31
|
readonly PiiScore: "pii_score";
|
|
32
32
|
readonly PiiTypes: "pii_types";
|
|
33
|
+
readonly PrivilegeScope: "privilege_scope";
|
|
33
34
|
readonly ProfanityScore: "profanity_score";
|
|
35
|
+
readonly Role: "role";
|
|
34
36
|
readonly RugPullDetected: "rug_pull_detected";
|
|
35
37
|
readonly RugPullScore: "rug_pull_score";
|
|
36
38
|
readonly SecretCount: "secret_count";
|
|
@@ -32,7 +32,9 @@ export const AiGatewayContextKey = {
|
|
|
32
32
|
PiiDetected: 'pii_detected',
|
|
33
33
|
PiiScore: 'pii_score',
|
|
34
34
|
PiiTypes: 'pii_types',
|
|
35
|
+
PrivilegeScope: 'privilege_scope',
|
|
35
36
|
ProfanityScore: 'profanity_score',
|
|
37
|
+
Role: 'role',
|
|
36
38
|
RugPullDetected: 'rug_pull_detected',
|
|
37
39
|
RugPullScore: 'rug_pull_score',
|
|
38
40
|
SecretCount: 'secret_count',
|
|
@@ -77,8 +77,10 @@ export declare const GuardrailsContextKey: {
|
|
|
77
77
|
readonly PiiDetected: "pii_detected";
|
|
78
78
|
readonly PiiScore: "pii_score";
|
|
79
79
|
readonly PiiTypes: "pii_types";
|
|
80
|
+
readonly PrivilegeScope: "privilege_scope";
|
|
80
81
|
readonly ProfanityScore: "profanity_score";
|
|
81
82
|
readonly RequestId: "request_id";
|
|
83
|
+
readonly Role: "role";
|
|
82
84
|
readonly RugPullDetected: "rug_pull_detected";
|
|
83
85
|
readonly RugPullScore: "rug_pull_score";
|
|
84
86
|
readonly RugPullType: "rug_pull_type";
|
|
@@ -79,8 +79,10 @@ export const GuardrailsContextKey = {
|
|
|
79
79
|
PiiDetected: 'pii_detected',
|
|
80
80
|
PiiScore: 'pii_score',
|
|
81
81
|
PiiTypes: 'pii_types',
|
|
82
|
+
PrivilegeScope: 'privilege_scope',
|
|
82
83
|
ProfanityScore: 'profanity_score',
|
|
83
84
|
RequestId: 'request_id',
|
|
85
|
+
Role: 'role',
|
|
84
86
|
RugPullDetected: 'rug_pull_detected',
|
|
85
87
|
RugPullScore: 'rug_pull_score',
|
|
86
88
|
RugPullType: 'rug_pull_type',
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from './context.gen.js';
|
|
|
4
4
|
export * from './schema.gen.js';
|
|
5
5
|
export * from './decision-effects.gen.js';
|
|
6
6
|
export * from './aarm-annotations.gen.js';
|
|
7
|
+
export * from './aarm-annotation.js';
|
|
7
8
|
export * from './engine.js';
|
|
8
9
|
export * from './builder.js';
|
|
9
10
|
export * from './parser.js';
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,9 @@ export * from './decision-effects.gen.js';
|
|
|
13
13
|
// AARM-aware annotation registry (typed Cedar annotation vocabulary
|
|
14
14
|
// Shield interprets at decision time; Studio/Admin use for lint).
|
|
15
15
|
export * from './aarm-annotations.gen.js';
|
|
16
|
+
// AARM annotation parser/validator (typed parse + fail-closed validation,
|
|
17
|
+
// parity with Go's aarm_annotation.go).
|
|
18
|
+
export * from './aarm-annotation.js';
|
|
16
19
|
// Non-generated modules (require Node.js)
|
|
17
20
|
export * from './engine.js';
|
|
18
21
|
export * from './builder.js';
|
|
@@ -36,9 +36,11 @@ export declare const OverwatchContextKey: {
|
|
|
36
36
|
readonly PiiDetected: "pii_detected";
|
|
37
37
|
readonly PiiScore: "pii_score";
|
|
38
38
|
readonly PiiTypes: "pii_types";
|
|
39
|
+
readonly PrivilegeScope: "privilege_scope";
|
|
39
40
|
readonly ProfanityScore: "profanity_score";
|
|
40
41
|
readonly PromptText: "prompt_text";
|
|
41
42
|
readonly ResponseContent: "response_content";
|
|
43
|
+
readonly Role: "role";
|
|
42
44
|
readonly RugPullDetected: "rug_pull_detected";
|
|
43
45
|
readonly RugPullScore: "rug_pull_score";
|
|
44
46
|
readonly SecretCount: "secret_count";
|
|
@@ -38,9 +38,11 @@ export const OverwatchContextKey = {
|
|
|
38
38
|
PiiDetected: 'pii_detected',
|
|
39
39
|
PiiScore: 'pii_score',
|
|
40
40
|
PiiTypes: 'pii_types',
|
|
41
|
+
PrivilegeScope: 'privilege_scope',
|
|
41
42
|
ProfanityScore: 'profanity_score',
|
|
42
43
|
PromptText: 'prompt_text',
|
|
43
44
|
ResponseContent: 'response_content',
|
|
45
|
+
Role: 'role',
|
|
44
46
|
RugPullDetected: 'rug_pull_detected',
|
|
45
47
|
RugPullScore: 'rug_pull_score',
|
|
46
48
|
SecretCount: 'secret_count',
|
|
@@ -50,7 +50,9 @@ export declare const SentryContextKey: {
|
|
|
50
50
|
readonly PiiDetected: "pii_detected";
|
|
51
51
|
readonly PiiScore: "pii_score";
|
|
52
52
|
readonly PiiTypes: "pii_types";
|
|
53
|
+
readonly PrivilegeScope: "privilege_scope";
|
|
53
54
|
readonly ProfanityScore: "profanity_score";
|
|
55
|
+
readonly Role: "role";
|
|
54
56
|
readonly ScriptConfidence: "script_confidence";
|
|
55
57
|
readonly SecretCount: "secret_count";
|
|
56
58
|
readonly SecretTypes: "secret_types";
|
|
@@ -52,7 +52,9 @@ export const SentryContextKey = {
|
|
|
52
52
|
PiiDetected: 'pii_detected',
|
|
53
53
|
PiiScore: 'pii_score',
|
|
54
54
|
PiiTypes: 'pii_types',
|
|
55
|
+
PrivilegeScope: 'privilege_scope',
|
|
55
56
|
ProfanityScore: 'profanity_score',
|
|
57
|
+
Role: 'role',
|
|
56
58
|
ScriptConfidence: 'script_confidence',
|
|
57
59
|
SecretCount: 'secret_count',
|
|
58
60
|
SecretTypes: 'secret_types',
|