@burojs/config 0.4.0 → 0.5.0
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/README.md +52 -17
- package/dist/index.cjs +153 -138
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -44
- package/dist/index.d.ts +50 -44
- package/dist/index.js +152 -137
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
Inert JSON and YAML declaration sources for [buro](https://github.com/buro/buro).
|
|
4
4
|
|
|
5
5
|
Parses a raw JSON or YAML string against a host-owned `SourcePolicy` and
|
|
6
|
-
produces a portable declaration layer
|
|
7
|
-
source is malformed, oversized, or requests
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
produces a portable declaration layer that carries the policy's grants — or a
|
|
7
|
+
list of diagnostics if the source is malformed, oversized, or requests
|
|
8
|
+
something its policy doesn't allow. *Source* here is a configuration source — a
|
|
9
|
+
JSON or YAML document a host loads into a layer runtime — not a screen's `with`
|
|
10
|
+
source. The output can only ever be data: no
|
|
11
|
+
functions, components, schemas, credentials, or provider/action/mapper bindings
|
|
12
|
+
survive the parse.
|
|
10
13
|
|
|
11
14
|
## Install
|
|
12
15
|
|
|
@@ -20,12 +23,24 @@ pnpm add @burojs/config
|
|
|
20
23
|
import { parseJsonLayer, type SourcePolicy } from '@burojs/config';
|
|
21
24
|
|
|
22
25
|
const policy: SourcePolicy = {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
grants: {
|
|
27
|
+
allowedKinds: ['resource'],
|
|
28
|
+
allowedNamespaces: ['crm'],
|
|
29
|
+
allowedTransformOps: [],
|
|
30
|
+
maxTransformScope: 'layer',
|
|
31
|
+
writePaths: {},
|
|
32
|
+
allowedResources: [],
|
|
33
|
+
allowedQueries: [],
|
|
34
|
+
allowedFlows: [],
|
|
35
|
+
allowedBackends: ['company'],
|
|
36
|
+
allowedBlocks: [],
|
|
37
|
+
allowedFrames: [],
|
|
38
|
+
allowedInlineFlows: [],
|
|
39
|
+
allowedFieldImplementations: [],
|
|
40
|
+
priority: { min: 0, max: 0 },
|
|
41
|
+
},
|
|
27
42
|
allowedCapabilities: {},
|
|
28
|
-
limits: { maxBytes: 16_000, maxDeclarations: 12, maxDepth: 16, maxStringLength: 1_000, maxDiagnostics: 8 },
|
|
43
|
+
limits: { maxBytes: 16_000, maxDeclarations: 12, maxDepth: 16, maxNodes: 1_000, maxStringLength: 1_000, maxDiagnostics: 8 },
|
|
29
44
|
};
|
|
30
45
|
|
|
31
46
|
const source = JSON.stringify({
|
|
@@ -34,25 +49,45 @@ const source = JSON.stringify({
|
|
|
34
49
|
defaultNamespace: 'crm',
|
|
35
50
|
revision: 'r1',
|
|
36
51
|
declarations: [
|
|
37
|
-
{ kind: 'resource', ref: 'company', schemaVersion:
|
|
52
|
+
{ kind: 'resource', ref: 'company', schemaVersion: 2, value: { name: 'company' } },
|
|
38
53
|
],
|
|
39
54
|
});
|
|
40
55
|
|
|
41
56
|
const result = parseJsonLayer(source, policy);
|
|
42
57
|
if (!result.ok) throw new Error(result.diagnostics.map((d) => d.code).join(', '));
|
|
43
|
-
// result.value is a
|
|
58
|
+
// result.value is a ParsedLayer carrying the grants of `policy`:
|
|
59
|
+
await runtime.publishLayer(result.value);
|
|
44
60
|
```
|
|
45
61
|
|
|
46
|
-
To load a source lazily instead of publishing it up front,
|
|
47
|
-
`createConfigRuntimeSource`
|
|
48
|
-
|
|
62
|
+
To load a source lazily instead of publishing it up front, give
|
|
63
|
+
`createConfigRuntimeSource` the policy and a `load` that returns the document's
|
|
64
|
+
text; it parses each load under the policy, and returns the runtime source with
|
|
65
|
+
the trust to register it under:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const configured = createConfigRuntimeSource('catalog', {
|
|
69
|
+
policy,
|
|
70
|
+
load: async () => ({ format: 'json', text: await fetch('/config/catalog.json').then((r) => r.text()) }),
|
|
71
|
+
});
|
|
72
|
+
runtime.registerSource(configured.source, configured.trust);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The parser checks keys (it refuses `action`, `component`, `validator` and the
|
|
76
|
+
other code-bearing keys), the trusted-attachment capabilities in
|
|
77
|
+
`allowedCapabilities`, and the `limits`; those checks are parser-only. The
|
|
78
|
+
compiler enforces the `grants` — the kinds, namespaces and transform operations
|
|
79
|
+
a layer uses, the paths a transform may write (`writePaths`, no default), its
|
|
80
|
+
targets, its references (`allowedResources`, `allowedQueries`,
|
|
81
|
+
`allowedFlows`), its backend bindings (`allowedBackends`) and its priority
|
|
82
|
+
band. Use a band strictly away from your code's priorities to fix whether a
|
|
83
|
+
parsed layer applies before or after your code. See the "Grants and trust"
|
|
84
|
+
section of the guide linked below.
|
|
49
85
|
|
|
50
86
|
## Main exports
|
|
51
87
|
|
|
52
88
|
- **`parseJsonLayer` / `parseYamlLayer`** — parse a source string against a `SourcePolicy`, returning a `ParseResult`.
|
|
53
|
-
- **`createConfigRuntimeSource`** —
|
|
54
|
-
- **`
|
|
55
|
-
- **`SourcePolicy` / `SourceLimits`** — the allow-list and resource limits a host defines for one untrusted source.
|
|
89
|
+
- **`createConfigRuntimeSource(id, { policy, load })`** — returns `{ source, trust }`: a core `RuntimeSource` that loads and parses a document on demand, and the policy's grants to register it with.
|
|
90
|
+
- **`SourcePolicy` / `SourceLimits`** — `{ grants, allowedCapabilities, limits }`, what a host allows one untrusted source: `grants` (the core `LayerGrants`) is what its parsed layers carry; `SCREEN_SOURCE_LIMITS` is a preset for sources that carry whole screen trees.
|
|
56
91
|
|
|
57
92
|
See [`docs/raw-config-manifests.md`](../../docs/raw-config-manifests.md) for the full source-policy boundary and how it composes with a `LayerRuntime`.
|
|
58
93
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,31 +1,19 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _burojs_core = require("@burojs/core");
|
|
3
|
+
let _burojs_core_internal = require("@burojs/core/internal");
|
|
2
4
|
let yaml = require("yaml");
|
|
5
|
+
//#region src/source-policy.ts
|
|
6
|
+
const MAX_SOURCE_DEPTH = _burojs_core.DEFAULT_COMPILER_LIMITS.maxPortableDepth;
|
|
7
|
+
const SCREEN_SOURCE_LIMITS = Object.freeze({
|
|
8
|
+
maxBytes: 256e3,
|
|
9
|
+
maxDeclarations: 64,
|
|
10
|
+
maxDepth: MAX_SOURCE_DEPTH,
|
|
11
|
+
maxNodes: 5e3,
|
|
12
|
+
maxStringLength: 4096,
|
|
13
|
+
maxDiagnostics: 32
|
|
14
|
+
});
|
|
15
|
+
//#endregion
|
|
3
16
|
//#region src/parse.ts
|
|
4
|
-
const RESERVED_KEYS = new Set([
|
|
5
|
-
"__proto__",
|
|
6
|
-
"prototype",
|
|
7
|
-
"constructor"
|
|
8
|
-
]);
|
|
9
|
-
const FORBIDDEN_VALUE_CATEGORIES = new Map([
|
|
10
|
-
["action", "action"],
|
|
11
|
-
["actions", "action"],
|
|
12
|
-
["provider", "provider"],
|
|
13
|
-
["providers", "provider"],
|
|
14
|
-
["mapper", "mapper"],
|
|
15
|
-
["mappers", "mapper"],
|
|
16
|
-
["service", "service"],
|
|
17
|
-
["services", "service"],
|
|
18
|
-
["credential", "credential"],
|
|
19
|
-
["credentials", "credential"],
|
|
20
|
-
["schema", "schema"],
|
|
21
|
-
["schemas", "schema"],
|
|
22
|
-
["component", "component"],
|
|
23
|
-
["components", "component"],
|
|
24
|
-
["attachment", "attachment"],
|
|
25
|
-
["attachments", "attachment"],
|
|
26
|
-
["validator", "validator"],
|
|
27
|
-
["validators", "validator"]
|
|
28
|
-
]);
|
|
29
17
|
const LAYER_KEYS = new Set([
|
|
30
18
|
"id",
|
|
31
19
|
"priority",
|
|
@@ -96,6 +84,7 @@ function messageFor(code) {
|
|
|
96
84
|
case "source.byte-limit": return "The source exceeds its byte limit.";
|
|
97
85
|
case "source.declaration-limit": return "The source exceeds its declaration limit.";
|
|
98
86
|
case "source.depth-limit": return "The source exceeds its nesting-depth limit.";
|
|
87
|
+
case "source.node-limit": return "The source exceeds its value-count limit.";
|
|
99
88
|
case "source.string-limit": return "The source contains an overlong string.";
|
|
100
89
|
case "source.diagnostic-limit": return "Additional source diagnostics were omitted.";
|
|
101
90
|
case "source.unknown-key": return "The source contains an unknown key.";
|
|
@@ -110,7 +99,7 @@ function messageFor(code) {
|
|
|
110
99
|
case "source.forbidden-transform-op": return "The source requests a forbidden transform operation.";
|
|
111
100
|
case "source.forbidden-transform-scope": return "The source requests a forbidden transform scope.";
|
|
112
101
|
case "source.forbidden-capability": return "The source requests a forbidden capability.";
|
|
113
|
-
case "source.forbidden-action": return "The source requests a forbidden action binding.";
|
|
102
|
+
case "source.forbidden-action": return "The source requests a forbidden action binding; a resource's flows are written under toolbar.";
|
|
114
103
|
case "source.forbidden-provider": return "The source requests a forbidden provider binding.";
|
|
115
104
|
case "source.forbidden-mapper": return "The source requests a forbidden mapper binding.";
|
|
116
105
|
case "source.forbidden-service": return "The source requests a forbidden service value.";
|
|
@@ -122,14 +111,68 @@ function messageFor(code) {
|
|
|
122
111
|
default: return "The source is invalid.";
|
|
123
112
|
}
|
|
124
113
|
}
|
|
114
|
+
const LIMIT_KEYS = [
|
|
115
|
+
"maxBytes",
|
|
116
|
+
"maxDeclarations",
|
|
117
|
+
"maxDepth",
|
|
118
|
+
"maxNodes",
|
|
119
|
+
"maxStringLength",
|
|
120
|
+
"maxDiagnostics"
|
|
121
|
+
];
|
|
122
|
+
const LIMIT_KEY_SET = new Set(LIMIT_KEYS);
|
|
125
123
|
function validLimits(limits) {
|
|
126
|
-
return Object.
|
|
124
|
+
return plainObject(limits) && Object.keys(limits).every((key) => LIMIT_KEY_SET.has(key)) && LIMIT_KEYS.every((key) => Number.isSafeInteger(limits[key]) && limits[key] > 0);
|
|
127
125
|
}
|
|
128
126
|
function validCapabilities(value) {
|
|
129
127
|
return value !== null && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((capabilities) => Array.isArray(capabilities) && capabilities.every((capability) => typeof capability === "string"));
|
|
130
128
|
}
|
|
131
|
-
function
|
|
132
|
-
|
|
129
|
+
function grantsError(policy) {
|
|
130
|
+
try {
|
|
131
|
+
(0, _burojs_core.normalizeLayerGrants)(policy.grants);
|
|
132
|
+
return;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
return error instanceof TypeError ? error.message : "";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function policyDiagnostic(policy) {
|
|
138
|
+
const generic = messageFor("source.invalid-policy");
|
|
139
|
+
const stray = strayPolicyKey(policy);
|
|
140
|
+
if (stray !== void 0) return Object.freeze({
|
|
141
|
+
code: "source.invalid-policy",
|
|
142
|
+
message: `The source policy is invalid: it carries ${stray} at the top level; a policy is { grants, allowedCapabilities, limits }, with every grant field inside grants.`
|
|
143
|
+
});
|
|
144
|
+
if (!validPolicyShape(policy)) return Object.freeze({
|
|
145
|
+
code: "source.invalid-policy",
|
|
146
|
+
message: generic
|
|
147
|
+
});
|
|
148
|
+
const reason = grantsError(policy);
|
|
149
|
+
if (reason === void 0) return void 0;
|
|
150
|
+
return Object.freeze({
|
|
151
|
+
code: "source.invalid-policy",
|
|
152
|
+
message: reason === "" ? generic : `The source policy is invalid: ${reason}`
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const POLICY_KEYS = new Set([
|
|
156
|
+
"grants",
|
|
157
|
+
"allowedCapabilities",
|
|
158
|
+
"limits"
|
|
159
|
+
]);
|
|
160
|
+
function strayPolicyKey(policy) {
|
|
161
|
+
return plainObject(policy) ? Object.keys(policy).find((key) => !POLICY_KEYS.has(key)) : void 0;
|
|
162
|
+
}
|
|
163
|
+
function validPolicyShape(policy) {
|
|
164
|
+
return plainObject(policy) && plainObject(policy.grants) && validCapabilities(policy.allowedCapabilities) && validLimits(policy.limits);
|
|
165
|
+
}
|
|
166
|
+
function ownedPolicy(policy) {
|
|
167
|
+
const invalid = policyDiagnostic(policy);
|
|
168
|
+
if (invalid !== void 0) throw new TypeError(invalid.message);
|
|
169
|
+
const capabilities = Object.create(null);
|
|
170
|
+
for (const [kind, values] of Object.entries(policy.allowedCapabilities)) capabilities[kind] = Object.freeze([...values]);
|
|
171
|
+
return Object.freeze({
|
|
172
|
+
grants: (0, _burojs_core.normalizeLayerGrants)(policy.grants),
|
|
173
|
+
allowedCapabilities: Object.freeze(capabilities),
|
|
174
|
+
limits: Object.freeze({ ...policy.limits })
|
|
175
|
+
});
|
|
133
176
|
}
|
|
134
177
|
function failure(diagnostics) {
|
|
135
178
|
return {
|
|
@@ -150,10 +193,37 @@ function hasData(value, key) {
|
|
|
150
193
|
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
151
194
|
return descriptor !== void 0 && "value" in descriptor;
|
|
152
195
|
}
|
|
196
|
+
const MAX_ENVELOPE_DEPTH = 8;
|
|
197
|
+
function childRole(role, key) {
|
|
198
|
+
switch (role) {
|
|
199
|
+
case "root": return key === "declarations" ? "declarations" : key === "transforms" ? "transforms" : "envelope";
|
|
200
|
+
case "declarations": return "declaration";
|
|
201
|
+
case "transforms": return "transform";
|
|
202
|
+
case "declaration": return key === "value" ? "value" : "envelope";
|
|
203
|
+
case "transform": return key === "operation" ? "operation" : "envelope";
|
|
204
|
+
case "operation": return key === "value" ? "value" : "envelope";
|
|
205
|
+
default: return role;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
153
208
|
function clonePortable(value, limits, diagnostics) {
|
|
154
209
|
const ancestors = new WeakSet();
|
|
155
|
-
|
|
156
|
-
|
|
210
|
+
const maxDepth = Math.min(limits.maxDepth, MAX_SOURCE_DEPTH);
|
|
211
|
+
let nodes = 0;
|
|
212
|
+
let nodesExceeded = false;
|
|
213
|
+
function overNodeBudget(children) {
|
|
214
|
+
if (children <= limits.maxNodes - nodes) return false;
|
|
215
|
+
if (!nodesExceeded) diagnostics.add("source.node-limit");
|
|
216
|
+
nodesExceeded = true;
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
function copy(current, depth, role) {
|
|
220
|
+
nodes += 1;
|
|
221
|
+
if (nodes > limits.maxNodes) {
|
|
222
|
+
if (!nodesExceeded) diagnostics.add("source.node-limit");
|
|
223
|
+
nodesExceeded = true;
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
if (depth > (role === "value" ? maxDepth : MAX_ENVELOPE_DEPTH)) {
|
|
157
227
|
diagnostics.add("source.depth-limit");
|
|
158
228
|
return null;
|
|
159
229
|
}
|
|
@@ -173,6 +243,12 @@ function clonePortable(value, limits, diagnostics) {
|
|
|
173
243
|
ancestors.add(current);
|
|
174
244
|
if (Array.isArray(current)) {
|
|
175
245
|
const output = [];
|
|
246
|
+
const length = ownData(current, "length");
|
|
247
|
+
const arrayLength = typeof length === "number" && Number.isSafeInteger(length) && length >= 0 ? length : void 0;
|
|
248
|
+
if (arrayLength !== void 0 && overNodeBudget(arrayLength)) {
|
|
249
|
+
ancestors.delete(current);
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
176
252
|
for (const key of Reflect.ownKeys(current)) {
|
|
177
253
|
if (typeof key !== "string" || key !== "length" && !/^(0|[1-9]\d*)$/u.test(key)) {
|
|
178
254
|
diagnostics.add("source.invalid-layer");
|
|
@@ -181,41 +257,47 @@ function clonePortable(value, limits, diagnostics) {
|
|
|
181
257
|
if (key === "length") continue;
|
|
182
258
|
if (!hasData(current, key)) diagnostics.add("source.accessor");
|
|
183
259
|
}
|
|
184
|
-
const length = ownData(current, "length");
|
|
185
|
-
const arrayLength = typeof length === "number" && Number.isSafeInteger(length) && length >= 0 ? length : void 0;
|
|
186
260
|
if (arrayLength === void 0) diagnostics.add("source.invalid-layer");
|
|
187
|
-
else for (let index = 0; index < arrayLength; index += 1) {
|
|
261
|
+
else for (let index = 0; index < arrayLength && !nodesExceeded; index += 1) {
|
|
188
262
|
if (!hasData(current, String(index))) diagnostics.add("source.accessor");
|
|
189
|
-
output.push(copy(ownData(current, String(index)), depth + 1));
|
|
263
|
+
output.push(copy(ownData(current, String(index)), depth + 1, childRole(role, void 0)));
|
|
190
264
|
}
|
|
191
265
|
ancestors.delete(current);
|
|
192
266
|
return Object.freeze(output);
|
|
193
267
|
}
|
|
194
268
|
if (!plainObject(current)) diagnostics.add("source.invalid-layer");
|
|
195
269
|
const output = Object.create(null);
|
|
196
|
-
|
|
270
|
+
const keys = Reflect.ownKeys(current);
|
|
271
|
+
if (overNodeBudget(keys.length)) {
|
|
272
|
+
ancestors.delete(current);
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
for (const key of keys) {
|
|
276
|
+
if (nodesExceeded) break;
|
|
197
277
|
if (typeof key !== "string") {
|
|
198
278
|
diagnostics.add("source.invalid-layer");
|
|
199
279
|
continue;
|
|
200
280
|
}
|
|
201
|
-
if (
|
|
281
|
+
if ((0, _burojs_core_internal.isReservedSegment)(key)) diagnostics.add("source.reserved-key");
|
|
202
282
|
if ([...key].length > limits.maxStringLength) diagnostics.add("source.string-limit");
|
|
203
283
|
if (!hasData(current, key)) {
|
|
204
284
|
diagnostics.add("source.accessor");
|
|
205
285
|
continue;
|
|
206
286
|
}
|
|
207
|
-
|
|
287
|
+
const next = childRole(role, key);
|
|
288
|
+
const nextDepth = next === "value" && role !== "value" ? 0 : depth + 1;
|
|
289
|
+
output[key] = copy(ownData(current, key), nextDepth, next);
|
|
208
290
|
}
|
|
209
291
|
ancestors.delete(current);
|
|
210
292
|
return Object.freeze(output);
|
|
211
293
|
}
|
|
212
|
-
return copy(value, 0);
|
|
294
|
+
return copy(value, 0, "root");
|
|
213
295
|
}
|
|
214
296
|
function unknownKeys(value, allowed, diagnostics) {
|
|
215
297
|
for (const key of Object.keys(value)) if (!allowed.has(key)) diagnostics.add("source.unknown-key");
|
|
216
298
|
}
|
|
217
299
|
function allowedNamespace(value, policy, diagnostics) {
|
|
218
|
-
if (typeof value !== "string" || !policy.allowedNamespaces.includes(value)) {
|
|
300
|
+
if (typeof value !== "string" || !policy.grants.allowedNamespaces.includes(value)) {
|
|
219
301
|
diagnostics.add("source.forbidden-namespace");
|
|
220
302
|
return false;
|
|
221
303
|
}
|
|
@@ -244,17 +326,15 @@ function validateValue(value, kind, policy, diagnostics) {
|
|
|
244
326
|
if (!plainObject(value)) return;
|
|
245
327
|
if (value.$buro === ATTACHMENT_TAG) {
|
|
246
328
|
const capability = ownData(value, "capability");
|
|
247
|
-
|
|
329
|
+
const allowed = Object.hasOwn(policy.allowedCapabilities, kind) ? policy.allowedCapabilities[kind] : void 0;
|
|
330
|
+
if (typeof capability !== "string" || !allowed?.includes(capability)) diagnostics.add("source.forbidden-capability");
|
|
248
331
|
}
|
|
249
332
|
for (const key of Object.keys(value)) {
|
|
250
|
-
const category =
|
|
333
|
+
const category = (0, _burojs_core_internal.blacklistedKeyCategory)(key);
|
|
251
334
|
if (category !== void 0) diagnostics.add(`source.forbidden-${category}`);
|
|
252
335
|
validateValue(ownData(value, key), kind, policy, diagnostics);
|
|
253
336
|
}
|
|
254
337
|
}
|
|
255
|
-
function scopeRank(scope) {
|
|
256
|
-
return scope === "layer" ? 0 : scope === "namespace" ? 1 : 2;
|
|
257
|
-
}
|
|
258
338
|
function transformOperation(value) {
|
|
259
339
|
if (!plainObject(value)) return void 0;
|
|
260
340
|
const operation = ownData(value, "op");
|
|
@@ -285,7 +365,7 @@ function validateTransforms(value, defaultNamespace, policy, diagnostics) {
|
|
|
285
365
|
unknownKeys(transform, TRANSFORM_KEYS, diagnostics);
|
|
286
366
|
const scope = ownData(transform, "scope") ?? "layer";
|
|
287
367
|
if (scope !== "layer" && scope !== "application") diagnostics.add("source.invalid-transform");
|
|
288
|
-
else if (scopeRank(scope) > scopeRank(policy.maxTransformScope)) diagnostics.add("source.forbidden-transform-scope");
|
|
368
|
+
else if ((0, _burojs_core_internal.scopeRank)(scope) > (0, _burojs_core_internal.scopeRank)(policy.grants.maxTransformScope)) diagnostics.add("source.forbidden-transform-scope");
|
|
289
369
|
const select = ownData(transform, "select");
|
|
290
370
|
if (!plainObject(select)) diagnostics.add("source.invalid-transform");
|
|
291
371
|
else {
|
|
@@ -294,7 +374,7 @@ function validateTransforms(value, defaultNamespace, policy, diagnostics) {
|
|
|
294
374
|
if (selectedKinds !== void 0) {
|
|
295
375
|
if (!Array.isArray(selectedKinds)) diagnostics.add("source.invalid-transform");
|
|
296
376
|
else for (const kind of selectedKinds) if (typeof kind !== "string") diagnostics.add("source.invalid-transform");
|
|
297
|
-
else if (!policy.allowedKinds.includes(kind)) diagnostics.add("source.forbidden-kind");
|
|
377
|
+
else if (!policy.grants.allowedKinds.includes(kind)) diagnostics.add("source.forbidden-kind");
|
|
298
378
|
}
|
|
299
379
|
const selectedNamespaces = ownData(select, "namespaces");
|
|
300
380
|
if (selectedNamespaces !== void 0) {
|
|
@@ -309,7 +389,7 @@ function validateTransforms(value, defaultNamespace, policy, diagnostics) {
|
|
|
309
389
|
if (typeof (plainObject(operation) ? ownData(operation, "op") : void 0) === "string" && op === void 0) diagnostics.add("source.forbidden-transform-op");
|
|
310
390
|
else if (op === void 0) diagnostics.add("source.invalid-transform");
|
|
311
391
|
else {
|
|
312
|
-
if (!policy.allowedTransformOps.includes(op)) diagnostics.add("source.forbidden-transform-op");
|
|
392
|
+
if (!policy.grants.allowedTransformOps.includes(op)) diagnostics.add("source.forbidden-transform-op");
|
|
313
393
|
if (plainObject(operation)) {
|
|
314
394
|
unknownKeys(operation, OPERATION_KEYS[op], diagnostics);
|
|
315
395
|
if (hasData(operation, "value")) validateValue(ownData(operation, "value"), "", policy, diagnostics);
|
|
@@ -318,12 +398,10 @@ function validateTransforms(value, defaultNamespace, policy, diagnostics) {
|
|
|
318
398
|
}
|
|
319
399
|
}
|
|
320
400
|
function validateLayerValue(value, policy) {
|
|
321
|
-
|
|
401
|
+
const invalidPolicy = policyDiagnostic(policy);
|
|
402
|
+
if (invalidPolicy !== void 0) return {
|
|
322
403
|
ok: false,
|
|
323
|
-
diagnostics: Object.freeze([
|
|
324
|
-
code: "source.invalid-policy",
|
|
325
|
-
message: messageFor("source.invalid-policy")
|
|
326
|
-
})])
|
|
404
|
+
diagnostics: Object.freeze([invalidPolicy])
|
|
327
405
|
};
|
|
328
406
|
const diagnostics = new Diagnostics(policy.limits.maxDiagnostics);
|
|
329
407
|
const cloned = clonePortable(value, policy.limits, diagnostics);
|
|
@@ -353,7 +431,7 @@ function validateLayerValue(value, policy) {
|
|
|
353
431
|
unknownKeys(declaration, DECLARATION_KEYS, diagnostics);
|
|
354
432
|
const kind = ownData(declaration, "kind");
|
|
355
433
|
if (typeof kind !== "string") diagnostics.add("source.invalid-declaration");
|
|
356
|
-
else if (!policy.allowedKinds.includes(kind)) diagnostics.add("source.forbidden-kind");
|
|
434
|
+
else if (!policy.grants.allowedKinds.includes(kind)) diagnostics.add("source.forbidden-kind");
|
|
357
435
|
const namespace = namespaceFor(ownData(declaration, "ref"), typeof defaultNamespace === "string" ? defaultNamespace : "default", diagnostics);
|
|
358
436
|
if (namespace !== void 0) allowedNamespace(namespace, policy, diagnostics);
|
|
359
437
|
if (!Number.isSafeInteger(ownData(declaration, "schemaVersion")) || !hasData(declaration, "value")) diagnostics.add("source.invalid-declaration");
|
|
@@ -363,14 +441,14 @@ function validateLayerValue(value, policy) {
|
|
|
363
441
|
if (diagnostics.values.length > 0) return failure(diagnostics);
|
|
364
442
|
return {
|
|
365
443
|
ok: true,
|
|
366
|
-
value: cloned
|
|
444
|
+
value: (0, _burojs_core_internal.createParsedLayer)(cloned, (0, _burojs_core.normalizeLayerGrants)(policy.grants))
|
|
367
445
|
};
|
|
368
446
|
}
|
|
369
447
|
function byteLength(value) {
|
|
370
448
|
return new TextEncoder().encode(value).byteLength;
|
|
371
449
|
}
|
|
372
450
|
function parseSource(source, policy, format) {
|
|
373
|
-
if (
|
|
451
|
+
if (policyDiagnostic(policy) !== void 0) return validateLayerValue(null, policy);
|
|
374
452
|
const diagnostics = new Diagnostics(policy.limits.maxDiagnostics);
|
|
375
453
|
if (byteLength(source) > policy.limits.maxBytes) {
|
|
376
454
|
diagnostics.add("source.byte-limit");
|
|
@@ -400,82 +478,12 @@ function parseYamlLayer(source, policy) {
|
|
|
400
478
|
return parseSource(source, policy, "yaml");
|
|
401
479
|
}
|
|
402
480
|
//#endregion
|
|
403
|
-
//#region src/adapter.ts
|
|
404
|
-
function diagnostic(code, message) {
|
|
405
|
-
return Object.freeze({
|
|
406
|
-
code,
|
|
407
|
-
message
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
function diagnosticLimit(policy) {
|
|
411
|
-
const candidate = policy?.limits?.maxDiagnostics;
|
|
412
|
-
return typeof candidate === "number" && Number.isSafeInteger(candidate) && candidate > 0 ? candidate : 1;
|
|
413
|
-
}
|
|
414
|
-
function frozenPolicy(policy) {
|
|
415
|
-
const capabilities = Object.create(null);
|
|
416
|
-
for (const [kind, values] of Object.entries(policy.allowedCapabilities)) capabilities[kind] = Object.freeze([...values]);
|
|
417
|
-
const limits = Object.freeze({ ...policy.limits });
|
|
418
|
-
return Object.freeze({
|
|
419
|
-
allowedKinds: Object.freeze([...policy.allowedKinds]),
|
|
420
|
-
allowedNamespaces: Object.freeze([...policy.allowedNamespaces]),
|
|
421
|
-
allowedTransformOps: Object.freeze([...policy.allowedTransformOps]),
|
|
422
|
-
maxTransformScope: policy.maxTransformScope,
|
|
423
|
-
allowedCapabilities: Object.freeze(capabilities),
|
|
424
|
-
limits
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
function copyDiagnostic(value) {
|
|
428
|
-
if (value !== null && typeof value === "object" && typeof value.code === "string" && typeof value.message === "string") return diagnostic(value.code, value.message);
|
|
429
|
-
return diagnostic("source.invalid-adapter-diagnostic", "The source adapter returned an invalid diagnostic.");
|
|
430
|
-
}
|
|
431
|
-
function adaptRawConfig(input, adapter, policy) {
|
|
432
|
-
const policyValidation = validateLayerValue(null, policy);
|
|
433
|
-
if (!policyValidation.ok && policyValidation.diagnostics.some((entry) => entry.code === "source.invalid-policy")) return policyValidation;
|
|
434
|
-
const ownedPolicy = frozenPolicy(policy);
|
|
435
|
-
const diagnostics = [];
|
|
436
|
-
const maxDiagnostics = diagnosticLimit(ownedPolicy);
|
|
437
|
-
let limited = false;
|
|
438
|
-
const add = (value) => {
|
|
439
|
-
if (limited) return;
|
|
440
|
-
if (diagnostics.length + 1 < maxDiagnostics) {
|
|
441
|
-
diagnostics.push(value);
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
limited = true;
|
|
445
|
-
diagnostics.push(diagnostic("source.diagnostic-limit", "Additional source diagnostics were omitted."));
|
|
446
|
-
};
|
|
447
|
-
const context = Object.freeze({
|
|
448
|
-
policy: ownedPolicy,
|
|
449
|
-
limits: ownedPolicy.limits,
|
|
450
|
-
diagnose(code, message) {
|
|
451
|
-
add(copyDiagnostic({
|
|
452
|
-
code,
|
|
453
|
-
message
|
|
454
|
-
}));
|
|
455
|
-
}
|
|
456
|
-
});
|
|
457
|
-
const result = adapter.adapt(input, context);
|
|
458
|
-
if (result === null || typeof result !== "object") add(diagnostic("source.invalid-adapter-result", "The source adapter returned an invalid result."));
|
|
459
|
-
else if (result.diagnostics !== void 0) {
|
|
460
|
-
if (!Array.isArray(result.diagnostics)) add(diagnostic("source.invalid-adapter-result", "The source adapter returned an invalid result."));
|
|
461
|
-
else for (const entry of result.diagnostics) add(copyDiagnostic(entry));
|
|
462
|
-
}
|
|
463
|
-
if (diagnostics.length > 0) return {
|
|
464
|
-
ok: false,
|
|
465
|
-
diagnostics: Object.freeze(diagnostics)
|
|
466
|
-
};
|
|
467
|
-
if (result === null || typeof result !== "object" || result.layer === void 0) return {
|
|
468
|
-
ok: false,
|
|
469
|
-
diagnostics: Object.freeze([diagnostic("source.invalid-adapter-result", "The source adapter did not return a declaration layer.")])
|
|
470
|
-
};
|
|
471
|
-
return validateLayerValue(result.layer, ownedPolicy);
|
|
472
|
-
}
|
|
473
|
-
const parseResultAdapter = Object.freeze({ adapt(input) {
|
|
474
|
-
return input.ok ? { layer: input.value } : { diagnostics: input.diagnostics };
|
|
475
|
-
} });
|
|
476
|
-
//#endregion
|
|
477
481
|
//#region src/runtime-source.ts
|
|
478
|
-
|
|
482
|
+
const INVALID_FORMAT = Object.freeze({
|
|
483
|
+
code: "source.invalid-format",
|
|
484
|
+
message: "The source format must be json or yaml."
|
|
485
|
+
});
|
|
486
|
+
function rejection(diagnostics) {
|
|
479
487
|
return Object.freeze({
|
|
480
488
|
status: "rejected",
|
|
481
489
|
diagnostics: Object.freeze(diagnostics.map((entry) => Object.freeze({
|
|
@@ -484,21 +492,28 @@ function adaptationFailure(diagnostics) {
|
|
|
484
492
|
})))
|
|
485
493
|
});
|
|
486
494
|
}
|
|
487
|
-
function createConfigRuntimeSource(id,
|
|
488
|
-
|
|
495
|
+
function createConfigRuntimeSource(id, options) {
|
|
496
|
+
const policy = ownedPolicy(options.policy);
|
|
497
|
+
const { load } = options;
|
|
498
|
+
const source = Object.freeze({
|
|
489
499
|
id,
|
|
490
500
|
async load(context) {
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
return
|
|
501
|
+
const raw = await load(context);
|
|
502
|
+
const parsed = raw.format === "json" ? parseJsonLayer(raw.text, policy) : raw.format === "yaml" ? parseYamlLayer(raw.text, policy) : void 0;
|
|
503
|
+
if (parsed === void 0) return rejection([INVALID_FORMAT]);
|
|
504
|
+
return parsed.ok ? parsed.value : rejection(parsed.diagnostics);
|
|
494
505
|
}
|
|
495
506
|
});
|
|
507
|
+
return Object.freeze({
|
|
508
|
+
source,
|
|
509
|
+
trust: policy.grants
|
|
510
|
+
});
|
|
496
511
|
}
|
|
497
512
|
//#endregion
|
|
498
|
-
exports.
|
|
513
|
+
exports.MAX_SOURCE_DEPTH = MAX_SOURCE_DEPTH;
|
|
514
|
+
exports.SCREEN_SOURCE_LIMITS = SCREEN_SOURCE_LIMITS;
|
|
499
515
|
exports.createConfigRuntimeSource = createConfigRuntimeSource;
|
|
500
516
|
exports.parseJsonLayer = parseJsonLayer;
|
|
501
|
-
exports.parseResultAdapter = parseResultAdapter;
|
|
502
517
|
exports.parseYamlLayer = parseYamlLayer;
|
|
503
518
|
|
|
504
519
|
//# sourceMappingURL=index.cjs.map
|