2020-12-03 16:54:36 +01:00
# See https://semgrep.dev/docs/writing-rules/rule-syntax/ for documentation on YAML rule syntax
2020-03-19 00:32:26 +01:00
rules :
2020-07-01 21:19:49 +02:00
####################### PYTHON RULES #######################
2020-03-19 00:32:26 +01:00
- id : deprecated-render-usage
pattern : django.shortcuts.render_to_response(...)
2020-04-27 13:46:53 +02:00
message : "Use render() (from django.shortcuts) instead of render_to_response()"
2020-03-19 00:32:26 +01:00
languages : [ python]
severity : ERROR
2020-04-27 13:46:53 +02:00
2020-04-29 13:50:36 +02:00
- id : dont-use-stream-objects-filter
pattern : Stream.objects.filter(...)
message : "Please use access_stream_by_*() to fetch Stream objects"
languages : [ python]
severity : ERROR
paths :
2020-06-05 10:10:37 +02:00
include :
- zerver/views/
2020-05-01 08:56:20 +02:00
2023-09-29 17:03:18 +02:00
- id : time-machine-travel-specify-tick
patterns :
- pattern : time_machine.travel(...)
- pattern-not : time_machine.travel(..., tick=..., ...)
message : |
Specify tick kwarg value for time_machine.travel(). Most cases will want to use False.
languages : [ python]
severity : ERROR
2023-08-30 21:19:37 +02:00
- id : limit-message-filter
patterns :
- pattern : Message.objects.filter(...)
- pattern-not : Message.objects.filter(..., realm=..., ...)
- pattern-not : Message.objects.filter(..., realm_id=..., ...)
- pattern-not : Message.objects.filter(..., realm_id__in=..., ...)
- pattern-not : Message.objects.filter(..., id=..., ...)
- pattern-not : Message.objects.filter(..., id__in=..., ...)
- pattern-not : Message.objects.filter(..., id__lt=..., ...)
- pattern-not : Message.objects.filter(..., id__gt=..., ...)
message : "Set either a realm limit or an id limit on Message queries"
languages : [ python]
severity : ERROR
paths :
exclude :
- "**/migrations/"
2020-05-01 08:56:20 +02:00
- id : dont-import-models-in-migrations
patterns :
- pattern-not : from zerver.lib.redis_utils import get_redis_client
2023-05-18 17:21:21 +02:00
- pattern-not : from zerver.lib.utils import generate_api_key
2020-05-01 08:56:20 +02:00
- pattern-not : from zerver.models import filter_pattern_validator
2022-10-02 22:52:31 +02:00
- pattern-not : from zerver.models import url_template_validator
2020-05-01 08:56:20 +02:00
- pattern-not : from zerver.models import generate_email_token_for_stream
2023-10-12 20:14:57 +02:00
- pattern-not : from zerver.models import generate_realm_uuid_owner_secret
2020-05-01 08:56:20 +02:00
- pattern-either :
- pattern : from zerver import $X
- pattern : from analytics import $X
- pattern : from confirmation import $X
2021-06-17 15:02:52 +02:00
message : "Don't import models or other code in migrations; see https://zulip.readthedocs.io/en/latest/subsystems/schema-migrations.html"
2020-05-01 08:56:20 +02:00
languages : [ python]
severity : ERROR
paths :
2020-06-05 10:10:37 +02:00
include :
- "**/migrations"
exclude :
- zerver/migrations/0032_verify_all_medium_avatar_images.py
- zerver/migrations/0104_fix_unreads.py
- zerver/migrations/0206_stream_rendered_description.py
- zerver/migrations/0209_user_profile_no_empty_password.py
- zerver/migrations/0260_missed_message_addresses_from_redis_to_db.py
2022-04-01 02:25:27 +02:00
- zerver/migrations/0387_reupload_realmemoji_again.py
2020-06-05 10:10:37 +02:00
- pgroonga/migrations/0002_html_escape_subject.py
2020-05-02 08:44:14 +02:00
2023-03-21 07:10:20 +01:00
- id : html-format
languages : [ python]
pattern-either :
- pattern : markupsafe.Markup(... .format(...))
- pattern : markupsafe.Markup(f"...")
- pattern : markupsafe.Markup(... + ...)
severity : ERROR
message : "Do not write an HTML injection vulnerability please"
2020-05-04 01:56:44 +02:00
- id : sql-format
languages : [ python]
pattern-either :
- pattern : ... .execute("...".format(...))
2020-06-15 23:39:16 +02:00
- pattern : ... .execute(f"...")
2023-03-21 07:10:20 +01:00
- pattern : ... .execute(... + ...)
2020-05-04 01:56:44 +02:00
- pattern : psycopg2.sql.SQL(... .format(...))
2020-06-15 23:39:16 +02:00
- pattern : psycopg2.sql.SQL(f"...")
2023-03-21 07:10:20 +01:00
- pattern : psycopg2.sql.SQL(... + ...)
2020-06-14 03:48:07 +02:00
- pattern : django.db.migrations.RunSQL(..., "..." .format(...), ...)
2020-06-15 23:39:16 +02:00
- pattern : django.db.migrations.RunSQL(..., f"...", ...)
2023-03-21 07:10:20 +01:00
- pattern : django.db.migrations.RunSQL(..., ... + ..., ...)
2020-06-14 04:05:38 +02:00
- pattern : django.db.migrations.RunSQL(..., [..., "..." .format(...), ...], ...)
2020-06-15 23:39:16 +02:00
- pattern : django.db.migrations.RunSQL(..., [..., f"...", ...], ...)
2023-03-21 07:10:20 +01:00
- pattern : django.db.migrations.RunSQL(..., [..., ... + ..., ...], ...)
2020-05-04 01:56:44 +02:00
severity : ERROR
message : "Do not write a SQL injection vulnerability please"
2020-06-13 05:24:42 +02:00
2020-10-17 02:53:53 +02:00
- id : translated-format-lazy
languages : [ python]
2021-04-16 00:57:30 +02:00
pattern : django.utils.translation.gettext_lazy(...).format(...)
2020-10-17 02:53:53 +02:00
severity : ERROR
message : "Immediately formatting a lazily translated string destroys its laziness"
2023-07-17 22:40:33 +02:00
- id : translated-positional-field
languages : [ python]
patterns :
- pattern-either :
- pattern : django.utils.translation.gettext("$MESSAGE")
- pattern : django.utils.translation.pgettext($CONTEXT, "$MESSAGE")
- pattern : django.utils.translation.gettext_lazy("$MESSAGE")
- pattern : django.utils.translation.pgettext_lazy($CONTEXT, "$MESSAGE")
- metavariable-regex :
metavariable : $MESSAGE
regex : (^|.*[^{])(\{\{)*\{[:!}].*
severity : ERROR
message : "Prefer {named} fields over positional {} in translated strings"
2020-06-13 05:24:42 +02:00
- id : mutable-default-type
languages : [ python]
pattern-either :
- pattern : |
def $F(..., $A : typing.List[...] = zerver.lib.request.REQ(..., default=[...], ...), ...) -> ... :
...
- pattern : |
def $F(..., $A : typing.Optional[typing.List[...]] = zerver.lib.request.REQ(..., default=[...], ...), ...) -> ... :
...
- pattern : |
def $F(..., $A : typing.Dict[...] = zerver.lib.request.REQ(..., default={}, ...), ...) -> ...:
...
- pattern : |
def $F(..., $A : typing.Optional[typing.Dict[...]] = zerver.lib.request.REQ(..., default={}, ...), ...) -> ...:
...
severity : ERROR
message : "Guard mutable default with read-only type (Sequence, Mapping, AbstractSet)"
2020-06-14 07:01:21 +02:00
- id : percent-formatting
languages : [ python]
pattern-either :
- pattern : '"..." % ...'
2021-04-16 00:57:30 +02:00
- pattern : django.utils.translation.gettext(...) % ...
2023-07-17 22:40:33 +02:00
- pattern : django.utils.translation.pgettext(...) % ...
2021-04-16 00:57:30 +02:00
- pattern : django.utils.translation.gettext_lazy(...) % ...
2023-07-17 22:40:33 +02:00
- pattern : django.utils.translation.pgettext_lazy(...) % ...
2020-06-14 07:01:21 +02:00
severity : ERROR
message : "Prefer f-strings or .format for string formatting"
2020-06-26 02:35:16 +02:00
2021-02-14 00:03:40 +01:00
- id : change-user-is-active
languages : [ python]
patterns :
- pattern-either :
- pattern : |
$X.is_active = ...
- pattern : |
setattr($X, 'is_active', ...)
- pattern-not-inside : |
def change_user_is_active(...) :
...
message : "Use change_user_is_active to mutate user_profile.is_active"
severity : ERROR
2021-10-06 17:13:57 +02:00
paths :
exclude :
- zerver/migrations/0373_fix_deleteduser_dummies.py
2021-12-02 20:31:42 +01:00
- id : confirmation-object-get
languages : [ python]
patterns :
- pattern-either :
- pattern : Confirmation.objects.get(...)
- pattern : Confirmation.objects.filter(..., confirmation_key=..., ...)
- pattern-not-inside : |
def get_object_from_key(...) :
...
paths :
exclude :
- zerver/tests/
message : "Do not fetch a Confirmation object directly, use get_object_from_key instead"
severity : ERROR
2023-06-08 01:04:25 +02:00
- id : dont-make-batched-migration-atomic
patterns :
- pattern : |
class Migration(migrations.Migration) :
...
- pattern-inside : |
...
BATCH_SIZE = ...
...
- pattern-not : |
class Migration(migrations.Migration) :
atomic = False
paths :
include :
- "**/migrations"
message : 'A batched migration should not be atomic. Add "atomic = False" to the Migration class'
languages : [ python]
severity : ERROR
api: Add new typed_endpoint decorators.
The goal of typed_endpoint is to replicate most features supported by
has_request_variables, and to improve on top of it. There are some
unresolved issues that we don't plan to work on currently. For example,
typed_endpoint does not support ignored_parameters_supported for 400
responses, and it does not run validators on path-only arguments.
Unlike has_request_variables, typed_endpoint supports error handling by
processing validation errors from Pydantic.
Most features supported by has_request_variables are supported by
typed_endpoint in various ways.
To define a function, use a syntax like this with Annotated if there is
any metadata you want to associate with a parameter, do note that
parameters that are not keyword-only are ignored from the request:
```
@typed_endpoint
def view(
request: HttpRequest,
user_profile: UserProfile,
*,
foo: Annotated[int, ApiParamConfig(path_only=True)],
bar: Json[int],
other: Annotated[
Json[int],
ApiParamConfig(
whence="lorem",
documentation_status=NTENTIONALLY_UNDOCUMENTED
)
] = 10,
) -> HttpResponse:
....
```
There are also some shorthands for the commonly used annotated types,
which are encouraged when applicable for better readability and less
typing:
```
WebhookPayload = Annotated[Json[T], ApiParamConfig(argument_type_is_body=True)]
PathOnly = Annotated[T, ApiParamConfig(path_only=True)]
```
Then the view function above can be rewritten as:
```
@typed_endpoint
def view(
request: HttpRequest,
user_profile: UserProfile,
*,
foo: PathOnly[int],
bar: Json[int],
other: Annotated[
Json[int],
ApiParamConfig(
whence="lorem",
documentation_status=INTENTIONALLY_UNDOCUMENTED
)
] = 10,
) -> HttpResponse:
....
```
There are some intentional restrictions:
- A single parameter cannot have more than one ApiParamConfig
- Path-only parameters cannot have default values
- argument_type_is_body is incompatible with whence
- Arguments of name "request", "user_profile", "args", and "kwargs" and
etc. are ignored by typed_endpoint.
- positional-only arguments are not supported by typed_endpoint. Only
keyword-only parameters are expected to be parsed from the request.
- Pydantic's strict mode is always enabled, because we don't want to
coerce input parsed from JSON into other types unnecessarily.
- Using strict mode all the time also means that we should always use
Json[int] instead of int, because it is only possible for the request
to have data of type str, and a type annotation of int will always
reject such data.
typed_endpoint's handling of ignored_parameters_unsupported is mostly
identical to that of has_request_variables.
2023-07-28 08:34:04 +02:00
- id : typed_endpoint_without_keyword_only_param
patterns :
- pattern : |
@typed_endpoint
def $F(...)-> ... :
...
- pattern-not-inside : |
@typed_endpoint
def $F(..., *, ...)-> ... :
...
message : |
@typed_endpoint should not be used without keyword-only parameters.
Make parameters to be parsed from the request as keyword-only,
or use @typed_endpoint_without_parameters instead.
languages : [ python]
severity : ERROR
api: Avoid programming errors due to nested Annotated types.
We want to reject ambiguous type annotations that set ApiParamConfig
inside a Union. If a parameter is Optional and has a default of None, we
prefer Annotated[Optional[T], ...] over Optional[Annotated[T, ...]].
This implements a check that detects Optional[Annotated[T, ...]] and
raise an assertion error if ApiParamConfig is in the annotation. It also
checks if the type annotation contains any ApiParamConfig objects that
are ignored, which can happen if the Annotated type is nested inside
another type like List, Union, etc.
Note that because
param: Annotated[Optional[T], ...] = None
and
param: Optional[Annotated[Optional[T], ...]] = None
are equivalent in runtime prior to Python 3.11, there is no way for us
to distinguish the two. So we cannot detect that in runtime.
See also: https://github.com/python/cpython/issues/90353
2023-08-17 00:35:10 +02:00
- id : dont-nest-annotated-types-with-param-config
patterns :
- pattern-not : |
def $F(..., invalid_param : typing.Optional[<... zerver.lib.typed_endpoint.ApiParamConfig(...) ...>], ...) -> ... :
...
- pattern-not : |
def $F(..., $A : typing_extensions.Annotated[<... zerver.lib.typed_endpoint.ApiParamConfig(...) ...>], ...) -> ... :
...
- pattern-not : |
def $F(..., $A : typing_extensions.Annotated[<... zerver.lib.typed_endpoint.ApiParamConfig(...) ...>] = ..., ...) -> ... :
...
- pattern-either :
- pattern : |
def $F(..., $A : $B[<... zerver.lib.typed_endpoint.ApiParamConfig(...) ...>], ...) -> ... :
...
- pattern : |
def $F(..., $A : $B[<... zerver.lib.typed_endpoint.ApiParamConfig(...) ...>] = ..., ...) -> ... :
...
message : |
Annotated types containing zerver.lib.typed_endpoint.ApiParamConfig should not be nested inside Optional. Use Annotated[Optional[...], zerver.lib.typed_endpoint.ApiParamConfig(...)] instead.
languages : [ python]
severity : ERROR
2023-09-07 18:22:41 +02:00
- id : exists-instead-of-count
patterns :
- pattern-either :
- pattern : ... .count() == 0
- pattern : |
if not ... .count() :
...
message : 'Use "not .exists()" instead; it is more efficient'
languages : [ python]
severity : ERROR
- id : exists-instead-of-count-not-zero
patterns :
- pattern-either :
- pattern : ... .count() != 0
- pattern : ... .count() > 0
- pattern : ... .count() >= 1
- pattern : |
if ... .count() :
...
message : 'Use ".exists()" instead; it is more efficient'
languages : [ python]
severity : ERROR
2023-09-11 20:22:32 +02:00
- id : functools-partial
pattern : functools.partial
message : "Replace functools.partial with returns.curry.partial for type safety"
languages : [ python]
severity : ERROR
2023-11-28 19:33:10 +01:00
- id : timedelta-positional-argument
patterns :
- pattern : timedelta(...)
- pattern-not : timedelta(0)
- pattern-not : timedelta(..., days=..., ...)
- pattern-not : timedelta(..., seconds=..., ...)
- pattern-not : timedelta(..., microseconds=..., ...)
- pattern-not : timedelta(..., milliseconds=..., ...)
- pattern-not : timedelta(..., minutes=..., ...)
- pattern-not : timedelta(..., hours=..., ...)
- pattern-not : timedelta(..., weeks=..., ...)
message : |
Specify timedelta with named arguments.
languages : [ python]
severity : ERROR
2023-11-28 19:16:58 +01:00
- id : time-machine
languages : [ python]
patterns :
- pattern-either :
- pattern : patch("$FUNCTION", return_value=$TIME)
- pattern : mock.patch("$FUNCTION", return_value=$TIME)
- metavariable-regex :
metavariable : $FUNCTION
regex : .*timezone_now
fix : time_machine.travel($TIME, tick=False)
severity : ERROR
message : "Use the time_machine package, rather than mocking timezone_now"
2023-12-05 21:25:00 +01:00
- id : urlparse
languages : [ python]
pattern-either :
- pattern : urllib.parse.urlparse
- pattern : urllib.parse.urlunparse
- pattern : urllib.parse.ParseResult
severity : ERROR
message : "Use urlsplit rather than urlparse"