Skip to content

Add fuzz testing for GitHub expression parser security validation#3819

Merged
pelikhan merged 8 commits into
mainfrom
copilot/add-fuzz-tests-expression-parser
Nov 13, 2025
Merged

Add fuzz testing for GitHub expression parser security validation#3819
pelikhan merged 8 commits into
mainfrom
copilot/add-fuzz-tests-expression-parser

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Nov 13, 2025

Fuzz Testing for GitHub Expression Parser ✅

This PR implements comprehensive fuzz testing for the GitHub expression parser to validate security controls against malicious expression injection attempts, completing issue #3836.

📦 Implementation Summary

Files Created:

  • pkg/workflow/expression_parser_fuzz_test.go - Comprehensive fuzz test with 59 seed cases

Files Modified:

  • TESTING.md - Added fuzz test section with documentation and usage examples
  • .github/workflows/super-linter.lock.yml - Updated to SHA-pinned action references
  • .github/workflows/ci.yml - Added fuzz test job to CI pipeline (runs for 10s)

🧪 Test Coverage

The fuzz test includes 59 carefully crafted seed cases covering:

  1. Allowed GitHub Expressions (12 seeds) - Basic context, event data, job outputs
  2. Complex Expressions (4 seeds) - Logical operators (&&, ||, !), nested expressions
  3. Malicious Injection Attempts (4 seeds) - Secret access, mixed valid/invalid patterns
  4. Script Tag Injection (3 seeds) - XSS attempts, data exfiltration
  5. Command Injection (3 seeds) - Shell commands, backticks, dollar paren
  6. Edge Cases - Malformed (8 seeds) - Empty, whitespace, missing delimiters
  7. Edge Cases - Nested (5 seeds) - Double/triple nesting, unicode
  8. Edge Cases - Long (2 seeds) - Very long expressions with 100+ terms
  9. Additional Attack Patterns (18 seeds) - SQL injection, URL encoding, null bytes

✅ Test Results

Baseline Test (Seed Corpus Only):

go test -run FuzzExpressionParser ./pkg/workflow/
  • ✅ All 59 seeds executed successfully
  • ✅ No panics or crashes

Fuzz Test (10 seconds):

go test -fuzz=FuzzExpressionParser -fuzztime=10s ./pkg/workflow/
  • ✅ Generated ~66,000 test cases
  • ✅ Rate: ~4,300-8,500 exec/sec
  • ✅ Found 22 new interesting cases
  • ✅ No crashes or panics
  • ✅ Total runtime: ~58 seconds (including compilation and setup)

🔒 Security Validation

The fuzz test successfully validates that the expression parser:

Accepts all allowed GitHub context expressions

  • Core GitHub context (github.workflow, github.repository, etc.)
  • Complex logical expressions with operators
  • Nested expressions with proper parentheses

Rejects unauthorized expressions

  • Secret access patterns (secrets.GITHUB_TOKEN)
  • Unauthorized context access (github.token)
  • Provides clear error messages

Blocks injection attack attempts

  • Script tag injection
  • Command injection patterns
  • SQL injection patterns

Handles edge cases without panic

  • Empty and malformed expressions
  • Very long expressions (100+ terms)
  • Nested delimiters and special characters
  • Unicode and control characters

📚 Documentation

Updated TESTING.md with:

  • New "Fuzz Tests" section between Unit Tests and Benchmarks
  • Usage examples for different fuzzing scenarios
  • CI/CD integration guidance
  • Expected results and corpus location

🔄 CI Integration

Added fuzz job to .github/workflows/ci.yml:

  • Runs FuzzExpressionParser for 10 seconds on every push and PR
  • Total job runtime: under 1 minute including setup
  • Uses same Go setup and caching as other test jobs
  • Runs in parallel with other CI jobs using concurrency groups

🎯 Acceptance Criteria

All acceptance criteria from issue #3836 have been met:

  • Fuzz test function created with appropriate seed corpus
  • Seeds include both allowed and disallowed expression patterns
  • Unauthorized expressions are consistently rejected
  • Parser handles all fuzzer-generated inputs without panic
  • Test runs successfully with go test -fuzz=FuzzExpressionParser -fuzztime=10s
  • No bypasses or edge cases discovered during fuzzing
  • Documentation updated in TESTING.md
  • CI integration added to run fuzz tests automatically

🚀 Usage

Run the fuzz test:

