form0-core 0.1.22 → 0.1.24

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/SECURITY.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Security Features
2
2
 
3
+ > [!CAUTION] > `SAFE` and `CUSTOM` mode are validation aids, not security sandboxes. The
4
+ > evaluator compiles customer expressions with `new Function`; blocked-pattern
5
+ > checks can be bypassed through dynamic property access, and
6
+ > `maxExecutionTime` is not currently enforced. Do not execute untrusted
7
+ > schemas in a privileged server or browser context. See
8
+ > `analyses/SAFE_MODE_SECURITY_ANALYSIS.md` for the accepted critical finding
9
+ > and `analyses/UNTRUSTED_JAVASCRIPT_EXECUTION_ARCHITECTURE.md` for the selected
10
+ > isolation architecture that preserves vanilla JavaScript and imperative event scripts.
11
+
3
12
  form0-core provides configurable security options for expression evaluation to balance flexibility and safety.
4
13
 
5
14
  ## Security Modes
@@ -23,11 +32,12 @@ const engine = createFormEngine({
23
32
  });
24
33
  ```
25
34
 
26
- ### SAFE
35
+ ### SAFE (validation only; not a trust boundary)
27
36
 
28
37
  - **Restricted context** - Only whitelisted globals available
29
38
  - **Pattern blocking** - Dangerous patterns are blocked
30
- - **Use case**: User-generated expressions, safer environments
39
+ - **Use case**: Diagnostics for trusted authors. It is not suitable for
40
+ adversarial or otherwise untrusted expressions.
31
41
 
32
42
  ```javascript
33
43
  import { createFormEngine, SECURITY_MODES, SAFE_SECURITY_CONFIG } from 'form0-core';
@@ -113,7 +123,7 @@ const engine = createFormEngine({ schema, initialValues });
113
123
  To add security, simply include the security parameter:
114
124
 
115
125
  ```javascript
116
- // Add safe mode
126
+ // Add validation-oriented safe mode. This is not a sandbox.
117
127
  const engine = createFormEngine({
118
128
  schema,
119
129
  initialValues,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "form0-core",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/"
6
6
  },
package/src/index.js CHANGED
@@ -36,6 +36,7 @@ export { buildRepeatableMetadata } from './utilities/repeatable-helpers.js';
36
36
  export {
37
37
  buildDatasetDescriptors,
38
38
  buildFieldIdentityMap,
39
+ getFieldExportKind,
39
40
  getFieldQuerySemantics,
40
41
  projectDatasetRowValues,
41
42
  resolveDatasetDescriptorById,
@@ -237,6 +237,39 @@ const DISPLAY_ONLY_SEMANTICS = {
237
237
  allowed_operators: [],
238
238
  };
239
239
 
240
+ const MEDIA_FIELD_TYPES = new Set([
241
+ 'PhotoField',
242
+ 'VideoField',
243
+ 'SignatureField',
244
+ 'Photo',
245
+ 'Video',
246
+ 'Signature',
247
+ ]);
248
+ const NON_DATA_FIELD_TYPES = new Set([
249
+ 'LabelField',
250
+ 'Section',
251
+ 'RepeatableSection',
252
+ 'BuildingPlanSection',
253
+ ]);
254
+
255
+ /**
256
+ * Exportability is intentionally independent from query semantics. Media fields are
257
+ * display-only in the query engine, but are first-class export columns whose values
258
+ * are resolved by an authorized media gateway.
259
+ */
260
+ export function getFieldExportKind(field) {
261
+ const fieldType = toTrimmedString(field?.type ?? field?.field_type);
262
+ if (!fieldType || NON_DATA_FIELD_TYPES.has(fieldType)) {
263
+ return 'none';
264
+ }
265
+
266
+ if (MEDIA_FIELD_TYPES.has(fieldType)) {
267
+ return 'media';
268
+ }
269
+
270
+ return 'value';
271
+ }
272
+
240
273
  const getCalculatedFieldSemantics = (field) => {
241
274
  const style = getCalculatedDisplayStyle(field);
242
275
 
@@ -402,6 +435,7 @@ const buildDatasetFieldDescriptor = (field, dataset) => {
402
435
  data_name: toTrimmedString(field.data_name),
403
436
  label: toFieldLabel(field),
404
437
  field_type: toTrimmedString(field.type) ?? 'UnknownField',
438
+ export_kind: getFieldExportKind(field),
405
439
  choices: Array.isArray(field.choices)
406
440
  ? field.choices.filter((choice) => isRecord(choice)).map((choice) => ({ ...choice }))
407
441
  : undefined,