Skip to content

feat(scripts): add workflow npm command scanning to Test-DependencyPinning.ps1 #525

@WilliamBerryiii

Description

@WilliamBerryiii

Summary

Extend Test-DependencyPinning.ps1 with a new workflow-npm-commands dependency type that scans GitHub Actions workflow run: steps for unpinned npm commands.

Problem

OpenSSF Scorecard's Pinned-Dependencies check flags npm install commands in workflow run: steps that don't use npm ci or git-URL-with-SHA syntax. The local security scanner (scripts/security/Test-DependencyPinning.ps1) has no equivalent check — its existing npm type only validates package.json dependency sections. Workflow run: step npm commands like npm install -g <package> bypass the lockfile entirely and are invisible to current scanning.

The four Scorecard alerts in #457 originated from workflow run: steps, not from package.json. Without this scanner type, similar regressions can reach main undetected.

Scorecard Context

The Scorecard check (isNpmUnpinnedDownload() in shell_download_validate.go) is syntactic:

  • npm cipinned (reads integrity hashes from package-lock.json)
  • npm install with git URL + 40-char SHA → pinned
  • Everything else → unpinned (including @version tags)

Implementation

1. Add dependency type to $DependencyPatterns

In scripts/security/Test-DependencyPinning.ps1, add a new entry to the $DependencyPatterns hashtable:

'workflow-npm-commands' = @{
    FilePatterns   = @('**/.github/workflows/*.yml', '**/.github/workflows/*.yaml')
    ValidationFunc = 'Get-WorkflowNpmCommandViolations'
    Description    = 'npm commands in workflow run steps must use npm ci or npx'
}

2. Implement Get-WorkflowNpmCommandViolations

Create a validation function that:

  1. Parses each workflow YAML file
  2. Extracts all run: block content from job steps
  3. Matches lines containing npm install, npm i, npm update, or npm install-test (including -g/--global variants)
  4. Flags any match that is not npm ci
  5. Allows npx commands (these use the local node_modules resolved by npm ci)
  6. Returns [DependencyViolation] objects with file path, line number, and violation description

3. Follow existing scanner patterns

Reference the existing validation functions in Test-DependencyPinning.ps1 for the return type, error handling, and output format conventions.

Acceptance Criteria

  • workflow-npm-commands type added to $DependencyPatterns hashtable
  • Get-WorkflowNpmCommandViolations function implemented
  • Function detects npm install / npm i / npm update / npm install-test commands in workflow run: steps
  • npm ci commands are NOT flagged as violations
  • npx commands are NOT flagged as violations
  • Function returns proper [DependencyViolation] objects matching existing scanner output format
  • Existing tests pass (npm run test:ps)

Verification

  1. Run the scanner with the new type:
    ./scripts/security/Test-DependencyPinning.ps1 -IncludeTypes 'workflow-npm-commands'
  2. After fix(build): resolve Pinned-Dependencies alerts for vsce npm commands in extension workflows #457 remediation lands, this should report 0 violations
  3. Temporarily add npm install -g some-package to a workflow file — should report 1 violation (then revert)
  4. Run existing tests: npm run test:ps

Dependencies

How to Build This

This is a PowerShell feature implementation task using the task-implementor workflow.

Workflow: /task-research/task-plan/task-implement/task-review

Tip

Between each phase, type /clear or start a new chat to reset context.

Phase 1: Research

Source Material

  • This issue body
  • #file:scripts/security/Test-DependencyPinning.ps1 (script to extend)
  • #file:scripts/security/Test-ActionVersionPinning.ps1 (scanning pattern reference)
  • #file:.github/workflows/main.yml (example workflow with run steps)
  • #file:.github/workflows/pr-validation.yml (example workflow with npm/pip commands)
  • #file:PSScriptAnalyzer.psd1 (linting rules)

Steps

  1. Type /clear to start a fresh context.
  2. Attach or open the files listed above.
  3. Copy and run this prompt:
/task-research topic="npm command scanning in GitHub Actions workflow run steps"

Research how to extend Test-DependencyPinning.ps1 to scan workflow YAML `run:` steps
for unpinned npm commands. Investigate:

1. How Test-DependencyPinning.ps1 currently parses workflow YAML files (regex patterns,
   AST parsing, or line-by-line analysis)
2. The different forms of npm install commands in workflow run steps (npm install -g,
   npm ci, npx, pip install, pip3 install)
3. What constitutes a "pinned" vs "unpinned" command in a run step context
4. How SARIF findings should be structured for run-step violations (file path, line number,
   rule ID, message)
5. How existing Pester tests are structured for the security scripts
6. Edge cases: multi-line run blocks, shell selection (bash vs pwsh), conditional steps

Output: Research document at .copilot-tracking/research/{{YYYY-MM-DD}}-npm-command-scanning-research.md

Phase 2: Plan

Source Material

  • Research document from Phase 1

Steps

  1. Type /clear to start a fresh context.
  2. Open the research document from Phase 1.
  3. Copy and run this prompt:
/task-plan

Create an implementation plan for adding npm/pip command scanning to
Test-DependencyPinning.ps1. The plan should cover new functions or code blocks,
SARIF finding format, Pester test cases, and integration with the existing
script structure.

Output: Plan at .copilot-tracking/plans/ and details at .copilot-tracking/details/

Phase 3: Implement

Source Material

  • Plan from Phase 2

Steps

  1. Type /clear to start a fresh context.
  2. Open the plan document from Phase 2.
  3. Copy and run this prompt:
/task-implement

Implement the npm command scanning feature in Test-DependencyPinning.ps1 following
the plan. Extend the existing script to detect unpinned npm, pip, and package manager
commands in workflow run steps and report them as SARIF findings.

Output: Modified script and test files, changes log at .copilot-tracking/changes/

Phase 4: Review

Source Material

  • Plan from Phase 2
  • Changes log from Phase 3

Steps

  1. Type /clear to start a fresh context.
  2. Open the plan and changes log.
  3. Copy and run this prompt:
/task-review

Review the npm command scanning implementation. Run these validation commands:
- npm run lint:ps (PSScriptAnalyzer validation)
- npm run test:ps (Pester tests including new test cases)
Verify that scanning correctly identifies unpinned commands across all 25 workflow files
and that SARIF output format matches existing findings.

Output: Review log at .copilot-tracking/reviews/

After Review

  • Pass: All criteria met. Create a PR referencing this issue.
  • Iterate: Review found issues. Run /clear, return to Phase 3 with the review feedback.
  • Escalate: Fundamental design issue discovered. Run /clear, return to Phase 1 to research the gap.

Authoring Standards

  • PowerShell scripts follow PSScriptAnalyzer rules from PSScriptAnalyzer.psd1
  • Include comment-based help for new functions
  • Pester tests follow existing patterns in scripts/tests/
  • SARIF output matches the schema used by existing findings in the script

Success Criteria

  • run: steps in workflow files are scanned for unpinned package manager commands
  • SARIF findings include file path, line number, rule ID, and descriptive message
  • Pester tests cover npm install, pip install, and edge cases
  • npm run lint:ps passes
  • npm run test:ps passes
  • No false positives on npm ci (lockfile-backed) commands

Metadata

Metadata

Labels

enhancementNew feature or requestfeatureNew feature triggering minor version bumpossf-complianceOpenSSF security compliancescriptsPowerShell, Bash, or Python scriptssecuritySecurity-related changes or concerns

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions