Skip to content

Commit 602ba04

Browse files
initial draft of the threat model
1 parent 7865ff7 commit 602ba04

2 files changed

Lines changed: 287 additions & 0 deletions

File tree

docs/security/review-template.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Security review record — `<package>` `<version>`
2+
3+
> Copy this file to `review-<package>-<version>.md` for each release under review, fill it
4+
> in, and merge it. It is the second artifact Microsoft requires alongside
5+
> [threat-model.md](threat-model.md). `IgniteUI.Blazor.Lite` and
6+
> `IgniteUI.Blazor.Templates` are reviewed and recorded separately.
7+
8+
| | |
9+
|---|---|
10+
| **Package / version** | <!-- TODO --> |
11+
| **Commit reviewed** | <!-- TODO: full SHA --> |
12+
| **Review date** | <!-- TODO --> |
13+
| **Threat model version** | <!-- TODO: commit SHA of threat-model.md at review time --> |
14+
| **Outcome** | <!-- Approved / Approved with conditions / Blocked --> |
15+
16+
## Reviewers
17+
18+
At least one reviewer must not be an author of the code under review.
19+
20+
| Name | Role | Author of reviewed code? |
21+
|---|---|---|
22+
| <!-- TODO --> | | |
23+
24+
## Coverage
25+
26+
Tick what was actually performed; an unticked row is a stated limitation, not an omission.
27+
28+
- [ ] Threat model walkthrough against the current code
29+
- [ ] Manual review of the JS interop surface (`src/componentsBase/WebViewCallback.cs`, `BaseRendererControl`, `RendererSerializer`)
30+
- [ ] Manual review of the unmarshalled path (`src/componentsBase/RuntimeHelper.cs`, `UnmarshalledDataSource`)
31+
- [ ] Manual review of the serialization boundary (`JsonDataSource`, `RendererSerializer`)
32+
- [ ] Rendering path review (`lit-html` / `igniteui-webcomponents` usage of `unsafeHTML`)
33+
- [ ] Dependency review (`package-lock.json`, `Directory.Packages.props`)
34+
- [ ] Static analysis results reviewed (CodeQL `csharp` + `javascript`)
35+
- [ ] Build and release pipeline review (`.github/workflows/`)
36+
- [ ] Package content inspection for **both** `.nupkg` files, including the template pack
37+
- [ ] Scaffolded-app secure-defaults checklist executed against `dotnet new`
38+
- [ ] Consumer-facing security documentation reviewed for accuracy
39+
40+
## Findings register — `IgniteUI.Blazor.Lite`
41+
42+
| ID | Summary | Sev | Disposition | Evidence / justification |
43+
|---|---|---|---|---|
44+
| TM-IX-01 | `WebCallback` public; client-supplied `containerId` addresses any control | High | <!-- Fixed / Mitigated / Accepted --> | |
45+
| TM-IX-02 | `OnInvokeReturn` accepts untyped `object` | Medium | | |
46+
| TM-IX-03 | `AdjustDynamicContentBatch` deserializes a client-supplied batch | Medium | | |
47+
| TM-IX-04 | `_controlsMap` registration is unvalidated; `Add` throws on duplicate keys | Medium | | |
48+
| TM-IX-05 | Untrusted event args reach consumer handlers | High | | |
49+
| TM-MEM-01 | `unsafe` + reflected `InvokeUnmarshalled` + raw WASM-heap pointers | High | | |
50+
| TM-MEM-02 | Silent loss of the unmarshalled fast path on runtime change | Low | | |
51+
| TM-SER-01 | Full object graph serialized to the client | High | | |
52+
| TM-SER-02 | Prerendered state embedded in initial HTML | Low | Accepted | Inherent to Blazor SSR; app-level cache headers |
53+
| TM-DOM-01 | Rendering path: text vs. markup (`unsafeHTML` usage) | TBD | | |
54+
| TM-DOM-02 | Consumer `RenderFragment` templates render consumer markup | Low | By design | Razor escapes by default; `MarkupString` is an explicit opt-in |
55+
| TM-SC-01 | Bundled third-party JS is not independently patchable | Medium | By design | Covered by the `SECURITY.md` SLAs |
56+
| TM-SC-02 | No CodeQL/SCA/`npm audit`/dependency-review gate | High | | |
57+
| TM-BLD-01 | Undefined `BUILD_CONFIGURATION` in release signing/validation paths | Medium | | |
58+
| TM-BLD-02 | Signature gate passes on an empty DLL result set | Medium | | |
59+
| TM-BLD-03 | `Nullable` disabled on the Lite project | Low | Accepted | Generated sources are unannotated |
60+
61+
## Findings register — `IgniteUI.Blazor.Templates`
62+
63+
| ID | Summary | Sev | Disposition | Evidence / justification |
64+
|---|---|---|---|---|
65+
| TM-PKG-01 | `NoDefaultExcludes=true` + broad `Content Include` packs unintended files | High | | |
66+
| TM-PKG-02 | `test-templates.ps1` / `.sh` may land under `content/` | Medium | | |
67+
| TM-TPL-01 | Insecure defaults replicated into every scaffolded app | High | | |
68+
| TM-TPL-02 | Template-pinned package versions go stale | Medium | | |
69+
| TM-TPL-03 | `<Version>0.0.1</Version>` hard-coded rather than tag-driven | Low | | |
70+
71+
**Disposition values**`Fixed` (code changed, link the PR) · `Mitigated` (compensating
72+
control, name it) · `Accepted` (residual risk, requires an approver in the table below).
73+
74+
## Accepted risks
75+
76+
Every `Accepted` disposition above needs a named approver here.
77+
78+
| ID | Justification | Approver | Date |
79+
|---|---|---|---|
80+
| <!-- TODO --> | | | |
81+
82+
## Release gate
83+
84+
- [ ] No finding of severity **High** or above is left `Open`
85+
- [ ] Every `Accepted` risk has a named approver
86+
- [ ] TM-DOM-01 has a definitive answer
87+
- [ ] `threat-model.md` has been updated to reflect this review
88+
89+
**Statement:** <!-- e.g. "As of <SHA>, <package> <x.y.z> has no open Critical or High
90+
findings. Reviewed by <names> on <date>." -->

