Host pbiplint yourself

pbiplint.com uploads nothing: the pbiplint Privacy Promise says so and shows how to check it. A firm that uses it still takes two things on trust: that the site serves the same code tomorrow that it serves today, and that the site itself is never compromised. A copy you verify, pin to a version, and serve yourself removes both. This page is for the security team that has to approve that copy: what you are approving, how to deploy it into your own Azure subscription, how to lock it down, and how to prove that no model, report, or finding ever leaves your network.

pbiplint ships one thing for this: the site's static files, built from the same commit as pbiplint.com, as an archive on every release. There is no server, container, or hosted service to run, and nothing to patch. A printable checklist records each check below for your approval ticket.

What you are approving

Get the files and verify them

Each release (opens in a new tab) carries four files for this, built by the release workflow from the tagged commit:

File What it is
pbiplint-site-0.3.1.zip The site, with its files at the root of the zip.
pbiplint-egress-check-0.3.1.mjs The egress check.
SHA256SUMS The SHA-256 of both.
pbiplint-site-0.3.1.sigstore.json Their build provenance attestation.

Download them from the v0.3.1 release page (opens in a new tab), or with the GitHub CLI:

gh release download v0.3.1 --repo pbiplint/pbiplint

Then check them:

Or build from source at the release tag, on Node 20.19 or a later 20.x, or Node 22.12 or later:

git clone https://github.com/pbiplint/pbiplint.git
cd pbiplint
git checkout v0.3.1
npm ci
npm run build -w @pbiplint/web -- --mode self-host

The site is then in packages/web/dist, the same files the archive holds. --mode self-host is what makes it the self-hosting build: it leaves out what only pbiplint.com needs (its domain file, its sitemap, and the /latest/ page), tells search engines to stay out, and adds a line to every page's footer naming the version and the date of its commit, with a link to check for a newer version.

Read the build marker. Every page carries <meta name="pbiplint-build" content="..."> with the first seven characters of the commit it was built from. View the source of any page of your copy and compare it with the commit shown beside the tag on the release page (opens in a new tab).

Deploy it in Azure

The template creates a private home for the files in your own subscription. It is a declarative template you can read before it runs (the template and its form (opens in a new tab)), not a script.

Deploy to Azure (opens in a new tab)

What it creates, in the resource group you pick:

It creates no script, container, or virtual machine, and it takes no pbiplint version: the files go up afterwards, so one deployment serves every release.

The form, field by field:

Field What it means
Subscription and resource group Where the storage account, endpoints, zones, and lock go. The group needs the roles listed below.
Region Where the storage account lives. The private endpoints go in your network's own region, whatever you pick here.
Storage account name 3 to 24 lowercase letters and digits, unique across Azure. It becomes part of the site's address.
Virtual network and subnet An existing network in this subscription, in any resource group. A new, unpeered network would reach no one, so the template does not create one.
Private DNS records Create the two privatelink zones here suits only a network that uses Azure-provided DNS, since private zones answer only queries that reach Azure DNS (opens in a new tab). Use existing zones is for a network linked to central privatelink.web.core.windows.net and privatelink.blob.core.windows.net zones, since a network cannot link two zones of the same name. None is for a firm whose Azure Policy writes private endpoint records itself.
Log Analytics workspace Optional. Receives the account's write and delete events, so any change to the files shows up.
Entra group Optional. The object ID of the one group that may change the files. Its members get Storage Blob Data Contributor on the site's $web container and nothing else. Left blank, the template assigns nothing.

The permissions the deployment needs:

Preview before it runs. The portal's last step lists every resource it will create. From the Azure CLI, az deployment group what-if shows the same without changing anything:

az deployment group what-if --resource-group <group> \
  --template-uri https://raw.githubusercontent.com/pbiplint/pbiplint/v0.3.1/deploy/azure/azuredeploy.json \
  --parameters storageAccountName=<name> subnetId=<subnet resource ID> networkLocation=<network region>

