Skip to content

Commit c09f2e2

Browse files
authored
Enable Bandit security linter (#633)
* nidaqmx: Add a dependency on the Bandit security linter * nidaqmx: Update poetry.lock * nidaqmx: Address or acknowledge potential security issues * github: Run Bandit in build.yml
1 parent c60e5a8 commit c09f2e2

File tree

5 files changed

+221
-17
lines changed

5 files changed

+221
-17
lines changed

.github/workflows/build.yml

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ jobs:
3232
run: poetry run mypy
3333
- name: Run mypy (Windows)
3434
run: poetry run mypy --platform win32
35+
- name: Run Bandit security checks
36+
run: poetry run bandit -c pyproject.toml -r generated/nidaqmx
3537
- name: Generate ni-daqmx files
3638
run: |
3739
rm -fr generated/nidaqmx

generated/nidaqmx/_install_daqmx.py

+22-7
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
import pathlib
88
import re
99
import shutil
10-
import subprocess
10+
import subprocess # nosec: B404
1111
import sys
1212
import tempfile
1313
import traceback
1414
import requests
1515
import zipfile
1616
from typing import Generator, List, Optional, Tuple
17+
from urllib.parse import urlparse
1718

1819
import click
1920

@@ -30,6 +31,7 @@
3031
_logger = logging.getLogger(__name__)
3132

3233
METADATA_FILE = "_installer_metadata.json"
34+
_NETWORK_TIMEOUT_IN_SECONDS = 60
3335

3436

3537
def _parse_version(version: str) -> Tuple[int, ...]:
@@ -96,7 +98,8 @@ def _get_daqmx_installed_version() -> Optional[str]:
9698
_logger.debug("Checking for installed NI-DAQmx version")
9799
commands_info = LINUX_COMMANDS[distribution]
98100
query_command = commands_info.get_daqmx_version
99-
query_output = subprocess.run(query_command, stdout=subprocess.PIPE, text=True).stdout
101+
# Run the package query command defined by _linux_installation_commands.py.
102+
query_output = subprocess.run(query_command, stdout=subprocess.PIPE, text=True).stdout # nosec: B603
100103

101104
if distribution == "ubuntu":
102105
version_match = re.search(r"ii\s+ni-daqmx\s+(\d+\.\d+\.\d+)", query_output)
@@ -233,16 +236,17 @@ def _install_daqmx_driver_windows_core(download_url: str) -> None:
233236
Download and launch NI-DAQmx Driver installation in an interactive mode
234237
235238
"""
236-
temp_file = None
239+
_validate_download_url(download_url)
237240
try:
238241
with _multi_access_temp_file() as temp_file:
239242
_logger.info("Downloading Driver to %s", temp_file)
240-
response = requests.get(download_url)
243+
response = requests.get(download_url, timeout=_NETWORK_TIMEOUT_IN_SECONDS)
241244
response.raise_for_status()
242245
with open(temp_file, 'wb') as f:
243246
f.write(response.content)
244247
_logger.info("Installing NI-DAQmx")
245-
subprocess.run([temp_file], shell=True, check=True)
248+
# Run the installer that we just downloaded from https://download.ni.com.
249+
subprocess.run([temp_file], shell=True, check=True) # nosec: B602
246250
except subprocess.CalledProcessError as e:
247251
_logger.info("Failed to install NI-DAQmx driver.", exc_info=True)
248252
raise click.ClickException(
@@ -262,10 +266,11 @@ def _install_daqmx_driver_linux_core(download_url: str, release: str) -> None:
262266
263267
"""
264268
if sys.platform.startswith("linux"):
269+
_validate_download_url(download_url)
265270
try:
266271
with _multi_access_temp_file(suffix=".zip") as temp_file:
267272
_logger.info("Downloading Driver to %s", temp_file)
268-
response = requests.get(download_url)
273+
response = requests.get(download_url, timeout=_NETWORK_TIMEOUT_IN_SECONDS)
269274
response.raise_for_status()
270275
with open(temp_file, 'wb') as f:
271276
f.write(response.content)
@@ -283,7 +288,10 @@ def _install_daqmx_driver_linux_core(download_url: str, release: str) -> None:
283288
directory_to_extract_to, distro.id(), distro.version(), release
284289
):
285290
print("\nRunning command:", " ".join(command))
286-
subprocess.run(command, check=True)
291+
# Run the commands defined in
292+
# _linux_installation_commands.py to install the package
293+
# that we just downloaded from https://download.ni.com.
294+
subprocess.run(command, check=True) # nosec: B603
287295

288296
# Check if the installation was successful
289297
if not _get_daqmx_installed_version():
@@ -312,6 +320,13 @@ def _install_daqmx_driver_linux_core(download_url: str, release: str) -> None:
312320
raise NotImplementedError("This function is only supported on Linux.")
313321

314322

323+
def _validate_download_url(download_url: str) -> None:
324+
"""Velidate that the download URL uses https and points to a trusted site."""
325+
parsed_url = urlparse(download_url)
326+
if parsed_url.scheme != "https" or parsed_url.netloc != "download.ni.com":
327+
raise click.ClickException(f"Unsupported download URL: {download_url}")
328+
329+
315330
def _ask_user_confirmation(user_message: str) -> bool:
316331
"""
317332
Prompt for user confirmation

poetry.lock

+169-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+6
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ matplotlib = {version=">=3.9.0", python = ">=3.9"}
7575
nptdms = ">=1.9.0"
7676

7777
[tool.poetry.group.lint.dependencies]
78+
bandit = { version = ">=1.7", extras = ["toml"] }
7879
ni-python-styleguide = ">=0.4.1"
7980
mypy = ">=1.0"
8081
types-protobuf = "^4.21"
@@ -149,3 +150,8 @@ module = [
149150
"nidaqmx.*",
150151
]
151152
ignore_missing_imports = true
153+
154+
[tool.bandit]
155+
skips = [
156+
"B101", # assert_used
157+
]

0 commit comments

Comments
 (0)