Skip to content

Commit d43feba

Browse files
authored
Add Semgrep in CircleCI (#1910)
Related tiny-pilot/tinypilot-pro#1595 This PR runs [Semgrep](https://github.com/semgrep/semgrep), a Static Application Security Testing (SAST) tool, on our code in CircleCI. This PR also addresses Semgrep's [9 initial findings](https://app.circleci.com/pipelines/github/tiny-pilot/tinypilot/4840/workflows/b0fb5361-fcb6-462e-bcbe-53dc42e16af9/jobs/36859?invite=true#step-104-1533_19). Notes: 1. Seeing as Semgrep runs SAST on multiple languages (not just Python), we run it via a new `dev-scripts/check-security` script. 2. We only use the free version of Semgrep (i.e., [local scans](https://semgrep.dev/docs/getting-started/cli)). Otherwise, Semgrep want us to login and pay for additional security rules. 3. Semgrep flagged `janus.js` as allowing any origin (i.e., `*`) to listen for [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#targetorigin) messages. Looking at the Janus code, `postMessage` is only used for an (outdated) [Janus WebRTC Screensharing chrome extension](https://chromewebstore.google.com/detail/janus-webrtc-screensharin/hapfgfdkleiggjjpfpenajgdnfckjpaj?hl=fil&gl=001). So I've just removed the origin wildcard: - https://github.com/tiny-pilot/tinypilot/blob/0276b1358f3fc976a27aa386a31e1966212dff03/app/static/third-party/janus-gateway/1.3.2/janus.js#L73 <a data-ca-tag href="https://codeapprove.com/pr/tiny-pilot/tinypilot/1910"><img src="https://codeapprove.com/external/github-tag-allbg.png" alt="Review on CodeApprove" /></a>
1 parent 5381c00 commit d43feba

File tree

13 files changed

+80
-7
lines changed

13 files changed

+80
-7
lines changed

.circleci/config.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,23 @@ jobs:
149149
- run:
150150
name: Run build script
151151
command: ./dev-scripts/check-javascript
152+
check_security:
153+
executor: python
154+
steps:
155+
- checkout
156+
- run:
157+
name: Create virtual environment
158+
command: python3 -m venv venv
159+
- run:
160+
name: Install requirements
161+
command: |
162+
. venv/bin/activate
163+
pip install --requirement dev_requirements.txt
164+
- run:
165+
name: Run Static Application Security Testing (SAST) on files
166+
command: |
167+
. venv/bin/activate
168+
./dev-scripts/check-security
152169
run_e2e_tests:
153170
docker:
154171
# To run tests against the dev server, Playwright needs a CircleCI image
@@ -303,6 +320,7 @@ workflows:
303320
- decode_edid
304321
- check_python
305322
- check_javascript
323+
- check_security
306324
- run_e2e_tests
307325
- build_debian_package:
308326
requires:

CONTRIBUTING.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,3 +601,29 @@ const confirmDeleteButton = document.querySelector("#delete .confirm-btn");
601601
- You are responsible for fixing formatting and tests to ensure that your code passes build checks in CI.
602602
603603
I try to review all PRs within one business day. If you've been waiting longer than this, feel free to comment on the PR to verify that it's on my radar.
604+
605+
## Quirks of this project
606+
607+
### Patch security in `janus.js`
608+
609+
Semgrep flagged `janus.js` as allowing any origin (i.e., `*`) to listen for [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#targetorigin) messages: [`javascript.browser.security.wildcard-postmessage-configuration.wildcard-postmessage-configuration`](https://semgrep.dev/r?q=javascript.browser.security.wildcard-postmessage-configuration.wildcard-postmessage-configuration)
610+
611+
Janus only uses `postMessage` for a seemingly outdated [Janus WebRTC Screensharing chrome extension](https://chromewebstore.google.com/detail/janus-webrtc-screensharin/hapfgfdkleiggjjpfpenajgdnfckjpaj?hl=fil&gl=001), which we don't use.
612+
613+
To resolve this issue, we simply removed the wildcard origin:
614+
615+
```diff
616+
--- app/static/third-party/janus-gateway/1.3.2/janus.js
617+
+++ app/static/third-party/janus-gateway/1.3.2/janus.js
618+
@@ -70,7 +70,7 @@ var Janus = (function (factory) {
619+
return callback(error);
620+
}, 1000);
621+
this.cache[pending] = callback;
622+
- window.postMessage({ type: 'janusGetScreen', id: pending }, '*');
623+
+ window.postMessage({ type: 'janusGetScreen', id: pending });
624+
},
625+
init: function () {
626+
let cache = {};
627+
```
628+
629+
Note that this patch should be reapplied whenever we update `janus.js`.

app/db/store.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,9 @@ def create_or_open(db_path):
130130
transaction.executescript('BEGIN; ' + _MIGRATIONS[i])
131131
# SQlite doesn’t allow prepared statements for PRAGMA queries.
132132
# That’s okay here, since we know our query is safe.
133-
transaction.execute(f'PRAGMA user_version={i+1}')
133+
# pylint: disable=line-too-long
134+
transaction.execute( # nosemgrep: formatted-sql-query, sqlalchemy-execute-raw-query
135+
f'PRAGMA user_version={i+1}')
134136
logger.info('Applied migration, counter is now at %d', i + 1)
135137

136138
return connection

app/license_notice.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,8 +317,10 @@ def _get_project_metadata(project_name):
317317
return None
318318

319319

320-
def _make_plaintext_response(response_body):
321-
response = flask.make_response(response_body, 200)
320+
def _make_plaintext_response(text):
321+
# The content is rendered as plain text which mitigates the risk of XSS.
322+
response = flask.make_response( # nosemgrep: make-response-with-unknown-content
323+
text, 200)
322324
response.mimetype = 'text/plain'
323325
return response
324326

app/secret_key.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ def _create():
7575
Raises:
7676
IOError: If an error occured while accessing the secret key file.
7777
"""
78-
logger.info('Creating new flask secret key at %s', _SECRET_KEY_FILE)
78+
# We're only logging the secret key file path, which isn't sensitive.
79+
logger.info( # nosemgrep: python-logger-credential-disclosure
80+
'Creating new flask secret key at %s', _SECRET_KEY_FILE)
7981
secret_key = os.urandom(_SECRET_KEY_BYTE_LENGTH)
8082
with atomic_file.create(_SECRET_KEY_FILE,
8183
chmod_mode=_SECRET_KEY_FILE_PERMS) as file:

app/secret_key_test.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ def test_get_or_create_will_recreate_if_file_has_invalid_perms(self):
2727
with tempfile.NamedTemporaryFile() as mock_secret_key_file:
2828
mock_secret_key_file.write(b'0' * 32)
2929
mock_secret_key_file.flush()
30-
os.chmod(mock_secret_key_file.name, 0o700)
30+
# We're testing that insecure file permissions aren't accepted.
31+
os.chmod( # nosemgrep: insecure-file-permissions
32+
mock_secret_key_file.name, 0o700)
3133

3234
with mock.patch.object(secret_key, '_SECRET_KEY_FILE',
3335
mock_secret_key_file.name):

app/static/third-party/janus-gateway/1.3.2/janus.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ var Janus = (function (factory) {
7070
return callback(error);
7171
}, 1000);
7272
this.cache[pending] = callback;
73-
window.postMessage({ type: 'janusGetScreen', id: pending }, '*');
73+
window.postMessage({ type: 'janusGetScreen', id: pending });
7474
},
7575
init: function () {
7676
let cache = {};

app/version.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ def latest_version():
8484
"""
8585
try:
8686
# The URL is trusted because it's not based on user input.
87-
with urllib.request.urlopen( # noqa: S310
87+
# pylint: disable=line-too-long
88+
with urllib.request.urlopen( # noqa: S310 # nosemgrep: dynamic-urllib-use-detected
8889
f'{env.GATEKEEPER_BASE_URL}/community/available-update',
8990
timeout=10) as response:
9091
response_bytes = response.read()

debian-pkg/opt/tinypilot-privileged/scripts/update

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ set -u
99
# Exit on first error.
1010
set -e
1111

12+
# The URL is trusted.
13+
# nosemgrep: curl-pipe-bash
1214
curl \
1315
--silent \
1416
--show-error \

dev-scripts/check-all

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ set -u
1818
./dev-scripts/check-javascript
1919
./dev-scripts/check-privilege-guard
2020
./dev-scripts/check-python
21+
./dev-scripts/check-security
2122
./dev-scripts/check-sql
2223
./dev-scripts/check-style
2324
./dev-scripts/check-trailing-whitespace

0 commit comments

Comments
 (0)