diff --git a/README.md b/README.md index 1325941..fdf716e 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,13 @@ This repository contains Semgrep rules developed and made public by @Saleor. -* [Saleor Semgrep Rules](#saleor-semgrep-rules) - * [Usage](#usage) - * [Rules](#rules) - * [Typescript](#typescript) - * [Contributing](#contributing) + +- [Saleor Semgrep Rules](#saleor-semgrep-rules) + - [Usage](#usage) + - [Rules](#rules) + - [Typescript](#typescript) + - [Contributing](#contributing) + ## Usage @@ -31,17 +33,19 @@ semgrep -c /path-to-the-clone-semgrep-rules . ### Typescript | ID | Impact | Confidence | Description | -|-------------------------------------------------------------------------------------------------------------------------|--------|------------|---------------------------------------------------| +| ----------------------------------------------------------------------------------------------------------------------- | ------ | ---------- | ------------------------------------------------- | | [typescript.lang.security.audit.timing-attack-comparison](typescript/lang/security/audit/timing-attack-comparison.yaml) | HIGH | LOW | Checks comparisons against secrets are time-safe. | ### YAML -| ID | Impact | Confidence | Description | -|-----------------------------------------------------------------------------------------------------------------------------|--------|------------|----------------------------------------------------------------------------------------------------| -| [yaml.github-actions.security.audit.shell-script-injection](yaml/github-actions/security/audit/shell-script-injection.yaml) | HIGH | HIGH | Ensures no string interpolations (`${{ ... }}`) are present inside `run` blocks of GitHub Actions. | +| ID | Impact | Confidence | Description | +| ------------------------------------------------------------------------------------------------------------------------------------- | ------ | ---------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| [yaml.github-actions.security.audit.shell-script-injection](yaml/github-actions/security/audit/shell-script-injection.yaml) | HIGH | HIGH | Ensures no string interpolations (`${{ ... }}`) are present inside `run` blocks of GitHub Actions. | +| [yaml.github-actions.security.audit.secrets-without-environment](yaml/github-actions/security/audit/secrets-without-environment.yaml) | HIGH | HIGH | Matches GitHub Workflows that use secrets (other than GITHUB_TOKEN) without providing a GitHub Environment (`environment` keyword). | ## Contributing Refer to our guidelines: + - [CONTRIBUTING.md](CONTRIBUTING.md) - [CODE_OF_CONDUCT.md](https://github.com/saleor/.github/blob/main/CODE_OF_CONDUCT.md) diff --git a/yaml/github-actions/security/audit/secrets-without-environment.test.yaml b/yaml/github-actions/security/audit/secrets-without-environment.test.yaml new file mode 100644 index 0000000..afb546c --- /dev/null +++ b/yaml/github-actions/security/audit/secrets-without-environment.test.yaml @@ -0,0 +1,216 @@ +name: CI + +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - env: + # ruleid: secrets-without-environment-in-env-block + foo: ${{ secrets.PROD_API_KEY }} + run: | + echo "$foo" +--- +name: CI + +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Deploy + env: + foo1: bar + # ruleid: secrets-without-environment-in-env-block + foo: "${{ secrets.PROD_API_KEY }}" + foo2: bar + run: | + echo "$foo" +--- +name: CI + +on: + push: + branches: [main] + +# Test surrounded by other jobs +jobs: + prepare: + runs-on: ubuntu-latest + steps: + - if: ${{ true }} + name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + deploy: + runs-on: ubuntu-latest + steps: + - name: do something + env: + # ruleid: secrets-without-environment-in-env-block + foo: ${{ secrets.PROD_API_KEY }} + run: | + echo "$foo" + finalize: + runs-on: ubuntu-latest + steps: + - if: ${{ true }} + name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd +--- +name: CI + +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Deploy + # Shouldn't match when using GITHUB_TOKEN + # ok: secrets-without-environment-in-env-block + run: echo "${{ secrets.GITHUB_TOKEN }}" +--- +name: CI + +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + environment: my-env + steps: + - if: ${{ true }} + env: + # Shouldn't match when an environment is used + # ok: secrets-without-environment-in-with-block + foo: ${{ secrets.PROD_API_KEY }} +--- +name: CI + +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - if: ${{ true }} + name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + # Should match when passing a secret to 'with' block + # ruleid: secrets-without-environment-in-with-block + token: ${{ secrets.MY_TOKEN }} +--- +name: CI + +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - if: ${{ true }} + name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + # Test with quoted secret + surrounded by other keys + with: + foo1: bar + # ruleid: secrets-without-environment-in-with-block + token: "${{ secrets.MY_TOKEN }}" + foo2: bar +--- +name: CI + +on: + push: + branches: [main] + +# Test surrounded by other jobs +jobs: + prepare: + runs-on: ubuntu-latest + steps: + - if: ${{ true }} + name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + deploy: + runs-on: ubuntu-latest + steps: + - if: ${{ true }} + name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + # ruleid: secrets-without-environment-in-with-block + token: "${{ secrets.MY_TOKEN }}" + finalize: + runs-on: ubuntu-latest + steps: + - if: ${{ true }} + name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd +--- +name: CI + +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - if: ${{ true }} + name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + # Shouldn't match when using GITHUB_TOKEN + # ok: secrets-without-environment-in-with-block + token: ${{ secrets.GITHUB_TOKEN }} +--- +name: CI + +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + environment: my-env + steps: + - if: ${{ true }} + name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + # Shouldn't match when an environment is used + # ok: secrets-without-environment-in-with-block + token: ${{ secrets.PROD_API_KEY }} +--- +name: CI + +on: + push: + branches: [main] + +jobs: + reusable-workflow: + uses: saleor/foo + with: + # Environments aren't supported in reusable workflows + # as per https://docs.github.com/en/actions/how-tos/reuse-automations/reuse-workflows + # ok: secrets-without-environment-in-with-block + token: ${{ secrets.PROD_API_KEY }} diff --git a/yaml/github-actions/security/audit/secrets-without-environment.yaml b/yaml/github-actions/security/audit/secrets-without-environment.yaml new file mode 100644 index 0000000..a2b9691 --- /dev/null +++ b/yaml/github-actions/security/audit/secrets-without-environment.yaml @@ -0,0 +1,78 @@ +# NOTE: this rule assumes ${{ secrets.SECRET }} is NEVER passed inside 'run' blocks +# Meaning the rule ./shell-script-injection.yaml is being used & respected. +rules: + - id: secrets-without-environment-in-env-block + message: > + This GitHub Actions workflow uses secrets but does not configure a job-level + environment. Secrets should be scoped to a GitHub Environment to enforce + protection rules (reviewers, deployment gates, etc) and to reduce blast radius. + severity: WARNING + languages: [yaml] + metadata: + category: security + technology: github-actions + cwe: "CWE-522: Insufficiently Protected Credentials" + likelihood: MEDIUM + confidence: HIGH + impact: HIGH + + patterns: + - pattern-inside: | + steps: [...] + - pattern-inside: | + env: ... + ... + - pattern: | + env: $X + - metavariable-pattern: + language: generic + metavariable: $X + patterns: + - pattern: "${{ secrets.$SECRET }}" + # GITHUB_TOKEN can be OK without environment as it's a dynamic secret + - pattern-not: "${{ secrets.GITHUB_TOKEN }}" + # Exclude jobs that properly define an environment + - pattern-not-inside: | + jobs: + ... + $JOB: + ... + environment: + ... + - focus-metavariable: $SECRET + - id: secrets-without-environment-in-with-block + message: > + This GitHub Actions workflow uses secrets but does not configure a job-level + environment. Secrets should be scoped to a GitHub Environment to enforce + protection rules (reviewers, deployment gates, etc) and to reduce blast radius. + severity: WARNING + languages: [yaml] + metadata: + category: security + technology: github-actions + cwe: "CWE-522: Insufficiently Protected Credentials" + confidence: medium + + patterns: + - pattern-inside: | + steps: [...] + - pattern-inside: | + with: ... + ... + - pattern: | + with: $X + - metavariable-pattern: + language: generic + metavariable: $X + patterns: + - pattern: "${{ secrets.$SECRET }}" + - pattern-not: "${{ secrets.GITHUB_TOKEN }}" + # GITHUB_TOKEN can be OK without environment as it's a dynamic secret + - pattern-not-inside: | + jobs: + ... + $JOB: + ... + environment: + ... + - focus-metavariable: $SECRET