Declare each TFLint plugin with a source and an exact version, leave signature verification at its default, and never set it to none in shared CI. TFLint prefers build attestations, falls back to PGP, and does not accept version ranges for plugins, which makes exact pinning straightforward.
Your linter has dependencies too
A linter feels like a safe tool because it only reads code. But TFLint extends itself with plugins, and a plugin is an executable that runs on the machine doing the linting. In CI that machine often holds cloud credentials for the plan step that follows. A compromised plugin is a foothold in the same job.
How plugins are declared
In the TFLint configuration file, each plugin has a name, an enabled flag, a source and a version. The source must look like github.com/org/repo. Once source and version are set, the init command installs the plugin automatically.
According to the plugin documentation, the version attribute cannot be omitted when a source is set, must not carry a v prefix, and does not support constraints such as greater than or equal. You get one exact version per declaration. That is stricter than many package managers, and it helps you here.
Signature verification
The signature attribute controls how TFLint verifies plugin releases.
| Value | Meaning |
|---|---|
| auto | Prefer attestation when available, then fall back to PGP. This is the default |
| attestation | Require artifact attestations |
| pgp | Require a PGP signature using a signing key you supply |
| none | Skip verification |
Attestation verification is not supported for private repositories, per the docs. If you host plugins privately, use pgp with a signing key you control, rather than none.
A review rule worth adding: fail the pipeline if any configuration file contains signature set to none.
Where plugins land and CI behavior
Plugins are usually installed under a hidden plugins folder in the home directory, in a path that includes source and version, so two versions can coexist. If a folder with the same name already exists in the working directory, TFLint installs there instead. You can change the location with the plugin_dir setting or an environment variable.
Installing calls the GitHub API to find release metadata. Unauthenticated calls are limited to 60 requests per hour per IP address, which in shared CI can be exhausted by other tenants. The docs suggest passing a GitHub token through the environment and caching the plugin directory, since TFLint only sends requests when plugins are not already installed.
If you cache, key the cache on your configuration file, so a version bump invalidates it. Otherwise an old plugin may linger after you change the pin.
Keeping pins current
The documentation recommends automatic updates and notes that Renovate supports TFLint plugins. Automatic pull requests keep exact pins from going stale, while still giving a human a diff to review. Read the release notes on any plugin that tightens or loosens rules, since a rule change can alter what your gate blocks.
A short checklist
- Every plugin has a source and an exact version.
- No plugin uses signature none.
- The plugin directory is cached, with a key tied to the config.
- A GitHub token is supplied for init in CI.
- Plugin updates arrive as reviewed pull requests.
- Lint runs before any step that has write credentials.
Where a scanner fits
Linting is one control. Safeguard's policy gates and pull request checks can sit beside it, and its SBOM generation covers the dependencies your applications ship. Confirm which infrastructure files your scanner reads. Free scan: gold.safeguard.sh/scan.
Quick check
Can I use a version range for a TFLint plugin?
No. The documentation says version constraints are not supported, and the version cannot be omitted when a source is set.
Is attestation checking available for private plugin repositories?
No. The documentation says attestation verification in private repositories is not supported. Use PGP verification instead.
Sources
Last reviewed: 19 October 2026.