@highflame/policy 2.1.40 → 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 +66 -0
- package/_schemas/ai_gateway/schema.cedarschema +18 -0
- package/_schemas/guardrails/context.json +66 -0
- package/_schemas/guardrails/schema.cedarschema +18 -0
- package/_schemas/overwatch/context.json +66 -0
- package/_schemas/overwatch/schema.cedarschema +18 -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 +3 -0
- package/dist/ai_gateway-context.gen.js +3 -0
- package/dist/guardrails-context.gen.d.ts +3 -0
- package/dist/guardrails-context.gen.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/overwatch-context.gen.d.ts +3 -0
- package/dist/overwatch-context.gen.js +3 -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 +107 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.js +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,120 @@
|
|
|
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
|
+
/**
|
|
40
|
+
* Parsed form of `@step_up_required`. Suspends the action pending approval
|
|
41
|
+
* from an approver carrying `role`; fail-closed DENY after `timeoutSeconds`.
|
|
42
|
+
*/
|
|
43
|
+
export interface StepUpRequiredDirective {
|
|
44
|
+
/** AuthN role the approver must carry (validated against authn.roles at deploy). */
|
|
45
|
+
role: string;
|
|
46
|
+
/** Seconds the action waits before fail-closed DENY. Defaults to the registry default (86400). */
|
|
47
|
+
timeoutSeconds: number;
|
|
48
|
+
}
|
|
49
|
+
/** Parsed form of `@defer_on_conflict`. Marker — presence is the signal. */
|
|
50
|
+
export interface DeferOnConflictDirective {
|
|
51
|
+
}
|
|
52
|
+
/** Parsed form of `@defer_below_confidence`. Defers when a detector confidence < threshold. */
|
|
53
|
+
export interface DeferBelowConfidenceDirective {
|
|
54
|
+
/** Threshold in [0.0, 1.0]. Detector scores strictly below trigger a deferral. */
|
|
55
|
+
threshold: number;
|
|
56
|
+
}
|
|
57
|
+
/** Parsed form of `@defer_until_context`. Defers when the named context field is empty/missing. */
|
|
58
|
+
export interface DeferUntilContextDirective {
|
|
59
|
+
/** Dotted Cedar context-attribute path (e.g. "session_max_sensitivity"). */
|
|
60
|
+
field: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Every typed AARM directive parsed from a single policy's annotation map.
|
|
64
|
+
* Each field is present iff the corresponding annotation was present and
|
|
65
|
+
* parsed successfully.
|
|
66
|
+
*/
|
|
67
|
+
export interface AARMDirectives {
|
|
68
|
+
stepUpRequired?: StepUpRequiredDirective;
|
|
69
|
+
deferOnConflict?: DeferOnConflictDirective;
|
|
70
|
+
deferBelowConfidence?: DeferBelowConfidenceDirective;
|
|
71
|
+
deferUntilContext?: DeferUntilContextDirective;
|
|
72
|
+
}
|
|
73
|
+
/** True iff at least one AARM directive was parsed. */
|
|
74
|
+
export declare function hasAnyAARMDirective(d: AARMDirectives | null | undefined): boolean;
|
|
75
|
+
export interface AARMAnnotationErrorInit {
|
|
76
|
+
/** Annotation key (e.g. "step_up_required"). */
|
|
77
|
+
key: string;
|
|
78
|
+
/** Parameter where the failure occurred; omit for annotation-level failures. */
|
|
79
|
+
parameter?: string;
|
|
80
|
+
/** Raw Cedar value (or parameter substring) that was rejected. */
|
|
81
|
+
rawValue?: string;
|
|
82
|
+
/** Short, actionable human-readable message. */
|
|
83
|
+
reason: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Structured error for a malformed AARM annotation. Mirrors Go's
|
|
87
|
+
* AARMAnnotationError. Studio's lint surfaces these inline; Admin serializes
|
|
88
|
+
* them in the policy-create rejection.
|
|
89
|
+
*/
|
|
90
|
+
export declare class AARMAnnotationError extends Error {
|
|
91
|
+
readonly key: string;
|
|
92
|
+
readonly parameter: string;
|
|
93
|
+
readonly rawValue: string;
|
|
94
|
+
readonly reason: string;
|
|
95
|
+
constructor(init: AARMAnnotationErrorInit);
|
|
96
|
+
}
|
|
97
|
+
/** True iff err is an AARMAnnotationError. */
|
|
98
|
+
export declare function isAARMAnnotationError(err: unknown): err is AARMAnnotationError;
|
|
99
|
+
export interface ParseAARMResult {
|
|
100
|
+
directives: AARMDirectives;
|
|
101
|
+
errors: AARMAnnotationError[];
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Walks the raw annotation map and extracts every AARM-recognized annotation
|
|
105
|
+
* into typed directives. Non-AARM keys are ignored (handled by the generic
|
|
106
|
+
* annotation surface in annotations.ts).
|
|
107
|
+
*
|
|
108
|
+
* Errors are COLLECTED, not fail-fast, so a single call surfaces every
|
|
109
|
+
* offending annotation at once — Studio shows all squiggles in one pass.
|
|
110
|
+
* Keys are processed in sorted order for stable error reporting.
|
|
111
|
+
*/
|
|
112
|
+
export declare function parseAARMAnnotations(raw: Record<string, string> | null | undefined): ParseAARMResult;
|
|
113
|
+
/**
|
|
114
|
+
* Validate-only variant: returns the error list without the typed
|
|
115
|
+
* directives. Suitable for Admin's policy-create endpoint, which only needs
|
|
116
|
+
* the yes/no verdict + error list.
|
|
117
|
+
*/
|
|
118
|
+
export declare function validateAARMAnnotations(raw: Record<string, string> | null | undefined): AARMAnnotationError[];
|
|
119
|
+
/** True iff key is a platform-recognized AARM annotation. */
|
|
120
|
+
export declare function isAARMAnnotationKey(key: string): boolean;
|
|
@@ -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";
|
|
@@ -58,6 +60,7 @@ export declare const AiGatewayContextKey: {
|
|
|
58
60
|
readonly ToolIsBuiltin: "tool_is_builtin";
|
|
59
61
|
readonly ToolIsSensitive: "tool_is_sensitive";
|
|
60
62
|
readonly ToolName: "tool_name";
|
|
63
|
+
readonly ToolOperationClasses: "tool_operation_classes";
|
|
61
64
|
readonly ToolPoisoningDetected: "tool_poisoning_detected";
|
|
62
65
|
readonly ToolPoisoningScore: "tool_poisoning_score";
|
|
63
66
|
readonly ToolRiskScore: "tool_risk_score";
|
|
@@ -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',
|
|
@@ -60,6 +62,7 @@ export const AiGatewayContextKey = {
|
|
|
60
62
|
ToolIsBuiltin: 'tool_is_builtin',
|
|
61
63
|
ToolIsSensitive: 'tool_is_sensitive',
|
|
62
64
|
ToolName: 'tool_name',
|
|
65
|
+
ToolOperationClasses: 'tool_operation_classes',
|
|
63
66
|
ToolPoisoningDetected: 'tool_poisoning_detected',
|
|
64
67
|
ToolPoisoningScore: 'tool_poisoning_score',
|
|
65
68
|
ToolRiskScore: 'tool_risk_score',
|
|
@@ -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";
|
|
@@ -111,6 +113,7 @@ export declare const GuardrailsContextKey: {
|
|
|
111
113
|
readonly ToolIsBuiltin: "tool_is_builtin";
|
|
112
114
|
readonly ToolIsSensitive: "tool_is_sensitive";
|
|
113
115
|
readonly ToolName: "tool_name";
|
|
116
|
+
readonly ToolOperationClasses: "tool_operation_classes";
|
|
114
117
|
readonly ToolPoisoningDetected: "tool_poisoning_detected";
|
|
115
118
|
readonly ToolPoisoningScore: "tool_poisoning_score";
|
|
116
119
|
readonly ToolPoisoningType: "tool_poisoning_type";
|