Security of netlify.toml contents

We need a way to remove or encrypt secrets present in netlify.toml.

Currently, if you only build on a master branch (or you build on non-master branches too but your secrets don’t differ by branch), you can set up sensitive env vars in the Netlify UI and non-sensitive env vars in netlify.toml and everything works fine.

However if you use branch builds and some of your secrets differ by branch, there is no way to provide env vars other than in netlify.toml (in a [context.ENVNAME.environment] section). The Netlify UI does not have a way to define env vars per context / branch.

Checking secrets (passwords, tokens etc.) into source code control is generally bad. I understand that everything else about a build comes through a git clone, so it’s convenient to have that info right there in the toml file. But the security implications of that are too negative.

I see three possible solutions:

(1) separate env vars for each build context in the Netlify UI
– overall env vars that apply unless overridden by the context
– env vars entered separately for each selected branch / context
– continue to have netlify.toml values override UI values for the same env var & context – user responsible to not overlap keys

(2) continue using netlify.toml but with a way to encrypt env vars
– e.g. TOML sections named like [secure.environment] and [secure.ENVNAME.environment] that parallel the existing [context.environment] and [context.ENVNAME.environment]
– for each site (or better: each context) Netlify would generate a encryption key and a decryption key, and provide the encryption key to the user – the user would push each secret through an encryption / base64 encoding tool and embed the base64 value in the netlify.toml file, e.g. something like:

[secure.ENVNAME.environment]
password = ‘BASE64-ENCODED-VALUE’

– the Netlify build process would decode and unencrypt that value, after which is could be used like any other env var in the build cycle

(3) Provide an alternate channel for a separate netlify-secrets.toml file. Before pushing a commit that would still include netlify.toml but without secrets in it, the user would push netlify-secrets.toml to a separate git repo, or a SFTP server, or an S3 bucket, or upload it using the Netlify API. Many options here – I think you could use any mechanism you want as long as (1) the secrets file is not in the main repo, and (2) the secrets file can be provided per context.

1 Like

Hi @chris-activewrite! I hear you about the difficulty of storing context-specific env vars securely. Your three possible solutions all make sense to me, and there are a couple of build plugins that might help with the first two.

For #1, you might want to try https://github.com/cball/netlify-plugin-contextual-env. I haven’t tried it yet, but it was recently submitted to the Netlify plugins repository. It automatically prefixes (or suffixes) your env vars with the current deploy context, so you can add those different-context variables in the UI without having to jump through extra hoops in your code to account for them.

For #2, you could use https://github.com/sw-yx/netlify-plugin-encrypted-files to encrypt the files (and combine that with the contextual-env plugin for context-specific encryption keys :exploding_head:). Of course, we don’t have that [secure.environment] section you suggest, so you’d have to get the env vars into the toml file by other means.

In the end, #1 sounds easier. :slight_smile: If you end up trying one of these, I’d love to hear how it goes!

3 Likes

thanks for sharing those resources, @verythorough!

I’ll try those. Thanks for the great suggestions.

2 Likes

I quite like the implementation of netlify-plugin-contextual-env but am not sure about the potential security risks associated with netlify plugins:

  • Does netlify perform a security review prior to adding the plugin to their directory?
  • If so, does this review happen once or on every update? The risk here is that (much like browser extensions), once secure plugins get bought by malicious parties who suddenly have access to your secrets after they push out and update.

In lieu of understanding this, I went ahead and replicated what the plugin does is my application. That is, the application derives the ENV_VAR to use by appending the CONTEXT_ (with uppercase and s/-/_/g applied).

Howdy @mwakerman, and sorry to be a bit slow in getting back to you.

There are two answers to your question:

a.) if the plugin is a netlify-created plugin, i.e written and supported by our team, then we assume responsibility for the security of that plugin.
b.) if the plugin is written by a third party, then that person is ultimately responsible for creating a secure plugin! we do some vetting of course - the plugin needs to work like it says it does after all, and we ask that you do a review of your own, and then take it up with the plugin developer if you are concerned, as they are the responsible party.

We are very responsible to feedback - in fact we actively seek it, so if you notice security issues with any build plugins, please let us and the developer know. If something turns out to be not safe, we will absolutely delist it in until it can be made more secure.

You always have the ability to report issues with any plugins via the UI:

