Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace bandit with ruff #6961

Merged
merged 1 commit into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -307,13 +307,6 @@ jobs:
DOCKER_BUILD_ARGUMENTS="--cache-from securedrop-test-focal-py3:${fromtag:-latest}" securedrop/bin/dev-shell \
bash -c "pip3 install -U -q --upgrade safety && make -C .. safety"

- run:
name: Run static security testing on source code with bandit
command: |
fromtag=$(docker images |grep securedrop-test-focal-py3 |head -n1 |awk '{print $2}')
DOCKER_BUILD_ARGUMENTS="--cache-from securedrop-test-focal-py3:${fromtag:-latest}" securedrop/bin/dev-shell \
bash -c "pip3 install -U -q --upgrade pip && pip3 install -U -q --upgrade bandit && make -C .. bandit"

- run:
name: Run static security testing on source code with semgrep
command: |
Expand Down
13 changes: 0 additions & 13 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -177,19 +177,6 @@ safety: ## Run `safety check` to check python dependencies for vulnerabilities.
done
@echo

# Bandit is a static code analysis tool to detect security vulnerabilities in Python applications
# https://wiki.openstack.org/wiki/Security/Projects/Bandit
.PHONY: bandit

bandit: test-config ## Run bandit with medium level excluding test-related folders.
@command -v bandit || (echo "Please run 'pip install -U bandit'."; exit 1)
@echo "███ Running bandit..."
@bandit -ll --exclude ./admin/.tox,./admin/.venv,./admin/.eggs,./molecule,./testinfra,./securedrop/tests,./.tox,./.venv*,securedrop/config.py,./target --recursive .
@echo "███ Running bandit on securedrop/config.py..."
@bandit -ll --skip B108 securedrop/config.py
@echo


