Repository navigation
Breaking. Passing an object to a script grants more than the object — it grants everything reachable from it. AllowOnly and DenyTypes make that surface a decision instead of a consequence, and Sandboxed() locks the runtime down. All three are opt-in. The upgrade to Jint 4.15.3 changes how a script sees a CLR array, which is the only change that can affect existing scripts.
Added
Sandboxed()— strict mode, noeval/Function, invariant culture/UTC, memory/recursion/stack/array/regex limits, noGetType()or reflection, no shared-memory primitives, frozen prototypes. Latches in both directions:EnableFetch()before or after it throws, so the guarantee never depends on builder order. CLR interop stays on — it hardens the runtime, it does not narrow the object graph.AllowOnly(a => a.Member(...).Method(...).Type<T>())— declares the members a script may reach; everything else stops existing for it, on nested objects and throughObject.keys,for..inandJSON.stringifyalike. Members are named through expressions, so a rename is a compile error rather than a silently narrower sandbox.DenyTypes(params Type[])— refuses types outright, checked on the declared and the runtime type, so a member declared asobjectcannot smuggle one through. Do not passtypeof(object); it denies everything.ConfigureJint(Action<Jint.Options>)— reaches Jint options that only apply at construction time, whichRegisterEngineConfiguratorcannot.WithMaxJsonDepth(int)onJsEngineOptions(default 512).TranslationOptions.IdentifierResolver(Cocoar.JsEval.Linq) — resolves a free identifier in a rule to a host object, which is how a rule reaches an imported module (its binding is module-scoped, while identifier resolution reads globals). A call on a resolved object whose arguments are all constant is folded during translation.
Changed
- Jint 4.8.0 → 4.15.3. A CLR
T[]is now a live view rather than a copy. Index writes,sortandreversereach the underlying array instead of being silently discarded;pushandlength =throw, because a fixed-size array cannot honour them.Array.isArray(hostArray)is nowfalse, andhost.Tags === host.Tagsis nowtrue.List<T>keeps full mutability and is unaffected, as aremap,filter,join,slice, spread,for..of,Object.keysandJSON.stringify. - Generated
.d.tsdeclares an outbound CLR array asClrArray<T>(helper inglobal.d.ts) so TypeScript rejectspushinstead of allowing it. Inbound parameters stayT[]. - A module exception keeps its own message instead of surfacing as
TargetInvocationException's "Exception has been thrown by the target of an invocation".
Security
Scriban7.1.0 → 7.2.6 (Cocoar.JsEval.Module.Template) andAngleSharp1.4.0 → 1.7.1 (Cocoar.JsEval.Module.AngleSharp) — both shipped versions carried published advisories (Scriban 2× high / 2× moderate, AngleSharp 1× moderate). Consumers of those two module packages get the updated dependency transitively.- Microsoft.Extensions.* and the SQLite/EF packages moved to 10.0.10,
Microsoft.SourceLink.GitHubto 10.0.301.Marten8.33.0 → 9.11.0 and aSQLitePCLRawpin affect only the test and experiment projects, which are not packaged. The solution now builds with no vulnerability warnings.
Known limitation
count,findandanyrequire a provider that permits synchronous execution. They are terminal and execute the query where they stand, because a JavaScript expression has to return a value. Marten 9 permits asynchronous data access only and throwsNotSupportedExceptionon all three;where,orderByandthenByare lazy and unaffected, as are EF Core, LINQ2DB, in-memory queryables and Marten 8. JsEval ships no async counterparts on purpose — that would tie a provider-neutral library to one provider. The LINQ guide shows the ten lines a host adds for its own provider, and the build-in-JS, terminate-in-C# alternative.
Fixed
- A script could terminate the host process. Jint's JSON serializer recurses per level, so a ~120-byte script nesting a few thousand objects exhausted the .NET stack and killed the process with an uncatchable
StackOverflowException— while staying inside every configured limit.GetValue<T>andJsonStringifynow check the shape first and throwInvalidOperationException. The guard runs beforeToObject(), which recurses too; wrapped host objects are exempt so reference identity is preserved. - A module could not accept a
JsValueparameter. Every argument went throughToObject(), which turns a JS arrow function into a delegate the parameter then rejected — so a module could not receive a rule to translate.
Migration
Existing code keeps working unchanged. Review scripts only if they write to a CLR T[]:
// New: decide the reachable surface instead of inheriting it
services.AddJsEval(b => b
.Sandboxed()
.AllowOnly(a => a.Member((Customer c) => c.Name))
.DenyTypes(typeof(DbContext)));// Silently lost before, now takes effect on the host's array:
host.Tags.sort();
// Worked before (and lost the write), now throws:
host.Tags.push('x');
// Was true before, now false:
Array.isArray(host.Tags);