Stupid config trick: Passwords as URLs

How can we unambiguously represent a password safely for shell, YAML, JSON, etc, and also know if it needs to be unescaped?

I was recently helping a teammate debug a network connection error. Or so we thought. It turns out the actual problem was the password.

The password in question? Ad6JdKv\tC!*4W

That’s a literal backslash. Not an escape sequence.

And many tools don’t work well with such characters (or many other possible characters). In our case, the problem stemmed from the fact that \t was in fact interpreted by bash as an escape sequence when handling the password variable as an environment variable in the Docker environment. So the string we were sending to the remote server for our password was actually Ad6JdKv C!*4W.

Ugh.

How can we solve this?

Well, one obvious solution might be to change the password not to contain unexpected characters. That will often work. But it feels a bit like a cop-out.

What if we Base64-encode the password? Now our configuration value looks like QWQ2SmRLdlx0Q25leHQ0Vwo=. No funny business! Except now we have to remember when to Base64-decode a value, and when not to. Maybe we can solve this with the variable name? B64_PASSWORD? Pseudo-Hungarian notation also feels a bit hacky.

What if we had some way to unambiguously make a password safe for shells, YAML, JSON, etc… and at the same time know that it needs to be unescaped?

Turns out we already had such a mechanism in place. And even if you’re not already using this mechanism, it can be easily adapted. What is this magical mechanism? The URL

You see, our full configuration for this service in question looked something like this:

SERVICE_URL=https://example.com/
SERVICE_USER=admin
SERVICE_PASSWORD=Ad6JdKv\tC!*4W

So we just combined the three variables into a single one:

SERVICE_DSN=https://admin:Ad6JdKv%5CtC%21%2A4W@example.com/

Now there’s never any question what the literal password is… or whether it ought to be un-escaped. And what’s better, most languages will have a simple standard library call to validate and parse the input, and return the component parts (if you indeed need that).

Share this