docs/security/threat-model.md

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
# Threat model — IgniteUI.Blazor.Lite and IgniteUI.Blazor.Templates
2+
3+
| | |
4+
|---|---|
5+
| **Status** | Draft — awaiting maintainer review |
6+
| **Packages in scope** | `IgniteUI.Blazor.Lite` (net8.0 / net9.0 / net10.0), `IgniteUI.Blazor.Templates` (netstandard2.0, `PackageType=Template`) |
7+
| **Repository** | https://github.com/IgniteUI/igniteui-blazor |
8+
| **Reviewed commit** | <!-- TODO(maintainer): SHA at time of sign-off --> |
9+
| **Document owner** | <!-- TODO(maintainer): name --> |
10+
| **Last updated** | 2026-08-11 |
11+
| **Method** | STRIDE per trust-boundary, mapped to Microsoft's Blazor threat-mitigation guidance |
12+
13+
## 1. Why this document exists
14+
15+
Microsoft requires a maintained security/threat model and a completed security review
16+
before a third-party Blazor component package can be endorsed alongside their own
17+
components. This document is the threat model half of that requirement. It is a **living
18+
document**: it is updated whenever the JS interop surface, the unmarshalled data path, the
19+
bundled third-party JavaScript, or the template content changes.
20+
21+
It is not a penetration test, not an audit, and not an attestation of security.
22+
23+
The two packages have **different threat classes** and are modelled separately:
24+
`IgniteUI.Blazor.Lite` is a runtime interop surface; `IgniteUI.Blazor.Templates` is a
25+
supply-chain and secure-defaults surface.
26+
27+
## 2. Scope
28+
29+
**In scope**
30+
31+
- `IgniteUI.Blazor.Lite`: managed code under `src/` (notably `src/componentsBase/`) and the
32+
webpack bundle produced from `src/src/` (`igniteui-webcomponents`, `igniteui-core`,
33+
`lit-html`) plus the themes copied into `src/wwwroot/`.
34+
- `IgniteUI.Blazor.Templates`: the `dotnet new` template content under
35+
`templates/IgniteUI.Blazor.Templates/templates/` and how it is packed.
36+
- The build and release pipelines that produce and sign both packages.
37+
38+
**Out of scope**
39+
40+
- The consuming application, and applications generated from the templates once the
41+
developer has modified them.
42+
- Internal implementation of `igniteui-webcomponents` / `igniteui-core` / `lit-html`
43+
trusted-but-verified dependencies; their behaviour at the rendering boundary *is* in
44+
scope (TM-DOM-01).
45+
- The ASP.NET Core Blazor framework. Framework guarantees are assumptions (§5).
46+
- Storybook stories, tests and samples.
47+
48+
## 3. Architecture and trust boundaries
49+
50+
```mermaid
51+
flowchart LR
52+
subgraph SRV["Server circuit / WASM runtime — trusted"]
53+
B["BaseRendererControl<br/>component wrappers"]
54+
W["WebCallback<br/>[JSInvokable] surface"]
55+
R["RuntimeHelper<br/>unsafe / InvokeUnmarshalled"]
56+
A["Consuming app<br/>event handlers, templates"]
57+
end
58+
subgraph BR["Browser — untrusted"]
59+
L["webpack bundle<br/>Loader / ComponentRenderer"]
60+
E["igniteui-webcomponents<br/>+ lit-html (shadow DOM)"]
61+
end
62+
B -- "TB1: RendererSerializer / JsonDataSource" --> L
63+
R -- "TB1b: unmarshalled column buffers (WASM only)" --> L
64+
L -- "TB2: invokeMethodAsync(containerId, ...)" --> W
65+
W --> A
66+
L --> E
67+
```
68+
69+
Three boundaries:
70+
71+
- **TB1 — server → client.** Component state and bound data serialized to the browser.
72+
- **TB1b — managed → WASM heap.** The unmarshalled fast path; a *memory-safety* boundary,
73+
not just a trust boundary. Unique to this package.
74+
- **TB2 — client → server.** Attacker-controlled input. Per Microsoft's guidance, *"Treat
75+
any .NET method exposed to JavaScript as you would a public endpoint to the app."*
76+
77+
## 4. Assets and security objectives
78+
79+
| Asset | Objective |
80+
|---|---|
81+
| Consumer data bound to components | Confidentiality — only intended fields reach the browser |
82+
| The Blazor circuit and the WASM heap | Availability and memory integrity |
83+
| The consuming app's browser origin | Integrity — components never introduce script execution |
84+
| The two published NuGet packages | Integrity — signed, reproducible, no unintended content |
85+
| Applications scaffolded from the templates | Integrity — secure-by-default starting posture |
86+
87+
## 5. Assumptions and consumer responsibilities
88+
89+
| # | Assumption |
90+
|---|---|
91+
| A1 | The consuming app enforces authentication/authorization; components perform none. |
92+
| A2 | The consuming app enforces a Content Security Policy appropriate to its render mode. |
93+
| A3 | The consuming app is free of XSS. Most TB2 threats require attacker script in the page; per Microsoft's guidance an XSS-compromised client can already forge interop calls. The library's obligation is to avoid *causing* XSS and to avoid *widening* the blast radius. |
94+
| A4 | Data bound to components has already passed the app's authorization filter. |
95+
| A5 | Framework limits (`CircuitOptions`, `MaximumReceiveMessageSize`, interop call timeout) are left at or below their defaults. |
96+
| A6 | Developers using the templates review and adapt the generated security configuration before production deployment. |
97+
98+
## 6. Threats — `IgniteUI.Blazor.Lite`
99+
100+
Severity is the residual severity **given** A1–A6. Status: `Open`, `Mitigated`,
101+
`By design`, `Accepted`, `Verified — no finding`.
102+
103+
### TB2 — client → server (JS interop callbacks)
104+
105+
| ID | Threat | STRIDE | Sev | Status |
106+
|---|---|---|---|---|
107+
| **TM-IX-01** | `WebCallback` is a **public** class whose `[JSInvokable]` methods (`OnReady`, `OnInvokeReturn`, `OnRaiseEvent`, `AdjustDynamicContent`, `AdjustDynamicContentBatch`) all take a **client-supplied `containerId`** used as a key into a process-wide `_controlsMap`. A caller that reaches the reference can address *any* registered control in the circuit, not only the one it legitimately owns — event raising and dynamic-content mutation can be driven cross-instance. This is the largest single item in the model. | S, T, E | **High** | **Open** |
108+
| **TM-IX-02** | `OnInvokeReturn` accepts `object returnValue` — an untyped, polymorphic value deserialized from the client and passed on to `control.OnInvokeReturn`. Weakest input contract in the surface. | T, E | Medium | **Open** |
109+
| **TM-IX-03** | `AdjustDynamicContentBatch` deserializes a client-supplied `batch` string into a dictionary array and iterates it, driving render-tree mutation from untrusted input. | T, D | Medium | **Open** |
110+
| **TM-IX-04** | `_controlsMap` is keyed by `ContainerId` and populated via `Register`, with no validation that the caller is entitled to that key, and `Add` (not indexer assignment) will throw on a duplicate key. | S, D | Medium | **Open** |
111+
| **TM-IX-05** | Untrusted event args flow into consumer event handlers. If the app forwards them into dynamic LINQ, SQL or reflection, this becomes injection. | T, E | High *(consumer-facing)* | **Open** — needs documentation |
112+
113+
### TB1b — memory safety (WASM unmarshalled path)
114+
115+
| ID | Threat | STRIDE | Sev | Status |
116+
|---|---|---|---|---|
117+
| **TM-MEM-01** | `RuntimeHelper` reflection-discovers `InvokeUnmarshalled` on the WASM runtime, builds a delegate with `Expression.Compile()`, and invokes it from `unsafe` methods passing `UnmarshalledColumn[]` — raw pointers into the WASM heap. `AllowUnsafeBlocks=true`. A mismatch between the managed layout and the JS-side reader is a memory-corruption / type-confusion condition rather than a normal exception. | T, E | **High** | **Open** — needs justification or scope limit |
118+
| **TM-MEM-02** | The unmarshalled API is deprecated and reached only by reflection, so a runtime change silently disables the fast path. Behaviour then diverges between runtimes with no signal. | R | Low | **Open** |
119+
| **TM-MEM-03** | `Expression.Compile()` requires a JIT and is incompatible with full AOT/trimming. A compatibility constraint rather than a vulnerability, recorded here because it constrains the mitigation options for TM-MEM-01. ||| **Note** |
120+
121+
### TB1 — server → client (serialization and rendering)
122+
123+
| ID | Threat | STRIDE | Sev | Status |
124+
|---|---|---|---|---|
125+
| **TM-SER-01** | Bound data is serialized to the browser through `JsonDataSource` / `RendererSerializer`. Consumers binding ORM entities ship every property — including PII and internal fields — to the client. | I | High *(consumer-facing)* | **Open** — needs documentation |
126+
| **TM-SER-02** | Under server-side prerendering the serialized state is embedded in the initial HTML response and subject to intermediary/browser caching. | I | Low | **Accepted** |
127+
| **TM-DOM-01** | Whether `igniteui-webcomponents` / `lit-html` render bound values as text or as markup determines whether untrusted data yields DOM XSS. `lit-html` escapes interpolations by default but exposes `unsafeHTML`; usage must be confirmed for the shipped component set. To resolve: confirm with the `igniteui-webcomponents` team whether any bound value reaches `unsafeHTML`, `innerHTML` or `insertAdjacentHTML`, and record the answer plus the version it was verified against. | T | **To determine** | **Open** — must be answered before sign-off |
128+
| **TM-DOM-02** | Consumer-supplied `RenderFragment` templates (`IgbTemplateContent`) render arbitrary consumer markup inside component-owned containers. Razor escapes `@value` by default, so this is safe unless the consumer opts into `MarkupString`. | T | Low | **By design** — documented consumer responsibility |
129+
|| `DynamicContentHolder.BuildRenderTree` calls `AddMarkupContent`. Microsoft's guidance explicitly names this API as an XSS vector when passed user input. **Verified**: every call site passes a static whitespace literal (`"\r\n"`, indentation) — never user data. ||| **Verified — no finding** |
130+
|| `eval` / `new Function` in first-party TypeScript. None present. ||| **Verified — no finding** |
131+
132+
### Supply chain, build and release
133+
134+
| ID | Threat | STRIDE | Sev | Status |
135+
|---|---|---|---|---|
136+
| **TM-SC-01** | `igniteui-webcomponents` (`~7.2.4`) and `lit-html` are bundled *inside* the .nupkg. Consumers cannot patch an upstream JS CVE independently. Upstream CVEs are handled under the `SECURITY.md` disclosure SLAs: acknowledgement within 3 business days, triage within 7 business days, fix timeline by severity. | T | Medium | **By design** — covered by the published SLAs |
137+
| **TM-SC-02** | No SCA, CodeQL, `npm audit` or dependency-review gate. `ci.yml` runs formatting, build and tests only. || **High** | **Open** |
138+
| **TM-BLD-01** | `igniteui-blazor-lite-release.yml` references `${{ env.BUILD_CONFIGURATION }}` in the signing and signature-validation steps, but that variable is **never defined**. It expands to empty, so the signing base directory becomes `src/bin/` rather than `src/bin/Release/`. It currently works only because the recursive `**/*.dll` glob still reaches the Release output — the integrity gate is scanning an unintended path. | T, R | Medium | **Open** |
139+
| **TM-BLD-02** | Unlike the DLL step, "Validate DLL signatures" does not fail when *zero* DLLs are found — an empty result set passes the gate. | R | Medium | **Open** |
140+
| **TM-BLD-03** | `<Nullable>disable</Nullable>` on `IgniteUI.Blazor.Lite.csproj` (generated sources are unannotated), removing compiler-enforced null safety across the shipped surface. || Low | **Accepted** — tracked TODO in the project file |
141+
142+
## 7. Threats — `IgniteUI.Blazor.Templates`
143+
144+
A template package executes no code at runtime; its risk is what it *emits* and what it
145+
*carries*.
146+
147+
| ID | Threat | STRIDE | Sev | Status |
148+
|---|---|---|---|---|
149+
| **TM-PKG-01** | `NoDefaultExcludes=true` combined with `Content Include="templates\**\*"` excluding only `bin`/`obj` packs **everything else in the tree** — dotfiles, `.env`, editor state, stray credentials — into the shipped package. | I | **High** | **Open** |
150+
| **TM-PKG-02** | `test-templates.ps1` / `test-templates.sh` live in the template project; must be confirmed not to land under `content/`. | I | Medium | **Open** |
151+
| **TM-TPL-01** | Insecure defaults in the scaffolded app (missing CSP, HSTS, HTTPS redirection, antiforgery; any CDN `<script>`/`<link>` without SRI) are replicated into every consumer project. Highest-leverage item in the template package. | T, I | **High** | **Open** — requires a secure-defaults checklist |
152+
| **TM-TPL-02** | Template-pinned package versions drift from the libraries and go stale, scaffolding projects onto known-vulnerable versions. | T | Medium | **Open** |
153+
| **TM-TPL-03** | `<Version>0.0.1</Version>` is hard-coded in the template project rather than driven by the release tag. | R | Low | **Open** |
154+
155+
## 8. Existing controls
156+
157+
Verified in `.github/workflows/igniteui-blazor-lite-release.yml`, `ci.yml` and the build props:
158+
159+
- **Release integrity** — Authenticode signing of all DLLs with a post-sign verification
160+
gate; NuGet package signing followed by `dotnet nuget verify`.
161+
- **Credential hygiene** — Azure OIDC federation and NuGet Trusted Publishing via
162+
short-lived OIDC-issued API keys; no long-lived publish secrets.
163+
- **Action pinning** — release-workflow actions are pinned to **commit SHAs**, not tags.
164+
- **Least privilege** — repository-level `permissions: contents: read`, job-scoped
165+
`id-token: write`, publishing gated behind the protected `nuget-org-publish` environment.
166+
- **Reproducible dependency install**`npm ci` with `package-manager-cache: false` in
167+
release builds ("never use caching in release builds").
168+
- **Deterministic builds**`<Deterministic>true</Deterministic>` in `Directory.Build.props`.
169+
- **Central package management**`Directory.Packages.props` with
170+
`ManagePackageVersionsCentrally`.
171+
- **Disclosure process** — a complete `SECURITY.md`: private reporting (GitHub PVR → email
172+
→ support case), 3/7-business-day acknowledgement/triage SLAs, severity bands,
173+
coordinated disclosure, advisories.
174+
- **Dependency updates** — Dependabot for GitHub Actions with a 14-day cooldown and
175+
security updates fast-tracked outside the batch.
176+
- **Testing** — bUnit unit tests plus Playwright integration tests with coverage in CI.
177+
178+
## 9. Residual risk
179+
180+
| ID | Accepted risk | Justification | Approver | Date |
181+
|---|---|---|---|---|
182+
| TM-SER-02 | Prerendered state in initial HTML | Inherent to Blazor SSR; mitigated by app-level cache headers | <!-- TODO --> | |
183+
| TM-DOM-02 | Consumer templates render consumer markup | Razor escapes by default; `MarkupString` is an explicit consumer opt-in | <!-- TODO --> | |
184+
| TM-SC-01 | Bundled third-party JS | Required for a single-package consumer experience; offset by the `SECURITY.md` disclosure SLAs (3-day acknowledgement, 7-day triage, fix by severity) applying equally to upstream CVEs | <!-- TODO --> | |
185+
| TM-BLD-03 | `Nullable` disabled on the Lite project | Generated sources are unannotated; enabling would emit thousands of warnings | <!-- TODO --> | |
186+
187+
## 10. Review and sign-off log
188+
189+
| Package | Version | Commit | Reviewers | Date | Open Critical/High | Outcome |
190+
|---|---|---|---|---|---|---|
191+
| <!-- TODO --> | | | | | | |
192+
193+
Release gate: **no `Open` finding of severity High or above may ship.**
194+
195+
## 11. References
196+
197+
See [PR.md](../../PR.md#references) in the repository root.

0 commit comments

Comments
 (0)