Skip to main content

codelaw-rules-security-python

codelaw's Python security rules (Bandit's set, Django, Flask, FastAPI, ML loaders) — one of codelaw's first-party rule packs (SR-RULE-PACKS). It ships on PyPI only, as codelaw-rules-security-python (L6 decision 11).

# codelaw.toml
extends = ["codelaw:py/recommended", "codelaw:sec-py/recommended"]
preview = true   # every rule is `preview` until a corpus run promotes it

codelaw init does not add it: a security pack is chosen, not detected.

Presets

preset rules ported from
sec-py/recommended 57 high-confidence rules, at error
sec-py/strict 61 sec-py/recommended and the heuristic rules, at warn

recommended holds only high-confidence rules, at error; strict adds the heuristic ones, at warn, so a team can start without noise.

Rules

61 rules, each a match: rule (no Rust). Every rule carries security: { cwe, owasp, severity } (SR-RULE-FORMAT decision 2a), so

  • its finding names the CWE (sec-py/<rule> (CWE-79) …);
  • codelaw rules --tag cwe:79 and --tag owasp:A05 find it, and codelaw rules --security-coverage puts it in the OWASP Top 10:2025 matrix;
  • SARIF carries its security and external/cwe/cwe-<n> tags and a security-severity that GitHub code scanning ranks by (SR-CLI-MCP decision 4).
