assign-gingerly 0.0.71 → 0.0.73

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.
Files changed (38) hide show
  1. package/DX/emojis.js +16 -0
  2. package/DX/emojis.ts +16 -0
  3. package/README.md +30 -3
  4. package/assignFrom.js +6 -6
  5. package/assignFrom.ts +11 -10
  6. package/assignFromAsync.js +3 -3
  7. package/assignFromAsync.ts +4 -4
  8. package/assignGingerly.js +136 -69
  9. package/assignGingerly.ts +170 -103
  10. package/assignPermissions/isAllowedImportPath.js +41 -0
  11. package/assignPermissions/isAllowedImportPath.ts +38 -0
  12. package/assignPermissions/restrictedProps.js +43 -0
  13. package/assignPermissions/restrictedProps.ts +53 -0
  14. package/assignTentatively.js +45 -30
  15. package/assignTentatively.ts +85 -60
  16. package/defineWithFeatures.js +38 -32
  17. package/defineWithFeatures.ts +46 -39
  18. package/eachTime.js +12 -4
  19. package/eachTime.ts +17 -7
  20. package/enhanceAll.js +2 -2
  21. package/enhanceAll.ts +3 -3
  22. package/evaluatePathWithAsyncMethods.js +29 -16
  23. package/evaluatePathWithAsyncMethods.ts +31 -16
  24. package/handlers/addEventListener.js +11 -11
  25. package/handlers/addEventListener.ts +18 -15
  26. package/handlers/lazyLoad.ts +10 -7
  27. package/handlers/lazyLoadSwitch.ts +4 -3
  28. package/handlers/manageTemplateList.js +9 -9
  29. package/handlers/manageTemplateList.ts +11 -10
  30. package/handlers/rangeSelector.ts +4 -3
  31. package/inferencer/types/assign-gingerly/types.d.ts +63 -4
  32. package/inferencer/types/nested-regex-groups/types.d.ts +12 -0
  33. package/package.json +6 -5
  34. package/processHandlerCommands.js +7 -4
  35. package/processHandlerCommands.ts +13 -10
  36. package/types/assign-gingerly/types.d.ts +63 -4
  37. package/isAllowedImportPath.js +0 -42
  38. package/isAllowedImportPath.ts +0 -53
@@ -30,7 +30,8 @@ import type { ManageTemplateListResolvedParams } from '../types/assign-gingerly/
30
30
  import { findMarkers, createMarkers, getNodesBetweenMarkers, MARKER_START_PREFIX, MARKER_END } from '../markerUtils.js';
31
31
  import { resolveValue } from '../resolveValues.js';
32
32
  import { assignFrom } from '../assignFrom.js';
33
- import { processInferredAssignments } from '../inferredAssignments.js';
33
+ import { processInferredAssignments } from '../inferredAssignments.js';
34
+ import type { AssignPermissions } from '../types/assign-gingerly/types.js';
34
35
 
