Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cloud Functions: Fix env var escaping #4271

Merged
merged 9 commits into from
Mar 25, 2022

Conversation

yellcorp
Copy link
Contributor

@yellcorp yellcorp commented Mar 9, 2022

Description

Fixes #4270 and a little bit more.

The bug results from the use of String.prototype.replace with a first (search) argument of type string. This is an unintuitive corner of JavaScript that only replaces the first occurrence of the search string. See MDN. To replace all occurrences, either the search argument must be expressed as an equivalent regex with the /g flag, or .replaceAll must be used. .replaceAll isn't supported below Node 15, and given this package's dependency on Node >= 12, the regex approach has been used here.

Quick demonstration:

Welcome to Node.js v16.13.2.
Type ".help" for more information.
> const aaaa = "aaaa";
undefined
> aaaa.replace("a", "b") // string argument; replaces only the first occurrence
'baaa'
> aaaa.replace(/a/, "b") // regex argument *without* /g flag; replaces only the first occurrence
'baaa'
> aaaa.replace(/a/g, "b") // regex argument *with* /g flag; replaces all occurrences
'bbbb'
> aaaa.replaceAll("a", "b") // replaceAll method in Node >= 15; replaces all occurrences
'bbbb'

There's another reason to use a regex approach - running the string through repeated invocations of .replace(All) processes the escapes in an implementation-defined order, whereas the string really should be scanned from start to end in order to correctly convert (for example) \\n to \ n. The popular npm package dotenv gets tripped up by this as well.

The changes outside .env files result from manually searching the repo for uses of String.prototype.replace having a non-regex first argument. There are more occurrences than the ones fixed here that probably should be audited, but 1. Not all instances are immediately obvious to me whether they intend to replace all occurrences of the search argument 2. I wanted to keep this PR focused.

Scenarios Tested

User journeys

  • User wishes to deploy or emulate Cloud Functions using an .env file in which one or more values contain a quoted value having 2 or more escape sequences.

Edge cases

  • Sequences that resemble escaped newlines, but actually aren't. For example \\n should be interpreted as backslash, letter n.

@yellcorp
Copy link
Contributor Author

yellcorp commented Mar 9, 2022

Apologies, looks like the entire commit history has been dragged along with this! Investigating…

Fixed!

These test cases reveal two bugs in functions/env.ts#parse:
1. In a string containing two or more instances of a repeated escape
   sequence (e.g. \n or \t), only the first one will be replaced with
   its corresponding unescaped character. This is because of a JS gotcha
   where String.prototype.replace, when called with a string as the
   search argument, only replaces the first occurrence.
2. Escapes are not processed in order of occurrence in the input string.
   With the current implementation, certain sequences are prioritized,
   even if they are preceded with a backslash that is itself escaped.
@yellcorp yellcorp force-pushed the yellcorp/functions-env-escape-fix branch from b28cfc1 to 272f80b Compare March 9, 2022 01:29
@yellcorp yellcorp changed the title Yellcorp/functions env escape fix Fix env var escaping Mar 9, 2022
@yellcorp
Copy link
Contributor Author

yellcorp commented Mar 9, 2022

Other String.prototype.replace usage of potential concern:

  • src/extensions/export.ts#L18-L19 - Not clear if projectId or projectNumber has the potential to appear more than once. If not, it's ok.
  • defaultCredentials.ts#L105 - This will only replace at most one dot with an underscore, so will fail on emails like firstname.lastname@example.com or name@subdomain.example.com. Coincidentally, the log I attached to the original issue shows this problem on line 16. Note that my default creds file is called jim_boswell_byhook.com_application_default_credentials.json - the . before com has not been replaced with _. Fixing this would possibly need to be supported by some migration process. The extra dot doesn't actually cause an issue in practice so may not be worth pursuing.

@yellcorp yellcorp changed the title Fix env var escaping Cloud Functions: Fix env var escaping Mar 9, 2022
@yellcorp yellcorp force-pushed the yellcorp/functions-env-escape-fix branch from 272f80b to 8d1d58b Compare March 9, 2022 02:43
functions/env.ts#parse now parses multiple occurrences of escaped
control characters (\n, \r, \t, \v) and does so in order of occurrence.
All instances of an escapable character are now escaped. This also adds
backslash to achieve parity with the escape sequences understood by
functions/env.ts#parse.
toUpperSnakeCase in src/functions/secrets.ts only substitutes at most
one of period/full-stop (.) or hyphen (-)
This ensures multiple occurrences of . or - are replaced with _.
@yellcorp yellcorp force-pushed the yellcorp/functions-env-escape-fix branch from 8d1d58b to 0f843d4 Compare March 9, 2022 02:46
@taeold
Copy link
Contributor

taeold commented Mar 9, 2022

@yellcorp This is an incredible contribution - I learned so much from your thorough writeup. Thank you so much for your time in reporting, fixing, and educating us about the issue!

It's late in US, so I'll defer reviewing the code to first thing tomorrow morning!

Copy link
Contributor

@taeold taeold left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️ Incredible work!

.replace("\t", "\\t")
.replace("\v", "\\v");
return result.replace(/(['"])/g, "\\$1");
// Escape newlines, tabs, backslashes and quotes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 on adding backlashes to the list of things we replace!

@taeold taeold enabled auto-merge (squash) March 10, 2022 20:26
@taeold taeold merged commit 529c3cd into firebase:master Mar 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Cloud Functions: Incorrect/incomplete transformation of escape sequences in .env files
2 participants