rule what it flags CWE OWASP 2025 fix preset
sec-py/no-exec-eval exec, eval and compile are not given a value that is not a string literal 94 A05 manual recommended
sec-py/no-shell A subprocess is not started with shell=True, and os.system / os.popen are not given a value that is not a literal 78 A05 suggest recommended
sec-py/no-sql-string A SQL statement is not built with an f-string, %, + or .format; the values go in as parameters 89 A05 manual recommended
sec-py/no-unsafe-deserialize Data is not loaded with pickle, cPickle, dill, marshal, shelve or jsonpickle 502 A08 manual recommended
sec-py/yaml-safe-load YAML is read with yaml.safe_load, or with yaml.load and SafeLoader 502 A08 suggest recommended
sec-py/torch-load-weights-only torch.load is called with weights_only=True 502 A08 suggest recommended
sec-py/hf-pin-revision from_pretrained, hf_hub_download, snapshot_download and load_dataset are called with revision= 494 A03 manual recommended
sec-py/no-xml-unsafe-parser XML from outside the program is parsed with defusedxml, not with xml.etree, minidom, sax or lxml 611 A05 suggest recommended
sec-py/no-tls-bypass Certificate verification is not switched off (verify=False, _create_unverified_context), and an SSL context is not pinned to an old protocol 295 A04 suggest recommended
sec-py/requests-timeout requests and httpx calls pass timeout= 400 A06 suggest recommended
sec-py/no-weak-hash hashlib.md5 and hashlib.sha1 pass usedforsecurity=False, or are replaced by SHA-256 327 A04 suggest recommended
sec-py/no-insecure-random A password, token, key, nonce or code is not made with the random module 338 A04 suggest recommended
sec-py/no-hardcoded-secret A password, key or token is not a string literal in the code, and a cloud access key is not pasted into it 798 A07 manual recommended
sec-py/no-insecure-tempfile Temporary files are made with tempfile.mkstemp, NamedTemporaryFile or TemporaryDirectory, not mktemp, and not at a fixed path under /tmp 377 A01 suggest recommended
sec-py/tarfile-filter TarFile.extractall and shutil.unpack_archive pass filter="data" 22 A01 suggest recommended
sec-py/no-path-traversal A value from the request is not passed to open, send_file, send_from_directory or FileResponse as a path 22 A01 manual recommended
sec-py/no-ssrf A value from the request is not the URL given to requests, httpx or urllib 918 A01 manual recommended
sec-py/no-open-redirect A value from the request is not passed to redirect, HttpResponseRedirect or RedirectResponse 601 A01 manual recommended
sec-py/jinja-autoescape Environment is not created with autoescape=False or without autoescape, and render_template_string / Template / from_string are not given a template that is not a literal 79, 1336 A05 suggest recommended
sec-py/no-mark-safe mark_safe, SafeString, Markup and format_html are not given a value that is not a literal 79 A05 manual recommended
sec-py/flask-debug app.run(debug=True) is not written in code 489 A02 suggest recommended
sec-py/django-settings In a settings module, DEBUG, ALLOWED_HOSTS, SECRET_KEY and the cookie flags are not left in their development values 489, 798, 614 A02 manual recommended
sec-py/no-csrf-exempt A view is not decorated with @csrf_exempt 352 A01 manual recommended
sec-py/fastapi-cors CORSMiddleware is not given allow_origins=["*"] together with allow_credentials=True 942 A02 manual recommended
sec-py/no-assert-for-auth A permission, role or authentication test is not written as an assert 703 A10 manual recommended
sec-py/no-silent-except An except that catches everything (bare, Exception or BaseException) does not just pass or continue 703 A10 manual recommended
sec-py/paramiko-host-key paramiko is not told to accept unknown host keys with AutoAddPolicy or WarningPolicy 295 A07 manual recommended
sec-py/no-world-writable os.chmod and os.umask do not give write access to everyone 732 A01 suggest recommended
sec-py/no-secret-in-log A value named password, token or secret is not written to a log 532 A09 manual recommended
sec-py/no-llm-output-to-sink The reply of an OpenAI, Anthropic or LangChain call does not reach exec, eval, a subprocess, SQL or mark_safe 94 A05 manual strict
sec-py/no-ldap-injection An LDAP search filter is not made with an f-string, %, + or .format 90 A05 manual recommended
sec-py/no-xpath-injection An XPath expression is not made with an f-string, %, + or .format; values go in as XPath variables 643 A05 manual recommended
sec-py/no-nosql-injection A value from the request is not used whole as a pymongo filter, and a $where expression is not built from a request value 943 A05 manual recommended
sec-py/no-header-injection A value from the request is not written into a response header or a cookie value without its line breaks removed 113 A05 manual recommended
sec-py/no-log-injection A value from the request is not passed to a logger without its line breaks removed 117 A09 manual strict
sec-py/cookie-flags set_cookie passes secure, httponly and samesite, and none of them is False or None 614, 1004 A02 suggest recommended
sec-py/no-insecure-protocol telnetlib, ftplib and SNMP community strings (v1/v2c) are not used 319 A04 manual recommended
sec-py/urlopen-scheme urllib.request.urlopen and urlretrieve are not given a value that is not an http(s) literal 22, 918 A01 manual recommended
sec-py/no-weak-cipher A weak cipher (DES, 3DES, ARC2, ARC4, Blowfish, CAST5, IDEA) or ECB mode is not used 327 A04 manual recommended
sec-py/no-pycrypto Crypto is not imported from the unmaintained pycrypto package 1104 A03 manual recommended
sec-py/weak-key-size An RSA or DSA key is not generated shorter than 2048 bits, and an elliptic curve is at least 224 bits 326 A04 suggest recommended
sec-py/password-hash-effort A password is not hashed with a plain hashlib digest, pbkdf2_hmac runs at least 600 000 iterations, and bcrypt uses at least 10 rounds 916 A04 manual recommended
sec-py/no-hardcoded-crypto-key A cipher key, HMAC key, IV or nonce is not a literal 321, 329 A04 manual recommended
sec-py/no-mako mako.template.Template is not used 79 A05 manual recommended
sec-py/logging-listen logging.config.listen() is not called 94 A08 manual recommended
sec-py/no-wildcard-injection A command that runs tar, chown, chmod or rsync on * is not started through a shell 155 A05 manual recommended
sec-py/no-partial-path A subprocess is not started with a bare program name such as "git" 426 A08 manual strict
sec-py/no-xmlrpc An xmlrpc client or server is not created without defusedxml.xmlrpc.monkey_patch() in the program 776 A05 manual recommended
sec-py/no-mass-assignment Model(**request.POST), Model.objects.create(**request.data) and update(**request.json) are not used, and a ModelForm or serializer does not set fields = "__all__" 915 A01 manual recommended
sec-py/no-reflected-xss A value from the request is not passed to HttpResponse, make_response or HTMLResponse, or built into a returned string, without escaping 79 A05 manual recommended
sec-py/flask-settings SECRET_KEY is not a literal, and WTF_CSRF_ENABLED and SESSION_COOKIE_SECURE are not switched off in the app configuration 798, 352, 614 A02 manual recommended
sec-py/no-dynamic-import A value from the request is not passed to __import__, importlib.import_module or getattr 470 A05 manual recommended
sec-py/upload-secure-filename A file from a request is not saved under the name the client gave it 434, 22 A01 suggest recommended
sec-py/ml-unsafe-load numpy.load(allow_pickle=True), joblib.load, pandas.read_pickle and Keras load_model(safe_mode=False) are not used 502 A08 manual recommended
sec-py/no-trust-remote-code from_pretrained, load_dataset and pipeline are not called with trust_remote_code=True 94 A03 manual recommended
sec-py/no-prompt-injection A value from the request is not part of the system prompt of a language-model call 1427 A05 manual recommended
sec-py/no-bind-all-interfaces A socket or server is not bound to 0.0.0.0 in the source 605 A02 manual strict
sec-py/no-regex-injection A value from the request is not used as the pattern of re.compile, re.search, re.match or the other re functions 730, 1333 A06 suggest recommended
sec-py/no-format-string-injection A value from the request is not the receiver of .format 134 A05 manual recommended
sec-py/no-cleartext-http requests and httpx are not called with an http:// URL, except for localhost 319 A04 manual recommended
sec-py/no-stack-trace-response A response does not contain traceback.format_exc() or the text of the exception 209 A10 manual recommended

