Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/npm-blacklist-self-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
##
## Self-test for the `rainix-npm-blacklist.yaml` reusable workflow.
##
## Exercises the reusable end to end against a real npm project in this repo's
## fixture: it installs the fixture's dependency tree and runs the blacklist
## scan over it, exactly as a downstream consumer does. The install command
## populates `node_modules` under `test/fixture/subgraph`, and that same
## directory is the one scanned.
##
name: npm-blacklist-self-test
on: [push]
jobs:
npm-blacklist:
uses: ./.github/workflows/rainix-npm-blacklist.yaml
with:
install-command: nix develop .#wasm-shell -c bash -c "cd test/fixture/subgraph && npm ci"
working-directories: test/fixture/subgraph
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
60 changes: 60 additions & 0 deletions .github/workflows/rainix-npm-blacklist.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
##
## Reusable npm-package blacklist security gate for rainlanguage repos.
##
## Scans the INSTALLED npm dependency tree of one or more workspace packages for
## known-malicious packages, using the shared blacklist data in the
## `rainlanguage/github-chore` `npm-blacklist` composite action. The check reads
## `npm ls --all`, so it must run AFTER dependencies are installed — this
## workflow installs once via the caller's `install-command`, then scans every
## listed directory from that single installed tree.
##
## Consumers replace an inline blacklist job with a short caller that supplies
## the `install-command` and the `working-directories` to scan.
##
name: rainix-npm-blacklist
on:
workflow_call:
inputs:
install-command:
description: >-
Command that installs the npm dependency tree before the scan, e.g. `nix develop .#wasm-shell -c npm install --no-check`. The blacklist checker runs `npm ls --all`, so every directory in `working-directories` must have its `node_modules` populated by this command.
required: true
type: string
working-directories:
description: >-
Whitespace-separated list of NPM project directories to scan, e.g. a YAML multiline of ". packages/raindex packages/ui-components". Each must contain a `package.json` and installed `node_modules` after `install-command` runs. Defaults to the repository root.
required: false
default: '.'
type: string
cachix-name:
description: Cachix binary cache name to substitute from / push to.
required: false
default: rainlanguage
type: string
secrets:
CACHIX_AUTH_TOKEN:
required: false
jobs:
npm-blacklist:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
# Shared nix + cachix CI preamble (checkout, nix-quick-install, Cachix,
# cache-nix-action). The pinned third-party action SHAs live ONCE in the
# composite; this reusable references it fully qualified at
# `rainlanguage/rainix/...@main`, exactly as downstream consumers do.
- uses: rainlanguage/rainix/.github/actions/nix-cachix-setup@main
with:
cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }}
cachix-name: ${{ inputs.cachix-name }}
# Install the dependency tree once; the blacklist checker inspects it but
# never installs, so a single install feeds every directory scanned below.
- name: Install npm dependencies
run: ${{ inputs.install-command }}
Comment on lines +51 to +54

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all workflows that call rainix-npm-blacklist
rg -t yaml -A 5 'uses:.*rainix-npm-blacklist' --glob '.github/workflows/*'

Repository: rainlanguage/rainix

Length of output: 212


🏁 Script executed:

#!/bin/bash
# First, verify the file exists
ls -la .github/workflows/rainix-npm-blacklist.yaml

# Check lines 51-54 to see the actual code
echo "=== Lines 51-54 ===" 
sed -n '51,54p' .github/workflows/rainix-npm-blacklist.yaml

# View more context around those lines
echo "=== Context (40-60) ==="
sed -n '40,60p' .github/workflows/rainix-npm-blacklist.yaml

# Check if there's input validation defined
echo "=== Looking for inputs definition ==="
grep -n "inputs:" .github/workflows/rainix-npm-blacklist.yaml -A 20

Repository: rainlanguage/rainix

Length of output: 2693


🏁 Script executed:

#!/bin/bash
# Search for workflow calls to rainix-npm-blacklist without file type restriction
# Try different search patterns
echo "=== Searching for uses: rainix-npm-blacklist ==="
rg 'rainix-npm-blacklist' .github/workflows/ -A 3

echo "=== Searching for any references to this workflow ==="
rg 'rainix-npm-blacklist' . --type yaml 2>/dev/null | head -20

echo "=== List all yaml files in .github/workflows ==="
find .github/workflows -name "*.yaml" -o -name "*.yml"

Repository: rainlanguage/rainix

Length of output: 2025


Command injection risk via template expansion in run step.

The install-command input is expanded directly into the run step without validation. While workflow_call limits callers to workflows within the same repository, an attacker with repository write access could modify calling workflows to inject arbitrary shell commands through this input.

Since the input is defined as a required string with no validation constraints, consider restricting it to a predefined set of safe commands or validating against a pattern. The self-test caller currently uses a hardcoded trusted input, but this pattern should not be assumed for future callers.

🧰 Tools
🪛 zizmor (1.25.2)

[error] 54-54: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/rainix-npm-blacklist.yaml around lines 51 - 54, The
`install-command` input is being directly interpolated into the `run` step
without any validation, creating a command injection vulnerability. Restrict the
`install-command` input to a predefined set of safe commands by adding a
`choices` constraint to the input definition in the `inputs` section of the
workflow, or alternatively implement validation logic before the `run` step that
checks the input against an allowlist of permitted commands and fails the
workflow if an unexpected value is provided. This ensures only trusted install
commands can be executed regardless of who modifies calling workflows.

Source: Linters/SAST tools

# One call scans every directory from the single installed tree
# (`working-directories` is whitespace-separated). The check fails if any
# listed directory resolves a blacklisted package.
- uses: rainlanguage/github-chore/.github/actions/npm-blacklist@main
with:
working-directory: ${{ inputs.working-directories }}
Loading