Security model
A report definition is untrusted input, a browser is untrusted ground, and a query is something only the host may run. Every seam in ReportWright is drawn with those three facts.
Nothing secret reaches a client
- Connection strings, credentials and file system paths live in the host's
DataSourceResolverand never appear in a DTO. When a data source fails, the client learns which source and dataset failed; the provider's own message stays in the server's logs. The designer's data pane receives field names, types and capped sample rows. - The designer lists the data sources a host offers by name and kind only (
DataSourceResolver.Available), and a data source it writes has an empty connect string: the connection stays in the host. - A REST data source's token is sent only to that source's own origin, even when a dataset names an absolute URL on another allowed host.
- A browser-side engine (the WebAssembly viewer) receives dataset rows the host executed; it never receives or executes a query.
- Report paths are names inside a store.
FileSystemReportStorerefuses traversal; a path that resolves outside the folder is a 404.
Sessions belong to their user
A run creates a session, and its id appears in URLs. Every session endpoint (pages, regions, stylesheet, search, toggle, sort, export, delete) answers only the user who started the session, and only while that user may still view the report; anyone else gets a 404, so an id seen in a log reveals nothing.
The Blazor Server backends ask the same questions
AddReportWrightComponents registers backends that run in the server process, with no REST call between the component and the engine. They ask ReportPermissions exactly what the endpoints ask, about the circuit's signed-in user and by the canonical path: viewing before a load, exporting before an export, editing before a designer load, save or schema question. The designer's report list holds only what the user may edit, and subreports resolve only when the user may view them.
The host decides
ReportPermissionsis asked per request for read, edit and export. The default allows everything and a host should replace it. It is always asked about the canonical path (forward slashes, no.or empty segments), whatever spelling the request used, and subreports are run only when the caller may view them.- Images, subreports and
Codeblocks are references a definition makes; the host suppliesImageResolver,SubreportResolverandReportCodeHostwith allow-lists, or the engine refuses with a diagnostic rather than fetching. - Hyperlink targets are handed to the host's
OnHyperlink; the projection'shrefpolicy is honoured, not overridden.
The engine bounds the work
DocWright's resource limits stay on: nesting depth, instance counts, image expansion, subreport depth, execution budgets. A hostile definition fails with a typed exception or a diagnostic, never a stack overflow or an unbounded allocation. Sessions are bounded by SessionLifetime and MaxSessions.
What is attacked in tests
- Every REST endpoint receives path traversals, broken and oversized JSON, bad identifiers, garbage and deeply nested RDL, and a flood of bad requests; the answer is 4xx with a message, never 500, never a stack trace, and the host keeps serving.
- The data router's guard is tested for server-side request forgery (REST provider), timeouts and row caps.
- The reader and the expression interpreter are fuzzed upstream in DocWright.
Reporting a problem
ReportWright's own code is small on purpose; most of the attack surface is the engine's, documented in DocWright's SECURITY.md. ReportWright's SECURITY.md says which versions receive fixes and what counts as a vulnerability. Report one privately through GitHub's private vulnerability reporting on the repository, never in a public issue, with the request or file that provoked it.