hashicorp/nomad · error

secret path cannot contain template delimiters or parenthesi

Error message

secret path cannot contain template delimiters or parenthesis

What it means

The Vault secrets template provider (vault_provider.go) builds a CT snippet from the secret stanza. NewVaultProvider rejects a secret.Path containing ( ) { } because those characters could inject Consul Template functions into the generated template. It returns 'secret path cannot contain template delimiters or parenthesis' as a construction error.

Source

Thrown at client/allocrunner/taskrunner/secrets/vault_provider.go:49

}

type VaultProvider struct {
	secret    *structs.Secret
	secretDir string
	tmplFile  string
	conf      *vaultProviderConfig
}

// NewVaultProvider takes a task secret and decodes the config, overwriting the default config fields
// with any provided fields, returning an error if the secret or secret's config is invalid.
func NewVaultProvider(secret *structs.Secret, secretDir string, tmplFile string) (*VaultProvider, error) {
	conf := defaultVaultConfig()
	if err := mapstructure.Decode(secret.Config, conf); err != nil {
		return nil, err
	}

	if strings.ContainsAny(secret.Path, "(){}") {
		return nil, errors.New("secret path cannot contain template delimiters or parenthesis")
	}

	return &VaultProvider{
		secret:    secret,
		secretDir: secretDir,
		tmplFile:  tmplFile,
		conf:      conf,
	}, nil
}

func (v *VaultProvider) BuildTemplate() *structs.Template {
	indexKey := ".Data"
	if v.conf.Engine == VAULT_KV_V2 {
		indexKey = ".Data.data"
	}

	data := fmt.Sprintf(`
		{{ with secret "%s" }}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Remove ( ) { } from the Vault secret path in the secret stanza
  2. Rename the Vault path/mount to use only alphanumerics, dashes, slashes, underscores
  3. If the character comes from upstream interpolation, fix the upstream template so only the resolved value is passed
  4. Keep the secret version/qualifier in Vault metadata (e.g. kv v2 version param) rather than in the path string

Example fix

// before
secret {
  path = "kv/prod(db)"
}
// after
secret {
  path = "kv/prod-db"
}
Defensive patterns

Strategy: validation

Validate before calling

// Go: pre-validate the Vault secret path before building providers
func validVaultPath(p string) bool {
	return p != "" && !strings.ContainsAny(p, "(){}")
}
// usage
if !validVaultPath(secret.Path) {
	return errors.New("vault secret path must not contain ( ) { }")
}

Type guard

func isDelimiterFree(s string) bool { return !strings.ContainsAny(s, "(){}") }

Try / catch

vp, err := NewVaultProvider(ctx, secret, dir, logger)
if err != nil {
	if strings.Contains(err.Error(), "secret path cannot contain") {
		return nil, fmt.Errorf("vault path invalid for template rendering: %w", err)
	}
	return nil, err
}

Prevention

When it happens

Trigger: buildSecretProviders -> NewVaultProvider with a secret stanza whose Path (from secret.Path) contains any of ( ) { }, e.g. path = "kv/app(v1)". The mapstructure decode of the secret config succeeds first; the delimiter check then fails the provider construction before any secret is fetched.

Common situations: Vault paths copied from tooling or docs that annotate with parentheses, paths assembled via Nomad template interpolation leaving residual braces, or teams using special characters in Vault KV mount/path names.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/9ec50e48e561e2ab. Report an issue: GitHub.