Skip to content

security: escape shell metacharacters in vault secrets used in shell model run fields #411

@stack72

Description

@stack72

Summary

Vault secret values are resolved before CEL evaluation in model_resolver.ts:resolveVaultExpressions(). The current escaping chain prevents breaking out of a CEL string literal (backslash, double quote, single quote, backtick, whitespace), but does not escape shell metacharacters.

When a vault secret is injected via CEL into the run field of a command/shell model, characters such as $, &, |, ;, (, ) pass through unescaped into the shell command.

Attack Path

A user writes a model definition like:

run: "echo " + vault.get('my-vault', 'SECRET') + " done"

If SECRET contains $(cat /etc/passwd), the vault resolution step produces the CEL expression:

"echo " + "$(cat /etc/passwd)" + " done"

CEL evaluates this to the string echo $(cat /etc/passwd) done. This string is then executed as:

sh -c 'echo $(cat /etc/passwd) done'

The $(...) command substitution executes cat /etc/passwd. Similar issues exist with &, |, ;.

Relevant Code

src/domain/expressions/model_resolver.ts lines 775–782:

const escapedValue = secretValue
  .replace(/\\/g, "\\\\")
  .replace(/"/g, '\\"')
  .replace(/'/g, "\\'")
  .replace(/`/g, "\\`")
  .replace(/\n/g, "\\n")
  .replace(/\r/g, "\\r")
  .replace(/\t/g, "\\t");

$ is not escaped.

Mitigating Factors

  • This requires the user to store a value in their own vault and write a CEL expression that injects it unquoted into a shell command. It is within the user's own trust domain.
  • The vault is local and under the user's control.
  • No remote attacker can influence vault contents without already having local write access.

However, this is a real foot-gun: users who write parameterised shell commands driven by vault secrets have no indication that special shell characters in secret values will be interpreted by the shell.

Recommended Fix

Add $ to the escaping chain in resolveVaultExpressions():

.replace(/\$/g, "\\$")

Add a test case: store a secret value containing $(echo injected) and verify it renders literally in shell output rather than being executed.

Also add documentation that vault values injected into shell run fields via CEL string concatenation are escaped for shell safety, so users understand the contract.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    betaIssues required to close out before public betabugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions