Class: Rules::CachePoisoning

Inherits:
Base
  • Object
show all
Defined in:
lib/rules/cache_poisoning.rb

Constant Summary collapse

CACHE_ACTIONS =
%w[
    actions/cache
    actions/cache/restore
    actions/cache/save
].freeze
DANGEROUS_KEY_PATTERNS =

Fork-controllable refs that should never appear in cache keys

[
    /\$\{\{\s*github\.head_ref\s*\}\}/,
    /\$\{\{\s*github\.event\.pull_request\.head\.ref\s*\}\}/,
].freeze
GITHUB_REF_PATTERN =

github.ref on pull_request triggers resolves to the PR merge ref

/\$\{\{\s*github\.ref\s*\}\}/
PR_TRIGGERS =
%w[pull_request pull_request_target].freeze

Instance Method Summary collapse

Instance Method Details

#check(workflow) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/rules/cache_poisoning.rb', line 24

def check(workflow)
    findings = []
    has_pr_trigger = pr_triggered?(workflow)

    workflow.uses_actions.each do |action|
        uses = action[:uses]
        next unless CACHE_ACTIONS.any? { |ca| uses&.start_with?(ca) }

        step = action[:step]
        key_value = step.dig("with", "key")
        next unless key_value

        # Check for directly dangerous patterns
        DANGEROUS_KEY_PATTERNS.each do |pattern|
            if key_value.match?(pattern)
                findings << finding(workflow,
                    line: action[:line] || 0,
                    code: "key: #{key_value}",
                    message: "Cache key contains fork-controllable reference — risk of cache poisoning",
                    fix: "Use hashFiles() for cache keys, not branch refs. Consider prefixing fork PR cache keys."
                )
                break
            end
        end

        # Check for github.ref on PR-triggered workflows
        if has_pr_trigger && key_value.match?(GITHUB_REF_PATTERN)
            findings << finding(workflow,
                line: action[:line] || 0,
                code: "key: #{key_value}",
                message: "Cache key uses github.ref on pull_request trigger — resolves to mutable PR merge ref",
                fix: "Use hashFiles() for cache keys, not branch refs. Consider prefixing fork PR cache keys."
            )
        end
    end

    findings
end

#descriptionObject



4
# File 'lib/rules/cache_poisoning.rb', line 4

def description = "Cache key uses mutable, fork-controllable reference"

#nameObject



3
# File 'lib/rules/cache_poisoning.rb', line 3

def name = "cache-poisoning"

#severityObject



5
# File 'lib/rules/cache_poisoning.rb', line 5

def severity = :medium