35
36
  /**
36
37
  * Reserved keys in fromEachItem config — not treated as shorthand patterns.
@@ -74,7 +75,7 @@ export class ManageTemplateListHandler implements AssignFromHandler {
74
75
  this.config = config;
75
76
  }
76
77
 
77
- async assign(lhsTarget: any, resolvedParams: ManageTemplateListResolvedParams, options?: any): Promise<void> {
78
+ async assign(lhsTarget: any, resolvedParams: ManageTemplateListResolvedParams, options?: any, permissions?: AssignPermissions): Promise<void> {
78
79
  //return;
79
80
  const {
80
81
  forEach: items,
@@ -183,7 +184,7 @@ export class ManageTemplateListHandler implements AssignFromHandler {
183
184
  const len = Math.min(elements.length, configs.length);
184
185
  for (let j = 0; j < len; j++) {
185
186
  const cfg = configs[j];
186
- assignFrom(elements[j], cfg.toClone ?? {}, { from: item, ...cfg.withOptions });
187
+ assignFrom(elements[j], cfg.toClone ?? {}, { from: item, ...cfg.withOptions }, permissions);
187
188
  }
188
189
  } else {
189
190
  const rootEl = existingNodes.find(n => n instanceof Element) as Element | undefined;
@@ -191,13 +192,13 @@ export class ManageTemplateListHandler implements AssignFromHandler {
191
192
  if (processInferred) {
192
193
  processInferred(rootEl, item, inferredConfig === true ? { byItemprop: true } : inferredConfig);
193
194
  } else {
194
- assignFrom(rootEl, toClone, { from: item, ...withOptions });
195
+ assignFrom(rootEl, toClone, { from: item, ...withOptions }, permissions);
195
196
  }
196
197
  if (hostToClone && options?.from) {
197
- assignFrom(rootEl, hostToClone, { from: options.from, ...hostWithOptions });
198
+ assignFrom(rootEl, hostToClone, { from: options.from, ...hostWithOptions }, permissions);
198
199
  }
199
200
  if (targetToClone) {
200
- assignFrom(rootEl, targetToClone, { from: lhsTarget, ...targetWithOptions });
201
+ assignFrom(rootEl, targetToClone, { from: lhsTarget, ...targetWithOptions }, permissions);
201
202
  }
202
203
  }
203
204
  }
@@ -228,7 +229,7 @@ export class ManageTemplateListHandler implements AssignFromHandler {
228
229
  const len = Math.min(elements.length, configs.length);
229
230
  for (let j = 0; j < len; j++) {
230
231
  const cfg = configs[j];
231
- assignFrom(elements[j], cfg.toClone ?? {}, { from: item, ...cfg.withOptions });
232
+ assignFrom(elements[j], cfg.toClone ?? {}, { from: item, ...cfg.withOptions }, permissions);
232
233
  }
233
234
  } else {
234
235
  const rootEl = clonedNodes.find(n => n instanceof Element) as Element | undefined;
@@ -239,14 +240,14 @@ export class ManageTemplateListHandler implements AssignFromHandler {
239
240
  if (processInferred) {
240
241
  processInferred(rootEl, item, inferredConfig === true ? { byItemprop: true } : inferredConfig);
241
242
  } else {
242
- assignFrom(rootEl, toClone, { from: item, ...withOptions });
243
+ assignFrom(rootEl, toClone, { from: item, ...withOptions }, permissions);
243
244
  }
244
245
 
245
246
  if (hostToClone && options?.from) {
246
- assignFrom(rootEl, hostToClone, { from: options.from, ...hostWithOptions });
247
+ assignFrom(rootEl, hostToClone, { from: options.from, ...hostWithOptions }, permissions);
247
248
  }
248
249
  if (targetToClone) {
249
- assignFrom(rootEl, targetToClone, { from: lhsTarget, ...targetWithOptions });
250
+ assignFrom(rootEl, targetToClone, { from: lhsTarget, ...targetWithOptions }, permissions);
250
251
  }
251
252
  }
252
253
  }
@@ -22,7 +22,8 @@
22
22
  */
23
23
 
24
24
  import type { AssignFromHandler } from '../assignFromAsync.js';
25
- import assignGingerly from '../assignGingerly.js';
25
+ import assignGingerly from '../assignGingerly.js';
26
+ import type { AssignPermissions } from '../types/assign-gingerly/types.js';
26
27
 