# Semgrep is a static code analysis tool to detect security vulnerabilities in Python applications
# This configuration uses the public "p/r2c-security-audit" ruleset
.PHONY: semgrep
Expand Down
4 changes: 2 additions & 2 deletions devops/scripts/verify-mo.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,11 @@ def diffoscope_call(
# because we want to inherit the Python virtual environment
# in which we're invoked.
# nosemgrep: python.lang.security.audit.subprocess-shell-true.subprocess-shell-true
return subprocess.run( # nosec B602
return subprocess.run(
cmd,
capture_output=True,
env=os.environ,
shell=True,
shell=True, # noqa: S602
)

def reproduce(self) -> None:
Expand Down
33 changes: 33 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ select = [
"PYI",
# flake8-return
"RET",
# flake8-bandit
"S",
# flake8-simplify
"SIM",
# pyupgrade
Expand Down Expand Up @@ -58,6 +60,16 @@ ignore = [
"RET503",
# superflous-else- rules, find they hurt readability
"RET505", "RET506", "RET507", "RET508",
# yes, we bind to 0.0.0.0
"S104",
# hardcoded passwords, lots of false positives
"S105", "S106",
# we intentionally don't log stuff sometimes
"S110",
# flags every instance of subprocess
"S603",
# we trust $PATH isn't hijacked
"S607",
# Find contextlib.suppress() is harder to read
"SIM105",
# Find ternary statements harder to read
Expand All @@ -74,6 +86,27 @@ extend-include = ["securedrop/scripts/*"]
# them as third-party to match O.G. isort until we fix our package layout
known-third-party = ["journalist_app", "management", "source_app", "tests"]

[tool.ruff.per-file-ignores]
"**/test**.py" = [
# use of `assert`
"S101",
# weak crypto
"S311",

# insecure temporary file/directory
"S108",
# HTTP request without timeout
"S113",
]
"securedrop/loaddata.py" = [
# ok to use weak crypto here
"S311",
]
"securedrop/pretty_bad_protocol/*.py" = [
# legacy code that still uses `assert`
"S101",
]

[tool.mypy]
ignore_missing_imports = true
no_implicit_optional = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def migrate_nulls() -> None:
conn = op.get_bind()
for table in tables:
result = conn.execute(
f"SELECT 1 FROM {table} WHERE journalist_id IS NULL;" # nosec
f"SELECT 1 FROM {table} WHERE journalist_id IS NULL;" # noqa: S608
).first()
if result is not None:
needs_migration.append(table)
Expand All @@ -80,12 +80,12 @@ def migrate_nulls() -> None:
# unique key violations.
op.execute(
sa.text(
f"UPDATE OR IGNORE {table} SET journalist_id=:journalist_id " # nosec
f"UPDATE OR IGNORE {table} SET journalist_id=:journalist_id " # noqa: S608
"WHERE journalist_id IS NULL;"
).bindparams(journalist_id=deleted_id)
)
# Then we delete any leftovers which had been ignored earlier.
op.execute(f"DELETE FROM {table} WHERE journalist_id IS NULL") # nosec
op.execute(f"DELETE FROM {table} WHERE journalist_id IS NULL") # noqa: S608


def upgrade() -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
def raw_sql_grab_orphaned_objects(table_name: str) -> str:
"""Objects that have a source ID that doesn't exist in the
sources table OR a NULL source ID should be deleted."""
return (
"SELECT id, filename, source_id FROM {table} " # nosec
return ( # noqa: S608
"SELECT id, filename, source_id FROM {table} "
"WHERE source_id NOT IN (SELECT id FROM sources) "
"UNION SELECT id, filename, source_id FROM {table} " # nosec
"UNION SELECT id, filename, source_id FROM {table} "
"WHERE source_id IS NULL"
).format(table=table_name)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ def upgrade() -> None:
# Now things that directly refer to sources
for table in ("source_stars", "submissions", "replies"):
op.execute(
f"DELETE FROM {table} WHERE source_id IN " # nosec
f"DELETE FROM {table} WHERE source_id IN " # noqa: S608
f"(SELECT id FROM sources WHERE filesystem_id IS NULL)"
) # nosec
)
# And now the sources
op.execute("DELETE FROM sources WHERE filesystem_id IS NULL")
with op.batch_alter_table("sources", schema=None) as batch_op:
Expand Down
2 changes: 1 addition & 1 deletion securedrop/journalist.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ def prime_keycache() -> None:
if __name__ == "__main__": # pragma: no cover
debug = getattr(config, "env", "prod") != "prod"
# nosemgrep: python.flask.security.audit.app-run-param-config.avoid_app_run_with_bad_host
app.run(debug=debug, host="0.0.0.0", port=8081) # nosec
app.run(debug=debug, host="0.0.0.0", port=8081)
3 changes: 2 additions & 1 deletion securedrop/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,8 @@ def valid_password(self, passphrase: "Optional[str]") -> bool:
)

# For type checking
assert isinstance(self.pw_hash, bytes)
if not isinstance(self.pw_hash, bytes):
raise RuntimeError("self.pw_hash isn't bytes")

is_valid = compare_digest(self._scrypt_hash(passphrase, self.pw_salt), self.pw_hash)

Expand Down
2 changes: 1 addition & 1 deletion securedrop/pretty_bad_protocol/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
# environs. https://github.com/isislovecruft/python-gnupg/issues/74
if not _user:
# FIXME: Shouldn't we use a randomized tmp directory? (Do we even hit this code path?)
_user = "/tmp/python-gnupg" # nosec B108
_user = "/tmp/python-gnupg" # noqa: S108
try:
os.makedirs(_user)
except OSError:
Expand Down
2 changes: 1 addition & 1 deletion securedrop/request_that_secures_file_uploads.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def _secure_file_stream(
# note in `config.py` for more info. Instead, we just use
# `/tmp`, which has the additional benefit of being
# automatically cleared on reboot.
return SecureTemporaryFile("/tmp") # nosec
return SecureTemporaryFile("/tmp") # noqa: S108
return BytesIO()

def make_form_data_parser(self) -> FormDataParser:
Expand Down
1 change: 0 additions & 1 deletion securedrop/requirements/python3/develop-requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ Jinja2>=3.0.0
ansible-lint>=4.2.0
ansible>=2.9.23,<2.10.0
argon2_cffi>=20.1.0
bandit
black
cffi>=1.14.2
cryptography>=41.0.3
Expand Down
24 changes: 0 additions & 24 deletions securedrop/requirements/python3/develop-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,6 @@ babel==2.12.1 \
babelgladeextractor==0.7.0 \
--hash=sha256:bcf805e28b4bb18c8b6909a65a7cf5c7c2bcbf4ae50b164878c9682d22271798
# via -r requirements/python3/translation-requirements.in
bandit==1.7.0 \
--hash=sha256:216be4d044209fa06cf2a3e51b319769a51be8318140659719aa7a115c35ed07 \
--hash=sha256:8a4c7415254d75df8ff3c3b15cfe9042ecee628a1e40b44c15a98890fbfc2608
# via -r requirements/python3/develop-requirements.in
bcrypt==3.1.3 \
--hash=sha256:05b35b9842b009b44496fa5433ce462f69966291e50fbd471dbb427f399f748f \
--hash=sha256:06280fe19ffbcf6cf904de25190dd6fcd313e30bc79da305f5642a8295d1616e \
Expand Down Expand Up @@ -341,14 +337,6 @@ git-url-parse==1.0.2 \
--hash=sha256:75d1cf1e19534678711e1a9293e6fb978461a5f734adf941cd468a802d08bbb2 \
--hash=sha256:bf0a20c48e8552fea1218b52714be84fa31b0d73c58897e70b765d5e7a26f4cb
# via python-gilt
gitdb2==2.0.3 \
--hash=sha256:b60e29d4533e5e25bb50b7678bbc187c8f6bcff1344b4f293b2ba55c85795f09 \
--hash=sha256:cf9a4b68e8c4da8d42e48728c944ff7af2d8c9db303ac1ab32eac37aa4194b0e
# via gitpython
gitpython==2.1.8 \
--hash=sha256:ad61bc25deadb535b047684d06f3654c001d9415e1971e51c9c20f5b510076e9 \
--hash=sha256:b8367c432de995dc330b5b146c5bfdc0926b8496e100fda6692134e00c0dcdc5
# via bandit
glom==22.1.0 \
--hash=sha256:1510c6587a8f9c64a246641b70033cbc5ebde99f02ad245693678038e821aeb5 \
--hash=sha256:5339da206bf3532e01a83a35aca202960ea885156986d190574b779598e9e772
Expand Down Expand Up @@ -654,7 +642,6 @@ pbr==5.1.1 \
# via
# git-url-parse
# python-gilt
# stevedore
peewee==3.15.0 \
--hash=sha256:48eac70be812ac84daa5400fb8e7b545e0c83adcfa05c8e2a8612f9ced4da495
# via semgrep
Expand Down Expand Up @@ -850,7 +837,6 @@ pyyaml==5.4.1 \
# ansible
# ansible-lint
# aspy.yaml
# bandit
# molecule
# molecule-vagrant
# pre-commit
Expand Down Expand Up @@ -968,7 +954,6 @@ six==1.15.0 \
# -r ../admin/requirements.in
# ansible-lint
# argon2-cffi
# bandit
# bcrypt
# cfgv
# click-completion
Expand All @@ -981,16 +966,7 @@ six==1.15.0 \
# prompt-toolkit
# pynacl
# python-dateutil
# stevedore
# websocket-client
smmap2==2.0.3 \
--hash=sha256:b78ee0f1f5772d69ff50b1cbdb01b8c6647a8354f02f23b488cf4b2cfc923956 \
--hash=sha256:c7530db63f15f09f8251094b22091298e82bf6c699a6b8344aaaef3f2e1276c3
# via gitdb2
stevedore==1.28.0 \
--hash=sha256:e3d96b2c4e882ec0c1ff95eaebf7b575a779fd0ccb4c741b9832bed410d58b3d \
--hash=sha256:f1c7518e7b160336040fee272174f1f7b29a46febb3632502a8f2055f973d60b
# via bandit
tabulate==0.8.7 \
--hash=sha256:ac64cb76d53b1231d364babcd72abbb16855adac7de6665122f97b593f1eb2ba \
--hash=sha256:db2723a20d04bcda8522165c73eea7c300eda74e0ce852d9022e0159d7895007
Expand Down
2 changes: 1 addition & 1 deletion securedrop/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
if __name__ == "__main__": # pragma: no cover
debug = getattr(config, "env", "prod") != "prod"
# nosemgrep: python.flask.security.audit.app-run-param-config.avoid_app_run_with_bad_host
app.run(debug=debug, host="0.0.0.0", port=8080) # nosec
app.run(debug=debug, host="0.0.0.0", port=8080)
2 changes: 1 addition & 1 deletion securedrop/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ def save_file_submission(

encrypted_file_name = f"{count}-{journalist_filename}-doc.gz.gpg"
encrypted_file_path = self.path(filesystem_id, encrypted_file_name)
with SecureTemporaryFile("/tmp") as stf: # nosec
with SecureTemporaryFile("/tmp") as stf: # noqa: S108
with gzip.GzipFile(filename=sanitized_filename, mode="wb", fileobj=stf, mtime=0) as gzf:
# Buffer the stream into the gzip file to avoid excessive
# memory consumption
Expand Down
4 changes: 2 additions & 2 deletions securedrop/tests/migrations/migration_de00920916bf.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ class Helper:
def __init__(self):
self.journalist_id = None

def create_journalist(self, otp_secret="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"):
def create_journalist(self):
if self.journalist_id is not None:
raise RuntimeError("Journalist already created")

params = {
"uuid": str(uuid.uuid4()),
"username": random_chars(50),
"session_nonce": 0,
"otp_secret": otp_secret,
"otp_secret": "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",
}
sql = """INSERT INTO journalists (uuid, username, otp_secret, session_nonce)
VALUES (:uuid, :username, :otp_secret, :session_nonce)
Expand Down
4 changes: 2 additions & 2 deletions securedrop/two_factor.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class HOTP:
_LENGTH = 6

# nosemgrep: python.cryptography.security.insecure-hash-algorithms.insecure-hash-algorithm-sha1
_ALGORITHM = SHA1() # nosec B303
_ALGORITHM = SHA1() # noqa: S303

_LOOK_AHEAD_WINDOW_SIZE = 20

Expand Down Expand Up @@ -84,7 +84,7 @@ class TOTP:
_TIME_STEP = 30

# nosemgrep: python.cryptography.security.insecure-hash-algorithms.insecure-hash-algorithm-sha1
_ALGORITHM = SHA1() # nosec B303
_ALGORITHM = SHA1() # noqa: S303

# Minimum length for ascii-encoded OTP secrets - by default, secrets are now 160-bit (32 chars)
# but existing Journalist users may still have 80-bit (16-char) secrets
Expand Down