Block composite local paths and detect transitive ref-moved by nodeselector · Pull Request #73 · github/gh-actions-lock · GitHub
Skip to content

Block composite local paths and detect transitive ref-moved - #73

Merged
nodeselector merged 4 commits into
mainfrom
nodeselector-block-composite-local-paths
Jun 29, 2026
Merged

nodeselector merged 4 commits into
mainfrom
nodeselector-block-composite-local-paths

Conversation

@nodeselector

Copy link
Copy Markdown
Collaborator

When a remote composite action uses a local path reference (./helper), the BFS
in ResolveAllRecursive silently skipped it because ParseActionRef returns nil
for ./ prefixed uses. This meant transitive deps behind those local paths went
undetected -- no error, no warning, just a quiet gap in the lockfile.

Similarly, checkRefMovedAndForgery only iterated direct workflow refs (pw.Refs),
so transitive deps from composite actions were never checked for upstream drift.
A tag like actions/setup-go@v6 could move to a new SHA and --rescan would
still report "All N workflows valid."

Approach

Composite local path blocking:

  • Added CompositeLocalPathError in discovery.go that fires when the BFS
    encounters a ./ prefixed uses in a remote composite's NestedUses
  • diagnoseOneParsed catches this error and emits a LocalAction finding at
    error severity, short-circuiting further checks for that workflow
  • Added a scenario test (composite_local_path_blocked) backed by a fixture in
    nodeselector/actions-test-fixtures

Transitive ref-moved detection:

  • Refactored checkRefMovedAndForgery to extract per-ref logic into
    checkOneRefMoved, then call it for both direct refs and depIndex entries
    not already covered by a direct ref
  • Added test cases for transitive ref-moved (positive and negative)

Notes

  • Ref-moved findings are IsValid() -> true (warning severity), so --rescan
    still reports valid. Terminal rendering intentionally swallows ref-moved warnings
    (line 288 of terminal.go) until an update subcommand exists. JSON output and
    resolution records do include them.

When a remote composite action's steps contain uses: ./path, we cannot
resolve transitive dependencies behind that local reference. Previously
these were silently skipped (ParseActionRef returns nil for ./ prefixes),
leaving a gap where unpinned transitive deps went undetected.

Now the BFS in ResolveAllRecursive emits a CompositeLocalPathError for
each ./path encountered in a remote composite's NestedUses. The diagnose
layer detects this error type and surfaces a LocalAction finding at error
severity, blocking the workflow.
Uses the nodeselector/actions-test-fixtures/composite-with-local-path
fixture which contains a composite action with uses: ./local-helper.
Asserts exit 1 and output mentioning local path actions and composite.
The checkRefMovedAndForgery loop only iterated pw.Refs (direct workflow
uses). Transitive deps from composite actions were already in depIndex
but never checked for upstream drift. Extract the per-ref logic into
checkOneRefMoved and call it for both direct refs and depIndex entries
not already covered by a direct ref.
Copilot AI review requested due to automatic review settings June 29, 2026 20:45
GitHub Advanced Security started work on behalf of nodeselector June 29, 2026 20:45
GitHub Advanced Security finished work on behalf of nodeselector June 29, 2026 20:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Not ready to approve

A newly added test assertion inspects got[0] instead of the matched RefMoved finding, which can mask failures and reduce confidence in the new behavior.

Pull request overview

This PR closes two correctness gaps in gh-actions-lock’s dependency analysis: (1) remote composite actions that reference local paths (./...) are now surfaced as an error instead of being silently skipped, and (2) ref-drift (“ref moved”) checks now include transitive dependencies recorded in the lockfile, not just direct workflow uses: refs.

Changes:

  • Block remote composite actions that contain uses: ./... by returning a typed CompositeLocalPathError during recursive resolution and surfacing it as a LocalAction error finding during diagnosis.
  • Extend checkRefMovedAndForgery to also evaluate lockfile-only (transitive) dependencies, via extracted shared logic in checkOneRefMoved.
  • Add/extend tests and scenario coverage for both composite-local-path blocking and transitive ref-moved detection.
File summaries
File Description
test/scenarios/catalog.yml Adds an integration scenario asserting remote composite ./ usage is blocked with an error.
internal/resolve/resolver_test.go Adds a unit test ensuring ResolveAllRecursive returns CompositeLocalPathError while still yielding partial results.
internal/resolve/discovery.go Introduces CompositeLocalPathError/IsCompositeLocalPath and detects uses: ./... within composite NestedUses.
internal/pipeline/diagnose.go Converts composite-local-path resolver errors into a high-confidence, error-severity LocalAction finding and short-circuits diagnostics.
internal/pipeline/checks/run_test.go Adds cases validating transitive ref-moved behavior (including “no drift” control).
internal/pipeline/checks/misleading.go Refactors ref-moved/forgery detection to reuse checkOneRefMoved and adds transitive lockfile pin coverage.

Review details

  • Files reviewed: 6/6 changed files
  • Comments generated: 1
  • Review effort level: Low

Note

Your feedback helps us improve the quality of this feature.
Please use 👍 or 👎 to tell us whether this assessment is correct.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/pipeline/checks/run_test.go
GitHub Advanced Security started work on behalf of nodeselector June 29, 2026 21:13
GitHub Advanced Security finished work on behalf of nodeselector June 29, 2026 21:14
@nodeselector
nodeselector merged commit f473586 into main Jun 29, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants