Security
How OpenBI Server keeps your data and your people’s sign-ins safe, what leaves the machine, and how to check what you installed. For IT and security reviewers.
What leaves the machine
Your data never goes to Norway Analytics. OpenBI Server runs on your machines, and Norway Analytics hosts nothing for you. On its own, the server makes one kind of request over the internet: the licence check, to Keygen, the licensing service Norway Analytics uses, where Norway Analytics sees what the check sends about the server (never about your data or your people). What leaves our network? lists exactly what it sends, and when. doctor also checks that Keygen answers. With a seat licence, the server also tells the seller’s billing service how many seats are in use, as a number (see the table).
Everything else happens only when you set it up, or when a model or a report uses it:
| When | Where it goes | What it is |
|---|---|---|
| The server has a seat licence | Querex AS’s billing service, billing.querex.no | The licence key and machine fingerprint, to prove which licence it is, and the number of seats in use, every 6 hours and a minute after it changes; and what you buy and change in the setup page and Billing. Never a name or an address, but the billing contact typed when buying (see What the server sends to the seller). |
| You get a certificate | Let’s Encrypt | https letsencrypt: the certificate for each address. With --check-public-ip, it also asks api.ipify.org for the server’s public IP address. |
| You set up IIS and HTTPS on Windows | Microsoft’s download site, GitHub | IIS’s URL Rewrite and Application Request Routing modules, and the win-acme certificate client. |
| You set up lakes | DuckDB’s own extension repository, and NVD | lake setup --apply downloads DuckDB’s extensions once, about 330 MB from extensions.duckdb.org, into the data folder, then looks up the libraries inside them in NVD, NIST’s vulnerability database (services.nvd.nist.gov). NVD is asked again when they are checked again, by sbom --verify, and when the service finds one of them changed. An engine downloads nothing (see DuckDB’s extensions). A server without internet gets them from a folder instead (see A server without internet). |
| The server sends e-mail | Your own mail service | Invitations, “Forgot password?”, alerts and failure notices, through your Microsoft 365 mailbox or your SMTP server. |
| A model refreshes, or is live | Your data sources | The databases, files and services the model reads, with the credentials you stored on the server. For the Power BI service and Fabric, Microsoft Entra ID gives the server its access tokens. |
| A report page shows a map | OpenStreetMap’s tile server | The map’s tiles, tile.openstreetmap.org by default, fetched by the server through the system’s own web client and proxy, and kept as long as the tile server’s answers say, 30 days when they say nothing. The tile server sees which tiles are asked for, the server’s address and its name, never who looks at the map (see Map tiles). |
| A report page shows pictures by their address | The address in your data | Product photos and the like, fetched by the server as map tiles are, and kept as long as their answers say, 30 days when they say nothing: only http or https on their usual ports, never this machine or a private network, only PNG, JPEG, WebP and GIF up to 10 MB. |
| A query reads an image’s colours from an address | The address in the model | OAX’s IMAGECOLOR and IMAGECOLORS, only when a server administrator has turned on Images from addresses, off by default, and never for a Viewer’s query (see Images from addresses). |
| A person’s AI agent reads something, once you turn agents on | The agent’s provider, such as Anthropic or OpenAI | What the agent reads, as that person, under their own agreement with its provider (see AI agents on the server). When an app comes by its metadata document, OpenBI Accounts reads that document at its exact allowed address, such as one at claude.ai. |
People’s browsers talk to your server only. An OpenBI Gateway, if you run one, talks to your server only too (see The OpenBI Gateway).
Images from addresses
New in 0.1.43 OAX’s IMAGECOLOR and IMAGECOLORS read the colours of an image. On a server they read images in the model’s own data, a data: URI or base64 text, and fetch none from an address, unless a server administrator allows it: Admin portal, Server, Images from addresses (IMAGECOLOR, IMAGECOLORS), Turn on…. It is off by default.
- Turned on, after a warning in red, the queries of a workspace’s Contributors, Members and Admins and of the server’s administrators may fetch public images, over
httporhttpson ports 80 and 443. Each address is checked as it is fetched: this server itself, private addresses and cloud metadata are always refused. - A Viewer’s queries never fetch, on or off.
- It holds at once: the running engines start again, and the change is in the audit log.
Why it is off: a model’s author chooses the address, so a fetch is the server reaching out to it from inside your network, and it could tell that address who opened a report and when. The pictures a report page shows by their address are another matter, fetched as in the table above.
DuckDB’s extensions
New in 0.1.43 An engine on the server installs and loads no DuckDB extension by itself, and downloads none. Every engine the server starts, for a report, for Excel or for a refresh, takes DuckDB’s extensions only from the server’s own folder, which lake setup --apply fills and checks: what the check refused is kept out of it. Until then, a query that needs one (a lake, a Delta table) fails, saying that the extension is not set up on this server and where its administrator sets it up. See The extension gate.
Map tiles
New in 0.1.44 The engine fetches a report map’s background tiles, and a page’s pictures, with the operating system’s own web client: WinHTTP on Windows, with Windows’ own TLS; macOS’s own libcurl on a Mac. No DuckDB extension is involved. Each goes through the system’s proxy settings: on Windows WinHTTP’s, on a Mac its static web proxies (a PAC file or WPAD is not read: set the proxy itself, or https_proxy in the service’s environment).
- The source is the service’s environment’s,
OPENBI_TILES: OpenStreetMap’s tiles by default, a tile server of your own, ornone. No model, report or viewer sets it. - What the tile server receives: the server’s network address; its name in the User-Agent,
OpenBI/<release> (map tiles; bi.<domain>; <OPENBI_TILES_CONTACT>; +https://www.openbi.no/), with the contact only when that variable is set in the service’s environment; the server’s own address as the Referer (https://bi.<domain>/, never a report’s); and which map squares people view. Never who looks at the map. A large deployment should use its own tile server or a paid provider. - Kept as the tile server says, as HTTP caching (RFC 9111) has it: as long as its answer’s
Cache-ControlorExpiresallows, 30 days when it says nothing; once stale, asked again with itsETagor date. An answer markedno-storeorprivateisn’t kept. - A limit per person: one person makes an engine fetch at most 100 tiles it doesn’t keep at once, then 10 a second; kept tiles are free. Past that, a tile answers
429withRetry-Afterand the map shows it blank, so nobody can make the server download tiles in bulk, which OpenStreetMap’s tile policy forbids.
Report pages, sandboxed
A report page can carry its own scripts: its author writes it in OpenBI Studio. So the server never lets a page act as the person looking at it.
- A frame of its own. The page is shown in a sandboxed frame, an origin of its own. Its scripts can’t read the portal, its cookie or its token, and can’t act as the person on the portal.
- A pass instead of a cookie. The frame’s requests carry a pass in their address, sealed by the server, that lasts 12 hours and is good for one person, one item and one version. Every request checks the pass and the person’s access to the workspace again: taken out of the workspace, they get nothing within 30 seconds.
- The server is the only door. Each published version runs in an engine of its own, listening on this machine only and answering only the server. The server passes on only what a report needs: the page and its files, the report runtime, map tiles and pictures, and the queries. Anything else is refused. The person’s cookies and headers never reach an engine. The engine reads no data source and runs no Python or R script for a report: it answers from the version’s data. A live model asks its own server.
- Viewers get no formulas. A workspace’s Viewers get the model without its OAX: no measure’s, calculated column’s or calculated table’s formula, in the browser or in Excel, unless the workspace’s Admins gave them Can see formulas (see Can see formulas). The measures still answer: they run on the server.
Every report page is served with this Content-Security-Policy: it loads scripts, styles, images, fonts and data from your server only, and runs sandboxed. Beside each 'self' the server adds its own address, such as 'self' https://bi.norwayanalytics.no, because a sandboxed page’s 'self' is no origin.
default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';
img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; object-src 'none';
base-uri 'self'; form-action 'self'; frame-ancestors 'self';
sandbox allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-modals allow-downloads
Anything else of a report’s that someone opens on its own, such as a picture, an SVG file or a script (a PDF export aside), gets sandbox; default-src 'none'; img-src 'self' data:; style-src 'unsafe-inline', so nothing a Contributor published runs as your server’s own pages. A page being edited in the browser is shown the same way, with a pass of its own that lasts 4 hours and ends when its editor signs out.
A PDF export is typeset on the server in an empty folder, with no packages and no network. Before every document a report sends to be typeset, the server puts its own rule for links: only http, https, mailto and tel links and the server’s own pages, with no user name or password in them, so no page’s script can put any other link in a PDF (see Links in a PDF).
Nothing from the internet
The portal and the report pages load no fonts, scripts, styles or trackers from another site: report pages can’t, by their policy, and the portal uses your computer’s own fonts. Map tiles and pictures come through the server, as described above, so a viewer’s browser never contacts a tile server or a picture’s host.
What Viewers and agents can’t see
New in 0.1.43 A workspace’s Viewers can send OAX queries of their own from a report page, and once AI agents are on, so can the agents people connect. OpenBI Server’s engine holds the model’s security against whatever is sent, in the browser, in Excel and through agents alike:
- Rows and columns a role hides stay hidden, whatever OAX or DAX is sent. Columns a query defines, ADDMISSINGITEMS, COLUMNSTATISTICS, PATH, a whole table and the like see only the role’s rows. A column the role’s object-level security hides is, for that role, as if it didn’t exist, as in Analysis Services. Row counts and distinct counts count only the rows the role sees.
- No formulas, by the engine’s own rule too. A Viewer’s model carries no measure’s, calculated column’s or calculated table’s OAX, unless the workspace’s Admins gave them Can see formulas.
- Live models. A Viewer’s query that would read a live model’s definitions (the INFO functions, COLUMNSTATISTICS) is refused before anything goes to its server. Saved optimization and valuation results, worked out over the whole model, are refused to a Viewer of a model with roles, or of a live model.
- Nested too deep is an error, and the engine goes on. A formula more than 1,000 levels deep in parentheses, functions, operators or variables, in OAX, M or MDX, answers “The expression is too deeply nested”, and the engine goes on with everyone else’s queries.
- A query stops at its time limit, or when its page no longer waits for it (see A query’s time limit).
- Kept answers are each person’s own. New in 0.1.44 The answers an engine keeps to give again at once are kept by the person, their roles and their access: nobody is given an answer kept for someone else (see Answers kept).
- The current version only. A Viewer opens a report’s current version only: an older version keeps its own roles as they were, which may have shown more. A Viewer whose report is open when a newer version is published is asked to reload.
A query’s time limit
A query runs at most 225 seconds by default, as the Power BI service’s report visuals do; then it stops with an error, and the engine goes on with the others. A report page that no longer waits for a query, because it was closed or reloaded or its filters changed, cancels it, and the engine stops it. An AI agent’s query has a shorter limit of its own. The limit is the server administrator’s to set: see Query time limit.
Sessions and sign-in
People sign in with OpenBI Accounts (a password and an authenticator app) or with your own OpenID Connect provider, such as Microsoft Entra ID (see Sign-in and OpenBI Accounts). The server never sees a password.
- The portal’s session lasts 10 hours from its last use, and is checked with the sign-in every 5 minutes.
- Ended at once. With OpenBI Accounts, the server checks the person on every request: someone disabled, or whose sign-in was reset, is refused within 30 seconds, in the portal, in a report’s frame, in Excel and with OpenBI Studio’s access tokens. A session from before a reset opens nothing.
- With your own provider, that provider looks after its people. The server learns that someone was disabled there at its next check with the sign-in, within minutes, but Excel can’t tell: revoke their Excel passwords too (see Excel passwords).
- Changes need the page’s token. Every request of the portal that changes something carries its page’s token, so no other site can make a signed-in browser change anything. OpenBI Studio and scripts use an access token instead, for this server only.
- On the server itself too. Everyone signs in, the server’s own machine included. There, only the setup surface answers without a sign-in, and only until sign-in is set up: the setup page, the licences and adding one. After that, the server’s administrator signs in like anyone else, or uses the command line (
domain add,doctor). - A forwarder in front of the server passes each visitor’s address. The services take a visitor’s address from
X-Forwarded-For, as IIS on Windows and the Mac’s front door send it. Anything else you put in front of them on the server’s machine, another reverse proxy or a port forward, must send it too: through one that doesn’t, every visitor looks like the server’s own machine, reaches at most the setup surface above, and is asked to sign in everywhere else. - Programs are never sent to a sign-in page: a request to the API without a valid token gets
401and what to do. - Excel signs in on every request with the person’s e-mail and an Excel password, never the browser’s cookie. Requests from a browser are refused at Excel’s door, and wrong passwords are slowed down: 10 a minute per address and per e-mail.
- A new password ends the other sessions. A password reset with Forgot password?, or changed on the person’s own account page, signs them out everywhere else, OpenBI Studio and their AI agents’ connections included; the browser they changed it in stays signed in.
- AI agents have tokens of their own, good for 15 minutes and for the server’s
/mcpaddress only, and taken nowhere else (see How agents sign in).
Who may do what
Two layers, as in Power BI: a workspace role says what someone may do with a workspace; the model’s roles say what they see in its data, however they reach it.
| Who | May |
|---|---|
| Server administrator | Everything on the server: workspaces, people, lakes and DuckDB’s extensions, AI agents, images from addresses, the audit log and the server’s settings, in the Admin portal, and the command line on the server itself. Sees every workspace; opening one they are not a member of is in the audit log. |
| Workspace Admin | Who has access to the workspace and which of its Viewers can see formulas, whether AI agents may reach it, its settings, removing its own lake, archiving it; and everything a Member may. |
| Member | A model’s settings: data sources, refresh and schedules; the workspace’s activities in the Monitor; archiving items; and everything a Contributor may. |
| Contributor | Publish, edit report pages in the browser, refresh, download a version, open older versions. |
| Viewer | Open reports at their current version, Analyze in Excel, set their own alerts, and connect an AI agent where the workspace allows it. The model’s roles decide which rows they see (see Row-level security). |
The command line runs on the server itself, for whoever administers that machine: the server’s records are open to its administrators and the service only. More in Workspaces and roles.
Secrets on the server
- Sealed for the machine. The licence keys, the sign-in’s client secret, the data sources’ and lakes’ credentials and the records’ database connection are sealed for this machine: with DPAPI’s machine key on Windows, and on a Mac with a key of OpenBI’s that only the service reads, kept outside the data folder. A copy of the data folder can’t unseal them on another machine. What is published is kept as it came, not encrypted: the models and their data in a copy of the folder, or in a backup, can be read, so keep both as closed as the server itself. In a cluster, the data sources’ and lakes’ credentials and the sign-in’s keys are sealed with the cluster’s key instead, which every node holds.
- Never shown again. A secret is typed hidden or piped in, never shown again, and never in the audit log or the logs. A data source’s secret reaches only the engine that signs in with it, in its environment, never on its command line or in a file.
- Hashes only. Excel passwords are kept as their SHA-256 only, compared in constant time.
- A closed data folder. On Windows,
C:\ProgramData\OpenBI Serveris open to SYSTEM, administrators and the two services only. On a Mac,/Library/Application Support/OpenBI Serverbelongs to the service’s own account,_openbi. - Their own accounts. On Windows the services run as their own virtual accounts,
NT SERVICE\OpenBIServerandNT SERVICE\OpenBIAccounts, and listen on this machine only (localhost:5780andlocalhost:5790). IIS holds the public addresses and ports 80 and 443. On a Mac they run as launchd daemons under_openbi.
The bill of materials
OpenBI Server installs a software bill of materials beside the program: every third-party component in the installer, what it is for, its licence, each file with its SHA-256, and its known vulnerabilities.
| File | Windows | Mac |
|---|---|---|
sbom.cdx.json, CycloneDX 1.5, for Dependency-Track or any SBOM tool | C:\Program Files\OpenBI Server\ | /Library/OpenBI Server/ |
sbom.html, the same to read in a browser; it loads nothing from the internet | C:\Program Files\OpenBI Server\ | /Library/OpenBI Server/ |
It lists the server’s and OpenBI Accounts’ .NET packages, the .NET and ASP.NET Core runtimes file by file, the engine and its native libraries, what is compiled into them (including the report pages’ chart and map libraries), and what the server fetches only when an administrator asks (win-acme, IIS’s modules, DuckDB’s extensions), marked as not shipped. In the portal, the Admin portal’s Server page links both files, for the server’s administrators only, and doctor reports its state.
OpenBI.Server.exe sbom
OpenBI.Server.exe sbom --json
OpenBI.Server.exe sbom --verify
sbom says where the bill is, what it lists and whether its vulnerabilities were checked; --json gives the CycloneDX document; --verify hashes every file it lists again, on this machine. Run as administrator on a server with lakes set up, --verify also checks DuckDB’s extensions for lakes, which the server fetched rather than shipped, as the extension gate does. On a Mac: openbi-server sbom. Its exit code:
| Exit code | Means |
|---|---|
0 | Checked, and no known vulnerability in what ships but those accepted (see Accepted findings). |
1 | No bill here, or a file differs from the bill (--verify). |
2 | The bill lists a known vulnerability in what ships that isn’t accepted, or (--verify) a DuckDB extension for lakes is refused. |
3 | Not checked: the vulnerability databases weren’t reached when the installer was built, or (--verify) DuckDB’s extensions for lakes couldn’t be checked. |
The vulnerability gate
Every build of the installer checks that each shipped .NET file is the version its package says, that every program and library in the installer is on the bill, and looks each component up in OSV (GitHub’s advisories) and NVD at its shipped version. A stale file, a file the bill doesn’t account for, or a known vulnerability in what ships stops the build, unless Norway Analytics has assessed that finding and accepted it, by its id, for that component and version alone.
Accepted findings
A finding Norway Analytics accepts is one that is not exploitable in OpenBI as shipped. The bill carries its assessment as CycloneDX analysis (not_affected, with its justification, and response update), so a scanner that imports sbom.cdx.json sees it too. It is never counted as a vulnerability in what ships:
sbomlists it on a line of its own,ACCEPTED <id> in <component>: not exploitable as shipped (…), and exits0;doctorand the Admin portal’s Server page say “no known vulnerability in what ships but 11 accepted as not exploitable as shipped”, with their ids;- any other finding, or this one in another version, still stops the build.
OpenBI Server 0.1.49 has eleven, as every version from 0.1.45, all in SQLite 3.53.3, inside the e_sqlite3 library the server keeps its records with. NVD published them on 30 October 2026 for SQLite before 3.54.0. Each is accepted by its id, for that version alone, until a release ships SQLite 3.54.0 or later:
| Findings | What they are | Why they can’t be reached as shipped |
|---|---|---|
| CVE-2026-119376, CVE-2026-119371, CVE-2026-119591 | SQL written by an attacker that exhausts SQLite’s stack: chained COLLATE operators, chained common table expressions, count(*) over a very long VALUES list. | No SQL written by a person or an agent reaches this SQLite. It runs only the server’s own statements (its records, through Entity Framework, parameterized) and lake backups reading a lake’s SQLite catalog. |
| CVE-2026-119378, CVE-2026-119375 | A crafted database file: an R-Tree index, a write-ahead log. | The one way in for a crafted file is a catalog in a shared lake, which only a server administrator can add. |
| CVE-2026-119372, CVE-2026-119373, CVE-2026-119374, CVE-2026-119379 | Full-text search (FTS3’s snippet(), FTS5) and the session extension’s changesets. | Code OpenBI never runs: it has no full-text table, no MATCH and no changeset anywhere. |
| CVE-2026-119377 | A way around sqlite3_set_authorizer(). | OpenBI sets no authorizer. |
| CVE-2026-119586 | The SQLite command-line shell’s .recover. | The shell isn’t shipped: e_sqlite3 is the library alone. |
At worst, one of them could make the server stop; none reads or changes data. sbom lists each with its reason. DuckDB’s SQLite scanner for lakes carries its own SQLite, which the extension gate judges apart.
Check the installer
The bill is inside the signed installer, so the signature seals it with every file it describes. Check the signature before you install, then run sbom --verify after.
Get-AuthenticodeSignature OpenBI-Server-0.1.49-x64.msi
Its status must be Valid, signed by Norway Analytics, thumbprint 48D6BE98B89BE0766F85A2BC9136C85409B569D7. On a Mac, the package is signed with a Developer ID Installer certificate, Team ID 896HW73HL8, and notarized by Apple: pkgutil must name that Team ID, and spctl must answer accepted, source=Notarized Developer ID.
pkgutil --check-signature OpenBI-Server-0.1.49-arm64.pkg
spctl -a -vvv -t install OpenBI-Server-0.1.49-arm64.pkg
The audit log
Every change is in the audit log: who did what, when, and in which workspace. That covers publishing, access given and removed, Can see formulas given and taken, data sources and their tests, refreshes, Excel passwords made and revoked, alerts, lakes and their backups, DuckDB’s extensions checked and their findings accepted, images from addresses turned on or off, AI agents’ switches, connections and every call they make (see The audit), and every change made with the command line, recorded as the command line. A server administrator opening a workspace they are not a member of is in it too. It never holds a secret.
In the Admin portal, Audit log has filters, a keyword search and an export to CSV for Excel. On the server:
OpenBI.Server.exe audit --workspace "<name>" --lines 200
Good practice
- HTTPS only. Send
http://tohttps://withhttps redirect --applyonce the certificates are in place. Excel’s password travels in every request, so Excel must use HTTPS. - At least two server administrators, each with their own sign-in. The last one can’t be removed.
- When someone leaves, disable them in OpenBI Accounts (
accounts user disable --email <e-mail>) or in your own provider, and revoke their Excel passwords (excel-passwords revoke --email <e-mail>). - Contributor is a publisher’s role. Give it to people you trust to publish: a report page may carry its own scripts, which run sandboxed in its viewers’ browsers.
- Mail that can send as one mailbox only. With Microsoft 365, give the app Exchange Online’s “Application Mail.Send” role scoped to that mailbox, not Mail.Send for the whole company.
- Treat backups of the data folder like the server itself. They hold the published models and their data. See Backing up the server.
- After every install or upgrade, run
doctorandsbom --verify. - AI agents where they are wanted. Turn them on for the server when people need them, let each workspace’s Admins allow them where its data may go to their people’s AI providers, and keep the allowed clients to the apps your company uses (see AI agents on the server).
- Images from addresses stays off unless a model needs it, and DuckDB’s extensions’ findings are accepted only for the extensions your lakes need.
Frequently asked questions
Does OpenBI Server send usage data or check for updates?
No. It sends no usage data; with a seat licence, it tells the seller only the number of seats in use. Apart from that and the licence check, it makes only the requests in What leaves the machine. Updates are installed by your IT (see Upgrade and rollback).
Can Norway Analytics see our data?
No. Your data, your people and your passwords stay on your machines, and Norway Analytics has no account on your server unless you make one.
Can we keep maps from fetching tiles from OpenStreetMap?
Yes. The engine reads its tile source from the variable OPENBI_TILES in the service’s environment: a tile server of your own, as a URL template with {z}, {x} and {y}, or none for maps without tiles. No model, report or viewer can set it. See Map tiles.