Skip to content

Commit cd9feed

Browse files
committed
feat: add rule matching secrets used without environment
This adds a Semgrep rule that ensures all `${{ secrets.XXX }}` interpolations use the `environment` keyword to ensure blast radius is limited and to ensure protection rules are followed (branch protections, tag protections, …).
1 parent 649e7b8 commit cd9feed

File tree

3 files changed

+307
-9
lines changed

3 files changed

+307
-9
lines changed

README.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
This repository contains Semgrep rules developed and made public by @Saleor.
44

55
<!-- TOC -->
6-
* [Saleor Semgrep Rules](#saleor-semgrep-rules)
7-
* [Usage](#usage)
8-
* [Rules](#rules)
9-
* [Typescript](#typescript)
10-
* [Contributing](#contributing)
6+
7+
- [Saleor Semgrep Rules](#saleor-semgrep-rules)
8+
- [Usage](#usage)
9+
- [Rules](#rules)
10+
- [Typescript](#typescript)
11+
- [Contributing](#contributing)
12+
1113
<!-- TOC -->
1214

1315
## Usage
@@ -31,17 +33,19 @@ semgrep -c /path-to-the-clone-semgrep-rules .
3133
### Typescript
3234

3335
| ID | Impact | Confidence | Description |
34-
|-------------------------------------------------------------------------------------------------------------------------|--------|------------|---------------------------------------------------|
36+
| ----------------------------------------------------------------------------------------------------------------------- | ------ | ---------- | ------------------------------------------------- |
3537
| [typescript.lang.security.audit.timing-attack-comparison](typescript/lang/security/audit/timing-attack-comparison.yaml) | HIGH | LOW | Checks comparisons against secrets are time-safe. |
3638

3739
### YAML
3840

39-
| ID | Impact | Confidence | Description |
40-
|-----------------------------------------------------------------------------------------------------------------------------|--------|------------|----------------------------------------------------------------------------------------------------|
41-
| [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. |
41+
| ID | Impact | Confidence | Description |
42+
| ------------------------------------------------------------------------------------------------------------------------------------- | ------ | ---------- | ----------------------------------------------------------------------------------------------------------------------------------- |
43+
| [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. |
44+
| [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). |
4245

4346
## Contributing
4447

4548
Refer to our guidelines:
49+
4650
- [CONTRIBUTING.md](CONTRIBUTING.md)
4751
- [CODE_OF_CONDUCT.md](https://github.com/saleor/.github/blob/main/CODE_OF_CONDUCT.md)
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
7+
jobs:
8+
deploy:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- env:
12+
# ruleid: secrets-without-environment-in-env-block
13+
foo: ${{ secrets.PROD_API_KEY }}
14+
run: |
15+
echo "$foo"
16+
---
17+
name: CI
18+
19+
on:
20+
push:
21+
branches: [main]
22+
23+
jobs:
24+
deploy:
25+
runs-on: ubuntu-latest
26+
steps:
27+
- name: Deploy
28+
env:
29+
foo1: bar
30+
# ruleid: secrets-without-environment-in-env-block
31+
foo: "${{ secrets.PROD_API_KEY }}"
32+
foo2: bar
33+
run: |
34+
echo "$foo"
35+
---
36+
name: CI
37+
38+
on:
39+
push:
40+
branches: [main]
41+
42+
# Test surrounded by other jobs
43+
jobs:
44+
prepare:
45+
runs-on: ubuntu-latest
46+
steps:
47+
- if: ${{ true }}
48+
name: Checkout
49+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
50+
deploy:
51+
runs-on: ubuntu-latest
52+
steps:
53+
- name: do something
54+
env:
55+
# ruleid: secrets-without-environment-in-env-block
56+
foo: ${{ secrets.PROD_API_KEY }}
57+
run: |
58+
echo "$foo"
59+
finalize:
60+
runs-on: ubuntu-latest
61+
steps:
62+
- if: ${{ true }}
63+
name: Checkout
64+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
65+
---
66+
name: CI
67+
68+
on:
69+
push:
70+
branches: [main]
71+
72+
jobs:
73+
deploy:
74+
runs-on: ubuntu-latest
75+
steps:
76+
- name: Deploy
77+
# Shouldn't match when using GITHUB_TOKEN
78+
# ok: secrets-without-environment-in-env-block
79+
run: echo "${{ secrets.GITHUB_TOKEN }}"
80+
---
81+
name: CI
82+
83+
on:
84+
push:
85+
branches: [main]
86+
87+
jobs:
88+
deploy:
89+
runs-on: ubuntu-latest
90+
environment: my-env
91+
steps:
92+
- if: ${{ true }}
93+
env:
94+
# Shouldn't match when an environment is used
95+
# ok: secrets-without-environment-in-with-block
96+
foo: ${{ secrets.PROD_API_KEY }}
97+
---
98+
name: CI
99+
100+
on:
101+
push:
102+
branches: [main]
103+
104+
jobs:
105+
deploy:
106+
runs-on: ubuntu-latest
107+
steps:
108+
- if: ${{ true }}
109+
name: Checkout
110+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
111+
with:
112+
# Should match when passing a secret to 'with' block
113+
# ruleid: secrets-without-environment-in-with-block
114+
token: ${{ secrets.MY_TOKEN }}
115+
---
116+
name: CI
117+
118+
on:
119+
push:
120+
branches: [main]
121+
122+
jobs:
123+
deploy:
124+
runs-on: ubuntu-latest
125+
steps:
126+
- if: ${{ true }}
127+
name: Checkout
128+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
129+
# Test with quoted secret + surrounded by other keys
130+
with:
131+
foo1: bar
132+
# ruleid: secrets-without-environment-in-with-block
133+
token: "${{ secrets.MY_TOKEN }}"
134+
foo2: bar
135+
---
136+
name: CI
137+
138+
on:
139+
push:
140+
branches: [main]
141+
142+
# Test surrounded by other jobs
143+
jobs:
144+
prepare:
145+
runs-on: ubuntu-latest
146+
steps:
147+
- if: ${{ true }}
148+
name: Checkout
149+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
150+
deploy:
151+
runs-on: ubuntu-latest
152+
steps:
153+
- if: ${{ true }}
154+
name: Checkout
155+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
156+
with:
157+
# ruleid: secrets-without-environment-in-with-block
158+
token: "${{ secrets.MY_TOKEN }}"
159+
finalize:
160+
runs-on: ubuntu-latest
161+
steps:
162+
- if: ${{ true }}
163+
name: Checkout
164+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
165+
---
166+
name: CI
167+
168+
on:
169+
push:
170+
branches: [main]
171+
172+
jobs:
173+
deploy:
174+
runs-on: ubuntu-latest
175+
steps:
176+
- if: ${{ true }}
177+
name: Checkout
178+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
179+
with:
180+
# Shouldn't match when using GITHUB_TOKEN
181+
# ok: secrets-without-environment-in-with-block
182+
token: ${{ secrets.GITHUB_TOKEN }}
183+
---
184+
name: CI
185+
186+
on:
187+
push:
188+
branches: [main]
189+
190+
jobs:
191+
deploy:
192+
runs-on: ubuntu-latest
193+
environment: my-env
194+
steps:
195+
- if: ${{ true }}
196+
name: Checkout
197+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
198+
with:
199+
# Shouldn't match when an environment is used
200+
# ok: secrets-without-environment-in-with-block
201+
token: ${{ secrets.PROD_API_KEY }}
202+
---
203+
name: CI
204+
205+
on:
206+
push:
207+
branches: [main]
208+
209+
jobs:
210+
reusable-workflow:
211+
uses: saleor/foo
212+
with:
213+
# Environments aren't supported in reusable workflows
214+
# as per https://docs.github.com/en/actions/how-tos/reuse-automations/reuse-workflows
215+
# ok: secrets-without-environment-in-with-block
216+
token: ${{ secrets.PROD_API_KEY }}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# NOTE: this rule assumes ${{ secrets.SECRET }} is NEVER passed inside 'run' blocks
2+
# Meaning the rule ./shell-script-injection.yaml is being used & respected.
3+
rules:
4+
- id: secrets-without-environment-in-env-block
5+
message: >
6+
This GitHub Actions workflow uses secrets but does not configure a job-level
7+
environment. Secrets should be scoped to a GitHub Environment to enforce
8+
protection rules (reviewers, deployment gates, etc) and to reduce blast radius.
9+
severity: WARNING
10+
languages: [yaml]
11+
metadata:
12+
category: security
13+
technology: github-actions
14+
cwe: "CWE-284: Improper Access Control"
15+
likelihood: MEDIUM
16+
confidence: HIGH
17+
impact: HIGH
18+
19+
patterns:
20+
- pattern-inside: |
21+
steps: [...]
22+
- pattern-inside: |
23+
env: ...
24+
...
25+
- pattern: |
26+
env: $X
27+
- metavariable-pattern:
28+
language: generic
29+
metavariable: $X
30+
patterns:
31+
- pattern: "${{ secrets.$SECRET }}"
32+
# GITHUB_TOKEN can be OK without environment as it's a dynamic secret
33+
- pattern-not: "${{ secrets.GITHUB_TOKEN }}"
34+
# Exclude jobs that properly define an environment
35+
- pattern-not-inside: |
36+
jobs:
37+
...
38+
$JOB:
39+
...
40+
environment:
41+
...
42+
- focus-metavariable: $SECRET
43+
- id: secrets-without-environment-in-with-block
44+
message: >
45+
This GitHub Actions workflow uses secrets but does not configure a job-level
46+
environment. Secrets should be scoped to a GitHub Environment to enforce
47+
protection rules (reviewers, deployment gates, etc) and to reduce blast radius.
48+
severity: WARNING
49+
languages: [yaml]
50+
metadata:
51+
category: security
52+
technology: github-actions
53+
cwe: "CWE-284: Improper Access Control"
54+
confidence: medium
55+
56+
patterns:
57+
- pattern-inside: |
58+
steps: [...]
59+
- pattern-inside: |
60+
with: ...
61+
...
62+
- pattern: |
63+
with: $X
64+
- metavariable-pattern:
65+
language: generic
66+
metavariable: $X
67+
patterns:
68+
- pattern: "${{ secrets.$SECRET }}"
69+
- pattern-not: "${{ secrets.GITHUB_TOKEN }}"
70+
# GITHUB_TOKEN can be OK without environment as it's a dynamic secret
71+
- pattern-not-inside: |
72+
jobs:
73+
...
74+
$JOB:
75+
...
76+
environment:
77+
...
78+
- focus-metavariable: $SECRET

0 commit comments

Comments
 (0)