Class: Rules::MissingFrozenLockfile
- Defined in:
- lib/rules/missing_frozen_lockfile.rb
Constant Summary collapse
- NPM_INSTALL =
JavaScript/TypeScript npm install without –ci (or use npm ci instead)
/\bnpm\s+install\b/- NPM_SAFE =
/--ci\b|\bnpm\s+ci\b/- PNPM_INSTALL =
pnpm install without –frozen-lockfile
/\bpnpm\s+install\b/- PNPM_SAFE =
/--frozen-lockfile/- YARN_INSTALL =
yarn install without –frozen-lockfile or –immutable
/\byarn\s+install\b/- YARN_SAFE =
/--frozen-lockfile|--immutable/- BUN_INSTALL =
bun install without –frozen-lockfile
/\bbun\s+install\b/- BUN_SAFE =
/--frozen-lockfile/- PIP_INSTALL =
Python pip install / pip3 install with package names (not local installs, not -r)
/\b(?:pip3?|uv\s+pip)\s+install\b/- PIP_SAFE =
/-r\b|--requirement\b|-c\b|--constraint\b|--require-hashes/- PIP_LOCAL =
/\binstall\s+(?:-e\s+)?\.(?:\s|$|\[)/- BUNDLE_INSTALL =
Ruby bundle install (or bare bundle) without –frozen or –deployment
/\bbundle\b(?:\s+install\b)?/- BUNDLE_SAFE =
/--frozen|--deployment|BUNDLE_FROZEN\s*=\s*(?:true|1)/- BUNDLE_OTHER =
Avoid matching unrelated bundle subcommands
/\bbundle\s+(?:exec|add|update|show|list|info|outdated|check|config|lock|cache|clean|console|open|gem|platform|env|doctor|viz|version|init|binstubs|pristine|plugin)\b/- GO_GET =
Go go get in CI is non-deterministic; suggest go mod download
/\bgo\s+get\b/- CARGO_INSTALL =
Rust cargo install without –locked
/\bcargo\s+install\b/- CARGO_SAFE =
/--locked/- COMPOSER_UPDATE =
PHP composer update resolves fresh, ignoring lockfile
/\bcomposer\s+update\b/- CHECKS =
[ { match: NPM_INSTALL, safe: NPM_SAFE, message: "npm install without lockfile enforcement — dependency resolution may differ from tested versions", fix: "Use `npm ci` instead of `npm install`", }, { match: PNPM_INSTALL, safe: PNPM_SAFE, message: "pnpm install without --frozen-lockfile — dependency resolution may differ from tested versions", fix: "Use `pnpm install --frozen-lockfile`", }, { match: YARN_INSTALL, safe: YARN_SAFE, message: "yarn install without lockfile enforcement — dependency resolution may differ from tested versions", fix: "Use `yarn install --frozen-lockfile` or `yarn install --immutable`", }, { match: BUN_INSTALL, safe: BUN_SAFE, message: "bun install without --frozen-lockfile — dependency resolution may differ from tested versions", fix: "Use `bun install --frozen-lockfile`", }, { match: PIP_INSTALL, safe: PIP_SAFE, safe_alt: PIP_LOCAL, message: "pip install with unpinned packages — no lockfile or constraints file ensuring reproducibility", fix: "Use `pip install -r requirements.txt --require-hashes` or a constraints file", }, { match: BUNDLE_INSTALL, safe: BUNDLE_SAFE, skip: BUNDLE_OTHER, message: "bundle install without --frozen — Gemfile.lock may be modified during install", fix: "Use `bundle install --frozen` or `bundle install --deployment`", }, { match: GO_GET, message: "go get in CI is non-deterministic — resolved versions may change between runs", fix: "Use `go mod download` instead (uses go.sum for verification)", }, { match: CARGO_INSTALL, safe: CARGO_SAFE, message: "cargo install without --locked — Cargo.lock will be ignored and dependencies re-resolved", fix: "Use `cargo install --locked`", }, { match: COMPOSER_UPDATE, message: "composer update in CI resolves fresh dependencies, ignoring composer.lock", fix: "Use `composer install` instead (respects composer.lock)", }, ]
Instance Method Summary collapse
Instance Method Details
#check(workflow) ⇒ Object
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/rules/missing_frozen_lockfile.rb', line 107 def check(workflow) findings = [] workflow.raw_lines.each_with_index do |line, i| stripped = line.strip next if stripped.start_with?("#") CHECKS.each do |chk| next unless line.match?(chk[:match]) next if chk[:skip] && line.match?(chk[:skip]) next if chk[:safe] && line.match?(chk[:safe]) next if chk[:safe_alt] && line.match?(chk[:safe_alt]) # For npm install, also check if the line contains "npm ci" separately next if chk[:match] == NPM_INSTALL && line.match?(/\bnpm\s+ci\b/) findings << finding(workflow, line: i + 1, code: stripped, message: chk[:message], fix: chk[:fix] ) end end findings end |
#description ⇒ Object
4 |
# File 'lib/rules/missing_frozen_lockfile.rb', line 4 def description = "Package install without lockfile enforcement" |
#name ⇒ Object
3 |
# File 'lib/rules/missing_frozen_lockfile.rb', line 3 def name = "missing-frozen-lockfile" |
#severity ⇒ Object
5 |
# File 'lib/rules/missing_frozen_lockfile.rb', line 5 def severity = :medium |