Skip to content

Secrets

API keys, bearer tokens and other credentials should never be typed directly into a node's configuration. A workflow is a Drupal config entity, so anything you type into it is written out by drush config:export and committed to git — and it is copied again into every job record the workflow produces, where anyone who can view pipeline runs can read it.

Instead, store the credential in the Key module and put a reference to it in the node config:

${{ secrets.NAME }}

The reference is what gets saved. The real value is substituted in memory while the node runs, and never reaches storage.

Setup

1. Install the Key module. It is an optional dependency:

composer require drupal/key
drush en key

Without it, references are left as written and nothing is resolved.

2. Add the secret at FlowDrop → Configuration → Secrets (/admin/flowdrop/config/secrets). Set the value as an environment variable in your hosting environment first, then register it here by name — FlowDrop stores only the variable name, never the value, so nothing sensitive reaches a configuration or database export. Adding it here also allows it in one step.

Nothing resolves until a secret is allowed

The allowlist starts empty, and a reference to a secret that is not on it fails exactly as a reference to one that does not exist. This is deliberate — see Why the allowlist — but it does mean a reference to a key created elsewhere will fail until an administrator ticks it on this screen.

Using a key created elsewhere

Keys defined directly in the Key module (Configuration → System → Keys) also appear on the FlowDrop screen; tick one to allow it. Key can read a value from an environment variable, a file outside the docroot, configuration, or state.

Prefer environment variables or files

The config and state providers store the value inside Drupal — the config provider writes it into a config entity, so drush config:export carries it into git, which is exactly the exposure secret references exist to remove. FlowDrop flags these on the settings screen. Use them only if you understand where the value ends up.

Using a reference

A reference works anywhere a string appears in node config, including inside a JSON field. The most common case is an HTTP Request node's headers:

{
  "Authorization": "Bearer ${{ secrets.stripe_api_key }}",
  "Content-Type": "application/json"
}

It does not have to be the whole value — it can sit inside a longer string, and a single field can contain more than one reference.

Whitespace inside the braces is optional: ${{secrets.name}} and ${{ secrets.name }} are the same.

What happens when something is wrong

Situation Result
Key module not installed Reference left as written; the node runs
Secret not allowed Node fails
Secret does not exist Node fails (identical message)
Secret exists but holds no value Node fails

The settings screen may say a variable is not set

Adding an environment secret warns if the variable is not visible to the web process — but it saves anyway. Cron and queue workers run under a different environment, so a variable set only for the CLI is a perfectly normal setup that resolves at execution time. Treat the warning as a spelling check, not a failure.

A missing secret fails the node rather than quietly becoming an empty string. Substituting empty would send Authorization: Bearer to the remote service, and you would be debugging somebody else's 401 instead of reading a local error naming the key.

The not-allowlisted and does-not-exist cases produce the same message on purpose, so the error cannot be used to work out which keys a site holds.

Where a secret can and cannot go

A reference is only resolved when it comes from configuration you control as the workflow author — the value you typed into the node, or a default set on the node type.

A reference arriving in a runtime value is never resolved. It stays literal text. That covers anything flowing in from an upstream node's output, a webhook payload, or a tool parameter an AI model chose. Without this, a prompt-injected model could simply ask for ${{ secrets.stripe_api_key }} and be handed it.

This has one practical consequence worth planning around:

Put secrets on inputs that are not wired

If a parameter's input port is connectable and exposed, a value arriving on a wire replaces your configured value. Your reference is not leaked — the incoming value is passed through unsubstituted — but it is not used either. Keep secrets on parameters whose ports are hidden, or that are not connectable at all.

Why the allowlist

A reference lets a workflow author use a credential without being able to read it. That is the point — but it also means that without a restriction, "can edit a workflow" would quietly amount to "can spend any credential this site holds". The allowlist keeps that an explicit administrator decision, one key at a time.

Leak protection

Two further precautions run automatically:

  • Resolved values are excluded from FlowDrop's per-parameter debug logging.
  • Resolved values are scrubbed out of job input, output and error messages before they are saved, so a node that echoes its own configuration — or an HTTP client that quotes the failing request in an exception — does not put the credential back into the database.

Common mistake: the missing $

{{ secrets.my_key }}     ← wrong: no $
${{ secrets.my_key }}    ← correct

Without the $, this is not a secret reference at all — it is ordinary Prompt Template variable syntax, which resolves to nothing and raises no error anywhere. FlowDrop warns about this when you save a workflow; that warning is the only signal you will get, so do not ignore it.