@highflame/policy 2.0.8 → 2.0.9
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/dist/actions.gen.d.ts +0 -1
- package/dist/actions.gen.js +0 -1
- package/dist/annotations.d.ts +0 -1
- package/dist/annotations.js +0 -1
- package/dist/builder.d.ts +0 -1
- package/dist/builder.js +0 -1
- package/dist/context.gen.d.ts +0 -1
- package/dist/context.gen.js +0 -1
- package/dist/engine.d.ts +0 -1
- package/dist/engine.js +0 -1
- package/dist/entities.gen.d.ts +0 -1
- package/dist/entities.gen.js +0 -1
- package/dist/entity-metadata-types.gen.d.ts +0 -1
- package/dist/entity-metadata-types.gen.js +0 -1
- package/dist/errors.d.ts +0 -1
- package/dist/errors.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/overwatch-context.gen.d.ts +0 -1
- package/dist/overwatch-context.gen.js +0 -1
- package/dist/overwatch-defaults.gen.d.ts +0 -1
- package/dist/overwatch-defaults.gen.js +0 -1
- package/dist/overwatch-entities.gen.d.ts +0 -1
- package/dist/overwatch-entities.gen.js +0 -1
- package/dist/palisade-context.gen.d.ts +0 -1
- package/dist/palisade-context.gen.js +0 -1
- package/dist/palisade-entities.gen.d.ts +0 -1
- package/dist/palisade-entities.gen.js +0 -1
- package/dist/parser.d.ts +0 -1
- package/dist/parser.js +0 -1
- package/dist/schema.gen.d.ts +0 -1
- package/dist/schema.gen.js +0 -1
- package/dist/schemas.d.ts +0 -1
- package/dist/schemas.js +0 -1
- package/dist/service-schemas.gen.d.ts +0 -1
- package/dist/service-schemas.gen.js +0 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.js +0 -1
- package/package.json +1 -2
- package/dist/actions.gen.d.ts.map +0 -1
- package/dist/actions.gen.js.map +0 -1
- package/dist/annotations.d.ts.map +0 -1
- package/dist/annotations.js.map +0 -1
- package/dist/builder.d.ts.map +0 -1
- package/dist/builder.js.map +0 -1
- package/dist/context.gen.d.ts.map +0 -1
- package/dist/context.gen.js.map +0 -1
- package/dist/engine.d.ts.map +0 -1
- package/dist/engine.js.map +0 -1
- package/dist/engine.test.d.ts +0 -8
- package/dist/engine.test.d.ts.map +0 -1
- package/dist/engine.test.js +0 -190
- package/dist/engine.test.js.map +0 -1
- package/dist/entities.gen.d.ts.map +0 -1
- package/dist/entities.gen.js.map +0 -1
- package/dist/entity-metadata-types.gen.d.ts.map +0 -1
- package/dist/entity-metadata-types.gen.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/overwatch-context.gen.d.ts.map +0 -1
- package/dist/overwatch-context.gen.js.map +0 -1
- package/dist/overwatch-defaults.gen.d.ts.map +0 -1
- package/dist/overwatch-defaults.gen.js.map +0 -1
- package/dist/overwatch-defaults.test.d.ts +0 -8
- package/dist/overwatch-defaults.test.d.ts.map +0 -1
- package/dist/overwatch-defaults.test.js +0 -145
- package/dist/overwatch-defaults.test.js.map +0 -1
- package/dist/overwatch-entities.gen.d.ts.map +0 -1
- package/dist/overwatch-entities.gen.js.map +0 -1
- package/dist/overwatch-rebac.test.d.ts +0 -25
- package/dist/overwatch-rebac.test.d.ts.map +0 -1
- package/dist/overwatch-rebac.test.js +0 -301
- package/dist/overwatch-rebac.test.js.map +0 -1
- package/dist/palisade-context.gen.d.ts.map +0 -1
- package/dist/palisade-context.gen.js.map +0 -1
- package/dist/palisade-entities.gen.d.ts.map +0 -1
- package/dist/palisade-entities.gen.js.map +0 -1
- package/dist/parser.d.ts.map +0 -1
- package/dist/parser.js.map +0 -1
- package/dist/parser.test.d.ts +0 -8
- package/dist/parser.test.d.ts.map +0 -1
- package/dist/parser.test.js +0 -212
- package/dist/parser.test.js.map +0 -1
- package/dist/schema.gen.d.ts.map +0 -1
- package/dist/schema.gen.js.map +0 -1
- package/dist/schemas.d.ts.map +0 -1
- package/dist/schemas.js.map +0 -1
- package/dist/schemas.test.d.ts +0 -8
- package/dist/schemas.test.d.ts.map +0 -1
- package/dist/schemas.test.js +0 -407
- package/dist/schemas.test.js.map +0 -1
- package/dist/service-schemas.gen.d.ts.map +0 -1
- package/dist/service-schemas.gen.js.map +0 -1
- package/dist/studio-ui.test.d.ts +0 -8
- package/dist/studio-ui.test.d.ts.map +0 -1
- package/dist/studio-ui.test.js +0 -687
- package/dist/studio-ui.test.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/src/actions.gen.ts +0 -57
- package/src/annotations.ts +0 -243
- package/src/builder.ts +0 -799
- package/src/context.gen.ts +0 -10
- package/src/engine.test.ts +0 -370
- package/src/engine.ts +0 -497
- package/src/entities.gen.ts +0 -65
- package/src/entity-metadata-types.gen.ts +0 -19
- package/src/errors.ts +0 -195
- package/src/index.ts +0 -62
- package/src/overwatch-context.gen.ts +0 -45
- package/src/overwatch-defaults.gen.ts +0 -1255
- package/src/overwatch-defaults.test.ts +0 -176
- package/src/overwatch-entities.gen.ts +0 -41
- package/src/overwatch-rebac.test.ts +0 -346
- package/src/palisade-context.gen.ts +0 -28
- package/src/palisade-entities.gen.ts +0 -49
- package/src/parser.test.ts +0 -251
- package/src/parser.ts +0 -579
- package/src/schema.gen.ts +0 -134
- package/src/schemas.test.ts +0 -477
- package/src/schemas.ts +0 -91
- package/src/service-schemas.gen.ts +0 -608
- package/src/studio-ui.test.ts +0 -813
- package/src/types.ts +0 -66
package/src/parser.test.ts
DELETED
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parser unit tests
|
|
3
|
-
*
|
|
4
|
-
* Tests the Cedar text → PolicyRule conversion using the official Cedar engine.
|
|
5
|
-
* These tests demonstrate how a client like highflame-authz would use the parser.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { readFileSync } from 'fs';
|
|
9
|
-
import { resolve, dirname } from 'path';
|
|
10
|
-
import { fileURLToPath } from 'url';
|
|
11
|
-
import { describe, it, expect } from 'vitest';
|
|
12
|
-
import { parseCedarToRules } from './parser.js';
|
|
13
|
-
import { rulesToCedar } from './builder.js';
|
|
14
|
-
import type { PolicyRule } from './builder.js';
|
|
15
|
-
|
|
16
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
-
|
|
18
|
-
describe('parseCedarToRules', () => {
|
|
19
|
-
it('should parse a simple permit policy', () => {
|
|
20
|
-
const cedarText = `
|
|
21
|
-
@id("allow-read-files")
|
|
22
|
-
permit(
|
|
23
|
-
principal is User,
|
|
24
|
-
action == Action::"read_file",
|
|
25
|
-
resource is FilePath
|
|
26
|
-
);
|
|
27
|
-
`;
|
|
28
|
-
|
|
29
|
-
const result = parseCedarToRules(cedarText);
|
|
30
|
-
|
|
31
|
-
expect(result.errors).toHaveLength(0);
|
|
32
|
-
expect(result.rules).toHaveLength(1);
|
|
33
|
-
expect(result.unstructured).toHaveLength(0);
|
|
34
|
-
|
|
35
|
-
const rule = result.rules[0];
|
|
36
|
-
expect(rule.annotations.id).toBe('allow-read-files');
|
|
37
|
-
expect(rule.effect).toBe('permit');
|
|
38
|
-
expect(rule.principal).toEqual({ type: 'User' });
|
|
39
|
-
expect(rule.action).toBe('read_file');
|
|
40
|
-
expect(rule.resource).toEqual({ type: 'FilePath' });
|
|
41
|
-
expect(rule.enabled).toBe(true);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('should parse a policy with when conditions', () => {
|
|
45
|
-
const cedarText = `
|
|
46
|
-
@id("block-high-risk")
|
|
47
|
-
forbid(
|
|
48
|
-
principal,
|
|
49
|
-
action == Action::"execute_tool",
|
|
50
|
-
resource
|
|
51
|
-
)
|
|
52
|
-
when {
|
|
53
|
-
context.threat_level == "high"
|
|
54
|
-
};
|
|
55
|
-
`;
|
|
56
|
-
|
|
57
|
-
const result = parseCedarToRules(cedarText);
|
|
58
|
-
|
|
59
|
-
expect(result.errors).toHaveLength(0);
|
|
60
|
-
expect(result.rules).toHaveLength(1);
|
|
61
|
-
|
|
62
|
-
const rule = result.rules[0];
|
|
63
|
-
expect(rule.annotations.id).toBe('block-high-risk');
|
|
64
|
-
expect(rule.effect).toBe('forbid');
|
|
65
|
-
expect(rule.action).toBe('execute_tool');
|
|
66
|
-
|
|
67
|
-
// Check condition was parsed
|
|
68
|
-
expect(rule.conditions.length).toBeGreaterThanOrEqual(0);
|
|
69
|
-
// If condition parsing works, it should have the condition
|
|
70
|
-
// If not, it should be in rawCondition
|
|
71
|
-
if (rule.conditions.length > 0) {
|
|
72
|
-
expect(rule.conditions[0].field).toBe('threat_level');
|
|
73
|
-
expect(rule.conditions[0].operator).toBe('eq');
|
|
74
|
-
expect(rule.conditions[0].value).toBe('high');
|
|
75
|
-
} else {
|
|
76
|
-
expect(rule.rawCondition).toBeDefined();
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('should return errors for invalid Cedar syntax', () => {
|
|
81
|
-
const invalidCedar = `
|
|
82
|
-
permit(
|
|
83
|
-
principal is User
|
|
84
|
-
// missing comma and rest of policy
|
|
85
|
-
`;
|
|
86
|
-
|
|
87
|
-
const result = parseCedarToRules(invalidCedar);
|
|
88
|
-
|
|
89
|
-
expect(result.errors.length).toBeGreaterThan(0);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('should handle multiple policies', () => {
|
|
93
|
-
const cedarText = `
|
|
94
|
-
@id("rule-1")
|
|
95
|
-
permit(principal, action == Action::"read", resource);
|
|
96
|
-
|
|
97
|
-
@id("rule-2")
|
|
98
|
-
forbid(principal, action == Action::"delete", resource);
|
|
99
|
-
`;
|
|
100
|
-
|
|
101
|
-
const result = parseCedarToRules(cedarText);
|
|
102
|
-
|
|
103
|
-
expect(result.errors).toHaveLength(0);
|
|
104
|
-
expect(result.rules).toHaveLength(2);
|
|
105
|
-
expect(result.rules[0].effect).toBe('permit');
|
|
106
|
-
expect(result.rules[1].effect).toBe('forbid');
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('should put policies with unless clauses in unstructured', () => {
|
|
110
|
-
const cedarText = `
|
|
111
|
-
permit(principal, action, resource)
|
|
112
|
-
unless {
|
|
113
|
-
context.is_blocked == true
|
|
114
|
-
};
|
|
115
|
-
`;
|
|
116
|
-
|
|
117
|
-
const result = parseCedarToRules(cedarText);
|
|
118
|
-
|
|
119
|
-
// Unless clauses can't be represented as PolicyRule
|
|
120
|
-
expect(result.rules).toHaveLength(0);
|
|
121
|
-
expect(result.unstructured.length).toBeGreaterThan(0);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('should store complex conditions as Cedar text in rawCondition', () => {
|
|
125
|
-
// Use a condition with boolean AND that can't be mapped to structured format
|
|
126
|
-
const cedarText = `
|
|
127
|
-
@id("complex-condition")
|
|
128
|
-
permit(principal, action, resource)
|
|
129
|
-
when {
|
|
130
|
-
context.a == "x" && context.b == "y"
|
|
131
|
-
};
|
|
132
|
-
`;
|
|
133
|
-
|
|
134
|
-
const result = parseCedarToRules(cedarText);
|
|
135
|
-
|
|
136
|
-
expect(result.errors).toHaveLength(0);
|
|
137
|
-
expect(result.rules).toHaveLength(1);
|
|
138
|
-
|
|
139
|
-
const rule = result.rules[0];
|
|
140
|
-
|
|
141
|
-
// The complex && condition should be in rawCondition as readable Cedar text
|
|
142
|
-
if (rule.rawCondition) {
|
|
143
|
-
// Verify it's Cedar expression text (not JSON AST)
|
|
144
|
-
expect(rule.rawCondition).toContain('context.a');
|
|
145
|
-
expect(rule.rawCondition).toContain('context.b');
|
|
146
|
-
// Should not be JSON
|
|
147
|
-
expect(rule.rawCondition.startsWith('[')).toBe(false);
|
|
148
|
-
}
|
|
149
|
-
// Either conditions were mapped or rawCondition contains Cedar text
|
|
150
|
-
expect(rule.conditions.length > 0 || rule.rawCondition).toBeTruthy();
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('should round-trip: parse → remove rule → rebuild → re-parse → add back → verify', () => {
|
|
154
|
-
// Read shared test fixture (builder output format, sorted by @id, Overwatch namespace)
|
|
155
|
-
const fixturePath = resolve(__dirname, '../../../test-fixtures/round-trip.cedar');
|
|
156
|
-
const originalCedar = readFileSync(fixturePath, 'utf-8').trimEnd();
|
|
157
|
-
|
|
158
|
-
// Step 1: Parse original
|
|
159
|
-
const originalResult = parseCedarToRules(originalCedar);
|
|
160
|
-
expect(originalResult.errors).toHaveLength(0);
|
|
161
|
-
expect(originalResult.rules).toHaveLength(4);
|
|
162
|
-
|
|
163
|
-
// Verify we have both simple (structured) and complex (raw) conditions
|
|
164
|
-
const simpleRule = originalResult.rules.find(r => r.annotations.id === 'simple-block-threats');
|
|
165
|
-
const complexRule = originalResult.rules.find(r => r.annotations.id === 'complex-block-injection');
|
|
166
|
-
expect(simpleRule).toBeDefined();
|
|
167
|
-
expect(complexRule).toBeDefined();
|
|
168
|
-
expect(simpleRule!.conditions.length).toBeGreaterThan(0);
|
|
169
|
-
expect(complexRule!.rawCondition).toBeDefined();
|
|
170
|
-
|
|
171
|
-
// Step 2: Remove complex rule → rebuild → re-parse
|
|
172
|
-
const remaining = originalResult.rules.filter(r => r.annotations.id !== 'complex-block-injection');
|
|
173
|
-
const removedRule = complexRule!;
|
|
174
|
-
expect(remaining).toHaveLength(3);
|
|
175
|
-
|
|
176
|
-
const partialCedar = rulesToCedar(remaining);
|
|
177
|
-
const partialResult = parseCedarToRules(partialCedar);
|
|
178
|
-
expect(partialResult.errors).toHaveLength(0);
|
|
179
|
-
expect(partialResult.rules).toHaveLength(3);
|
|
180
|
-
|
|
181
|
-
// Verify each remaining rule is preserved
|
|
182
|
-
for (const orig of remaining) {
|
|
183
|
-
const reparsed = partialResult.rules.find(r => r.annotations.id === orig.annotations.id);
|
|
184
|
-
expect(reparsed).toBeDefined();
|
|
185
|
-
assertRulesEquivalent(orig, reparsed!);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Step 3: Add back → rebuild → re-parse
|
|
189
|
-
const allRules = [...partialResult.rules, removedRule];
|
|
190
|
-
const finalCedar = rulesToCedar(allRules);
|
|
191
|
-
const finalResult = parseCedarToRules(finalCedar);
|
|
192
|
-
expect(finalResult.errors).toHaveLength(0);
|
|
193
|
-
expect(finalResult.rules).toHaveLength(4);
|
|
194
|
-
|
|
195
|
-
// Verify ALL rules match original
|
|
196
|
-
for (const orig of originalResult.rules) {
|
|
197
|
-
const final_ = finalResult.rules.find(r => r.annotations.id === orig.annotations.id);
|
|
198
|
-
expect(final_).toBeDefined();
|
|
199
|
-
assertRulesEquivalent(orig, final_!);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Step 4: Verify parse→build cycle is idempotent.
|
|
203
|
-
// The engine may normalize rawCondition text (e.g., adding parens), so we
|
|
204
|
-
// compare against canonical form (first parse→build) rather than original fixture.
|
|
205
|
-
const originalSorted = [...originalResult.rules].sort((a, b) => a.annotations.id.localeCompare(b.annotations.id));
|
|
206
|
-
originalSorted.forEach((r, i) => { r.order = i; });
|
|
207
|
-
const canonicalCedar = rulesToCedar(originalSorted);
|
|
208
|
-
|
|
209
|
-
const finalSorted = [...finalResult.rules].sort((a, b) => a.annotations.id.localeCompare(b.annotations.id));
|
|
210
|
-
finalSorted.forEach((r, i) => { r.order = i; });
|
|
211
|
-
const finalReconstructed = rulesToCedar(finalSorted);
|
|
212
|
-
expect(finalReconstructed).toBe(canonicalCedar);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it('should warn about duplicate policy IDs', () => {
|
|
216
|
-
const cedarText = `
|
|
217
|
-
@id("duplicate-id")
|
|
218
|
-
permit(principal, action, resource);
|
|
219
|
-
|
|
220
|
-
@id("unique-id")
|
|
221
|
-
forbid(principal, action, resource);
|
|
222
|
-
|
|
223
|
-
@id("duplicate-id")
|
|
224
|
-
permit(principal is User, action, resource);
|
|
225
|
-
`;
|
|
226
|
-
|
|
227
|
-
const result = parseCedarToRules(cedarText);
|
|
228
|
-
|
|
229
|
-
// All policies should still be parsed
|
|
230
|
-
expect(result.rules).toHaveLength(3);
|
|
231
|
-
|
|
232
|
-
// Should have a warning about duplicate ID
|
|
233
|
-
const duplicateError = result.errors.find(e => e.includes("HFP-PARSE-005"));
|
|
234
|
-
expect(duplicateError).toBeDefined();
|
|
235
|
-
expect(duplicateError).toContain("duplicate-id");
|
|
236
|
-
expect(duplicateError).toContain("[0"); // First occurrence at index 0
|
|
237
|
-
expect(duplicateError).toContain("2"); // Second occurrence at index 2
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
/** Assert two PolicyRules are semantically equivalent after round-trip. */
|
|
242
|
-
function assertRulesEquivalent(a: PolicyRule, b: PolicyRule): void {
|
|
243
|
-
expect(b.annotations.id).toBe(a.annotations.id);
|
|
244
|
-
expect(b.annotations.name).toBe(a.annotations.name);
|
|
245
|
-
expect(b.effect).toBe(a.effect);
|
|
246
|
-
expect(b.action).toEqual(a.action);
|
|
247
|
-
expect(b.principal).toEqual(a.principal);
|
|
248
|
-
expect(b.resource).toEqual(a.resource);
|
|
249
|
-
expect(b.conditions).toEqual(a.conditions);
|
|
250
|
-
expect(b.rawCondition).toBe(a.rawCondition);
|
|
251
|
-
}
|