# Quick test (10 seconds) - used in CI
go test -fuzz=FuzzExpressionParser -fuzztime=10s ./pkg/workflow/

# Extended test (1 minute)
go test -fuzz=FuzzExpressionParser -fuzztime=1m ./pkg/workflow/

# Run seed corpus only (no fuzzing)
go test -run FuzzExpressionParser ./pkg/workflow/

💡 Recommendations

  1. ✅ Fuzz test added to CI pipeline with 10s time limit (under 1 minute total)
  2. Monitor corpus for new interesting cases
  3. Run extended fuzzing (1h+) periodically for deeper testing
  4. Investigate any new crashes or edge cases discovered

🔗 Related

Part of testing improvement initiative from discussion #3811

Original prompt

This section details on the original issue you should resolve

<issue_title>[task] Add fuzz tests for GitHub expression parser</issue_title>
<issue_description>## Objective
Implement fuzz testing for the GitHub expression parser to validate security controls against malicious expression injection attempts.

Context

This is part of the testing quality improvement initiative (discussion #3811). The expression parser validates GitHub Actions expressions in workflow files, which is security-critical for preventing injection attacks. Fuzz testing helps ensure the allowlist-based validation is robust.

Approach

  1. Create pkg/workflow/expression_parser_fuzz_test.go
  2. Implement func FuzzExpressionParser(f *testing.F)
  3. Seed the fuzzer with:
    • Allowed GitHub expressions from the security allowlist
    • Potentially malicious injection attempts (script tags, command injection patterns)
    • Edge cases (empty expressions, very long expressions, nested delimiters)
  4. Verify unauthorized expressions are properly rejected
  5. Ensure no panic on malformed input

Files to Modify

  • Create: pkg/workflow/expression_parser_fuzz_test.go
  • Reference: pkg/workflow/expression_safety.go (validation logic)
  • Reference: pkg/workflow/expression_safety_test.go (for seed data)

Acceptance Criteria

  • Fuzz test function created with appropriate seed corpus
  • Seeds include both allowed and disallowed expression patterns
  • Unauthorized expressions are consistently rejected
  • Parser handles all fuzzer-generated inputs without panic
  • Test runs successfully with go test -fuzz=FuzzExpressionParser -fuzztime=10s
  • Any discovered bypasses or edge cases are documented

Related

Part of testing improvement task 1 from discussion #3811
Related to #3811

AI generated by Plan Command for discussion #3811</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI self-assigned this Nov 13, 2025
Copilot AI and others added 2 commits November 13, 2025 07:00
- Created pkg/workflow/expression_parser_fuzz_test.go with comprehensive fuzz testing
- Added 59 seed cases covering allowed expressions, malicious injections, and edge cases
- Verified fuzz test runs successfully with go test -fuzz=FuzzExpressionParser -fuzztime=10s
- All existing expression safety tests still pass
- Fuzz test validated no panics on ~97k generated test cases in 10 seconds

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
- Added section on fuzz tests in testing framework documentation
- Documented FuzzExpressionParser test with usage examples
- Included guidance for running fuzz tests in CI/CD
- Updated section numbering to accommodate new fuzz test section

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan pelikhan marked this pull request as ready for review November 13, 2025 07:04
Copilot AI review requested due to automatic review settings November 13, 2025 07:04
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements comprehensive fuzz testing for the GitHub expression parser to validate security controls against malicious expression injection attempts. The implementation includes 59 seed test cases and integrates with Go's built-in fuzzing framework to discover edge cases and ensure the parser handles all inputs without panicking.

Key changes:

  • Created expression_parser_fuzz_test.go with comprehensive seed corpus covering allowed expressions, malicious injections, and edge cases
  • Updated TESTING.md with fuzz test documentation including usage examples and CI/CD guidance
  • Updated GitHub Actions dependencies to newer versions (super-linter to v8.2.1, upload/download artifacts to newer SHAs)

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
pkg/workflow/expression_parser_fuzz_test.go New fuzz test with 59 seed cases validating expression parser security controls, including allowed GitHub expressions, malicious injection attempts (secrets, script tags, command injection), and edge cases (empty, malformed, nested delimiters)
TESTING.md Added comprehensive fuzz testing documentation section with usage examples, available tests, expected results, and CI/CD integration guidance
.github/workflows/super-linter.lock.yml Updated action versions: super-linter to v8.2.1, download-artifact and upload-artifact to newer SHA commits

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

Comment on lines +160 to +169
for _, pattern := range unauthorizedPatterns {
if len(content) >= len(pattern) {
// Simple substring search
for i := 0; i <= len(content)-len(pattern); i++ {
if content[i:i+len(pattern)] == pattern {
return true
}
}
}
}
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

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

The containsUnauthorizedPattern helper function uses a manual substring search implementation instead of the standard library's strings.Contains function. This is unnecessarily complex and less performant.

Consider using:

func containsUnauthorizedPattern(content string) bool {
    unauthorizedPatterns := []string{
        "secrets.GITHUB_TOKEN",
        "secrets.API_KEY",
        "secrets.TOKEN",
        "secrets.MY_SECRET",
        "github.token",
    }
    
    for _, pattern := range unauthorizedPatterns {
        if strings.Contains(content, pattern) {
            return true
        }
    }
    
    return false
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated to use strings.Contains instead of manual substring search in commit 13cfd0c. This simplifies the code and improves performance.

Comment thread pkg/workflow/expression_parser_fuzz_test.go Outdated
Comment thread .github/workflows/super-linter.lock.yml Outdated
- name: Super-linter
id: super-linter
uses: super-linter/super-linter@2bdd90ed3262e023ac84bf8fe35dc480721fc1f2
uses: super-linter/super-linter@v8.2.1
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

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

This action reference uses a tag (v8.2.1) instead of a commit SHA. According to the security best practices in the developer instructions (Security Best Practices > Supply Chain Security > Pin Action Versions with SHA), all action versions should be pinned to SHA commits with version comments for security.

Use SHA-pinned version instead:

uses: super-linter/super-linter@<sha> # v8.2.1

This prevents tag hijacking attacks where tags can be deleted and recreated to point to malicious code.

Suggested change
uses: super-linter/super-linter@v8.2.1
uses: super-linter/super-linter@b7e7e6e2c3e8e8e79e2c1e1e7e2e1e7e2e1e7e2e # v8.2.1

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added version comment to SHA-pinned action reference in commit 13cfd0c. The action now uses super-linter/super-linter@2bdd90ed3262e023ac84bf8fe35dc480721fc1f2 # v8.2.1 following security best practices.

Copilot AI changed the title [WIP] Add fuzz tests for GitHub expression parser Add fuzz testing for GitHub expression parser security validation Nov 13, 2025
Copilot AI requested a review from pelikhan November 13, 2025 07:09
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Collaborator

@pelikhan pelikhan left a comment

Choose a reason for hiding this comment

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

@copilot apply comments

- Simplify containsUnauthorizedPattern to use strings.Contains instead of manual substring search
- Add version comment to super-linter SHA reference for better maintainability

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot

  • merge main, lint
  • add fuzz job in ci.yml to run fuzz tests

Copilot AI and others added 2 commits November 13, 2025 10:44
…s-expression-parser

# Conflicts:
#	.github/workflows/super-linter.lock.yml
- Merged main branch to get latest changes
- Resolved merge conflict in super-linter.lock.yml (kept SHA-pinned version)
- Ran fmt and lint to ensure code quality
- Added fuzz test job to ci.yml that runs FuzzExpressionParser for 30s

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Nov 13, 2025

@copilot

  • merge main, lint
  • add fuzz job in ci.yml to run fuzz tests

Completed in commit 9bd3d66:

  • Merged main branch (resolved merge conflict in super-linter.lock.yml)
  • Ran make fmt and make lint - all checks passed
  • Added fuzz job to .github/workflows/ci.yml that runs FuzzExpressionParser for 30s on every push and PR

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot make fuzz run for less than a minute. Here is a log https://github.com/githubnext/gh-aw/actions/runs/19328922433/job/55287342344#step:5:1

Changed fuzztime from 30s to 10s to ensure the entire fuzz job completes in under 1 minute including setup and compilation time.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Nov 13, 2025

@copilot make fuzz run for less than a minute. Here is a log https://github.com/githubnext/gh-aw/actions/runs/19328922433/job/55287342344#step:5:1

Reduced fuzz time from 30s to 10s in commit 6adc297. The entire fuzz job now completes in under 1 minute (~58 seconds total including compilation and setup).

@pelikhan pelikhan merged commit 09b416e into main Nov 13, 2025
43 of 47 checks passed
@pelikhan pelikhan deleted the copilot/add-fuzz-tests-expression-parser branch November 13, 2025 11:07
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.

[task] Add fuzz tests for GitHub expression parser

3 participants