Repository navigation
Mask license keys in log entries and error messages - #58
Merged
Merged
Conversation
The Logging page says a license key must not appear in full in a log entry or an error message, and that only its last 4 characters are shown, with every character before them replaced, including in a URL.
…goes The cloud refuses a license key with an error that names the key in full, so the tests give the cloud request engine and the 51Did client that answer and check the log entries, the flow data errors and the rejected errors. They also check text inside objects, arrays and an error's other properties, a key followed by a full stop, and keys joined by a bar that the URL formatters write as %7C.
The cloud refuses a license key with an error that names the key, for example "Invalid license key(s) supplied: '<key>'". The mask found a key only after a parameter name, so that key reached the log entries, flowData.errors and the rejected errors in full. - LicenseKeys.mask takes the known keys as a second argument and masks each wherever it appears, as written or encoded for a URL. A value holding several keys separated by a comma or a bar is masked as each. - The cloud request engine masks its own license key in the text of every error it makes from the cloud's answer, before the error is logged, rejected or kept on the flow data. - The 51Did client masks its licence key in the message and body of each redeem error. The 51Did package depends on nothing in the Pipeline, so it has its own short mask. - The mask now reaches text inside arrays and plain objects, and every own property of an error, including the errors and the cause it holds. - A parameter's value stops before punctuation that ends a sentence, so ABCDEFGH followed by a full stop gives ****EFGH and the full stop. - %7C, the encoded bar the URL formatters write, separates keys.
jwrosewell
marked this pull request as ready for review
November 10, 2026 10:45
This was referenced Nov 10, 2026
Draft
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Keeps license keys out of log entries and error messages, as the License keys section of the logging specification on
rcsays. specifications#55 added that section and #123 reworded it, and both are onrc. The rule is that a license key must not appear in full in a log entry or an error message at any level. Only its last 4 characters are shown, with each character before them replaced by one*, soABCDEFGHis written****EFGH, and a key of 4 characters or fewer is replaced entirely. A URL that carries a key, and the message of an error an HTTP library raises, are masked the same way. There must be a test that a failed request carrying a license key produces log entries and error messages that hold only the masked form. The Cloud Request Engine page says the same of its License Key.The problem
licenseKeysto the update URL, and the data update service logs that URL. The open data update draft #52 adds the specification's messages that end the sentence straight after the URL, so the full stop follows the key.GET https://cloud.51degrees.com/api/v4/accessibleproperties?resource=...&license=TESTKEYNOTREALABCDEFGH1234, and toPOST https://cloud.51degrees.com/api/v4/id/redeemwith the same key in the form, is answered on 10 November with HTTP 401 and{ "status":"401", "errors": ["Invalid license key(s) supplied: 'TESTKEYNOTREALABCDEFGH1234'"] }. The Cloud Request Engine made each of those into aCloudRequestErrorwhose message held the key, and the 51Did client put the body in the message and body of its redeem error. From there the key reached the log entries,flowData.errors, theAggregateErrorraised to the caller and the redeem error.{ source, message: errors }, an object, and keeps an array of errors on the flow data. An error also carries properties other than its message, such asCloudRequestError.errorMessage, which repeats the message, and theerrorsandcauseof anAggregateError.What changed
LicenseKeys.maskinfiftyone.pipeline.core/licenseKeys.js.license,licence,licenseKey,licenseKeys,licenceKeyorlicenceKeysparameter in any case. Several keys separated by a comma or a bar, as written or encoded (%2C,%7C), are masked one by one.querystring.stringifyin both URL formatters writes a bar as%7C..,,,;,:,),!,?), solicenseKeys=ABCDEFGH.giveslicenseKeys=****EFGH..errorsand thecauseit holds, as well as its message and stack. A value that holds no key is returned as it is, so log entries that never held a key do not change. A value that holds one is returned as a masked copy, and the original is left alone.Pipeline.logandFlowData.setErrorcall it, as before, so every log entry and every error kept on the flow data is masked by parameter name.getErrorsFromResponse) masks its own license key in the text of every error it makes from the cloud's answer, before the error is logged, rejected or kept on the flow data. This is how .NET draft 51degrees-dotnet#119 and Java draft 51degrees-java#85 mask the engine's key, and like Java it masks each key in a value separated by a bar.redeem) masks its licence key in the message and the body of each error it makes from a redeem answer, as Java draft Mask license keys where a key can reach a message, not on the path every detection takes #85 does inDidClient.readRedeem. The body is masked too, because Node prints an error's own properties when it is logged. The 51Did package depends on nothing in the Pipeline, so it masks with a short function of its own that gives the same result.Tests
licenseKeys.test.jscovers the mask, includingABCDEFGHfollowed by a full stop,%7C, known keys, text inside objects and arrays, an object that refers to itself, and an error's other properties, errors and cause.cloudLicenseKeyMasking.test.jsgives the Cloud Request Engine the cloud's exact answer, and a failing status with a body that is not JSON, and checks that the log entries,flowData.errorsand the rejectedAggregateErrorhold only the masked key, both when processing fails and when the engine cannot get ready. This is the test the page asks for.dataUpdateKeyMasking.test.jsalso builds the update URL with each URL formatter and checks the message "An error occurred while downloading a data file update for ... from <url>. Update will be attempted again later." giveslicenseKeys=****EFGH., and that two keys joined by a bar are masked one by one.didClient.test.jschecks a redeem answer naming the licence key, for a 401, a 400 and a 404.Runs.
7bbadf7(25 of 498 fail). For example, the log entries held the cloud's key in full, the formatter's URL gavelicenseKeys=*****FGH., two keys joined by%7CgavelicenseKeys=***********************9999, and the 401 redeem error readInvalid license key(s) supplied: 'TEST-LICENCE-KEY': https://github.com/51Degrees/51degrees-node/actions/runs/42794326565faab2f7(498 passed): https://github.com/51Degrees/51degrees-node/actions/runs/42794394028faab2f7. Configure and the Ubuntu, Ubuntu ARM and Windows jobs on Node 22 and 24 pass, Configure printed "Left out 2 macOS entries", and all 7Setting 'PullRequestSha'lines arefaab2f7536fc0ea73779f7099757eb11230180af: https://github.com/51Degrees/51degrees-node/actions/runs/42795073749macOS was left out of this draft's full build under the 9 November decision. The rc head is built with macOS before it merges into main.
Not done here