For the other DNS choices, add dnsMode=existing webDnsZoneId=<zone ID> blobDnsZoneId=<zone ID>, or dnsMode=none.

The site's address is the account's static website endpoint, shown on the deployment's Outputs tab as siteUrl: https://<account>.z<number>.web.core.windows.net/. HTTPS works on it with no certificate of your own, which the "Choose a folder" button needs. A friendlier name such as pbiplint.corp.example would need a gateway in front of the account, which this template does not cover.

What it costs. About $16 a month, nearly all of it the two private endpoints at $0.01 an hour each (opens in a new tab). With Create, the two DNS zones add $0.50 a month each (opens in a new tab), and storage and logging for a site this size cost cents.

Upload the files

From any machine inside your network that resolves the account's private names, signed in to Azure as a member of the group that may change the files (az login), in the folder where you verified the release:

unzip pbiplint-site-0.3.1.zip -d site
az storage blob delete-batch --account-name <account> --source '$web' --auth-mode login
az storage blob upload-batch --account-name <account> --destination '$web' --source site --auth-mode login
az storage blob upload-batch --account-name <account> --destination '$web' --source site \
  --pattern '*.js' --content-type 'text/javascript' --overwrite --auth-mode login
az storage blob upload-batch --account-name <account> --destination '$web' --source site \
  --pattern '*.css' --content-type 'text/css' --overwrite --auth-mode login

On Windows, Expand-Archive pbiplint-site-0.3.1.zip site replaces unzip. In PowerShell the az commands are the same, but put each one on a single line, or end each line with a backtick in place of \. If you built from source, use --source packages/web/dist in place of site.

Open the site's address from a machine on the network, then run the egress check against it.

Lock it down

Updates

Watch the network and prove it

In the browser

Open the developer tools on the Network tab, turn on "Preserve log", load your copy, lint a real model, and export a HAR file. Every request is for the page's own files at your copy's address, and none follows the drop.

At your egress

Watch your forward proxy or secure web gateway logs for a test workstation during a lint. They should show nothing beyond links a person chooses to follow. There is no server to watch: the storage account serves files and runs nothing.

With the egress check, after every deploy and on a schedule

pbiplint-egress-check-0.3.1.mjs, from the release, runs with Node 22 or later and a Chrome or Edge already on the machine, and installs nothing:

node pbiplint-egress-check-0.3.1.mjs https://<account>.z<number>.web.core.windows.net/

It opens your copy in a headless browser with a fresh profile and watches every request from before the first one, from the page and from any frame or worker it starts. It lints the sample project built into the site, then opens the rules index, a rule page, the Privacy Promise, and About. It exits 0 when every request stayed on your copy's own address and no page wrote a console error, 1 otherwise, listing what it saw, and 2 when it could not run at all. Its report names the address, the time, the browser, and the version and build marker it found: what the checklist asks for.

What you will see, and why it is fine

What a static host cannot do

An Azure static website cannot set response headers. The policy that enforces the no-request rule is in every page and holds regardless. What is lost:

The risk is low for a tool that stores nothing, changes nothing, and runs only inside your network. A web server of your own could send those headers. That route is not covered here; it is recorded with the other parked routes on issue #93 (opens in a new tab), so open an issue (opens in a new tab) if your firm needs it.

The command line and the GitHub Action

The pbiplint CLI makes no network requests either; installing it is the only network step. npx pbiplint fetches from the public npm registry, so install it through your own registry mirror (Artifactory, Nexus, Azure Artifacts, and the like), or from the tarball npm pack pbiplint writes. Neither package has runtime dependencies. The GitHub Action and the Azure Pipelines task run the same command line, so the same applies to your own runners and agents.

The license

pbiplint is free software under the GNU Affero General Public License, version 3 or later (opens in a new tab). Hosting an unmodified copy inside your firm asks nothing more of you. If you modify the code and let people use the modified site over a network, you must offer those users the modified source (section 13 (opens in a new tab)). For anything beyond that, ask your own counsel.