For AI agents: A markdown version of this page is available at https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/cicd-github-unsound-contains-no-controllable-input.md.
A documentation index is available at /llms.txt.
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 checksif:github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'# Or use a JSON array for exact membership testingif:contains(fromJSON('["refs/heads/main","refs/heads/develop"]'), github.ref)
Compliant Code Examples
name:Sound Contains Usageon:pull_requestjobs:# Case 1: Explicit equality checks (best practice)test_explicit_equality:runs-on:ubuntu-latestif:${{ 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-latestif:${{ contains(fromJSON('["refs/heads/main", "refs/heads/develop"]'), github.ref) }}steps:- uses:actions/checkout@v4- run:npm test# Case 3: Step-level with explicit checkstest_step_explicit:runs-on:ubuntu-lateststeps:- name:Check actorif:${{ 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-lateststeps:- name:Check if contains featureif:${{ contains(github.ref, 'feature') }}run:echo "Feature branch"# Case 5: Using startsWith() for prefix matchingtest_starts_with:runs-on:ubuntu-latestif:${{ startsWith(github.ref, 'refs/heads/feature/') }}steps:- run:echo "Feature branch"# Case 6: Using endsWith() for suffix matchingtest_ends_with:runs-on:ubuntu-latestif:${{ endsWith(github.ref, '/main') }}steps:- run:echo "Main branch"
Non-Compliant Code Examples
name:Unsound Contains Usageon:pull_requestjobs:# Case 1: Contains with non-user-controllable contexttest_safe_context:runs-on:ubuntu-latestif:${{ contains('push pull_request', github.event_name) }}steps:- run:echo "This is informational, not high severity"
name:Composite action with unsafe containsdescription:Composite step uses contains() against a non-user-controllable contextruns:using:compositesteps:- name:Check triggerif:${{ contains('push pull_request', github.event_name) }}shell:bashrun:echo "Trigger check"
1
2
rulesets:- CICD / GitHub # Rules to enforce / GitHub.
Request a personalized demo
Get Started with Datadog
Ask AI
AI-generated responses may be inaccurate. Verify important info.