https://github.com/thoughtbot/terrapin
Run shell commands safely, even with user-supplied values
https://github.com/thoughtbot/terrapin
Keywords from Contributors
rubygems mvc activerecord activejob rspec thoughtbot fixtures factory-girl factory-bot factories
Last synced: about 22 hours ago
JSON representation
Repository metadata
Run shell commands safely, even with user-supplied values
- Host: GitHub
- URL: https://github.com/thoughtbot/terrapin
- Owner: thoughtbot
- License: other
- Created: 2018-01-12T15:50:55.000Z (about 8 years ago)
- Default Branch: main
- Last Pushed: 2025-07-09T20:24:03.000Z (8 months ago)
- Last Synced: 2026-02-04T18:55:50.350Z (27 days ago)
- Language: Ruby
- Size: 208 KB
- Stars: 281
- Watchers: 6
- Forks: 23
- Open Issues: 5
- Releases: 3
-
Metadata Files:
- Readme: README.md
- Changelog: NEWS.md
- License: LICENSE
- Codeowners: CODEOWNERS
- Security: SECURITY.md
README.md
Terrapin
Run shell commands safely, even with user-supplied values
Usage
The basic, normal stuff:
line = Terrapin::CommandLine.new("echo", "hello 'world'")
line.command # => "echo hello 'world'"
line.run # => "hello world\n"
Interpolated arguments:
line = Terrapin::CommandLine.new("convert", ":in -scale :resolution :out")
line.command(in: "omg.jpg",
resolution: "32x32",
out: "omg_thumb.jpg")
# => "convert 'omg.jpg' -scale '32x32' 'omg_thumb.jpg'"
It prevents attempts at being bad:
line = Terrapin::CommandLine.new("cat", ":file")
line.command(file: "haha`rm -rf /`.txt") # => "cat 'haha`rm -rf /`.txt'"
line = Terrapin::CommandLine.new("cat", ":file")
line.command(file: "ohyeah?'`rm -rf /`.ha!") # => "cat 'ohyeah?'\\''`rm -rf /`.ha!'"
NOTE: It only does that for arguments interpolated via run, NOT arguments
passed into new (see 'Security' below):
line = Terrapin::CommandLine.new("echo", "haha`whoami`")
line.command # => "echo haha`whoami`"
line.run # => "hahawebserver\n"
This is the right way:
line = Terrapin::CommandLine.new("echo", "haha:whoami")
line.command(whoami: "`whoami`") # => "echo haha'`whoami`'"
line.run(whoami: "`whoami`") # => "haha`whoami`\n"
You can ignore the result:
line = Terrapin::CommandLine.new("noisy", "--extra-verbose", swallow_stderr: true)
line.command # => "noisy --extra-verbose 2>/dev/null"
# ... and on Windows...
line.command # => "noisy --extra-verbose 2>NUL"
If your command errors, you get an exception:
line = Terrapin::CommandLine.new("git", "commit")
begin
line.run
rescue Terrapin::ExitStatusError => e
e.message # => "Command 'git commit' returned 1. Expected 0"
end
If your command might return something non-zero, and you expect that, it's cool:
line = Terrapin::CommandLine.new("/usr/bin/false", "", expected_outcodes: [0, 1])
begin
line.run
rescue Terrapin::ExitStatusError => e
# => You never get here!
end
You don't have the command? You get an exception:
line = Terrapin::CommandLine.new("lolwut")
begin
line.run
rescue Terrapin::CommandNotFoundError => e
e # => the command isn't in the $PATH for this process.
end
But don't fear, you can specify where to look for the command:
Terrapin::CommandLine.path = "/opt/bin"
line = Terrapin::CommandLine.new("lolwut")
line.command # => "lolwut", but it looks in /opt/bin for it.
You can even give it a bunch of places to look:
FileUtils.rm("/opt/bin/lolwut")
File.open('/usr/local/bin/lolwut') { |f| f.write('echo Hello') }
Terrapin::CommandLine.path = ["/opt/bin", "/usr/local/bin"]
line = Terrapin::CommandLine.new("lolwut")
line.run # => prints 'Hello', because it searches the path
Or just put it in the command:
line = Terrapin::CommandLine.new("/opt/bin/lolwut")
line.command # => "/opt/bin/lolwut"
You can see what's getting run. The 'Command' part it logs is in green for
visibility! (where applicable)
line = Terrapin::CommandLine.new("echo", ":var", logger: Logger.new(STDOUT))
line.run(var: "LOL!") # => Logs this with #info -> Command :: echo 'LOL!'
Or log every command:
Terrapin::CommandLine.logger = Logger.new(STDOUT)
Terrapin::CommandLine.new("date").run # => Logs this -> Command :: date
Security
Short version: Only pass user-generated data into the run method and NOT
new.
As shown in examples above, Terrapin will only shell-escape what is passed in as
interpolations to the run method. It WILL NOT escape what is passed in to the
second argument of new. Terrapin assumes that you will not be manually
passing user-generated data to that argument and will be using it as a template
for your command line's structure.
Runners
Terrapin will choose from among a couple different ways of running commands.
The simplest is Process.spawn, which is also the default. Terrapin can also just use backticks, so if for some reason you'd prefer that, you can ask Terrapin to use that:
Terrapin::CommandLine.runner = Terrapin::CommandLine::BackticksRunner.new
And if you really want to, you can define your own Runner, though I can't imagine why you would.
Terrapin::CommandLine.runner = Terrapin::CommandLine::BackticksRunner.new
And if you really want to, you can define your own Runner, though I can't
imagine why you would.
JRuby issues
Caveat
If you get Error::ECHILD errors and are using JRuby, there is a very good
chance that the error is actually in JRuby. This was brought to our attention
in https://github.com/thoughtbot/terrapin/issues/24 and probably fixed in
http://jira.codehaus.org/browse/JRUBY-6162. You will want to use the
BackticksRunner if you are unable to update JRuby.
Spawn warning
If you get unsupported spawn option: out warning (like in issue
38), try to use
PopenRunner:
Terrapin::CommandLine.runner = Terrapin::CommandLine::PopenRunner.new
Thread Safety
Terrapin should be thread safe. As discussed here, in this climate_control
thread, climate_control,
which modifies the environment under which commands are run for the
BackticksRunner and PopenRunner, is thread-safe but not reentrant. Please let us
know if you find this is ever not the case.
Feedback
Security concerns must be privately emailed to
security@thoughtbot.com.
Question? Idea? Problem? Bug? Comment? Concern? Like using question marks?
Credits
Thank you to all the
contributors!
License
Copyright © 2011 Jon Yurek and thoughtbot, inc. This is free software, and
may be redistributed under the terms specified in the
LICENSE
file.
About thoughtbot
This repo is maintained and funded by thoughtbot, inc.
The names and logos for thoughtbot are trademarks of thoughtbot, inc.
We love open source software!
See our other projects.
We are available for hire.
Owner metadata
- Name: thoughtbot, inc.
- Login: thoughtbot
- Email: hello@thoughtbot.com
- Kind: organization
- Description: We work with organizations of all sizes to design, develop, and grow their web and mobile products.
- Website: https://thoughtbot.com
- Location:
- Twitter:
- Company:
- Icon url: https://avatars.githubusercontent.com/u/6183?v=4
- Repositories: 434
- Last ynced at: 2024-04-14T06:41:37.100Z
- Profile URL: https://github.com/thoughtbot
GitHub Events
Total
- Release event: 1
- Delete event: 15
- Pull request event: 17
- Fork event: 7
- Issues event: 3
- Watch event: 29
- Issue comment event: 11
- Push event: 15
- Pull request review event: 4
- Pull request review comment event: 3
- Create event: 11
Last Year
- Release event: 1
- Delete event: 15
- Pull request event: 15
- Fork event: 5
- Issues event: 3
- Watch event: 25
- Issue comment event: 6
- Push event: 14
- Pull request review event: 3
- Pull request review comment event: 2
- Create event: 10
Committers metadata
Last synced: 3 days ago
Total Commits: 202
Total Committers: 47
Avg Commits per committer: 4.298
Development Distribution Score (DDS): 0.559
Commits in past year: 12
Committers in past year: 4
Avg Commits per committer in past year: 3.0
Development Distribution Score (DDS) in past year: 0.333
| Name | Commits | |
|---|---|---|
| Jon Yurek | j****k@t****m | 89 |
| Mike Burns | m****e@m****m | 17 |
| Gabe Berke-Williams | g****e@t****m | 10 |
| Nick Charlton | n****k@n****t | 8 |
| Adarsh Pandit | a****h@t****m | 6 |
| github-actions[bot] | g****] | 6 |
| Elisa Verna | e****a@g****m | 5 |
| Gabe Berke-Williams | g****w@g****m | 4 |
| Jonas S | j****s@p****e | 4 |
| sapslaj | s****j@g****m | 4 |
| Josh Clayton | j****n@g****m | 3 |
| Ken Dreyer | k****r@k****m | 3 |
| Prem Sichanugrist | s@s****u | 2 |
| Jeremy Neander | s****o@g****m | 2 |
| James Dean Shepherd | j****7@g****m | 2 |
| Guillermo Pascual | a****e@g****m | 2 |
| Aleksei Gusev | a****v@g****m | 2 |
| Brian Durand | b****n@e****m | 2 |
| Sara Jackson | 1****6 | 2 |
| Ivo van Hurne | i****e@b****l | 2 |
| jon Yurek and Joel Quenneville | j****q@t****m | 1 |
| Stefanni Brasil | s****l@g****m | 1 |
| Cédric FABIANSKI | c****i@l****m | 1 |
| Alban Peignier | a****n@t****u | 1 |
| sshaw | s****w@g****m | 1 |
| cthulhu666 | j****i@g****m | 1 |
| Tanner Smith | t****r@t****t | 1 |
| Stefanni Brasil | s****l@p****e | 1 |
| Spencer Steffen | s****r@c****m | 1 |
| Radosław Bułat | r****t@g****m | 1 |
| and 17 more... | ||
Committer domains:
- thoughtbot.com: 4
- mike-burns.com: 1
- nickcharlton.net: 1
- gabebw.com: 1
- posteo.de: 1
- ktdreyer.com: 1
- sikac.hu: 1
- embellishedvisions.com: 1
- bluetools.nl: 1
- leadformance.com: 1
- tryphon.eu: 1
- tssoftware.net: 1
- proton.me: 1
- citrusme.com: 1
- neil.pro: 1
- nixpulvis.com: 1
- jankowski.online: 1
- me.com: 1
- jjb.cc: 1
- lostapathy.com: 1
- viseztrance.com: 1
- amoe.ba: 1
Issue and Pull Request metadata
Last synced: 11 days ago
Total issues: 13
Total pull requests: 56
Average time to close issues: 12 months
Average time to close pull requests: 4 months
Total issue authors: 13
Total pull request authors: 21
Average comments per issue: 1.23
Average comments per pull request: 0.95
Merged pull request: 41
Bot issues: 0
Bot pull requests: 13
Past year issues: 1
Past year pull requests: 20
Past year average time to close issues: N/A
Past year average time to close pull requests: 14 days
Past year issue authors: 1
Past year pull request authors: 5
Past year average comments per issue: 0.0
Past year average comments per pull request: 0.5
Past year merged pull request: 14
Past year bot issues: 0
Past year bot pull requests: 2
Top Issue Authors
- jayjlawrence (1)
- briankung (1)
- thomasdarde (1)
- masudhossain (1)
- RevathiNarjala (1)
- cdesch (1)
- lorennorman (1)
- jjb (1)
- hector (1)
- gustavobap (1)
- robstove (1)
- edward-ellis (1)
- RebelLion420 (1)
Top Pull Request Authors
- nickcharlton (13)
- github-actions[bot] (13)
- stefannibrasil (4)
- biow0lf (3)
- rocket-turtle (3)
- neilvcarvalho (2)
- leifg (2)
- franmomu (2)
- jamesds (2)
- jyurek (1)
- epistrephein (1)
- mjankowski (1)
- brimatteng (1)
- mike-burns (1)
- lostapathy (1)
Top Issue Labels
Top Pull Request Labels
Package metadata
- Total packages: 6
-
Total downloads:
- rubygems: 139,894,665 total
- Total docker downloads: 34,571,380
- Total dependent packages: 30 (may contain duplicates)
- Total dependent repositories: 4,741 (may contain duplicates)
- Total versions: 38
- Total maintainers: 3
gem.coop: terrapin
Run shell commands safely, even with user-supplied values
- Homepage: https://github.com/thoughtbot/terrapin
- Documentation: http://www.rubydoc.info/gems/terrapin/
- Licenses: MIT
- Latest release: 1.1.1 (published 8 months ago)
- Last Synced: 2026-03-02T03:02:30.647Z (1 day ago)
- Versions: 6
- Dependent Packages: 0
- Dependent Repositories: 0
- Downloads: 69,955,664 Total
- Docker Downloads: 17,285,690
-
Rankings:
- Dependent repos count: 0.0%
- Dependent packages count: 0.0%
- Average: 0.358%
- Downloads: 0.413%
- Docker downloads count: 1.021%
- Maintainers (3)
rubygems.org: terrapin
Run shell commands safely, even with user-supplied values
- Homepage: https://github.com/thoughtbot/terrapin
- Documentation: http://www.rubydoc.info/gems/terrapin/
- Licenses: MIT
- Latest release: 1.1.1 (published 8 months ago)
- Last Synced: 2026-02-28T20:00:48.982Z (3 days ago)
- Versions: 6
- Dependent Packages: 30
- Dependent Repositories: 4,741
- Downloads: 69,939,001 Total
- Docker Downloads: 17,285,690
-
Rankings:
- Downloads: 0.398%
- Dependent repos count: 0.458%
- Dependent packages count: 0.803%
- Docker downloads count: 1.111%
- Average: 2.093%
- Stargazers count: 3.724%
- Forks count: 6.061%
- Maintainers (3)
proxy.golang.org: github.com/thoughtbot/terrapin
- Homepage:
- Documentation: https://pkg.go.dev/github.com/thoughtbot/terrapin#section-documentation
- Licenses: other
- Latest release: v1.1.1 (published 8 months ago)
- Last Synced: 2026-02-28T20:00:50.153Z (3 days ago)
- Versions: 23
- Dependent Packages: 0
- Dependent Repositories: 0
-
Rankings:
- Stargazers count: 3.508%
- Forks count: 4.996%
- Average: 7.212%
- Dependent packages count: 9.564%
- Dependent repos count: 10.779%
ubuntu-20.04: ruby-terrapin
- Homepage: https://github.com/thoughtbot/terrapin
- Licenses:
- Latest release: 0.6.0-2 (published 18 days ago)
- Last Synced: 2026-02-13T07:24:00.181Z (18 days ago)
- Versions: 1
- Dependent Packages: 0
- Dependent Repositories: 0
-
Rankings:
- Dependent repos count: 0.0%
- Dependent packages count: 0.0%
- Average: 100%
debian-11: ruby-terrapin
- Homepage: https://github.com/thoughtbot/terrapin
- Documentation: https://packages.debian.org/bullseye/ruby-terrapin
- Licenses:
- Latest release: 0.6.0-2 (published 21 days ago)
- Last Synced: 2026-02-13T08:25:36.846Z (18 days ago)
- Versions: 1
- Dependent Packages: 0
- Dependent Repositories: 0
-
Rankings:
- Dependent repos count: 0.0%
- Dependent packages count: 0.0%
- Average: 100%
ubuntu-22.04: ruby-terrapin
- Homepage: https://github.com/thoughtbot/terrapin
- Licenses:
- Latest release: 0.6.0-4 (published 18 days ago)
- Last Synced: 2026-02-13T13:27:21.286Z (18 days ago)
- Versions: 1
- Dependent Packages: 0
- Dependent Repositories: 0
-
Rankings:
- Dependent repos count: 0.0%
- Dependent packages count: 0.0%
- Average: 100%
Dependencies
- posix-spawn >= 0
- activesupport >= 3.0.0, < 5.0 development
- bourne >= 0 development
- mocha >= 0 development
- pry >= 0 development
- rake >= 0 development
- rspec >= 0 development
- climate_control >= 0.0.3, < 1.0
- actions/checkout v2 composite
- ruby/setup-ruby v1 composite
Score: 28.483408056416565