﻿# FlowMarkup: Cross-Language Engine Implementation Guide

**Version:** 0.9.0
**Date:** 2026-03-28
**Designed by:** Łukasz Nawojczyk
**Copyright:** © 2026 Progralink Łukasz Nawojczyk. All rights reserved.
**License:** Open Web Foundation Agreement 1.0 (OWFa 1.0)

### Conformance

The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", and "MAY" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119).

### Related Documents

| Document | Purpose |
|---|---|
| [FLOWMARKUP-ENGINE.md](FLOWMARKUP-ENGINE.md) | Engine implementation guide (normative requirements) |
| JAVA-REFERENCE.md *(planned for a future release)* | Java reference implementations |
| [FLOWMARKUP-SPECIFICATION.md](FLOWMARKUP-SPECIFICATION.md) | Format specification (YAML structure, directives, actions) |

### Disclaimer

> **Libraries listed in this document are suggestions to aid initial evaluation — not endorsements or mandated dependencies.** Engine implementors MUST independently validate every dependency for: (1) active maintenance status, (2) known vulnerabilities (CVE databases, security advisories), (3) license compatibility with their project, (4) fitness for their specific use case and scale requirements. The library ecosystem evolves rapidly; entries may become outdated between document revisions.

---

## CL-1: Numeric Type (BigDecimal Equivalents)

**Requirement:** All YAML number literals → arbitrary-precision decimal (34-digit precision, HALF_UP rounding, value-based equality: `1.0 == 1` is true). See ENGINE.md §5 and JAVA-REFERENCE.md `NumberFactory`.

