ZuloOne
English
Sign in
Menu

🧰 What a script can do: what is available and what is forbidden

Updated

A business script is compiled by the platform inside a sandbox. The boundary is set by three different mechanisms, and confusing them is expensive.

← Developer wiki

Three mechanisms, not one#

The RuntimeCompiler reference set The ScriptSecurityService policy The ScriptServices deny-list
What it is which assemblies the compiler can see semantic analysis for dangerous BCL APIs a filter on RESOLVING a service out of DI
When compilation checking/saving the script GetService<T>() at runtime
The refusal CS0103 / CS0246 / CS0012 a finding in the check InvalidOperationException
Its nature an omission, if something is missing a decision a decision

Hence the rule: before you design your way around a "ban", find out which of the three lists it sits in. Absence from the reference set is something to fix; presence in the other two is something to respect.

The third mechanism is the one most often missed, and it guards the most sensitive boundary. IOutboundTransport and ICredentialResolver sit NOT in the policy — ScriptSecurityService parses syntax and knows BCL types only — but in the ScriptServices.Denied dictionary, by full type name. The practical consequence: ScriptSecurity.Mode = Off does not lift these two bans.

And about the policy's mode: by default it is Warn, not Enforce — the finding is reported, the save goes through. On top of that, IsExemptAsync exempts the system models always, and everything listed in ScriptSecurity.TrustedModels.

Cryptography is not on the deny-list; it is a reference-set assembly. Without System.Security.Cryptography, CS0103: SHA256 does not exist looks like a sandbox rule, and a country's tax hash, CSR and QR signature would have to live in the platform kernel that is installed everywhere. JSON is the same class: a script that reads an external response needs System.Text.Json in the reference set, or the gap shows up as CS0012 rather than a policy hit.

What is available#

  • System, the collections, LINQ, System.Text, tasks.
  • Cryptography — System.Security.Cryptography and X509Certificates: hashes, ECDSA, CertificateRequest, reading PEM.
  • JSON — System.Text.Json (plus System.Memory: the public surface of JsonDocument mentions ReadOnlySequence<byte>, and without it the script fails with CS0012, naming neither JSON nor the missing reference).
  • System.Data (SlimTable), DataAnnotations, System.Linq.Expressions.
  • Platform services and model contracts through GetService<T>(): IDocumentManager, IDictionaryManager, IDataEntityManager (Data Entity projection — data-entities.md), ILinkTableManager, …

What is forbidden — and what replaces it#

The deny-list returns an exception, not null, including for TryGet: a silent null would look like "not registered" and would send the author hunting for a posting error that does not exist.

Forbidden Why What instead
IOutboundTransport sends bytes outside the outbox, the unique index and the attempt log IOutboundGateway.EnqueueAsync / IOutboundCall.SendAsync by channel name
ICredentialResolver returns the secret in clear text name the credential; the host substitutes it itself
HttpClient, sockets, WebRequest, SMTP the network the same gateway; mail — IEmailService; print — IPrintQueueService
Process, the file system leaving the bounds of the application no replacement, and there should not be one
Reflection.Emit, Assembly.Load loading code past the compiler —

The first two rows of the table come from ScriptServices.Denied; the rest come from the policy.

The boundary runs along the secret, not along cryptography. A script hashes and signs by itself; what it cannot do is read the configured key. If it is the host's key you need to sign with, there is a wrapper that encapsulates the secret instead of handing it over:

var stamp = ScriptServices.Get<ICredentialSigner>().SignHash("fatoora-csid", hashBase64);
// the signature, the public key and the certificate's signature — all public;
// null if the certificate is not configured

A script can log#

A widespread misconception says it cannot; because of it, empty catch blocks breed in the tree. It can:

ScriptServices.Get<ZuloOne.Log.IZuloOneLogger<MyService>>().Info("…", arg);

The generic form only, and the full type name. The output goes to docker logs.

Implementing a platform seam#

A model's service may implement a PLATFORM interface, and the platform will find it:

public partial class FatooraSigner : IOutboundSigner { … }
  • ServiceRegistry.ResolveImplementingAsync — "who overrides the behavior"; two implementations in one layer is an error, and it throws.
  • ServiceRegistry.ResolveAllImplementingAsync — a keyed set, where coexistence is normal: signing profiles, one per country.

If something is missing#

Do not work around it — check whether it is an omission. Adding an assembly to RuntimeCompiler.GetDefaultReferences() is cheap and does not widen the deny-list; ScriptCryptoSandboxTests holds both halves: cryptography and JSON compile and produce no findings, the network and processes still do.

Where next#

Was this page helpful?