69a2017000
Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
118 lines
9.9 KiB
Markdown
118 lines
9.9 KiB
Markdown
# Script Analysis
|
|
|
|
Script Analysis (#25) is the shared, stateless library that decides whether a C# script is acceptable before it is deployed or run: it owns the script trust model (a deny-list of forbidden APIs), the design-time compile gate, an advisory blocking-wait check and the static flow-shape predictor used by the Flow Debugger. It has no actors, configuration section or persistence.
|
|
|
|
## Overview
|
|
|
|
The library lives in `src/ZB.MOM.WW.ScadaBridge.ScriptAnalysis/` (net10.0, `Microsoft.CodeAnalysis.CSharp.Scripting` and `.Workspaces`, project reference to Commons only). Before it existed the deny-list, reference set and imports were duplicated across four call sites; `ScriptTrustPolicy` is now the single source of truth and the others delegate to it.
|
|
|
|
| Type | Role |
|
|
|---|---|
|
|
| `ScriptTrustPolicy` | The deny-list, reflection-gateway names, forbidden identifiers, reference sets and default imports. |
|
|
| `ScriptTrustValidator` | Two-pass trust analysis: `FindViolations(code, extraReferences?)`. |
|
|
| `TrustVerdictCache` | Internal process-wide memo of default-reference verdicts. |
|
|
| `RoslynScriptCompiler` | Diagnostics-only compile gate: `ParseDiagnostics`, `Compile`. |
|
|
| `CachingScriptMetadataResolver` | Cross-compilation metadata resolver cache. |
|
|
| `ScriptCompileSurface`, `TriggerCompileSurface` | Compile-only globals types. |
|
|
| `BlockingWaitAnalyzer` | Advisory `.Result` / `.Wait()` detection: `Find(code, globalsType)`. |
|
|
| `FlowShapePredictor` | Syntax-only flow prediction: `PredictInboundMethod`, `PredictSiteScript`. |
|
|
|
|
## Key Concepts
|
|
|
|
### The trust policy
|
|
|
|
`ScriptTrustPolicy.ForbiddenScopes` lists prefixes matched against a resolved symbol's containing namespace and type: `System.IO`, `System.Diagnostics.Process` (a single type, so `Stopwatch` stays allowed), `System.Threading`, `System.Reflection`, `System.Net`, `System.Runtime.InteropServices`, `System.Runtime.CompilerServices.Unsafe`, `System.Runtime.CompilerServices.RuntimeHelpers`, `System.Runtime.Loader`, `Microsoft.Win32`, `System.Environment`, `System.GC`, and the concrete ADO.NET providers `Microsoft.Data`, `System.Data.SqlClient`, `System.Data.Odbc`, `System.Data.OleDb`. `System.Data` and `System.Data.Common` are deliberately not forbidden, so scripts reach connections only through the `Database` helper.
|
|
|
|
`AllowedExceptions` re-allows `System.Threading.Tasks`, `System.Threading.CancellationToken` and `System.Threading.CancellationTokenSource`. `ReflectionGatewayMembers` (for example `GetType`, `Assembly`, `GetMethod`, `InvokeMember`, `DynamicInvoke`) are rejected regardless of receiver; `Invoke` is deliberately excluded because `delegate.Invoke()` is common. `ForbiddenIdentifiers` is `dynamic` and `Activator`.
|
|
|
|
### Two validation passes
|
|
|
|
`ScriptTrustValidator.FindViolations` parses the script as `SourceCodeKind.Script` and returns a sorted, de-duplicated list of messages; empty means clean, and blank code returns empty.
|
|
|
|
1. **Semantic pass** resolves each identifier, type reference, member access and object creation to its symbol and checks the containing namespace and type against `ForbiddenScopes`. This catches `global::` prefixes, aliases and `using static`.
|
|
2. **Syntactic hardening pass** (a `CSharpSyntaxWalker`) rejects reflection-gateway member accesses, forbidden identifiers, and forbidden `using` directives and qualified names, which catches reflection reached through permitted types such as `typeof(string).Assembly.GetType(...)`.
|
|
|
|
The class documentation states neither pass is a true sandbox; it is defence in depth. A forbidden type inside `nameof(...)` is flagged on purpose.
|
|
|
|
### Reference sets
|
|
|
|
Two reference sets are kept apart on purpose:
|
|
|
|
- `DefaultReferences` is built from `DefaultAssemblies` (the assemblies of `object`, `Enumerable`, `Math`, the C# runtime binder and `DynamicJsonElement`). It is minimal and mirrors what the site runtime compiles against. It must never include the assemblies hosting forbidden APIs, so that a forbidden type is an undefined symbol and the compile gate independently rejects it.
|
|
- `AnalysisReferences` is the full trusted-platform-assemblies set (`TRUSTED_PLATFORM_ASSEMBLIES`) plus `DefaultAssemblies`, used only by the trust validator so every named type resolves to its true namespace. When the TPA list is unavailable (single-file, AOT or trimmed host) it falls back to the minimal set plus `ForbiddenAnchorAssemblies`, sets `AnalysisReferencesDegraded` to `true` and emits a `Trace` warning. `BuildMinimalFallbackReferences()` exposes that fallback for tests.
|
|
|
|
Extra references passed to `FindViolations` can only improve resolution; the verdict is by resolved namespace, so they cannot whitelist anything.
|
|
|
|
### Verdict cache
|
|
|
|
When no extra references are supplied, verdicts are memoised by SHA-256 of the code in `TrustVerdictCache`: two generations of up to 1024 entries each (hot and cold); a cold hit is promoted and overflow discards the cold generation. An analysis that throws is not cached, and stored lists are read-only.
|
|
|
|
### Compile gate
|
|
|
|
`RoslynScriptCompiler.Compile(code, globalsType, extraReferences, extraImports)` creates a script over `DefaultReferences` and `DefaultImports` (`System`, `System.Collections.Generic`, `System.Linq`, `System.Threading.Tasks`, `ZB.MOM.WW.ScadaBridge.Commons.Types.Scripts`) and returns the error-severity messages from `GetCompilation().GetDiagnostics()`. It does not emit or load, so no assembly is created per script; it also has no catch-all, so an unexpected exception propagates instead of being cached as a "failed to compile" verdict. `ParseDiagnostics` returns syntax errors only. All scripts share one `InteractiveAssemblyLoader` and the `CachingScriptMetadataResolver.Instance`, which memoises `ResolveReference` and `ResolveMissingAssembly` results (including "not found") so repeated compiles do not re-resolve the transitive reference closure.
|
|
|
|
### Compile-only surfaces
|
|
|
|
`ScriptCompileSurface` is the globals type for compiling scripts: it exposes `Instance`, `Parameters`, `CancellationToken`, `Alarm`, `Scope`, and the members `ExternalSystem`, `Database`, `Notify`, `Scripts`, `Attributes`, `Children`, `Parent`, `Alarms`, `Log`, `Files` with nested stand-in types whose bodies throw `NotSupportedException("compile-only surface")`. It must be kept in step with the real runtime script API by hand. `TriggerCompileSurface` is the narrower surface for trigger expressions (`Attributes`, `Children`, `Parent`), used by the Template Engine validation.
|
|
|
|
### Advisory blocking-wait check
|
|
|
|
`BlockingWaitAnalyzer.Find` binds the script against the given globals type and reports `.Result`, `.Wait()`, `Task.WaitAll/WaitAny` and `.GetAwaiter().GetResult()` on `Task`/`ValueTask` types. It is advisory, not a trust violation, because making it blocking would break already-deployed scripts on redeploy. Unresolved code is skipped.
|
|
|
|
### Flow shape prediction
|
|
|
|
`FlowShapePredictor` walks script syntax (text-matching member-access chains with whitespace stripped) and returns a `PredictedFlow` of `PredictedNode` and `PredictedEdge` records in source order. `PredictInboundMethod(methodName, script, resolveRouteScript, maxDepth)` starts with an inbound-request node, expands routed site scripts through the caller's resolver, and ends with a response node. `PredictSiteScript(scriptName, body, maxDepth)` starts with a script-run node. `DefaultMaxDepth` is 4. Non-literal targets are flagged `InputDependent`; branching is not modelled; an early `if (...) return` guard becomes a note on the next node. A parse failure degrades to the request/response pair with the note `parse error`.
|
|
|
|
## Architecture
|
|
|
|
The library is called synchronously from several components:
|
|
|
|
- Site Runtime `ScriptCompilationService` and Inbound API `ForbiddenApiChecker` call `ScriptTrustValidator.FindViolations`.
|
|
- Template Engine `ScriptCompiler` runs `RoslynScriptCompiler.Compile(code, typeof(ScriptCompileSurface))`, the trust validator and `BlockingWaitAnalyzer`; `ValidationService` uses the validator and `RoslynScriptCompiler.Compile(expression, typeof(TriggerCompileSurface))` for trigger expressions.
|
|
- Transport `BundleImporter` runs the validator on script bodies at preview and apply.
|
|
- Central UI `FlowDebugService` calls the predictor.
|
|
|
|
## Usage
|
|
|
|
```csharp
|
|
var violations = ScriptTrustValidator.FindViolations(code); // trust model
|
|
var errors = RoslynScriptCompiler.Compile(code, typeof(ScriptCompileSurface)); // compile gate
|
|
var advisories = BlockingWaitAnalyzer.Find(code, typeof(ScriptCompileSurface));
|
|
var flow = FlowShapePredictor.PredictSiteScript("Start", code);
|
|
```
|
|
|
|
## Configuration
|
|
|
|
None. All policy is compiled in `ScriptTrustPolicy`.
|
|
|
|
## Dependencies & Interactions
|
|
|
|
- [Commons (#16)](./Commons.md) — script-facing types (`ScriptAlarm`, `TrackedOperationId`, `DynamicJsonElement`, flow enums) referenced by the compile surface and predictor.
|
|
- [Template Engine (#1)](./TemplateEngine.md) — design-time script and trigger validation.
|
|
- [Site Runtime (#3)](./SiteRuntime.md) — execution-time compile and trust gate.
|
|
- [Inbound API (#14)](./InboundAPI.md) — method script gate.
|
|
- [Transport (#24)](./Transport.md) — import-time gate.
|
|
- [Central UI (#9)](./CentralUI.md) — Flow Debugger prediction.
|
|
|
|
## Troubleshooting
|
|
|
|
### A script passes the trust check but fails to compile
|
|
|
|
Expected for forbidden types: `DefaultReferences` omits their assemblies, so the compile gate rejects them as undefined symbols.
|
|
|
|
### Compile gate and runtime disagree
|
|
|
|
`ScriptCompileSurface` is a hand-maintained mirror of the runtime API; a member present at runtime but absent here compiles at runtime only and fails the design-time gate.
|
|
|
|
### `AnalysisReferencesDegraded` is true
|
|
|
|
The host has no `TRUSTED_PLATFORM_ASSEMBLIES`; only the documented anchor types are guaranteed to resolve in the semantic pass.
|
|
|
|
## Related Documentation
|
|
|
|
- [Script Analysis design specification](../requirements/Component-ScriptAnalysis.md)
|
|
- [Template Engine](./TemplateEngine.md)
|
|
- [Site Runtime](./SiteRuntime.md)
|
|
- [Inbound API](./InboundAPI.md)
|
|
- [Transport](./Transport.md)
|