glsec

Rules / GL022

GL022 Package manager install without version pin

Severity: warn
OWASP: CICD-SEC-3 – Dependency Chain Abuse

What glsec checks

glsec flags four categories of problematic package manager usage in CI scripts:

1. Install without a pinned version — pulls whatever is latest at the time the job runs, making the pipeline non-reproducible. A compromised or updated package can silently change pipeline behaviour.

2. Ad-hoc dependency installs — a named package installed on top of the manifest (npm install lodash, yarn add left-pad, pnpm add chalk, bundle add rails) is in no lockfile, so the registry decides the version on every run and the dependency never shows up in a lockfile diff. yarn add and bundle add additionally rewrite the manifest in place, so a later strict install works from modified input.

3. Fetch-and-run launchers: npx -y, pnpm dlx, yarn dlx, uvx and pipx run resolve a package from the registry and execute it in the same step. No lockfile or manifest is involved, and the code runs immediately with the job's environment and tokens.

4. Explicit update-to-latest commands — commands like npm update, composer update, or pip install --upgrade are designed for local development workflows and are almost never appropriate in CI.

Detected package managers

Unpinned installs

Package manager Unpinned (flagged) Pinned (safe)
pip pip install requests pip install requests==2.31.0
npm (global) npm install -g typescript (also npm i -g, npm add -g) npm install -g typescript@5.4.5
npm (ad-hoc) npm install lodash (also npm i, npm add) npm install lodash@4.17.21
yarn (ad-hoc) yarn add left-pad (also yarn global add) yarn add left-pad@1.3.0
pnpm (ad-hoc) pnpm add chalk pnpm add chalk@5.3.0
bundler (ad-hoc) bundle add rails bundle add rails -v 7.1.3
apt-get / apt apt-get install jq apt-get install jq=1.6-2
apk apk add curl apk add curl=8.5.0-r0
gem gem install bundler gem install bundler -v 2.5.6
cargo cargo install cargo-audit cargo install cargo-audit --version 0.20.0
composer composer require guzzlehttp/guzzle composer require guzzlehttp/guzzle:^7.8
yum yum install httpd yum install httpd-2.4.6
dnf dnf install nginx dnf install nginx-1.20.1
zypper zypper install curl (also zypper in) zypper install curl=7.66.0
go install go install example.com/cmd@latest go install example.com/cmd@v1.2.3

Fetch-and-run launchers

Launcher Unpinned (flagged) Pinned (safe)
npx npx -y surge dist (also --yes, npm_config_yes=true npx, npm exec --yes, npm x -y), npx create-vite@latest app npx -y surge@0.24.6 dist
pnpm/yarn dlx pnpm dlx node-gyp rebuild, yarn dlx @redocly/cli lint pnpm dlx node-gyp@10.2.0 rebuild
uvx / pipx run uvx ruff check (also uv tool run), pipx run black --check . uvx ruff@0.6.9 check, uvx --from ruff==0.6.9 ruff, pipx run black==24.8.0, pipx run --spec black==24.8.0 black

Explicit update commands (always flagged)

npm update, yarn upgrade, pnpm update, composer update, bundle update, gem update, cargo update, pip install --upgrade <pkg>

Trigger examples

build:
  script:
    - pip install ansible              # unpinned
    - npm install lodash               # ad-hoc dependency, no lockfile entry
    - yarn add left-pad@latest         # dist-tag, not a version
    - bundle add rails                 # ad-hoc gem
    - npm install -g @angular/cli      # unpinned scoped package
    - apt-get install -y jq            # unpinned
    - composer require guzzlehttp/guzzle  # unpinned
    - npm update                       # explicit update
    - composer update                  # explicit update
    - pip install --upgrade ansible    # explicit upgrade
    - npx -y surge dist                # fetched and run, unpinned
    - pnpm dlx node-gyp rebuild        # fetched and run, unpinned
    - uvx ruff check                   # fetched and run, unpinned

Safe alternatives

build:
  script:
    - pip install ansible==9.2.0
    - npm install lodash@4.17.21
    - yarn add left-pad@1.3.0
    - bundle add rails -v 7.1.3
    - npm install -g @angular/cli@17.3.6
    - apt-get install -y jq=1.6-2
    - apk add --no-cache curl=8.5.0-r0
    - gem install bundler -v 2.5.6
    - cargo install cargo-audit --version 0.20.0
    - composer require guzzlehttp/guzzle:^7.8
    - npx -y surge@0.24.6 dist
    - pnpm dlx node-gyp@10.2.0 rebuild
    - uvx ruff@0.6.9 check

Notes

Documentation for glsec 1.23.0. Source of this page · Rule implementation