[wrangler] Sanitize sensitive commands in telemetry to prevent capturing secrets#11856
[wrangler] Sanitize sensitive commands in telemetry to prevent capturing secrets#11856
Conversation
🦋 Changeset detectedLatest commit: 753706d The changes in this PR will be included in the next version bump. Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
create-cloudflare
@cloudflare/kv-asset-handler
miniflare
@cloudflare/pages-shared
@cloudflare/unenv-preset
@cloudflare/vite-plugin
@cloudflare/vitest-pool-workers
@cloudflare/workers-editor-shared
@cloudflare/workers-utils
wrangler
commit: |
| // Sanitize sensitive commands to prevent accidentally capturing secrets | ||
| // or credentials that users may have pasted as arguments | ||
| const sensitiveCommandPrefixes = [ | ||
| "wrangler login", |
There was a problem hiding this comment.
One problem with this implementation is that we duplicate the info in the command definition and here.
Meaning that if we tweak the command format or add a new command, we also have to update this code and there are chances we will miss that.
IMO a better implementation would be to move that to the command definition itself in some form.
Also we should probably remove "wrangler" from properties.command. Who knows, it might change some day ;)
There was a problem hiding this comment.
Thanks Vic! That's a good point. So your idea would be that each command would have a property that marks the following args as potentially sensitive and skips them, and make the analytics aware of that?
Agree with ridding ourselves of wrangler in the properties.command, but the challenge with all logging data is that once it's there it's there and you kinda just have to move forward with your mistakes else you'll be forever adding additional filters to catch users on older versions, and then you'll forget a filter etc etc. Though I guess we're going to see that if the command changes anyway...
Instead of maintaining a hardcoded list of sensitive command prefixes in the metrics dispatcher, this adds a sensitiveArgs property to the command definition metadata. This ensures: - No duplication between command definitions and metrics code - New sensitive commands only need sensitiveArgs: true in their definition - The command registry is the single source of truth Changes: - Add sensitiveArgs to Metadata type in core/types.ts - Add findCommandDefinition() to CommandRegistry for looking up commands - Pass sensitiveArgs flag from command definition to sendCommandEvent() - Remove hardcoded prefix list from metrics-dispatcher.ts - Add sensitiveArgs: true to login, secret put/bulk, pages secret put/bulk, and versions secret put/bulk commands
| expect(std.debug).toContain('"command":"wrangler secret put'); | ||
| expect(std.debug).toContain('"sensitiveArgs":true'); | ||
| // Ensure the accidentally pasted secret is not in the debug output | ||
| expect(std.debug).not.toContain("accidentallyPastedSecret"); |
There was a problem hiding this comment.
just to make sure i'm clear on what is an arg and what is a flag and what is a value
what would happen in the test case npx wrangler secret put ACTUAL_SECRET RANDOM_POSITIONAL --foo=bar? do we still get --foo=bar somehow in telemetry or is that dropped too?
There was a problem hiding this comment.
No we drop everything in this case. I will live to regret this but I think it's more important that we drop possibly useful data than capture something that we shouldn't capture. In the future if we want to add explicit checks for specific arguments in we can do that.
…nsitivity default - Renamed 'command' to 'safe_command' and 'args' to 'safe_args' to allow server-side differentiation from historical potentially-leaked data - Changed sensitiveArgs default from false to true (opt-in for sending args) - Fixed CommandRegistry.findCommandDefinition() to follow command aliases - Marked ~80 safe commands with sensitiveArgs: false (list, info, get, etc.) - Commands handling secrets remain sensitive by default (secret put, etc.)
- Strip 'wrangler' prefix from safe_command for future-proofing (e.g., 'secret put' instead of 'wrangler secret put') - Update COMMAND_ARG_ALLOW_LIST keys accordingly - Remove temporary audit document
Co-authored-by: emily-shen <69125074+emily-shen@users.noreply.github.com>
| // or credentials that users may have pasted as arguments. | ||
| // The sensitiveArgs flag is set from the command definition's metadata. | ||
| if (properties.sensitiveArgs) { | ||
| argv = []; |
There was a problem hiding this comment.
@emily-shen I think this is the line that should remove global flags also if the sensitiveArgs property is false
There was a problem hiding this comment.
soz what i meant is that every command is a command that could have file paths, given -c is a global arg. so we shouldn't include that in the changeset list (also, i think its an llm implementation detail, what we should include is the actual commands affected)
Claude went a bit over the top here and committed classically hyperbolic language, so I'm toning it down.
vicb
left a comment
There was a problem hiding this comment.
See my inline comments.
I'm pretty sure there is better way that findCommandDefinition, let me know if you want me to look at it.
| * Returns the command definition and the number of args segments that form the command | ||
| * (excluding positional args), or undefined if no command is found. | ||
| */ | ||
| findCommandDefinition( |
There was a problem hiding this comment.
They are available in the command handler function because they are in the function's closure.
The problem we have is that some of these metrics dispatches are done outside of the handler and sometimes before the handler has even been invoked.
Co-authored-by: lrapoport-cf <107272160+lrapoport-cf@users.noreply.github.com>
Co-Authored-By: pbacondarwin@cloudflare.com <pete@bacondarwin.com>
Co-Authored-By: pbacondarwin@cloudflare.com <pete@bacondarwin.com>
…getAllowedArgs() Co-Authored-By: pbacondarwin@cloudflare.com <pete@bacondarwin.com>
| // Look up the command definition to check if it has logArgs enabled | ||
| // Default to false - commands must explicitly set logArgs: true to send args | ||
| const commandResult = registry.findCommandDefinition(commandParts); | ||
| logArgs = commandResult?.definition?.metadata?.logArgs ?? false; | ||
|
|
||
| // Build safeCommand without "wrangler" prefix for future-proofing | ||
| // If this command does not log args, strip positional args from the command string | ||
| // to prevent accidentally capturing secrets in telemetry | ||
| safeCommand = logArgs | ||
| ? commandParts.join(" ") | ||
| : commandParts.slice(0, commandResult?.commandSegments ?? 0).join(" "); | ||
|
|
There was a problem hiding this comment.
🟡 Telemetry command name becomes empty for commands not found in CommandRegistry
In main(), telemetry’s safeCommand is derived from the command registry lookup. When the current invocation doesn’t match a registry command (e.g. legacy yargs commands that are not yet migrated to CommandRegistry), findCommandDefinition() returns undefined, so commandSegments falls back to 0 and the slice becomes empty.
const commandResult = registry.findCommandDefinition(commandParts);
logArgs = commandResult?.definition?.metadata?.logArgs ?? false;
safeCommand = logArgs
? commandParts.join(" ")
: commandParts.slice(0, commandResult?.commandSegments ?? 0).join(" ");
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -> 0Actual behavior: for any non-registry command, safeCommand becomes "" and is sent in telemetry events (wrangler command started/completed/errored).
Expected behavior: telemetry should still include the command name (at least the command prefix like "dev", "kv", etc.) even if the command isn’t in the registry, while still avoiding positional args/secrets.
Impact: loss of command attribution in telemetry for all legacy commands not yet in the registry, which can break analytics and any downstream filters keyed on safeCommand.
Recommendation: If findCommandDefinition() returns undefined, fall back to a safe prefix like commandParts[0] (or commandParts.join(" ") if you’re confident it contains no positional args), rather than producing an empty string. For example:
const segments = commandResult?.commandSegments ?? Math.min(1, commandParts.length);safeCommand = logArgs ? commandParts.join(" ") : commandParts.slice(0, segments).join(" ");
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Sanitizes sensitive commands in telemetry to prevent accidentally capturing secrets or credentials that users may have pasted as arguments.
wrangler loginsanitization into a unified approach with other sensitive commandswrangler secret put,wrangler secret bulk, and theirpagesandversionsequivalents