27
28
  /**
28
29
  * Operator keys recognized in case objects.
@@ -72,7 +73,7 @@ export class RangeSelectorHandler implements AssignFromHandler {
72
73
  this.config = config;
73
74
  }
74
75
 
75
- async assign(lhsTarget: any, resolvedParams: any): Promise<void> {
76
+ async assign(lhsTarget: any, resolvedParams: any, _options?: any, permissions?: AssignPermissions): Promise<void> {
76
77
  const { value, when } = resolvedParams;
77
78
 
78
79
  if (!Array.isArray(when)) return;
@@ -81,7 +82,7 @@ export class RangeSelectorHandler implements AssignFromHandler {
81
82
  for (const caseObj of when) {
82
83
  if (caseMatches(value, caseObj)) {
83
84
  if (caseObj.merge && typeof caseObj.merge === 'object') {
84
- assignGingerly(lhsTarget, caseObj.merge);
85
+ assignGingerly(lhsTarget, caseObj.merge, undefined, permissions);
85
86
  }
86
87
  return; // First match wins
87
88
  }
@@ -1,3 +1,5 @@
1
+ import {ParserOptions} from '../nested-regex-groups/types.js';
2
+
1
3
  export type EnhKey = string | symbol;
2
4
 
3
5
  // type NoUnderscore<T extends string> = T extends `_${string}` ? never : T;
@@ -196,7 +198,7 @@ export interface AttrConfig<T = unknown, TParserConfig = unknown> {
196
198
  * For named parsers like 'parse-pattern-statements', this is forwarded
197
199
  * as the options argument to the underlying parse function.
198
200
  */
199
- parserOptions?: any;
201
+ parserOptions?: ParserOptions;
200
202
  }
201
203
 