Let me know if you have any further questions!

But as far as security goes,

Hi @mwakerman. Could you describe how you were able to do this? I’ve been trying to access $CONTEXT in my netlify functions by through process.env.CONTEXT, but have had no luck. Is there another way to determine the context in a netlify function body?

I ended up using the netlify-plugin-inline-functions-env plugin because Netlify functions do NOT have access to the CONTEXT variable at runtime (only at build time). So I have a small utility function for reading environment variables that has a CONTEXT variable that is overwritten at build time and then looks for $VARIABLE_$CONTEXT.

// envvar.js
// this var is replaced at build time with the Netlify context value by this build plugin:
// https://github.com/bencao/netlify-plugin-inline-functions-env
const CONTEXT = process.env.CONTEXT || process.env.REACT_APP_CONTEXT;

export function getContext() {
  return CONTEXT || '';
}

export function getEnv(variable) {
  const context = getContext();
  const contextVariable = `${variable}_${context.toUpperCase().replace(/-/g, '_')}`;
  return process.env[contextVariable] || process.env[variable];
}


// netlify.toml
...
[[plugins]]
package = "netlify-plugin-inline-functions-env"
  [plugins.inputs]
  buildEvent = "onBuild"

In general, I was disappointed with how much time and manual tweaking/setup had to be done for what I imagine is a very common use case (supporting a production and pre-production environment).

Interesting, @mwakerman. I tried adding those functions to one of my netlify functions, but am confused about the process.env.REACT_APP_CONTEXT variable. Where is that variable’s value being set? I tried in a local .env file. Here’s my code:

// setting VUE_APP_CONTEXT=DEV in local .env file
const CONTEXT = process.env.CONTEXT || process.env.VUE_APP_CONTEXT;

function getContext() {
  return CONTEXT || "";
}
function getEnv(variable) {
  const context = getContext();
  const contextVariable = `${variable}_${context
    .toUpperCase()
    .replace(/-/g, "_")}`;
  return process.env[contextVariable] || process.env[variable];
}

exports.handler = async (event, context) => {
  try {
    return {
      statusCode: 200,
      body: JSON.stringify({
        context: getEnv("ACCESS_TOKEN"),
      }),
    };
  } catch (err) {
    return { statusCode: 500, body: err.toString() };
  }
};

Yes sorry for missing that part. I’m using Create React App and updated package.json to call react-scripts start and build:client with that envvar (note that when building with react-scripts, all environment variables without the REACT_APP prefix are ignored, hence this chaining):

"scripts": {
    "start": "REACT_APP_CONTEXT=dev react-scripts start",
    "build": "run-s build:**",
    "build:client": "REACT_APP_CONTEXT=$CONTEXT react-scripts build",
    "build:server": "netlify-lambda build src/server",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },

I wanted a single repository with my client and (admittedly very simple) server and it took a fair bit of fiddling with these build configurations and environment variables to get it working with local development (netlify dev) but now works really well.

Interesting @mwakerman ! What does your netlify.toml look like? I think this approach requires some config for the netlify dev command right?

Nothing specific:

[build]
command = “yarn build”
publish = “build”
functions = “build/api”

route all /api/* -> Netlify functions

[[redirects]]
from = “/api/*”
to = “/.netlify/functions/:splat”
status = 200
force = true

route everything else to index.html for frontend routing

[[redirects]]
from = “/*”
to = “/index.html”
status = 200

[[plugins]]
package = “netlify-plugin-inline-functions-env”
[plugins.inputs]
buildEvent = “onBuild”

Apologies for formatting, on phone and this editor is non-functional.

1 Like

Thanks @mwakerman. Am I correct that placing the functions in build/api means that the function code will be visible over the web? I see this in the netlify docs about setting the functions directory:

Though we process the files in the functions directory after running your build command, the directory is not visible over the web unless you place it in your site’s publish directory (not recommended).

Also, I’m having some trouble when trying to use the netlify-plugin-inline-functions-env. No issues locally, but it seems to be causing a 502 error in production. Maybe it’s not getting installed properly? Do you have a postinstall script to install function dependencies? I thought netlify dev did this automatically.

I agree with @chris-activewrite separate environment variable for each build context in the Netlify UI would be really convenient. Build plugins are an ok solution, but it would be nice if this were as easy as using the netlify.toml file.