| Language | Type | Suggested Library | Notes |
|---|---|---|---|
| Java | `BigDecimal` | JDK stdlib | `MathContext.DECIMAL128` |
| Go | `shopspring/decimal.Decimal` | [github.com/shopspring/decimal](https://github.com/shopspring/decimal) | v1.4.0, ~6.2K stars, MIT. Alternative: `ericlagergren/decimal` (~2.5× faster in benchmarks) |
| Python | `decimal.Decimal` | stdlib | `getcontext().prec = 34`. C-accelerated via libmpdec since 3.3. No third-party dependency needed |
| TypeScript | `Decimal` | [decimal.js](https://github.com/MikeMcl/decimal.js) | v10.6.0, ~6.3K stars, MIT. Set `precision: 34`. Native `Number` (IEEE 754) MUST NOT be used for financial/decimal work |
| Rust | `rust_decimal::Decimal` | [crates.io](https://crates.io/crates/rust_decimal) | v1.40.0, 128-bit fixed-point, 28–29 significant digits, stack-allocated. For full 34-digit: `bigdecimal` crate (arbitrary precision, heap-allocated) |
| C# | `decimal` | BCL stdlib | 128-bit, 28–29 digits. For 34-digit: `ExtendedNumerics.BigDecimal` (NuGet, arbitrary precision) |

**Precision gap note:** C# `decimal` and Rust `rust_decimal` provide 28–29 digits, not the full 34 of IEEE 754 Decimal128. For most use cases this suffices; document the gap and let implementors decide if `bigdecimal` (Rust) or `ExtendedNumerics.BigDecimal` (C#) is needed.

**Conformance tests:** `1.0 == 1` → true, `1/3` doesn't throw, `toFixed(2)` returns string, `round(2)` returns number.

**Security note (Rust):** A malicious typosquat crate `rustdecimal` (no underscore) was published to crates.io in 2022. The legitimate crate is `rust_decimal` (with underscore). The typosquat has been removed.

---

## CL-2: Sum Types / Sealed Types

No third-party libraries — language patterns only. Applies to `Step` (directive vs action), `CapabilityScope`, `SecretCapability`, `FlowError` hierarchy, and all union types in the engine.

| Language | Mechanism |
|---|---|
| Java | `sealed interface` + `record` variants |
| Go | Unexported interface method + concrete structs; `type switch` dispatch |
| Python | ABC + `@dataclass` subclasses; `match` (3.10+) or `isinstance` chains |
| TypeScript | Discriminated union: `type Step = { kind: "if"; ... } \| { kind: "forEach"; ... } \| ...` |
| Rust | `enum` with data variants + `match` (exhaustive) |
| C# | Abstract `record` + sealed derived records; pattern matching (`switch` expression) |

---

## CL-3: Concurrency Primitives

**Requirements from ENGINE.md / JAVA-REFERENCE.md:**
- `GlobalVariableStore`: per-key atomic writes, snapshot-consistent reads
- `VersionedVariable`: atomic version counter + read-write lock (pessimistic)
- `VariableLockManager`: canonical-order acquisition (deadlock prevention)
- *These primitives apply to all data elements (both `vars:` and `const:` entries).*
- `OccScopeFork`: snapshot + read-set tracking + CAS commit

| Concern | Java | Go | Python | TypeScript | Rust | C# |
|---|---|---|---|---|---|---|
| Atomic map | `ConcurrentHashMap` | `sync.Map` or sharded `sync.RWMutex` | `threading.Lock` + dict | `Map` (single-threaded) | `DashMap` or `RwLock<HashMap>` | `ConcurrentDictionary` |
| Per-key lock | `ReentrantReadWriteLock` | `sync.RWMutex` per key | `threading.RLock` per key | [async-mutex](https://www.npmjs.com/package/async-mutex) per key | `tokio::sync::RwLock` per key | `ReaderWriterLockSlim` per key |
| Fair/FIFO lock | `ReentrantLock(true)` | `sync.Mutex` (starvation mode after 1ms) | N/A (GIL) | N/A (event loop) | `tokio::sync::Mutex` (FIFO by default) | `SemaphoreSlim` with FIFO behavior |
| CAS | `AtomicReference.compareAndSet` | `atomic.CompareAndSwapPointer` | N/A (GIL provides atomicity) | N/A (single-threaded) | `AtomicPtr::compare_exchange` | `Interlocked.CompareExchange` |

**Go fairness note:** Go's `sync.Mutex` has two modes since Go 1.9 — normal (biased toward new arrivals) and starvation (FIFO handoff after 1ms wait). This is "eventually fair" but not strictly FIFO. For strict FIFO, consider `neilotoole/fifomu` (1.5–10× slower). For most FlowMarkup engines, starvation mode is sufficient.

**Rust note:** `tokio::sync::Mutex` is FIFO and async-aware (can hold across `.await`). `parking_lot::Mutex` is 1.5–5× faster for synchronous code; `parking_lot::FairMutex` provides strict FIFO for sync contexts.

**TypeScript note:** `async-mutex` (v0.5.0, ~4M weekly npm downloads) provides `Mutex`, `Semaphore`, priority queueing, and cancellation. The standard choice for Node.js engines.

---

## CL-4: Plugin/SPI Discovery (ActionProvider)

| Language | Suggested Mechanism | Notes |
|---|---|---|
| Java | `ServiceLoader<ActionProvider>` | Standard SPI pattern |
| Go | Compile-time `init()` registration + global registry | `plugin` package is Linux/macOS-only with severe limitations. For dynamic plugins: [HashiCorp go-plugin](https://github.com/hashicorp/go-plugin) (gRPC-based, process-isolated) |
| Python | `importlib.metadata.entry_points(group='flowmarkup.actions')` | Standard since Python 3.9+. Uses `pyproject.toml` `[project.entry-points]` |
| TypeScript | Config-driven: module paths → dynamic `import()` | Each module exports `ActionProvider` interface |
| Rust | `inventory` crate (dtolnay, v0.3.20) or `linkme` crate | `inventory`: distributed registration, broad platform support. `linkme`: zero-cost via linker sections. `libloading` for dynamic `.so`/`.dll` loading (`unsafe`) |
| C# | `AssemblyLoadContext` + DI container (.NET 8+) | Modern standard. MEF (`System.Composition`) for attribute-based implicit discovery. Contract interfaces MUST live in the default ALC |

---

## CL-5: CEL Implementation

**Target CEL specification:** v0.15 ([github.com/google/cel-spec](https://github.com/google/cel-spec)). Library versions below are validated against this specification version.

| Language | Suggested Library | Maturity | Macro Support | Custom Functions | Notes |
|---|---|---|---|---|---|
| Java | `dev.cel:cel-java` | Production | Full | Yes | Google official (v0.12.0). Extension libraries: strings, math, sets, encoders, optional types |
| Go | `github.com/google/cel-go` | Production | Full | Yes | Google official (v0.24.0), ~2.9K stars. Used in Kubernetes. Most feature-complete |
| Python | `cel-python` | Maturing | Full | Yes | Cloud Custodian (v0.5.0). New: `cel-expr-python` (Google, Mar 2026) wraps cel-cpp — evaluate for long-term |
| TypeScript | `cel-js` (ChromeGG) | Limited | Full | Yes | v0.8.2. Alternative: `@marcbachmann/cel-js` (22× faster but had prototype pollution CVE). Fallback: cel-go via WASM. TypeScript engines MUST run CEL evaluation in a separate V8 isolate (`vm2`, `isolated-vm`, or `worker_threads` with `transferList`-only communication). Engines using CEL libraries with known CVE history MUST NOT run CEL in the main process context |
| Rust | `cel-interpreter` | Growing | Full | Yes | v0.10.0, ~465 stars, heading toward CNCF Sandbox. Alternative: `cel-cxx` (FFI to Google cel-cpp). Pure Rust implementations (`cel-interpreter`) are STRONGLY PREFERRED over C++ FFI wrappers (`cel-cxx`). If `cel-cxx` is used, the engine MUST fuzz the CEL evaluation path with malicious expressions. Rust engines using C++ FFI CEL implementations (`cel-cxx`) MUST run CEL evaluation in a sandboxed subprocess or WASM module. Direct in-process FFI to C++ CEL MUST NOT be used without sandbox isolation due to memory safety risks |
| C# | `Cel.NET` (telus-oss) | Early | Unclear | Yes | v1.0.0 on NuGet. Low community activity (~17 stars). Thorough conformance testing required |

**Critical requirements regardless of library:**
- Support all 6 standard CEL macros (`has`, `all`, `exists`, `exists_one`, `filter`, `map`) plus 15 FlowMarkup extension macros (`sortBy`, `sortByDesc`, `flatMap`, `first` (predicate), `last` (predicate), `distinctBy`, `groupBy`, `reduce`, `minBy`, `maxBy`, `count`, `filterKeys`, `filterValues`, `mapValues`, `meta`) — 21 total macros requiring AST-level support

> **Macros vs functions quick reference.** Macros require AST-level support; functions are registered as runtime extensions. Standard CEL macros: `has`, `all`, `exists`, `exists_one`, `filter`, `map`. FlowMarkup extension macros: `sortBy`, `sortByDesc`, `flatMap`, `first` (predicate form), `last` (predicate form), `distinctBy`, `groupBy`, `reduce`, `minBy`, `maxBy`, `count`, `filterKeys`, `filterValues`, `mapValues`, `meta`. All other list/map extensions in SPECIFICATION.md section 2.7 are runtime functions. See SPECIFICATION.md section 2.7 "Kind" column for the authoritative classification.

- Register all FlowMarkup extension functions (see SPECIFICATION.md §2.7), comprising extension functions, macros, and constants defined across §2.7 subsections.
- Block introspection/reflection (see CL-7)
- Enforce AST depth limit (default 32, max 128)
- Enforce collection size limit (default 10K, max 1M)
- Register `meta` as a FlowMarkup-specific CEL macro (AST-level, not a runtime function). `meta()` must capture the variable identifier from the AST, not the evaluated value. For libraries without custom macro support, pre-process `meta(identifier)` at the expression string level before compilation.

**`meta` macro conformance test vectors:**
```
meta(my_var).type        -> metaType lookup on "my_var"
meta(RESOURCES.cfg).name -> metaName lookup on "RESOURCES.cfg"
meta(CONTEXT.x).kind     -> metaKind lookup on "CONTEXT.x"
meta(42).type            -> compile error (not an identifier)
meta(my_var).readonly    -> metaReadonly lookup on "my_var"
```

**Known gap:** TypeScript and C# CEL libraries have the weakest ecosystem maturity. Engine implementors for these languages MUST test macro and extension function coverage before committing. For languages without mature CEL, consider: (a) cel-go via FFI/WASM, (b) custom evaluator, (c) transpilation from CEL AST.

---

## CL-6: YAML 1.2 Parser Selection

**Requirement:** FlowMarkup requires YAML 1.2.2 (SPECIFICATION.md §2.1). In YAML 1.2, `on`/`off`/`yes`/`no` are plain strings — only `true`/`false`/`null` and numbers are auto-typed. FlowMarkup independently forbids bare `on:` as a YAML key for broad parser compatibility.

**The real risk:** YAML 1.1 parsers silently corrupt boolean/null handling and may introduce subtler issues with numeric parsing and tag resolution.

| Language | Suggested Parser | Version | YAML 1.2 | Known CVEs | Notes |
|---|---|---|---|---|---|
| Java | `snakeyaml-engine` | 3.0.1 | Full | None | Requires Java 11+. Legacy `snakeyaml` 1.x/2.x is YAML 1.1 — **CVE-2022-1471** (RCE), **CVE-2022-25857** (DoS) |
| Go | `gopkg.in/yaml.v3` | 3.0.1 | Hybrid 1.1/1.2 | CVE-2022-28948 (fixed) | `on`/`off` = strings in generic maps. Forward path: `go.yaml.in/yaml/v4` (Jan 2026). All engines MUST parse numeric literals according to YAML 1.2 rules. Conformance test vectors: `010` MUST parse as integer 10 (not octal 8), `0o10` MUST parse as integer 8, `1:30` MUST parse as string `"1:30"` (not sexagesimal 90). For Go, use `go.yaml.in/yaml/v4` when available, or add a post-parse normalization step that re-interprets YAML 1.1 octal/sexagesimal values according to YAML 1.2 rules |
| Python | `ruamel.yaml` | 0.19.1 | Full | CVE-2019-20478 (old, fixed) | YAML 1.2 by default. `PyYAML` 6.x is still YAML 1.1 only — "Norway problem" persists. Python engines MUST use `YAML(typ='safe')` or equivalent safe loader mode. Conformance test: parse a YAML document containing `!!python/object:os.system ['id']` — the parser MUST raise an error, not execute the command. The same applies to Java (`!!java.io.`), Go (`!!binary` abuse), and C# (`!!python/object` equivalent) — each language MUST have a conformance test for YAML tag-based code execution rejection |
| TypeScript | `yaml` (eemeli/yaml) | 2.8.1 | Full | None | `js-yaml` 4.x also defaults to 1.2 now, but `eemeli/yaml` has better TS types, richer API. `js-yaml` had **CVE-2025-64718** (prototype pollution, fixed in 4.1.1) |
| Rust | **GAP** | — | — | — | See notes below |
| C# | `YamlDotNet` | 16.1.0 | Claims 1.2 | CVE-2018-1000210 (old, fixed) | C# engines MUST pass the boolean/null conformance test: parse `{on: value, yes: 1, no: 0, off: x, tls: no}` — all keys and values MUST be strings, not booleans. If YamlDotNet fails this test, the engine MUST use a custom resolver that treats all unquoted scalars as strings, then apply FlowMarkup's own type coercion |

**Rust YAML ecosystem note:**
- `serde_yaml` (dtolnay): **ARCHIVED** March 2024, unmaintained — do not use
- `serde_yml`: **UNSOUND** (RUSTSEC-2025-0068), archived — **do not use**
- `serde_yaml_ng`: Maintained fork but **YAML 1.1 only**
- `yaml-rust2`: Pure Rust, **YAML 1.2 compliant**, passes YAML test suite — but **no serde integration**
- **Suggestion for Rust engines:** `yaml-rust2` for YAML 1.2 parsing + manual struct conversion, or `serde_yaml_ng` with YAML 1.1 caveats documented and mitigated

**Key ordering requirement:** FlowMarkup requires all YAML parsers to preserve mapping key insertion order. This is required for `catch:` maps (first-match-wins), `switch.match:` maps, and other order-dependent contexts. While YAML 1.2 defines mappings as unordered, all mainstream YAML libraries preserve insertion order. Conformance tests MUST verify key ordering preservation.

**Conformance test:** Parse `{on: value, yes: 1, no: 0, off: x}` — all four keys MUST be strings.

> **Java deserialization prohibition.** Engine implementations in Java MUST NOT use `java.io.ObjectInputStream` for deserializing any data originating from flow definitions, flow inputs, action results, or inter-node communication. Use of `ObjectInputStream` with untrusted data enables arbitrary code execution (CVE-2015-4852 and related gadget-chain attacks). Permitted alternatives: Jackson `ObjectMapper`, Gson `JsonParser`, or Protocol Buffers. If `ObjectInputStream` is unavoidable for legacy integration, implementations MUST use an allowlist-based `ObjectInputFilter` (JEP 290, Java 9+) restricting deserialized classes to a known-safe set. *(CWE-502: Deserialization of Untrusted Data)*

> **Python deserialization prohibition.** Engine implementations in Python MUST NOT use `pickle`, `shelve`, `marshal`, or `yaml.load()` (without `SafeLoader`) for deserializing any data originating from flow definitions, flow inputs, action results, or inter-node communication. Use of `pickle.loads()` with untrusted data enables arbitrary code execution. Permitted alternatives: `json.loads()`, `yaml.safe_load()`, `msgpack.unpackb(raw=False)`, or Protocol Buffers. *(CWE-502: Deserialization of Untrusted Data)*

> **Prototype pollution prevention.** Engine implementations in TypeScript/Node.js MUST sanitize all object keys before performing deep merge operations (including `$deepMerge` in test fixtures). Specifically, the keys `__proto__`, `constructor`, and `prototype` MUST be rejected or stripped from any user-supplied or flow-defined objects before merging. Implementations MUST use `Object.create(null)` for intermediate merge targets to avoid prototype chain inheritance (CL-17). *(CWE-1321: Improperly Controlled Modification of Object Prototype Attributes)*

---

## CL-7: Introspection Denylist (Language-Specific)

No third-party libraries — security patterns only. **Use explicit allowlists as the primary defense; the denylist is a safety net.**

Per-language table of reflection/introspection APIs to block in CEL adapters and service operation dispatch:

| Language | Blocked APIs / Patterns |
|---|---|
| Java | `getClass`, `Class.forName`, `Method.invoke`, `Field.setAccessible`, `Constructor.newInstance`, `Proxy.newProxyInstance`, `Runtime.exec`, `ProcessBuilder`, `getMethod`, `getDeclaredMethod`, `getConstructor`, `getDeclaredConstructor`, `defineClass`, `getProtectionDomain`, `MethodHandles`, `MethodHandles.Lookup`, `VarHandle`, `Unsafe` |
| Go | `reflect.*` — cel-go uses its own type system; block `reflect.Value.Elem`, `Kind`, `Method`, `FieldByName`, `Call` in any custom function bindings |
| Python | `__class__`, `__dict__`, `__subclasses__`, `__bases__`, `__mro__`, `__globals__`, `__code__`, `__init_subclass__`, `__set_name__`, `__class_getitem__`, `__init__`, `__new__`, `__del__`, `__reduce__`, `__format__`, `eval`, `exec`, `compile`, `getattr`, `setattr`, `delattr`, `type`, `dir`, `globals`, `locals`, `vars`, `importlib`, `subprocess` |
| TypeScript | `constructor`, `prototype`, `__proto__`, `__defineGetter__`, `__defineSetter__`, `__lookupGetter__`, `__lookupSetter__`, `importScripts`, `Object.getPrototypeOf`, `Reflect.*`, `Proxy`, `eval`, `Function()`, `Symbol.toPrimitive`, `Symbol.hasInstance`, `Symbol.iterator`, `Symbol.asyncIterator` |
| Rust | No runtime reflection; primary concern is `unsafe` blocks in CEL evaluator code and FFI boundaries |
| C# | `Type.GetType`, `GetMethod`, `GetProperty`, `Invoke`, `Assembly.Load`, `Activator.CreateInstance`, `Expression.Compile`, `DynamicMethod`, `Marshal.*` |

**Chain-aware blocking:** The engine MUST apply denylist checks to the full member access chain, not just the final name. Example: `obj.x.__proto__` is denied because `__proto__` appears in the chain, even though `x` is allowed. Property access chains like `obj.__proto__.constructor` MUST be caught even when individual segments are accessed incrementally.

---

## CL-8: XML Security Configuration

**Requirement:** Engines MUST prevent XXE (XML External Entity), billion-laughs, remote file access, and oversized document attacks. See ENGINE.md §5.4 items 24–25.

| Language | Suggested Library | Safety | Known CVEs | Notes |
|---|---|---|---|---|
| Java | `DocumentBuilderFactory` (OWASP config) | Secure when configured | CVE-2014-6517 (ancient, fixed) | MUST set: `disallow-doctype-decl`, `external-general-entities=false`, `external-parameter-entities=false` |
| Go | `encoding/xml` | Safe by default | None XXE-related | Does not process external entities at all |
| Python | `defusedxml` | Safe by default | None | Low maintenance but stable. Python 3.7.1+ improved stdlib safety; `defusedxml` still safer for untrusted input |
| TypeScript | `fast-xml-parser` | **Evaluate carefully** | **5 CVEs in early-mid 2026** (CVE-2026-25896 CVSS 9.3, CVE-2026-26278, CVE-2026-25128, CVE-2026-27942, CVE-2026-33036) | All fixed in v5.5.6+ (v5.5.5 has CVE-2026-33036: numeric entity expansion bypass). High CVE volume — monitor closely. Given the CVE history, TypeScript engines SHOULD consider `sax-wasm` or a WASM-compiled libxml2 wrapper as alternatives. Engines MUST run XML parser conformance tests from ENGINE.md §5.4 Item 24 as part of CI regardless of which library is used |
| Rust | `quick-xml` / `roxmltree` | Safe by default | None | `quick-xml` 0.38.4 for streaming, `roxmltree` 0.20.0 for read-only DOM |
| C# | `XmlReaderSettings` | Secure when configured | None | MUST set `DtdProcessing.Prohibit` AND `XmlResolver = null` |

**Content schema non-validation (ENGINE.md §5.4 item 25):** Engines MUST NOT auto-validate loaded XML/JSON/YAML against embedded schemas (XSD, `$schema`, etc.).

---

## CL-9: Error Hierarchy Modeling

**Requirement:** FlowMarkup defines user error types with `$parent` inheritance. Polymorphic catch MUST resolve: catching `ParentError` also catches all descendants.

| Language | Suggested Approach | Polymorphic catch |
|---|---|---|
| Java | Exception class hierarchy | `catch (ParentError)` — natural |
| Go | Struct + type registry + `errors.Is()`/`errors.As()` | Walk parent chain in type registry |
| Python | Exception class hierarchy | `except ParentError` — natural |
| TypeScript | Class hierarchy extending `Error` | `instanceof` — natural |
| Rust | Enum + type registry + `is_subtype_of()` | Walk parent chain in type registry. `thiserror` crate (v2.0.18, dtolnay) for ergonomic engine-internal errors |
| C# | Exception class hierarchy | `catch (ParentError)` — natural |

**Note for Go and Rust:** These languages lack exception hierarchies. Maintain a type registry mapping error type names to parent chains (`map[string]string` for type → parent). At catch time, walk the registry to check if the thrown error type is a descendant of the catch clause's error type. For engine-internal errors (not flow-defined), use idiomatic error types.

---

## CL-10: Charset/Encoding Detection

**Requirement:** Detection priority: `$charset` > MIME charset > BOM > heuristic > UTF-8 fallback. See SPECIFICATION.md §4.14.

| Language | Suggested Library | Version | Status | Notes |
|---|---|---|---|---|
| Java | ICU4J `CharsetDetector` | 78.2 | Active (Unicode Consortium) | Large (~13MB). Compact alternative: `chardet4j` |
| Go | `saintfish/chardet` + `golang.org/x/text/encoding` | — | chardet: stable ICU port; x/text: active | `x/text/encoding` for codec work |
| Python | `charset-normalizer` | 3.x | Active | `chardet` 7.x is faster but has licensing controversy (disputed LGPL→MIT). `charset-normalizer` is legally clearer |
| TypeScript | `chardet` (runk/node-chardet) | 2.1.1 | Active, TS-native | ~32M downloads/week, built-in TS types. Replaces `jschardet` (declining) |
| Rust | `chardetng` + `encoding_rs` | 0.1.17 / 0.8.35 | Active (Mozilla) | Both used in Firefox. No CVEs |
| C# | `UTF.Unknown` | 2.6.0 | Active | .NET Standard 1.0+ through .NET 8+. Replaces `Ude.NetStandard` (unmaintained since 2018) |

---

## CL-11: JSON Schema Validation (Draft 2020-12)

**Requirement:** Engines that validate flow files at load time need a Draft 2020-12 compliant validator.

| Language | Suggested Library | Version | 2020-12 Support | Known CVEs | Notes |
|---|---|---|---|---|---|
| Java | `networknt/json-schema-validator` | 3.0.1 | Full (100% test suite) | None | Active |
| Go | `santhosh-tekuri/jsonschema` v6 | 6.0.2 | Full | None | Custom vocabulary support |
| Python | `jsonschema` | 4.26.0 | Full | None | Use `jsonschema[format]` for format validation |
| TypeScript | `ajv` 8.x | 8.18.0+ | Full | CVE-2025-69873 (ReDoS with `$data: true`, fixed in 8.18.0) | Use separate import path for 2020-12 support |
| Rust | `jsonschema` crate | 0.38.1 | Supported | None | Verify keyword coverage via [bowtie.report](https://bowtie.report) |
| C# | `JsonSchema.Net` | 9.1.2 | Full + 2026 spec | None | Part of `json-everything` ecosystem |

---

## CL-12: Content Hash (SHA-256)

**Requirement:** `sha256-<standard-base64>` format (RFC 4648 §4, standard alphabet with padding). Used for flow identity (`FlowRef.hash`), audit log value hashes, and checkpoint integrity.

All languages use stdlib (except Rust):

| Language | Implementation |
|---|---|
| Java | `MessageDigest.getInstance("SHA-256")` + `Base64.getEncoder()` |
| Go | `crypto/sha256` + `encoding/base64.StdEncoding` |
| Python | `hashlib.sha256()` + `base64.b64encode()` |
| TypeScript | `crypto.createHash('sha256')` + `.digest('base64')` |
| Rust | `sha2::Sha256` (crate) + `base64` (crate) — no stdlib SHA-256 |
| C# | `SHA256.HashData()` + `Convert.ToBase64String()` |

**Cross-language gotchas:**
1. **Input encoding:** Always UTF-8 encode source before hashing. C# `Encoding.Default` may vary — use `Encoding.UTF8` explicitly.
2. **Raw bytes vs hex:** Base64-encode the raw 32-byte digest, NOT a hex string representation.
3. **Base64 variant:** Use standard Base64 (`+`, `/`, `=` padding), NOT Base64URL (`-`, `_`, no padding).
4. **Line wrapping:** Use no-wrap mode. Java: `Base64.getEncoder()` not `getMimeEncoder()`. Python: `base64.b64encode()` not `encodebytes()`.

**Conformance test vector:** SHA-256 of `FlowMarkup` (8 bytes UTF-8) = `sha256-rJcEAXOGVQ8wVWuydRywS++8bBWrF6LCuT+RFmwLFvY=`

---

## CL-13: Regex Engine Selection

**Requirement:** ENGINE.md §5.4 item 4 requires regex evaluation to complete in time linear in the input length, OR enforce a per-evaluation timeout. See SA-REGEX-1 for static analysis of nested quantifiers.

| Language | Recommended Engine | Alternative | Notes |
|---|---|---|---|
| Java | `com.google.re2j` | `java.util.regex.Pattern` with `Thread.interrupt()` timeout | `re2j` is linear-time by construction. `Pattern` uses backtracking — MUST enforce timeout |
| Go | `regexp` (stdlib) | — | Uses RE2 algorithm natively. Linear-time by construction |
| Python | `google-re2` package | `re` module with timeout (Python 3.14+: `re.compile().search(string, timeout=...)`) | `google-re2` wraps C++ RE2. For Python < 3.14, `re` has no native timeout — use `google-re2` or signal-based timeout |
| TypeScript | `re2-wasm` package | `RegExp` with `AbortSignal` timeout (when available) | `re2-wasm` wraps RE2 via WebAssembly. Native `RegExp` uses backtracking — MUST enforce timeout |
| Rust | `regex` crate | — | Uses finite automaton. Linear-time by construction |
| C# | `Regex` with `matchTimeout` parameter | — | .NET `Regex` supports built-in timeout since .NET Framework 4.5. Always set `matchTimeout` |

---

## CL-14: Secure Memory Clearing

After action execution completes and the resolved secret value is no longer needed, the engine MUST clear the secret value from memory using platform-appropriate mechanisms. Engines MUST NOT cache resolved secret values beyond the action execution boundary unless the secret provider explicitly supports caching with a declared TTL.

| Language | Mechanism | Notes |
|----------|-----------|-------|
| Java | `Arrays.fill(charArray, '\0')` for `char[]` | `byte[]`/`char[]` preferred over `String` for secrets; `String` is immutable and GC-managed |
| Go | Explicit zeroing of byte slices before GC | Use `runtime.KeepAlive()` to prevent premature collection |
| Python | `ctypes.memset` on underlying buffer; avoid `str` (immutable) | Use `bytearray`, zero on `del` |
| TypeScript | `Uint8Array.fill(0)`; avoid `string` (immutable, GC-managed) | Node.js: use `Buffer.alloc()` + `buf.fill(0)` |
| Rust | `zeroize` crate (`ZeroizeOnDrop` trait) | Compile-time guarantees |
| C# | `SecureString` or `Array.Clear()`; pin with `GCHandle` | `SecureString` deprecated in .NET Core but still clears |

> **Unsafe FFI boundary.** When Rust engine implementations use `unsafe` blocks for FFI calls to C libraries (e.g., OpenSSL, libssh2), the engine MUST validate all pointer arguments and buffer lengths before the FFI call, and MUST verify return values after the call. Secret values passed through FFI boundaries MUST be zeroized in both the Rust and C memory spaces. The `zeroize` crate's `Zeroize` trait MUST be implemented on all types that hold secret material.

> **Note:** `SecureString` is deprecated in .NET Core / .NET 5+ and does not provide encryption on non-Windows platforms. For .NET Core implementations, use `byte[]` or `char[]` with explicit `Array.Clear()` and GCHandle pinning. Consider the `Microsoft.AspNetCore.DataProtection` API for at-rest secret protection.

---

## CL-15: Java ObjectInputStream Prohibition

Engines running on JVM platforms MUST NOT use `java.io.ObjectInputStream` for deserializing flow state, checkpoint data, or any untrusted input. Use allow-list–based deserialization filters (`ObjectInputFilter`) if `ObjectInputStream` is unavoidable for legacy interop. *(CWE-502)*

---

## CL-16: Python Pickle/Marshal Prohibition

Python-based engines MUST NOT use `pickle`, `marshal`, `shelve`, or `cPickle` modules for deserializing flow state, checkpoint data, or any untrusted input. Use JSON, MessagePack, or Protocol Buffers instead. *(CWE-502)*

---

## CL-17: Node.js Prototype Pollution Mitigation for `$deepMerge`

JavaScript/TypeScript engines MUST use prototype-pollution–safe merge functions (e.g., structured clone, `Object.create(null)` target objects, or libraries with `__proto__`/`constructor`/`prototype` key filtering) when implementing `$deepMerge` or any recursive object merge. All FlowMarkup TypeScript engine objects MUST use `Object.create(null)` as their base — not just `$deepMerge` targets. CEL evaluation output MUST be sanitized: any map returned from CEL evaluation MUST have `__proto__`, `constructor`, and `prototype` keys stripped before being assigned to flow variables. TypeScript engines MUST run `$deepMerge` operations in a V8 isolate or verify `$deepMerge` stripping via conformance tests that attempt prototype pollution through all supported merge paths (nested merges, array element merges, Symbol-keyed properties). *(CWE-1321)*

---

## CL-18: C# SecureString Deprecation (.NET Core)

.NET engines SHOULD NOT rely on `SecureString` for secret protection on .NET Core / .NET 5+ where `SecureString` contents are not encrypted in memory. Use `CryptProtectMemory` (Windows), pinned byte arrays with explicit zeroing, or platform-specific secure memory APIs instead. *(CWE-316)*

---

## CL-19: Rust Unsafe FFI Guidance

Rust-based engines MUST minimize `unsafe` blocks at FFI boundaries. All data crossing the FFI boundary MUST be validated before use. Secret values passed through FFI MUST be pinned (`Pin<Box<[u8]>>`) and explicitly zeroed after use via `zeroize`. *(CWE-119, CWE-316)*
