@renderify/security 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Web LLM
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # @renderify/security
2
+
3
+ ![Node CI](https://github.com/webllm/renderify/workflows/CI/badge.svg)
4
+ ![npm](https://img.shields.io/npm/v/@renderify/security.svg)
5
+ ![license](https://img.shields.io/npm/l/@renderify/security)
6
+
7
+ Security policy engine for Renderify RuntimePlan execution.
8
+
9
+ `@renderify/security` validates plans, capabilities, module sources, and runtime source code before execution.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pnpm add @renderify/security @renderify/ir
15
+ # or
16
+ npm i @renderify/security @renderify/ir
17
+ ```
18
+
19
+ ## Main API
20
+
21
+ - `DefaultSecurityChecker`
22
+ - `listSecurityProfiles()`
23
+ - `getSecurityProfilePolicy(profile)`
24
+ - Types: `RuntimeSecurityPolicy`, `SecurityCheckResult`, `RuntimeSecurityProfile`
25
+
26
+ ## Profiles
27
+
28
+ - `strict`
29
+ - `balanced` (default)
30
+ - `relaxed`
31
+
32
+ ## Quick Start
33
+
34
+ ```ts
35
+ import { DefaultSecurityChecker } from "@renderify/security";
36
+
37
+ const checker = new DefaultSecurityChecker();
38
+ checker.initialize({ profile: "strict" });
39
+
40
+ const result = await checker.checkPlan(plan);
41
+ if (!result.safe) {
42
+ console.error(result.issues);
43
+ }
44
+ ```
45
+
46
+ ## What It Checks
47
+
48
+ - Blocked HTML tags and tree limits
49
+ - Allowed module specifiers and network hosts
50
+ - Execution profile limits and capability quotas
51
+ - Runtime source module constraints
52
+ - Spec version and module manifest requirements
53
+
54
+ ## Notes
55
+
56
+ - `checkPlan()` is async.
57
+ - Use policy overrides via `initialize({ profile, overrides })` for environment-specific constraints.
@@ -0,0 +1,582 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ DefaultSecurityChecker: () => DefaultSecurityChecker,
24
+ getSecurityProfilePolicy: () => getSecurityProfilePolicy,
25
+ listSecurityProfiles: () => listSecurityProfiles
26
+ });
27
+ module.exports = __toCommonJS(src_exports);
28
+ var import_ir = require("@renderify/ir");
29
+ var SECURITY_PROFILE_POLICIES = {
30
+ strict: {
31
+ blockedTags: ["script", "iframe", "object", "embed", "link", "meta"],
32
+ maxTreeDepth: 8,
33
+ maxNodeCount: 250,
34
+ allowInlineEventHandlers: false,
35
+ allowedModules: ["/", "npm:"],
36
+ allowedNetworkHosts: ["ga.jspm.io", "cdn.jspm.io"],
37
+ allowArbitraryNetwork: false,
38
+ allowedExecutionProfiles: [
39
+ "standard",
40
+ "isolated-vm",
41
+ "sandbox-worker",
42
+ "sandbox-iframe"
43
+ ],
44
+ maxTransitionsPerPlan: 40,
45
+ maxActionsPerTransition: 20,
46
+ maxAllowedImports: 80,
47
+ maxAllowedExecutionMs: 5e3,
48
+ maxAllowedComponentInvocations: 120,
49
+ allowRuntimeSourceModules: true,
50
+ maxRuntimeSourceBytes: 2e4,
51
+ supportedSpecVersions: [import_ir.DEFAULT_RUNTIME_PLAN_SPEC_VERSION],
52
+ requireSpecVersion: true,
53
+ requireModuleManifestForBareSpecifiers: true,
54
+ requireModuleIntegrity: true,
55
+ allowDynamicSourceImports: false,
56
+ sourceBannedPatternStrings: [
57
+ "\\beval\\s*\\(",
58
+ "\\bnew\\s+Function\\b",
59
+ "\\bfetch\\s*\\(",
60
+ "\\bXMLHttpRequest\\b",
61
+ "\\bWebSocket\\b",
62
+ "\\bimportScripts\\b",
63
+ "\\bdocument\\s*\\.\\s*cookie\\b",
64
+ "\\blocalStorage\\b",
65
+ "\\bsessionStorage\\b",
66
+ "\\bindexedDB\\b",
67
+ "\\bnavigator\\s*\\.\\s*sendBeacon\\b",
68
+ "\\bchild_process\\b",
69
+ "\\bprocess\\s*\\.\\s*env\\b"
70
+ ],
71
+ maxSourceImportSpecifiers: 30
72
+ },
73
+ balanced: {
74
+ blockedTags: ["script", "iframe", "object", "embed", "link", "meta"],
75
+ maxTreeDepth: 12,
76
+ maxNodeCount: 500,
77
+ allowInlineEventHandlers: false,
78
+ allowedModules: ["/", "npm:"],
79
+ allowedNetworkHosts: ["ga.jspm.io", "cdn.jspm.io"],
80
+ allowArbitraryNetwork: false,
81
+ allowedExecutionProfiles: [
82
+ "standard",
83
+ "isolated-vm",
84
+ "sandbox-worker",
85
+ "sandbox-iframe"
86
+ ],
87
+ maxTransitionsPerPlan: 100,
88
+ maxActionsPerTransition: 50,
89
+ maxAllowedImports: 200,
90
+ maxAllowedExecutionMs: 15e3,
91
+ maxAllowedComponentInvocations: 500,
92
+ allowRuntimeSourceModules: true,
93
+ maxRuntimeSourceBytes: 8e4,
94
+ supportedSpecVersions: [import_ir.DEFAULT_RUNTIME_PLAN_SPEC_VERSION],
95
+ requireSpecVersion: true,
96
+ requireModuleManifestForBareSpecifiers: true,
97
+ requireModuleIntegrity: false,
98
+ allowDynamicSourceImports: false,
99
+ sourceBannedPatternStrings: [
100
+ "\\beval\\s*\\(",
101
+ "\\bnew\\s+Function\\b",
102
+ "\\bfetch\\s*\\(",
103
+ "\\bXMLHttpRequest\\b",
104
+ "\\bWebSocket\\b",
105
+ "\\bimportScripts\\b",
106
+ "\\bdocument\\s*\\.\\s*cookie\\b",
107
+ "\\blocalStorage\\b",
108
+ "\\bsessionStorage\\b",
109
+ "\\bchild_process\\b"
110
+ ],
111
+ maxSourceImportSpecifiers: 120
112
+ },
113
+ relaxed: {
114
+ blockedTags: ["script", "iframe", "object", "embed"],
115
+ maxTreeDepth: 24,
116
+ maxNodeCount: 2e3,
117
+ allowInlineEventHandlers: true,
118
+ allowedModules: [
119
+ "/",
120
+ "npm:",
121
+ "https://ga.jspm.io/",
122
+ "https://cdn.jspm.io/"
123
+ ],
124
+ allowedNetworkHosts: ["ga.jspm.io", "cdn.jspm.io", "esm.sh", "unpkg.com"],
125
+ allowArbitraryNetwork: true,
126
+ allowedExecutionProfiles: [
127
+ "standard",
128
+ "isolated-vm",
129
+ "sandbox-worker",
130
+ "sandbox-iframe"
131
+ ],
132
+ maxTransitionsPerPlan: 400,
133
+ maxActionsPerTransition: 150,
134
+ maxAllowedImports: 1e3,
135
+ maxAllowedExecutionMs: 6e4,
136
+ maxAllowedComponentInvocations: 4e3,
137
+ allowRuntimeSourceModules: true,
138
+ maxRuntimeSourceBytes: 2e5,
139
+ supportedSpecVersions: [import_ir.DEFAULT_RUNTIME_PLAN_SPEC_VERSION],
140
+ requireSpecVersion: false,
141
+ requireModuleManifestForBareSpecifiers: false,
142
+ requireModuleIntegrity: false,
143
+ allowDynamicSourceImports: true,
144
+ sourceBannedPatternStrings: ["\\bchild_process\\b"],
145
+ maxSourceImportSpecifiers: 500
146
+ }
147
+ };
148
+ var DEFAULT_SECURITY_PROFILE = "balanced";
149
+ function listSecurityProfiles() {
150
+ return Object.keys(SECURITY_PROFILE_POLICIES);
151
+ }
152
+ function getSecurityProfilePolicy(profile) {
153
+ return clonePolicy(SECURITY_PROFILE_POLICIES[profile]);
154
+ }
155
+ var DefaultSecurityChecker = class {
156
+ policy = getSecurityProfilePolicy(
157
+ DEFAULT_SECURITY_PROFILE
158
+ );
159
+ profile = DEFAULT_SECURITY_PROFILE;
160
+ initialize(input) {
161
+ const normalized = normalizeInitializationInput(input);
162
+ const profile = normalized.profile ?? DEFAULT_SECURITY_PROFILE;
163
+ const basePolicy = getSecurityProfilePolicy(profile);
164
+ this.policy = {
165
+ ...basePolicy,
166
+ ...normalized.overrides,
167
+ blockedTags: normalized.overrides?.blockedTags ?? basePolicy.blockedTags,
168
+ allowedModules: normalized.overrides?.allowedModules ?? basePolicy.allowedModules,
169
+ allowedNetworkHosts: normalized.overrides?.allowedNetworkHosts ?? basePolicy.allowedNetworkHosts,
170
+ allowedExecutionProfiles: normalized.overrides?.allowedExecutionProfiles ?? basePolicy.allowedExecutionProfiles,
171
+ supportedSpecVersions: normalized.overrides?.supportedSpecVersions ?? basePolicy.supportedSpecVersions,
172
+ sourceBannedPatternStrings: normalized.overrides?.sourceBannedPatternStrings ?? basePolicy.sourceBannedPatternStrings
173
+ };
174
+ this.profile = profile;
175
+ }
176
+ getPolicy() {
177
+ return { ...this.policy };
178
+ }
179
+ getProfile() {
180
+ return this.profile;
181
+ }
182
+ async checkPlan(plan) {
183
+ const issues = [];
184
+ const diagnostics = [];
185
+ const planSpecVersion = (0, import_ir.resolveRuntimePlanSpecVersion)(plan.specVersion);
186
+ const moduleManifest = plan.moduleManifest;
187
+ if (this.policy.requireSpecVersion && (typeof plan.specVersion !== "string" || plan.specVersion.trim().length === 0)) {
188
+ issues.push("Runtime plan specVersion is required by policy");
189
+ }
190
+ if (!this.policy.supportedSpecVersions.includes(planSpecVersion)) {
191
+ issues.push(
192
+ `Runtime plan specVersion ${planSpecVersion} is not supported by policy`
193
+ );
194
+ }
195
+ if (moduleManifest) {
196
+ issues.push(...this.checkModuleManifest(moduleManifest));
197
+ }
198
+ if (this.policy.requireModuleManifestForBareSpecifiers) {
199
+ issues.push(...await this.checkManifestCoverage(plan, moduleManifest));
200
+ }
201
+ const capabilityResult = this.checkCapabilities(
202
+ plan.capabilities,
203
+ moduleManifest
204
+ );
205
+ issues.push(...capabilityResult.issues);
206
+ diagnostics.push(...capabilityResult.diagnostics);
207
+ let nodeCount = 0;
208
+ const walk = (node, depth) => {
209
+ nodeCount += 1;
210
+ if (depth > this.policy.maxTreeDepth) {
211
+ issues.push(
212
+ `Node depth ${depth} exceeds maximum ${this.policy.maxTreeDepth}`
213
+ );
214
+ }
215
+ if (node.type === "element") {
216
+ const normalizedTag = node.tag.toLowerCase();
217
+ if (this.policy.blockedTags.includes(normalizedTag)) {
218
+ issues.push(`Blocked tag detected: <${normalizedTag}>`);
219
+ }
220
+ if (node.props) {
221
+ for (const key of Object.keys(node.props)) {
222
+ if (!this.policy.allowInlineEventHandlers && /^on[A-Z]|^on[a-z]/.test(key)) {
223
+ issues.push(`Inline event handler is not allowed: ${key}`);
224
+ }
225
+ }
226
+ }
227
+ }
228
+ if (node.type === "component") {
229
+ const componentSpecifier = this.resolveManifestSpecifier(
230
+ node.module,
231
+ moduleManifest
232
+ );
233
+ const componentResult = this.checkModuleSpecifier(componentSpecifier);
234
+ issues.push(...componentResult.issues);
235
+ }
236
+ if (node.type === "text") {
237
+ return;
238
+ }
239
+ for (const child of node.children ?? []) {
240
+ walk(child, depth + 1);
241
+ }
242
+ };
243
+ walk(plan.root, 0);
244
+ if (nodeCount > this.policy.maxNodeCount) {
245
+ issues.push(
246
+ `Node count ${nodeCount} exceeds maximum ${this.policy.maxNodeCount}`
247
+ );
248
+ }
249
+ const importSpecifiers = plan.imports ?? [];
250
+ for (const specifier of importSpecifiers) {
251
+ const effectiveSpecifier = this.resolveManifestSpecifier(
252
+ specifier,
253
+ moduleManifest
254
+ );
255
+ const importCheck = this.checkModuleSpecifier(effectiveSpecifier);
256
+ issues.push(...importCheck.issues);
257
+ }
258
+ if (plan.state) {
259
+ issues.push(...this.checkStateModel(plan.state));
260
+ }
261
+ if (plan.source) {
262
+ issues.push(
263
+ ...await this.checkRuntimeSource(plan.source, moduleManifest)
264
+ );
265
+ }
266
+ for (const issue of issues) {
267
+ diagnostics.push({
268
+ level: "error",
269
+ code: "SECURITY_POLICY_VIOLATION",
270
+ message: issue
271
+ });
272
+ }
273
+ return {
274
+ safe: issues.length === 0,
275
+ issues,
276
+ diagnostics
277
+ };
278
+ }
279
+ checkModuleSpecifier(specifier) {
280
+ const issues = [];
281
+ const diagnostics = [];
282
+ if (specifier.includes("..")) {
283
+ issues.push(
284
+ `Path traversal is not allowed in module specifier: ${specifier}`
285
+ );
286
+ }
287
+ const isUrl = this.isUrl(specifier);
288
+ if (isUrl) {
289
+ const parsedUrl = new URL(specifier);
290
+ if (!this.policy.allowArbitraryNetwork && !this.policy.allowedNetworkHosts.includes(parsedUrl.host)) {
291
+ issues.push(`Network host is not in allowlist: ${parsedUrl.host}`);
292
+ }
293
+ } else {
294
+ const allowed = this.policy.allowedModules.length === 0 || this.policy.allowedModules.some(
295
+ (prefix) => specifier.startsWith(prefix)
296
+ );
297
+ if (!allowed) {
298
+ issues.push(`Module specifier is not in allowlist: ${specifier}`);
299
+ }
300
+ }
301
+ for (const issue of issues) {
302
+ diagnostics.push({
303
+ level: "error",
304
+ code: "SECURITY_MODULE_REJECTED",
305
+ message: issue
306
+ });
307
+ }
308
+ return {
309
+ safe: issues.length === 0,
310
+ issues,
311
+ diagnostics
312
+ };
313
+ }
314
+ checkCapabilities(capabilities, moduleManifest) {
315
+ const issues = [];
316
+ const diagnostics = [];
317
+ const requestedHosts = capabilities.networkHosts ?? [];
318
+ if (!this.policy.allowArbitraryNetwork) {
319
+ for (const host of requestedHosts) {
320
+ if (!this.policy.allowedNetworkHosts.includes(host)) {
321
+ issues.push(`Requested network host is not allowed: ${host}`);
322
+ }
323
+ }
324
+ }
325
+ const requestedModules = capabilities.allowedModules ?? [];
326
+ for (const moduleSpecifier of requestedModules) {
327
+ const effectiveSpecifier = this.resolveManifestSpecifier(
328
+ moduleSpecifier,
329
+ moduleManifest
330
+ );
331
+ const checkResult = this.checkModuleSpecifier(effectiveSpecifier);
332
+ issues.push(...checkResult.issues);
333
+ if (this.policy.requireModuleManifestForBareSpecifiers && this.isBareSpecifier(moduleSpecifier) && !moduleManifest?.[moduleSpecifier]) {
334
+ issues.push(
335
+ `Missing moduleManifest entry for bare specifier: ${moduleSpecifier}`
336
+ );
337
+ }
338
+ }
339
+ if (capabilities.executionProfile !== void 0 && !this.policy.allowedExecutionProfiles.includes(
340
+ capabilities.executionProfile
341
+ )) {
342
+ issues.push(
343
+ `Requested executionProfile ${capabilities.executionProfile} is not allowed`
344
+ );
345
+ }
346
+ if (typeof capabilities.maxImports === "number" && capabilities.maxImports > this.policy.maxAllowedImports) {
347
+ issues.push(
348
+ `Requested maxImports ${capabilities.maxImports} exceeds policy limit ${this.policy.maxAllowedImports}`
349
+ );
350
+ }
351
+ if (typeof capabilities.maxExecutionMs === "number" && capabilities.maxExecutionMs > this.policy.maxAllowedExecutionMs) {
352
+ issues.push(
353
+ `Requested maxExecutionMs ${capabilities.maxExecutionMs} exceeds policy limit ${this.policy.maxAllowedExecutionMs}`
354
+ );
355
+ }
356
+ if (typeof capabilities.maxComponentInvocations === "number" && capabilities.maxComponentInvocations > this.policy.maxAllowedComponentInvocations) {
357
+ issues.push(
358
+ `Requested maxComponentInvocations ${capabilities.maxComponentInvocations} exceeds policy limit ${this.policy.maxAllowedComponentInvocations}`
359
+ );
360
+ }
361
+ for (const issue of issues) {
362
+ diagnostics.push({
363
+ level: "error",
364
+ code: "SECURITY_CAPABILITY_REJECTED",
365
+ message: issue
366
+ });
367
+ }
368
+ return {
369
+ safe: issues.length === 0,
370
+ issues,
371
+ diagnostics
372
+ };
373
+ }
374
+ checkStateModel(state) {
375
+ const issues = [];
376
+ const transitions = state.transitions ?? {};
377
+ const transitionEntries = Object.entries(transitions);
378
+ if (transitionEntries.length > this.policy.maxTransitionsPerPlan) {
379
+ issues.push(
380
+ `Transition count ${transitionEntries.length} exceeds maximum ${this.policy.maxTransitionsPerPlan}`
381
+ );
382
+ }
383
+ for (const [eventType, actions] of transitionEntries) {
384
+ if (actions.length > this.policy.maxActionsPerTransition) {
385
+ issues.push(
386
+ `Transition ${eventType} has ${actions.length} actions which exceeds maximum ${this.policy.maxActionsPerTransition}`
387
+ );
388
+ }
389
+ for (const action of actions) {
390
+ issues.push(...this.checkAction(eventType, action));
391
+ }
392
+ }
393
+ return issues;
394
+ }
395
+ async checkRuntimeSource(source, moduleManifest) {
396
+ const issues = [];
397
+ if (!this.policy.allowRuntimeSourceModules) {
398
+ issues.push("Runtime source modules are disabled by policy");
399
+ return issues;
400
+ }
401
+ const sourceBytes = typeof TextEncoder !== "undefined" ? new TextEncoder().encode(source.code).length : source.code.length;
402
+ if (sourceBytes > this.policy.maxRuntimeSourceBytes) {
403
+ issues.push(
404
+ `Runtime source size ${sourceBytes} exceeds maximum ${this.policy.maxRuntimeSourceBytes} bytes`
405
+ );
406
+ }
407
+ const sourceImports = await this.parseSourceImports(source.code);
408
+ if (sourceImports.length > this.policy.maxSourceImportSpecifiers) {
409
+ issues.push(
410
+ `Runtime source import count ${sourceImports.length} exceeds maximum ${this.policy.maxSourceImportSpecifiers}`
411
+ );
412
+ }
413
+ for (const sourceImport of sourceImports) {
414
+ const effectiveSpecifier = this.resolveManifestSpecifier(
415
+ sourceImport,
416
+ moduleManifest
417
+ );
418
+ const importCheck = this.checkModuleSpecifier(effectiveSpecifier);
419
+ issues.push(...importCheck.issues);
420
+ if (this.policy.requireModuleManifestForBareSpecifiers && this.isBareSpecifier(sourceImport) && !moduleManifest?.[sourceImport]) {
421
+ issues.push(
422
+ `Runtime source bare import requires manifest entry: ${sourceImport}`
423
+ );
424
+ }
425
+ }
426
+ if (!this.policy.allowDynamicSourceImports && /\bimport\s*\(/.test(source.code)) {
427
+ issues.push("Runtime source dynamic import() is disabled by policy");
428
+ }
429
+ for (const patternText of this.policy.sourceBannedPatternStrings) {
430
+ let pattern;
431
+ try {
432
+ pattern = new RegExp(patternText, "i");
433
+ } catch {
434
+ continue;
435
+ }
436
+ if (pattern.test(source.code)) {
437
+ issues.push(`Runtime source contains blocked pattern: ${patternText}`);
438
+ }
439
+ }
440
+ return issues;
441
+ }
442
+ checkModuleManifest(moduleManifest) {
443
+ const issues = [];
444
+ for (const [specifier, descriptor] of Object.entries(moduleManifest)) {
445
+ if (specifier.trim().length === 0) {
446
+ issues.push("moduleManifest contains an empty specifier key");
447
+ continue;
448
+ }
449
+ if (descriptor.resolvedUrl.trim().length === 0) {
450
+ issues.push(`moduleManifest entry has empty resolvedUrl: ${specifier}`);
451
+ }
452
+ if (this.policy.requireModuleIntegrity && this.isUrl(descriptor.resolvedUrl) && (!descriptor.integrity || descriptor.integrity.trim().length === 0)) {
453
+ issues.push(
454
+ `moduleManifest entry requires integrity for remote module: ${specifier}`
455
+ );
456
+ }
457
+ const resolvedCheck = this.checkModuleSpecifier(descriptor.resolvedUrl);
458
+ issues.push(...resolvedCheck.issues);
459
+ }
460
+ return issues;
461
+ }
462
+ async checkManifestCoverage(plan, moduleManifest) {
463
+ const issues = [];
464
+ const requiredSpecifiers = /* @__PURE__ */ new Set();
465
+ const imports = plan.imports ?? [];
466
+ for (const specifier of imports) {
467
+ if (this.isBareSpecifier(specifier)) {
468
+ requiredSpecifiers.add(specifier);
469
+ }
470
+ }
471
+ for (const specifier of plan.capabilities.allowedModules ?? []) {
472
+ if (this.isBareSpecifier(specifier)) {
473
+ requiredSpecifiers.add(specifier);
474
+ }
475
+ }
476
+ walkNodes(plan.root, (node) => {
477
+ if (node.type === "component" && this.isBareSpecifier(node.module)) {
478
+ requiredSpecifiers.add(node.module);
479
+ }
480
+ });
481
+ for (const sourceImport of await this.parseSourceImports(
482
+ plan.source?.code ?? ""
483
+ )) {
484
+ if (this.isBareSpecifier(sourceImport)) {
485
+ requiredSpecifiers.add(sourceImport);
486
+ }
487
+ }
488
+ for (const specifier of requiredSpecifiers) {
489
+ if (!moduleManifest?.[specifier]) {
490
+ issues.push(
491
+ `Missing moduleManifest entry for bare specifier: ${specifier}`
492
+ );
493
+ }
494
+ }
495
+ return issues;
496
+ }
497
+ resolveManifestSpecifier(specifier, moduleManifest) {
498
+ const descriptor = moduleManifest?.[specifier];
499
+ if (!descriptor || descriptor.resolvedUrl.trim().length === 0) {
500
+ return specifier;
501
+ }
502
+ return descriptor.resolvedUrl;
503
+ }
504
+ async parseSourceImports(code) {
505
+ if (code.trim().length === 0) {
506
+ return [];
507
+ }
508
+ return (0, import_ir.collectRuntimeSourceImports)(code);
509
+ }
510
+ isBareSpecifier(specifier) {
511
+ return !specifier.startsWith("./") && !specifier.startsWith("../") && !specifier.startsWith("/") && !specifier.startsWith("http://") && !specifier.startsWith("https://") && !specifier.startsWith("data:") && !specifier.startsWith("blob:");
512
+ }
513
+ checkAction(eventType, action) {
514
+ const issues = [];
515
+ if (!(0, import_ir.isSafePath)(action.path)) {
516
+ issues.push(`Unsafe action path in ${eventType}: ${action.path}`);
517
+ }
518
+ if (action.type === "increment" && typeof action.by === "number" && !Number.isFinite(action.by)) {
519
+ issues.push(`Invalid increment value for ${eventType}: ${action.by}`);
520
+ }
521
+ if (action.type === "set" || action.type === "push") {
522
+ const value = action.value;
523
+ if ((0, import_ir.isRuntimeValueFromPath)(value)) {
524
+ const source = value.$from;
525
+ const allowedPrefix = source.startsWith("state.") || source.startsWith("event.") || source.startsWith("context.") || source.startsWith("vars.");
526
+ if (!allowedPrefix) {
527
+ issues.push(`Unsupported value source in ${eventType}: ${source}`);
528
+ }
529
+ if (!(0, import_ir.isSafePath)(source.replace(/^(state|event|context|vars)\./, ""))) {
530
+ issues.push(`Unsafe value source path in ${eventType}: ${source}`);
531
+ }
532
+ }
533
+ }
534
+ return issues;
535
+ }
536
+ isUrl(specifier) {
537
+ try {
538
+ const parsed = new URL(specifier);
539
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
540
+ } catch {
541
+ return false;
542
+ }
543
+ }
544
+ };
545
+ function clonePolicy(policy) {
546
+ return {
547
+ ...policy,
548
+ blockedTags: [...policy.blockedTags],
549
+ allowedModules: [...policy.allowedModules],
550
+ allowedNetworkHosts: [...policy.allowedNetworkHosts],
551
+ allowedExecutionProfiles: [...policy.allowedExecutionProfiles],
552
+ supportedSpecVersions: [...policy.supportedSpecVersions],
553
+ sourceBannedPatternStrings: [...policy.sourceBannedPatternStrings]
554
+ };
555
+ }
556
+ function walkNodes(node, visitor) {
557
+ visitor(node);
558
+ if (node.type === "text") {
559
+ return;
560
+ }
561
+ for (const child of node.children ?? []) {
562
+ walkNodes(child, visitor);
563
+ }
564
+ }
565
+ function normalizeInitializationInput(input) {
566
+ if (!input) {
567
+ return {};
568
+ }
569
+ if (isSecurityInitializationOptions(input)) {
570
+ return input;
571
+ }
572
+ return {
573
+ overrides: input
574
+ };
575
+ }
576
+ function isSecurityInitializationOptions(value) {
577
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
578
+ return false;
579
+ }
580
+ return "profile" in value || "overrides" in value;
581
+ }
582
+ //# sourceMappingURL=security.cjs.js.map