A rule that says suggest has a fix: that codelaw fix --unsafe applies to the common shape of the code; a match of another shape stays manual, with the steps in the rule's How to fix. Three rules the catalogue called suggest are manual here, because no edit of the code alone is right: sec-py/no-xpath-injection (the expression has to be rewritten around variables), sec-py/no-xmlrpc (the patch must run at start-up) and sec-py/no-cleartext-http (the server must serve HTTPS first).

What the rules see

The rules match the code as written. They follow an alias only where a rule says so (sec-py/no-xml-unsafe-parser and sec-py/no-mako use the imported-from predicate); import numpy as np is matched by its common name np, and from subprocess import run is not matched at all. A value that a function receives as a parameter is not a request value.

from_source: is not taint analysis

A rule here that asks whether a sink's argument comes from a request uses the from_source: predicate (SR-RULE-FORMAT decision 3a). It holds when the argument is a listed request source (or a method called on one), is built from one by concatenation, interpolation or ?:, or is a local name bound to one in the same function — one or more assignments or destructurings away. It never follows a call, a return value, a parameter, a closure's outer variable or another file, so a value that crosses a function boundary is not seen (cross-function flow waits on a deeper analysis, as B-049's type-aware rules do). Every rule page that uses it says so. The sources are listed per framework in this pack's data/sources/<framework>.json: Django (request.GET, request.POST, request.COOKIES, request.body, request.FILES, request.META, request.headers, DRF's request.data and request.query_params), Flask (request.args, form, values, json, get_json, data, cookies, files, headers, view_args) and FastAPI/Starlette's Request (query_params, path_params, headers, cookies, form, json, body).

Two limits follow. FastAPI's typed handler parameters (def f(q: str)) are function parameters, so they are not sources; only the request object is. And a method called on a source stays a source: request.args.get("x").strip() is still the request value, so the way out of a finding is to pass the value through a function of your own.

Not ported, and why

This pack accounts for every check of Bandit (Apache-2.0) and the Ruff S rules (MIT) that mirror it. The rules are written for codelaw; ported_from: names the Bandit and Ruff ids they cover (NOTICE).

Bandit Ruff status where
B101 assert_used S101 partly sec-py/no-assert-for-auth: only an assert that is an access check; an assert in general is not a security flaw
B102 exec_used S102 ported sec-py/no-exec-eval
B103 set_bad_file_permissions S103 ported sec-py/no-world-writable
B104 hardcoded_bind_all_interfaces S104 ported sec-py/no-bind-all-interfaces (strict)
B105, B106, B107 hard-coded passwords S105, S106, S107 ported sec-py/no-hardcoded-secret; Django and Flask settings in sec-py/django-settings and sec-py/flask-settings
B108 hardcoded_tmp_directory S108 ported sec-py/no-insecure-tempfile
B110, B112 try_except_pass, try_except_continue S110, S112 ported sec-py/no-silent-except
B113 request_without_timeout S113 ported sec-py/requests-timeout
B201 flask_debug_true S201 ported sec-py/flask-debug
B202 tarfile_unsafe_members S202 ported sec-py/tarfile-filter
B301, B302 pickle, marshal S301, S302 ported sec-py/no-unsafe-deserialize
B303 md5, sha1 S303 ported sec-py/no-weak-hash
B304, B305 ciphers and modes S304, S305 ported sec-py/no-weak-cipher
B306 mktemp_q S306 ported sec-py/no-insecure-tempfile
B307 eval S307 ported sec-py/no-exec-eval
B308 mark_safe S308 ported sec-py/no-mark-safe
B309 httpsconnection - not ported Python 2.7 and early 3.x only: http.client.HTTPSConnection verifies certificates by default since 3.4.3
B310 urllib_urlopen S310 ported sec-py/urlopen-scheme
B311 random S311 ported sec-py/no-insecure-random: only when the value is bound to a secret-like name; random for a dice roll is fine
B312 telnetlib S312 ported sec-py/no-insecure-protocol (at the import)
B313 to B320 XML calls S313 to S320 ported sec-py/no-xml-unsafe-parser
B321 ftplib S321 ported sec-py/no-insecure-protocol (at the import)
B322 input - not ported Python 2's input evaluates its input; Python 3's does not
B323 unverified_context S323 ported sec-py/no-tls-bypass
B324 hashlib S324 ported sec-py/no-weak-hash
B325 tempnam S325 ported sec-py/no-insecure-tempfile (os.tempnam, os.tmpnam)
B401, B402 telnetlib, ftplib imports S401, S402 ported sec-py/no-insecure-protocol
B403 pickle import S403 covered elsewhere the load call is flagged by sec-py/no-unsafe-deserialize; importing pickle to write it is not a flaw
B404 subprocess import S404 not ported an import alone flags every program that runs a command; the calls are sec-py/no-shell and sec-py/no-partial-path
B405 to B410 XML imports S405 to S410 covered elsewhere the parse calls are flagged by sec-py/no-xml-unsafe-parser; the import of a parser is not
B411 xmlrpc import S411 ported sec-py/no-xmlrpc (at the client or server call)
B412 httpoxy S412 not ported CGI handlers (wsgiref.handlers.CGIHandler) are rare, and web servers now drop the Proxy header
B413 pycrypto S413 ported sec-py/no-pycrypto
B414 pycryptodome - not ported Bandit dropped it: pycryptodome is maintained
B415 pyghmi S415 not ported plain-text IPMI, a niche library; sec-py/no-insecure-protocol covers the common ones
B501 to B504 TLS S501 to S504 ported sec-py/no-tls-bypass
B505 weak_cryptographic_key S505 ported sec-py/weak-key-size
B506 yaml_load S506 ported sec-py/yaml-safe-load
B507 ssh_no_host_key_verification S507 ported sec-py/paramiko-host-key
B508 snmp_insecure_version S508 ported sec-py/no-insecure-protocol
B509 snmp_weak_cryptography S509 not ported needs the UsmUserData protocol arguments checked; only the v1/v2c community string is flagged
B601 paramiko_calls S601 not ported exec_command runs on the remote host and is flagged by Bandit for any call; no safe rewrite to name
B602, B604, B605 shell S602, S604, S605 ported sec-py/no-shell
B603 subprocess_without_shell_equals_true S603 not ported informational in Bandit and flags every subprocess call; the dangerous cases are sec-py/no-shell and sec-py/no-wildcard-injection
B606 start_process_with_no_shell S606 not ported os.exec* and os.spawn* are rare; sec-py/no-partial-path covers subprocess and os.system
B607 start_process_with_partial_path S607 ported sec-py/no-partial-path (strict)
B608 hardcoded_sql_expressions S608 ported sec-py/no-sql-string: from the call that runs the statement, not from a string that looks like SQL
B609 linux_commands_wildcard_injection S609 ported sec-py/no-wildcard-injection
B610, B611 Django extra, RawSQL S610, S611 ported sec-py/no-sql-string
B612 logging_config_insecure_listen S612 ported sec-py/logging-listen
B613 trojansource - not ported bidirectional control characters in the source bytes; a rule reads the syntax tree and does not see them
B614 pytorch_load - ported sec-py/torch-load-weights-only
B615 huggingface_unsafe_download - ported sec-py/hf-pin-revision
B701 jinja2_autoescape_false S701 ported sec-py/jinja-autoescape
B702 use_of_mako_templates S702 ported sec-py/no-mako
B703 django_mark_safe S703 ported sec-py/no-mark-safe
B704 markupsafe_markup_xss S704 ported sec-py/no-mark-safe

Beyond Bandit and Ruff the pack adds the framework and library rules of the catalogue: Django, Flask and FastAPI settings and sinks, mass assignment, uploads, header, LDAP, XPath and NoSQL injection, ML model loading (torch.load, joblib, numpy, Hugging Face), trust_remote_code, prompt injection and language-model output reaching a sink. Two parts of the catalogue are not built: the strict ReDoS check for a literal pattern with nested quantifiers (sec-py/no-regex-injection handles only a request-supplied pattern), and a from_source check on every argument of a call (it reads the first, and for a logger the second).

Corpus

packs/security-python/corpus.toml holds the finding count per rule over the pinned repositories (scripts/corpus.json): Flask, Starlette, full-stack-fastapi-template and djangoproject.com, 946 files. On 2026-10-26 the rules gave 34 findings, all of them read by hand: one false positive (sec-py/no-open-redirect on a redirect to a trusted URL that had ?q=… appended from the request, which from_source: cannot tell from a hostile target), about 3%. The other 33 are what the rules say: a mark_safe of a non-literal, an MD5 or SHA-1 digest, an exec of a config file, a request without a timeout, a subprocess started by a bare name (strict), and the like. Two limits of that number: four repositories are a small sample, and the noisy rules skip test files (exclude: in their frontmatter) because the first run, before they did, was mostly asserts and password="password" in tests. Every rule is preview until its count has been reviewed again over a larger corpus.

Metadata

Release files for codelaw-rules-security-python 0.4.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for codelaw-rules-security-python 0.4.3
File Interpreter ABI Platform Actions
codelaw_rules_security_python-0.4.3-py3-none-any.whl Python 3 none any Details for codelaw_rules_security_python-0.4.3-py3-none-any.whl

Release files / codelaw_rules_security_python-0.4.3-py3-none-any.whl

Download URL codelaw_rules_security_python-0.4.3-py3-none-any.whl
Size 115.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
432405b288d1a98c6d5b206223c5f708ad2e00aa2845410729d10319053cb0b1
BLAKE2b-256 checksum
How to use checksums
b063bd379adb4c23bfee44cff9ce18601a76ef8f9d07c703a389116fce58c80f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.

Transparency log

Security for codelaw-rules-security-python 0.4.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

1 file in this release has been uploaded with provenance metadata.

Attestation for codelaw_rules_security_python-0.4.3-py3-none-any.whl

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page