202
204
  export type AttrPatterns<T = any> = {
@@ -252,6 +254,12 @@ export interface IAssignGingerlyOptions {
252
254
  bypassChecks?: boolean;
253
255
  /**
254
256
  * Method names to call during path evaluation (e.g., for `?.method()` calls)
257
+ *
258
+ * Append `|` to a path segment (e.g., `?.deref|?.classList?.add`) to call a
259
+ * listed method with zero arguments instead of consuming the next path segment
260
+ * as its argument. On the last segment, `|` calls the method with no arguments
261
+ * and ignores the value. The `|` suffix only applies to names listed here —
262
+ * for any other segment it is treated as part of a literal property name.
255
263
  */
256
264
  withMethods?: string[] | Set<string>;
257
265
  /**
@@ -343,7 +351,7 @@ export interface AssignFromOptions {
343
351
  /** Protocol handlers (sync or async) */
344
352
  protocols?: Record<string, (key: string) => any | Promise<any>>;
345
353
 
346
- /** Method names to call during path evaluation */
354
+ /** Method names to call during path evaluation (append `|` to a path segment for a zero-argument call) */
347
355
  withMethods?: string[] | Set<string>;
348
356
 
349
357
  /** Alias mappings for path segments */
@@ -701,7 +709,7 @@ export interface HandlerConfig {
701
709
  * Handlers are invoked when a LHS key ends with ' =>'.
702
710
  */
703
711
  export interface AssignFromHandler {
704
- assign(lhsTarget: any, resolvedParams: Record<string, any>, options: any): Promise<void> | void;
712
+ assign(lhsTarget: any, resolvedParams: Record<string, any>, options: any, permissions?: AssignPermissions): Promise<void> | void;
705
713
  }
706
714
 
707
715
  /**
@@ -879,7 +887,7 @@ export interface LazyLoadInstantiatedContext {
879
887
  export declare class LazyLoadHandler implements AssignFromHandler {
880
888
  config: any;
881
889
  constructor(config: any);
882
- assign(lhsTarget: any, resolvedParams: Record<string, any>, options?: any): Promise<void>;
890
+ assign(lhsTarget: any, resolvedParams: Record<string, any>, options?: any, permissions?: AssignPermissions): Promise<void>;
883
891
  protected onCloneInserted(nodes: Node[], lhsTarget: Element, resolvedParams: Record<string, any>): Promise<void>;
884
892
  }
885
893
 
@@ -927,3 +935,54 @@ export interface AddEventListenerConfig extends AssignDispatchVector {
927
935
 
928
936
  }
929
937
  //#endregion
938
+
939
+
940
+ // =============================================================================
941
+ // Permissions
942
+ // =============================================================================
943
+
944
+ /**
945
+ * Phase II+: object form for a restricted property setting.
946
+ */
947
+ export interface RestrictedPropSetting {
948
+ prop: string;
949
+ useMethod?: string; // Phase II: redirect to a safe method
950
+ attr?: string; // Phase III: also watch setAttribute for this attr
951
+ allowFromSameHost?: boolean; // Phase III
952
+ allowCrossDomain?: boolean; // Phase III
953
+ }
954
+
955
+ /**
956
+ * Phase IV+: object form for a restricted method setting.
957
+ */
958
+ export interface RestrictedMethodConfig {
959
+ method: string;
960
+ addArgs?: string[]; // Phase V: append sanitizer args
961
+ }
962
+
963
+ /**
964
+ * Permissions interface for controlling security-sensitive operations.
965
+ * Only trusted script can set these — never parsed from HTML attributes.
966
+ */
967
+ export interface AssignPermissions {
968
+ /** Allow imports from cross-domain URLs (default: false) */
969
+ crossDomainImports?: boolean;
970
+
971
+ /**
972
+ * Restricted property settings.
973
+ * Phase I: string entries are property names that cannot be assigned.
974
+ * Phase II: an object with useMethod redirects ordinary assignment to that
975
+ * method; command operations remain blocked. Phase III+ adds attr support.
976
+ *
977
+ * NOTE: This is a property-assignment guard only. Method calls (setAttribute, etc.)
978
+ * are not blocked — see Phase III+. Event listeners can still be registered, but
979
+ * assignments performed by their vectors inherit these permissions.
980
+ */
981
+ restrictedPropSettings?: Array<string | RestrictedPropSetting>;
982
+
983
+ /** Sanitizer options (Phase III+) */
984
+ sanitizerOptions?: Record<string, any>;
985
+
986
+ /** Restricted method settings (Phase IV+) */
987
+ restrictedMethodSettings?: Array<string | RestrictedMethodConfig>;
988
+ }
@@ -91,6 +91,18 @@ export interface ParserOptions {
91
91
  * as statement delimiters during splitting.
92
92
  */
93
93
  ignorePeriodInsideBraces?: boolean;
94
+
95
+ /**
96
+ * When true, normalizes whitespace in the input string before parsing.
97
+ * This replaces multiple whitespace characters with a single space and trims the string.
98
+ * Default is false.
99
+ *
100
+ * @example
101
+ * // Input: " First. Second. "
102
+ * // With normalizeWhitespace: true -> "First. Second."
103
+ * // With normalizeWhitespace: false -> " First. Second. "
104
+ */
105
+ normalizeWhitespace?: boolean;
94
106
  }
95
107
 
96
108
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assign-gingerly",
3
- "version": "0.0.71",
3
+ "version": "0.0.73",
4
4
  "description": "This package provides a utility function for carefully merging one object into another.",
5
5
  "homepage": "https://github.com/bahrus/assign-gingerly#readme",
6
6
  "bugs": {
@@ -18,6 +18,7 @@
18
18
  "*.js",
19
19
  "*.ts",
20
20
  "DX/**",
21
+ "assignPermissions/**",
21
22
  "handlers/**",
22
23
  "inferencer/**",
23
24
  "README.md",
@@ -143,9 +144,9 @@
143
144
  "default": "./enhanceAll.js",
144
145
  "types": "./enhanceAll.ts"
145
146
  },
146
- "./isAllowedImportPath.js": {
147
- "default": "./isAllowedImportPath.js",
148
- "types": "./isAllowedImportPath.ts"
147
+ "./assignPermissions/isAllowedImportPath.js": {
148
+ "default": "./assignPermissions/isAllowedImportPath.js",
149
+ "types": "./assignPermissions/isAllowedImportPath.ts"
149
150
  },
150
151
  "./markerUtils.js": {
151
152
  "default": "./markerUtils.js",
@@ -203,7 +204,7 @@
203
204
  "chrome": "npx playwright cr http://localhost:8000"
204
205
  },
205
206
  "devDependencies": {
206
- "@playwright/test": "1.62.0",
207
+ "@playwright/test": "1.62.1",
207
208
  "spa-ssi": "0.0.27"
208
209
  }
209
210
  }
@@ -6,7 +6,8 @@
6
6
  import { resolveValues } from './resolveValues.js';
7
7
  import { getValues } from './getValues.js';
8
8
  import { evaluatePathWithMethods } from './assignGingerly.js';
9
- import { isAllowedImportPath } from './isAllowedImportPath.js';
9
+ import { isAllowedImportPath } from './assignPermissions/isAllowedImportPath.js';
10
+ import { buildRestrictedPropSet, redirectRestrictedProp } from './assignPermissions/restrictedProps.js';
10
11
  /**
11
12
  * Map of built-in handler names to their module paths.
12
13
  * These are auto-loaded on demand — no explicit import required.
@@ -80,7 +81,7 @@ async function resolveFromHandlers(name, handlers, permissions) {
80
81
  // Import path string — validate and dynamically import
81
82
  if (!permissions?.crossDomainImports && !isAllowedImportPath(entry)) {
82
83
  throw new Error(`assignFrom: handler "${name}" has an invalid import path "${entry}". ` +
83
- `Only relative, absolute, or bare specifier paths are allowed (no cross-domain URLs). ` +
84
+ `Only same-origin paths or import-map-covered specifiers are allowed. ` +
84
85
  `Pass { crossDomainImports: true } in permissions to override.`);
85
86
  }
86
87
  const module = await import(entry);
@@ -102,6 +103,7 @@ async function resolveFromHandlers(name, handlers, permissions) {
102
103
  * @param handlerRegistry - The registry of handler classes
103
104
  */
104
105
  export async function processHandlerCommands(target, handlerKeys, pattern, options, permissions) {
106
+ const restrictedPropSet = buildRestrictedPropSet(permissions);
105
107
  for (const key of handlerKeys) {
106
108
  const lhsPath = key.substring(0, key.length - 3); // Remove ' =>'
107
109
  const rhs = pattern[key];
@@ -211,11 +213,12 @@ export async function processHandlerCommands(target, handlerKeys, pattern, optio
211
213
  // Instantiate and invoke the handler
212
214
  const handler = new HandlerClass(config);
213
215
  //return; //1.5ms
214
- const result = await handler.assign(lhsTarget, resolvedParams, options);
216
+ const result = await handler.assign(lhsTarget, resolvedParams, options, permissions);
215
217
  //return; //1.5ms
216
218
  // Return-value protocol: if handler returns a non-undefined value,
217
219
  // assign it back to the LHS path
218
- if (result !== undefined && lhsParent != null && lhsKey != null) {
220
+ if (result !== undefined && lhsParent != null && lhsKey != null
221
+ && !redirectRestrictedProp(restrictedPropSet, lhsParent, lhsKey, result)) {
219
222
  lhsParent[lhsKey] = result;
220
223
  }
221
224
  }
@@ -7,8 +7,9 @@
7
7
  import { resolveValues } from './resolveValues.js';
8
8
  import { getValues } from './getValues.js';
9
9
  import { evaluatePathWithMethods } from './assignGingerly.js';
10
- import { isAllowedImportPath } from './isAllowedImportPath.js';
11
- import type { AssignPermissions } from './isAllowedImportPath.js';
10
+ import { isAllowedImportPath } from './assignPermissions/isAllowedImportPath.js';
11
+ import { buildRestrictedPropSet, redirectRestrictedProp } from './assignPermissions/restrictedProps.js';
12
+ import type { AssignPermissions } from './types/assign-gingerly/types.js';
12
13
  import type { AssignFromOptions, AssignFromHandlerConstructor } from './assignFromAsync.js';
13
14
 
14
15
  /**
@@ -93,7 +94,7 @@ async function resolveFromHandlers(
93
94
  if (!permissions?.crossDomainImports && !isAllowedImportPath(entry)) {
94
95
  throw new Error(
95
96
  `assignFrom: handler "${name}" has an invalid import path "${entry}". ` +
96
- `Only relative, absolute, or bare specifier paths are allowed (no cross-domain URLs). ` +
97
+ `Only same-origin paths or import-map-covered specifiers are allowed. ` +
97
98
  `Pass { crossDomainImports: true } in permissions to override.`
98
99
  );
99
100
  }
@@ -125,8 +126,9 @@ export async function processHandlerCommands(
125
126
  pattern: Record<string, any>,
126
127
  options: AssignFromOptions,
127
128
  permissions?: AssignPermissions
128
- ): Promise<void> {
129
-
129
+ ): Promise<void> {
130
+ const restrictedPropSet = buildRestrictedPropSet(permissions);
131
+
130
132
  for (const key of handlerKeys) {
131
133
  const lhsPath = key.substring(0, key.length - 3); // Remove ' =>'
132
134
  const rhs = pattern[key];
@@ -238,12 +240,13 @@ export async function processHandlerCommands(
238
240
  // Instantiate and invoke the handler
239
241
  const handler = new HandlerClass(config);
240
242
  //return; //1.5ms
241
- const result = await handler.assign(lhsTarget, resolvedParams, options);
243
+ const result = await handler.assign(lhsTarget, resolvedParams, options, permissions);
242
244
  //return; //1.5ms
243
- // Return-value protocol: if handler returns a non-undefined value,
244
- // assign it back to the LHS path
245
- if (result !== undefined && lhsParent != null && lhsKey != null) {
246
- lhsParent[lhsKey] = result;
245
+ // Return-value protocol: if handler returns a non-undefined value,
246
+ // assign it back to the LHS path
247
+ if (result !== undefined && lhsParent != null && lhsKey != null
248
+ && !redirectRestrictedProp(restrictedPropSet, lhsParent, lhsKey, result)) {
249
+ lhsParent[lhsKey] = result;
247
250
  }
248
251
  }
249
252
  }
@@ -1,3 +1,5 @@
1
+ import {ParserOptions} from '../nested-regex-groups/types.js';
2
+
1
3
  export type EnhKey = string | symbol;
2
4
 
3
5
  // type NoUnderscore<T extends string> = T extends `_${string}` ? never : T;
@@ -196,7 +198,7 @@ export interface AttrConfig<T = unknown, TParserConfig = unknown> {
196
198
  * For named parsers like 'parse-pattern-statements', this is forwarded
197
199
  * as the options argument to the underlying parse function.
198
200
  */
199
- parserOptions?: any;
201
+ parserOptions?: ParserOptions;
200
202
  }
201
203
 
202
204
  export type AttrPatterns<T = any> = {
@@ -252,6 +254,12 @@ export interface IAssignGingerlyOptions {
252
254
  bypassChecks?: boolean;
253
255
  /**
254
256
  * Method names to call during path evaluation (e.g., for `?.method()` calls)
257
+ *
258
+ * Append `|` to a path segment (e.g., `?.deref|?.classList?.add`) to call a
259
+ * listed method with zero arguments instead of consuming the next path segment
260
+ * as its argument. On the last segment, `|` calls the method with no arguments
261
+ * and ignores the value. The `|` suffix only applies to names listed here —
262
+ * for any other segment it is treated as part of a literal property name.
255
263
  */
256
264
  withMethods?: string[] | Set<string>;
257
265
  /**
@@ -343,7 +351,7 @@ export interface AssignFromOptions {
343
351
  /** Protocol handlers (sync or async) */
344
352
  protocols?: Record<string, (key: string) => any | Promise<any>>;
345
353
 
346
- /** Method names to call during path evaluation */
354
+ /** Method names to call during path evaluation (append `|` to a path segment for a zero-argument call) */
347
355
  withMethods?: string[] | Set<string>;
348
356
 
349
357
  /** Alias mappings for path segments */
@@ -701,7 +709,7 @@ export interface HandlerConfig {
701
709
  * Handlers are invoked when a LHS key ends with ' =>'.
702
710
  */
703
711
  export interface AssignFromHandler {
704
- assign(lhsTarget: any, resolvedParams: Record<string, any>, options: any): Promise<void> | void;
712
+ assign(lhsTarget: any, resolvedParams: Record<string, any>, options: any, permissions?: AssignPermissions): Promise<void> | void;
705
713
  }
706
714
 
707
715
  /**
@@ -879,7 +887,7 @@ export interface LazyLoadInstantiatedContext {
879
887
  export declare class LazyLoadHandler implements AssignFromHandler {
880
888
  config: any;
881
889
  constructor(config: any);
882
- assign(lhsTarget: any, resolvedParams: Record<string, any>, options?: any): Promise<void>;
890
+ assign(lhsTarget: any, resolvedParams: Record<string, any>, options?: any, permissions?: AssignPermissions): Promise<void>;
883
891
  protected onCloneInserted(nodes: Node[], lhsTarget: Element, resolvedParams: Record<string, any>): Promise<void>;
884
892
  }
885
893
 
@@ -927,3 +935,54 @@ export interface AddEventListenerConfig extends AssignDispatchVector {
927
935
 
928
936
  }
929
937
  //#endregion
938
+
939
+
940
+ // =============================================================================
941
+ // Permissions
942
+ // =============================================================================
943
+
944
+ /**
945
+ * Phase II+: object form for a restricted property setting.
946
+ */
947
+ export interface RestrictedPropSetting {
948
+ prop: string;
949
+ useMethod?: string; // Phase II: redirect to a safe method
950
+ attr?: string; // Phase III: also watch setAttribute for this attr
951
+ allowFromSameHost?: boolean; // Phase III
952
+ allowCrossDomain?: boolean; // Phase III
953
+ }
954
+
955
+ /**
956
+ * Phase IV+: object form for a restricted method setting.
957
+ */
958
+ export interface RestrictedMethodConfig {
959
+ method: string;
960
+ addArgs?: string[]; // Phase V: append sanitizer args
961
+ }
962
+
963
+ /**
964
+ * Permissions interface for controlling security-sensitive operations.
965
+ * Only trusted script can set these — never parsed from HTML attributes.
966
+ */
967
+ export interface AssignPermissions {
968
+ /** Allow imports from cross-domain URLs (default: false) */
969
+ crossDomainImports?: boolean;
970
+
971
+ /**
972
+ * Restricted property settings.
973
+ * Phase I: string entries are property names that cannot be assigned.
974
+ * Phase II: an object with useMethod redirects ordinary assignment to that
975
+ * method; command operations remain blocked. Phase III+ adds attr support.
976
+ *
977
+ * NOTE: This is a property-assignment guard only. Method calls (setAttribute, etc.)
978
+ * are not blocked — see Phase III+. Event listeners can still be registered, but
979
+ * assignments performed by their vectors inherit these permissions.
980
+ */
981
+ restrictedPropSettings?: Array<string | RestrictedPropSetting>;
982
+
983
+ /** Sanitizer options (Phase III+) */
984
+ sanitizerOptions?: Record<string, any>;
985
+
986
+ /** Restricted method settings (Phase IV+) */
987
+ restrictedMethodSettings?: Array<string | RestrictedMethodConfig>;
988
+ }
@@ -1,42 +0,0 @@
1
- /**
2
- * isAllowedImportPath.ts — Security utility for validating import paths.
3
- *
4
- * Checks that a path is local (relative, absolute, or bare specifier) and
5
- * not a cross-domain URL. Used to prevent untrusted HTML attributes from
6
- * triggering imports to arbitrary external domains.
7
- *
8
- * @example
9
- * import { isAllowedImportPath } from 'assign-gingerly/isAllowedImportPath.js';
10
- *
11
- * isAllowedImportPath('./local.js'); // true
12
- * isAllowedImportPath('../parent/file.js'); // true
13
- * isAllowedImportPath('/absolute/path.js'); // true
14
- * isAllowedImportPath('bare-specifier/mod.js'); // true
15
- * isAllowedImportPath('https://evil.com/x.js'); // false
16
- * isAllowedImportPath('//cdn.example.com/x.js'); // false
17
- */
18
- /**
19
- * Check if an import path is allowed (non-cross-domain).
20
- *
21
- * Allowed:
22
- * - Relative paths: ./foo.js, ../bar.js
23
- * - Absolute paths: /path/to/file.js
24
- * - Bare specifiers: package-name/file.js, @scope/package/file.js
25
- *
26
- * Blocked:
27
- * - Protocol URLs: https://..., http://..., data:..., etc.
28
- * - Protocol-relative: //cdn.example.com/...
29
- *
30
- * @param path - The import path to validate
31
- * @returns true if the path is local/safe, false if cross-domain
32
- */
33
- export function isAllowedImportPath(path) {
34
- if (path.startsWith('./') || path.startsWith('../') || path.startsWith('/')) {
35
- return true;
36
- }
37
- if (path.includes('://') || path.startsWith('//')) {
38
- return false;
39
- }
40
- // Bare specifier (no protocol, no //) — allowed
41
- return true;
42
- }
@@ -1,53 +0,0 @@
1
- /**
2
- * isAllowedImportPath.ts — Security utility for validating import paths.
3
- *
4
- * Checks that a path is local (relative, absolute, or bare specifier) and
5
- * not a cross-domain URL. Used to prevent untrusted HTML attributes from
6
- * triggering imports to arbitrary external domains.
7
- *
8
- * @example
9
- * import { isAllowedImportPath } from 'assign-gingerly/isAllowedImportPath.js';
10
- *
11
- * isAllowedImportPath('./local.js'); // true
12
- * isAllowedImportPath('../parent/file.js'); // true
13
- * isAllowedImportPath('/absolute/path.js'); // true
14
- * isAllowedImportPath('bare-specifier/mod.js'); // true
15
- * isAllowedImportPath('https://evil.com/x.js'); // false
16
- * isAllowedImportPath('//cdn.example.com/x.js'); // false
17
- */
18
-
19
- /**
20
- * Permissions interface for controlling security-sensitive operations.
21
- * Passed as the last parameter to assignGingerly, assignFrom, and enhanceAll.
22
- * Only trusted script can set these — they are never parsed from HTML attributes.
23
- */
24
- export interface AssignPermissions {
25
- /** Allow imports from cross-domain URLs (default: false) */
26
- crossDomainImports?: boolean;
27
- }
28
-
29
- /**
30
- * Check if an import path is allowed (non-cross-domain).
31
- *
32
- * Allowed:
33
- * - Relative paths: ./foo.js, ../bar.js
34
- * - Absolute paths: /path/to/file.js
35
- * - Bare specifiers: package-name/file.js, @scope/package/file.js
36
- *
37
- * Blocked:
38
- * - Protocol URLs: https://..., http://..., data:..., etc.
39
- * - Protocol-relative: //cdn.example.com/...
40
- *
41
- * @param path - The import path to validate
42
- * @returns true if the path is local/safe, false if cross-domain
43
- */
44
- export function isAllowedImportPath(path: string): boolean {
45
- if (path.startsWith('./') || path.startsWith('../') || path.startsWith('/')) {
46
- return true;
47
- }
48
- if (path.includes('://') || path.startsWith('//')) {
49
- return false;
50
- }
51
- // Bare specifier (no protocol, no //) — allowed
52
- return true;
53
- }