Rails application templates are one of the most powerful and most under-appreciated parts of the Rails toolchain. A single rails new myapp -m https://example.com/template.rb command can scaffold a complete project with custom gems, configuration files, initializers, database setup, even git commits. The power comes from the fact that the template file is arbitrary Ruby code that runs with the full permissions of the invoking user. The danger comes from exactly the same thing. This post walks through how templates actually work, the security properties you should care about, and a set of practices for using templates without accidentally giving a third party shell access to every developer machine in your organization.
Rails templates have been part of the framework since Rails 2.3 in 2009, and the template DSL has been stable since Rails 4. The security story around them has been largely informal, communicated through blog posts and tribal knowledge. As templates have become more common in enterprise Rails shops for standardizing new-project scaffolding, the need for a clearer set of practices has become more pronounced.
How does a Rails template execute?
When you run rails new myapp -m <template>, the Rails generator loads the template file and executes it using the Thor-based template DSL. The template has access to helpers like gem, generate, rake, environment, initializer, file, route, and a number of others. It also has access to the full Ruby standard library and can call system, exec, Kernel#%x, Net::HTTP, or anything else you can invoke from Ruby.
The -m argument accepts a local file path, an HTTP(S) URL, or a git repository URL. When you pass a URL, the Rails generator downloads the file and executes it. There is no sandboxing, no signature verification, no certificate pinning beyond whatever Ruby's OpenSSL-bound Net::HTTP does by default. If you trust the domain, you trust the author of whatever file lives there at that moment. If the domain is compromised, or the DNS is hijacked, or the author pushes a malicious update, your rails new command runs that malicious code.
The execution context is the directory where rails new was invoked, which is typically a parent of the new project. But the template can easily chdir elsewhere, and it runs with the full permissions of the invoking user. A template that decides to read ~/.ssh/id_rsa or ~/.aws/credentials can do so without any constraint from the Rails generator.
What are the realistic attack paths?
The most realistic attack path is typosquat or URL confusion. A developer copies a rails new -m command from a blog post, Stack Overflow answer, or Slack message, and the URL is subtly wrong. The domain is a look-alike, or the path has been swapped for a malicious version. The developer does not notice, the template runs, and the machine is compromised.
The second realistic path is a supply-chain attack on a legitimate template host. A popular template repository on GitHub or a corporate template server gets compromised. Developers pull the latest template and run it. The compromise can be subtle, for example the template adds a gem that looks legitimate but is actually a typosquat, or it configures an outbound connection to an attacker-controlled endpoint. Because templates are rarely reviewed line-by-line, these additions can persist for weeks.
The third path is internal supply chain within an organization. A shared template repository, maintained by a platform team, is modified by an insider, malicious or simply careless, in a way that affects every new project created in the organization. Without controls on who can modify templates and what review process they go through, the blast radius is large.
What should a secure template look like?
The practices that reduce risk are straightforward but require discipline.
First, templates should be version-pinned and cryptographically verified. Hosting the template in a git repository and pinning to a specific commit SHA is the minimum. A typical invocation looks like rails new myapp -m https://github.com/myorg/rails-template/raw/<sha>/template.rb, where the SHA is a specific commit. This gives you a stable artifact that cannot change out from under you, and git's content-addressable storage gives you content-integrity verification.
Second, templates should be reviewed through the same pull-request process as any other code. No merges without review. No direct pushes to the main branch. Branch protection rules enforcing this should apply even to maintainers. The review should focus on new gems being added, network calls being made, and any system or shell invocations the template performs.
Third, templates should avoid dynamic gem sources. A template that adds a source "https://custom-gems.example.com" to a new Gemfile is introducing a new supply-chain dependency on that custom source. In most cases you want every project to default to https://rubygems.org and handle any custom sources as a deliberate, separate decision for specific projects.
Fourth, templates should prefer declarative helpers over procedural code. The template DSL's gem, initializer, environment, and route helpers are much easier to review than arbitrary file calls that write shell-generated content. When you see a template that uses run "curl https://... | sh" or similar, that is a signal to rewrite it using more auditable primitives.
What about private templates?
Many organizations maintain internal templates that encode their standard gem list, authentication setup, observability configuration, and so on. These are hugely valuable for consistency and usually live in an internal git repository. The security considerations are largely the same as for public templates, with a few additions.
Authentication to the template source matters. If your template lives on a private GitHub repository, developers fetching it need to authenticate, which means the rails new -m command needs credentials. The path of least resistance is often to bake a personal access token into the URL, which is bad because it ends up in shell history and process lists. Better options include using GitHub's SSH-based clone support (rails new -m ssh://git@github.com:myorg/rails-template.git), or wrapping the template fetch in a script that uses a machine user's short-lived credentials.
Internal templates should also be versioned. Developers should be able to create projects from "template version 4.2.1" rather than "template as it exists right now on the main branch." This gives you a stable reference for debugging when a new project's setup looks different from an older one, and it gives the platform team a controlled release process for template updates.
The generate command is also a template
A related and often forgotten surface is the rails generate command. Custom generators are Ruby code in lib/generators or in gems that are installed in the project. When a developer runs rails generate <custom_generator>, that code executes with the same permissions as the template DSL. Generators pulled in from third-party gems are effectively templates that run every time the generator is invoked, which can be much more frequently than the one-time rails new.
A gem like devise includes generators that are largely safe, but a less-vetted gem that provides generators can do anything it wants when those generators run. Treat third-party generators with the same scrutiny as templates, and audit the lib/generators directory of any gem whose generators you plan to invoke.
How should templates evolve?
The Rails community has discussed adding signature verification to templates a handful of times over the years. The most recent RFC, from 2023, proposed a Sigstore-based verification flow where a template could ship with a signature that rails new -m would verify against a configured trust root. The RFC did not advance, largely because the ecosystem value is limited when most templates come from a small number of sources that are already trusted via TLS and organization conventions.
The more likely evolution is better tooling around template auditing. A community-maintained linter that flags risky patterns in templates, unscoped system calls, curl | sh patterns, unpinned gem sources, dynamic code evaluation, would provide most of the benefit of signature verification without the ecosystem coordination problem. Several internal versions of this exist at large Rails shops. A public version would be a useful contribution.
How Safeguard Helps
Safeguard scans Rails templates in your organization's repositories for risky patterns including unpinned gem sources, shell-escape invocations, and network calls to non-approved hosts. We track which projects were created from which template versions, so when a template vulnerability surfaces, you can identify the downstream projects that inherited the issue. Our integration with SCM providers enforces review and branch-protection policies on template repositories the same way we do for application code, closing the most common path to a compromised internal template. Rails templates are too powerful to leave outside your security program, and we bring them into it.