Rules / Error Prevention

Secret written into a query

error HARDCODED_SECRET_IN_QUERY · built in · Power Query layer · scope: Partition, NamedExpression, Table

What it checks

Credentials written into Power Query queries as text: a key, token, or password in a web request's headers or Query record, in a web address, as a Bearer token, or in an ODBC or OLE DB connection string.

Each finding names the partition, the shared expression, or, for an incremental refresh policy's own query, the table, and says where the credential is, never what it is: query 'Orders API', step 'Source': the Authorization header in Web.Contents holds a credential written into the query (value not shown). No output of pbiplint shows any part of the value, its length, or anything computed from it. The location is the line and column where the text starts in the TMDL file, so you can find it in your own copy. When a parameter or a step holds the credential, the finding is on that text, once, however many queries read it.

Example

Fires the rule
table 'Orders API'
	column Content
		dataType: binary
		summarizeBy: none
		sourceColumn: Content

	partition 'Orders API' = m
		mode: import
		source =
				let
					Source = Web.Contents("https://api.example.com/v1/orders", [Headers = [Authorization = "Bearer pbiplint-fixture-example-0909-notreal"]])
				in
					Source
After the fix
table 'Orders API'
	column Content
		dataType: binary
		summarizeBy: none
		sourceColumn: Content

	partition 'Orders API' = m
		mode: import
		source =
				let
					Source = Web.Contents("https://api.example.com/v1/orders")
				in
					Source

Why it matters

A Power BI project is text made for source control: every copy of the repository, every clone, and every fork holds the queries as written, and so holds the credential. Removing the line later does not take it back, since the history keeps it. Microsoft's reference for Web.Contents names the risk while showing the alternative: "Instead of hard-coding the secret key in M (which would pose a security risk), the key can be provided securely by specifying its name (not its value) in M, choosing Web API authentication, and entering the key value as part of the Web API credential." (Web.Contents, Example 3 (opens in a new tab)).

How to fix it

  1. Revoke or rotate the credential with whoever issued it, first. The old value is in the repository's history, so taking it out of the query does not unpublish it.
  2. In Power BI Desktop, choose Transform data, select the query, and select the step the finding names in Applied steps. Delete the credential from the step's address, header, or Query record in the formula bar, or from the parameter that holds it (Home, Manage Parameters). In the TMDL file, the text is in the partition's source block, or in the expression's value for a parameter.
  3. For a model that is published, give the source a credential the service can refresh with. Where the source accepts it, sign in with an organizational account: when Desktop asks for credentials, choose Organizational account, one of the authentication kinds the Web connector lists for Web.Contents (Web connector, Summary (opens in a new tab)). Where the source takes only a key, Microsoft's route is a custom connector that holds the credential: "If you build reports in Power BI Desktop that use custom data connectors, you can use an on-premises data gateway to refresh those reports in the Power BI service." (Use custom data connectors with the on-premises data gateway (opens in a new tab)).
  4. For a model that is never published, ApiKeyName with a Web API credential keeps the key out of the files, as Microsoft's Web.Contents, Example 3 (opens in a new tab) shows. Do not rely on it for a published model: "The Web API authentication type is available in Power BI Desktop and Power Query in Excel, but isn't supported in the Power BI service." (Web connector, Summary (opens in a new tab)).

When to ignore it

When the text is not a working credential, such as a public demo key the provider publishes for everyone, or a value already revoked. Even then, a placeholder that reads as one, such as <your key>, says so to the next reader without looking like a leak.

To ignore this rule on one object, add annotation pbiplint.ignore = HARDCODED_SECRET_IN_QUERY under the object in its TMDL file. Power BI Desktop keeps the annotation. To turn the rule off for a whole project, set "HARDCODED_SECRET_IN_QUERY": "off" under rules in pbiplint.config.json.

Quirks

Check a model for this Improve this page (opens in a new tab)