이 제품은 선택한 Datadog 사이트에서 지원되지 않습니다. ().
이 페이지는 아직 한국어로 제공되지 않습니다. 번역 작업 중입니다.
현재 번역 프로젝트에 대한 질문이나 피드백이 있으신 경우 언제든지 연락주시기 바랍니다.

Metadata

Id: cicd-github-unsound-contains-no-controllable-input

Provider: GitHub

Platform: CICD

Severity: Low

Category: Insecure Defaults

Learn More

Description

Using contains() with a string literal to validate a non-controllable value is unsafe because contains() performs substring matching. Crafted values such as refs/heads/mai or ain can satisfy the check and bypass intended restrictions.

The rule inspects job if expressions and flags calls of the form contains(<string literal>, <Context>). Replace substring checks with explicit equality comparisons such as github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop', or use a JSON array with fromJSON and contains.

# Explicit equality checks
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'

# Or use a JSON array for exact membership testing
if: contains(fromJSON('["refs/heads/main","refs/heads/develop"]'), github.ref)

Compliant Code Examples

name: Sound Contains Usage
on: pull_request

jobs:
  # Case 1: Explicit equality checks (best practice)
  test_explicit_equality:
    runs-on: ubuntu-latest
    if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' }}
    steps:
      - uses: actions/checkout@v4
      - run: npm test

  # Case 2: JSON array with contains() (safe approach)
  test_json_array:
    runs-on: ubuntu-latest
    if: ${{ contains(fromJSON('["refs/heads/main", "refs/heads/develop"]'), github.ref) }}
    steps:
      - uses: actions/checkout@v4
      - run: npm test

  # Case 3: Step-level with explicit checks
  test_step_explicit:
    runs-on: ubuntu-latest
    steps:
      - name: Check actor
        if: ${{ github.actor == 'dependabot' || github.actor == 'renovate' }}
        run: echo "Bot detected"

  # Case 4: Using contains() for single substring (no spaces)
  test_single_substring:
    runs-on: ubuntu-latest
    steps:
      - name: Check if contains feature
        if: ${{ contains(github.ref, 'feature') }}
        run: echo "Feature branch"

  # Case 5: Using startsWith() for prefix matching
  test_starts_with:
    runs-on: ubuntu-latest
    if: ${{ startsWith(github.ref, 'refs/heads/feature/') }}
    steps:
      - run: echo "Feature branch"

  # Case 6: Using endsWith() for suffix matching
  test_ends_with:
    runs-on: ubuntu-latest
    if: ${{ endsWith(github.ref, '/main') }}
    steps:
      - run: echo "Main branch"

Non-Compliant Code Examples

name: Unsound Contains Usage
on: pull_request

jobs:
  # Case 1: Contains with non-user-controllable context
  test_safe_context:
    runs-on: ubuntu-latest
    if: ${{ contains('push pull_request', github.event_name) }}
    steps:
      - run: echo "This is informational, not high severity"
name: Composite action with unsafe contains
description: Composite step uses contains() against a non-user-controllable context
runs:
  using: composite
  steps:
    - name: Check trigger
      if: ${{ contains('push pull_request', github.event_name) }}
      shell: bash
      run: echo "Trigger check"