Skip to content

Commit 55cb543

Browse files
committed
updates
1 parent 5fe09ea commit 55cb543

13 files changed

Lines changed: 974 additions & 2 deletions

‎appendices/definitions.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ A Python module is a single `.py` file (or compiled equivalent) containing Pytho
2222
**Python Library**
2323
The term “library” has no strict formal definition in Python. It is commonly used interchangeably with “module” or “package”. The **Python Standard Library** is the best-known example of a Python library.
2424

25+
(PSL)=
2526
**Python Standard Library (PSL)**
2627
The Python Standard Library is the collection of modules and packages included with every standard Python installation. It provides broad functionality for common tasks (file I/O, networking, cryptography, data processing, etc.), making Python a “batteries-included” language. While generally robust, certain functions within the PSL can introduce security weaknesses depending on how they are used.
2728

‎constructs/assert.md‎

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
# Assert Statement
2+
3+
The Python `assert` statement itself is not insecure, but its *misuse* can lead to security vulnerabilities.
4+
5+
:::{danger}
6+
Using `assert` for checks can introduce **serious security** risks!
7+
:::
8+
9+
10+
## Rationale
11+
12+
1. Assertions are primarily for debugging and development, mainly testing, and **NOT** for error handling in production code.
13+
14+
* **Assertiontions can be disabled:** When Python is run in optimized mode (with the `python -O` or `python -OO` flags, or by setting the `PYTHONOPTIMIZE` environment variable), `assert` statements are completely ignored.
15+
16+
17+
When using `python -O` or `python -OO` the Python interpreter removes all assert statements from the bytecode.
18+
19+
The special Python `__debug__` built-in variable is also set to False when -O is used. So code that checks if `__debug__`: will also be removed.
20+
21+
This means any crucial checks you rely on for security or data integrity will simply vanish in production when code is run with `python -O`, leaving your application vulnerable.
22+
23+
* **Not for user input validation:** So never use `assert` to validate user input or external data. If assertions are disabled in production, malicious or malformed input will bypass your checks, potentially leading to crashes, data corruption, or even arbitrary code execution. Use `if/else` statements with proper exception handling (e.g., `ValueError`, `TypeError`) for this.
24+
25+
* **Not for graceful error handling:** Assertions are designed to signal "this should never happen, it's a bug." They raise an `AssertionError` which typically halts the program. In a production environment, you usually want to handle anticipated errors gracefully, log them, and potentially recover or inform the user, rather than crashing the application.
26+
27+
2. Dangers of Side effects in assert Statements in production code:
28+
29+
* If an assert statement contains code with side effects (e.g., modifying a variable or calling a function that performs an action), those side effects will also be skipped when assertions are disabled. For example, when running Python with the -O optimization flag. This can lead to unexpected behaviour and even security vulnerabilities.
30+
31+
* If you rely on `assert` to prevent a `ZeroDivisionError` or validate user input, those checks will silently disappear in optimized environments. As a result, code may behave differently than intended, creating potential security gaps.
32+
33+
* Relying on assert for input validation is especially dangerous. If assertions are used to block malicious input (e.g., to prevent SQL injection or enforce type checks), an attacker could exploit the fact that these validations are stripped out in production.
34+
35+
* If an `assert` statement contains code with side effects (e.g., modifying a variable, calling a function that performs an action), those side effects will also be skipped when assertions are disabled. This can lead to unexpected behaviour and security gaps.
36+
37+
* Instead of catching an AssertionError, a program can run differently. This can have severe security consequences. E.g. if asserts are used to validate user input to prevent sql injections or user input.
38+
39+
40+
For AI-generated, Python programs you will see a lot of assert statement warnings. So mind that by default [AI generated Python code](fundamentals/ai-security) is not secure.
41+
42+
43+
## Preventive Measures
44+
45+
46+
1. Use `assert` for testing code and during development only!
47+
48+
* `assert` is good to use for `pytest` or other development constructs.
49+
50+
* `assert` helps to find mistakes during development. But it is not a security fence to protect against external threats or a robust mechanism for handling runtime issues in a live system. For production code, especially when dealing with external inputs or critical business logic, rely on explicit `if/else` checks and robust exception handling.
51+
52+
* `assert` statements should in general only be used for testing and debugging purposes.
53+
54+
`assert` statements **SHOULD** be removed when not running in debug mode. This means when invoking the Python command with the -O or -OO options.
55+
56+
2. Use explicit condition checks and raise proper exceptions (e.g., `ValueError`, `TypeError`) for input validation and critical logic.
57+
58+
3. **Never ever** use `assert` in production Python code for checks.
59+
60+
4. The **only** safe alternative: Explicit Checks!
61+
62+
63+
For production code, especially when handling external inputs or executing critical logic, always rely on explicit condition checks and robust exception handling.
64+
65+
:::{admonition} Instead of an assert, use standard control flow and exception raising
66+
:class: hint
67+
68+
* Use if/else checks: Explicitly validate inputs and conditions.
69+
70+
* Raise proper exceptions: Use dedicated exceptions like `ValueError`, `TypeError`, or a custom exception to signal an issue. This provides a robust mechanism that can be caught and handled gracefully by upstream code, logging a clear error message instead of silently failing or creating a security hole.
71+
:::
72+
73+
74+
75+
## Example
76+
77+
The following Python example shows how trusting on `assert` can lead to another behaviour.
78+
79+
80+
```python
81+
"""Example of Python script using the assert statement - which can lead to security issues!"""
82+
83+
def divide_numbers(x,y):
84+
"""
85+
Divides two numbers.
86+
Uses an assert statement to ensure the denominator is not zero.
87+
"""
88+
# Assert that the denominator is not zero.
89+
# If denominator is 0, an AssertionError will be raised with the given message.
90+
assert y != 2, "Error: diving is crying!"
91+
return x / y
92+
93+
print("--- Demonstrating danger of assertions ---")
94+
result = divide_numbers(10, 3)
95+
print(f"Dividing result : {result}")
96+
97+
result2 = 'Error- Dividing is crying!' # A default value to show
98+
try:
99+
# If run with 'python -O', the 'assert y != 0' line above will be removed.
100+
# In that scenario, this call will directly raise a ZeroDivisionError,
101+
# as the assert check won't be present.
102+
result2 = divide_numbers(10, 2)
103+
except AssertionError as e:
104+
# This block will be executed if assert is active (without -O)
105+
print(f"Caught an AssertionError: {e}")
106+
print('Never divide! Take it all or divide in more parts.')
107+
108+
109+
print(f"Dividing result - Result should be error, not 5! - : {result2}")
110+
```
111+
112+
Run this program with:
113+
114+
```bash
115+
python assert_example.py
116+
```
117+
118+
And after that another time with:
119+
```bash
120+
python -O assert_example.py
121+
```
122+
And notice the different outcome.
123+
124+
125+
So this examples shows that if you rely on `assert` to prevent an `AssertionError` (or any other critical condition), that check will simply disappear in an optimized environment!
126+
127+
Instead of catching an `AssertionError`, a program will run differently when `python -O` is used. And this can and will have consequences for the functional working of a program. But worse it can have severe security consequences. E.g. if asserts are used to validate user input to prevent sql injections e.g.
128+
129+
For robust validation and error handling in production code, always use standard if statements combined with raising appropriate exceptions (like `ValueError`, `TypeError`, or custom exceptions) rather than assert.
130+
131+
## Discussion
132+
133+
Some aspects of Python security are the subject of long-standing debates and occasional flamewars. Whether using `assert` statements in production Python code represents a security weakness is particularly contentious.
134+
135+
There are certain edge cases in which using assert in production code can be harmless, even when the code is executed with **Python’s -O** (optimisation) flag.
136+
137+
138+
### Use Case:Type/State Checking in Hot Loops (Performance Tuning):
139+
140+
You use `assert` to check an internal invariant in a performance-critical loop during development. In production, you deliberately run with `python -O` to remove the check for speed. You accept that if a bug exists, the program might silently corrupt data instead of crashing. This use case is only safe if the corrupted data cannot affect security-critical decisions. In practice, that is very hard to guarantee. From a security perspective, silent data corruption is often worse than a crash.
141+
142+
143+
### Use Case:Detecting Impossible State Corruption (Fail-Fast)
144+
145+
You use `assert` to check a condition that should literally never be false if your code is bug-free. This is a “programming error detector”, not a runtime guard.
146+
147+
So you could argue for the use of `assert` in production code to check that code is internally consistent.
148+
149+
However, the truth is that almost every running program requires some form of input from the outside world. Validating all logic from a security perspective would quickly become too complex. So it's better to be safe than sorry. If you do choose to use `assert` statements in your production Python code, you should at minimum include a comment (or a marker) explaining why. This way, security reviewers will know that you are aware of the potential impact.
150+
151+
152+
153+
### Use Case: Type assurance
154+
155+
Occasionally, a developer knows the type of a variable, but a type checker or linter (such as `mypy`, `basedpyright`, or `pyright`) cannot infer it correctly. This is frustrating because you are certain the code is correct.
156+
157+
You can resolve this by using:
158+
159+
```python
160+
assert isinstance(var, SomeType)
161+
```
162+
163+
This is not necessarily bad in production:
164+
165+
| Concern | Clarification |
166+
|---------|---------------|
167+
| **Performance overhead** | `assert` statements can be disabled globally with the `-O` (optimise) flag. In many production environments, this flag is **not** used, so `assert` remains active. |
168+
| **Risk of `AssertionError`** | If the assertion fails, the program crashes. However, if you are **absolutely certain** the type is correct, this crash will never happen. The `assert` serves as a developer assertion and a type narrowing hint. |
169+
| **Alternative approaches** | You could use `cast(SomeType, var)` from `typing`, but `cast` does nothing at runtime. `assert isinstance(...)` provides both runtime checking (helpful for debugging) and type narrowing for static analysis. |
170+
171+
Only use this pattern when:
172+
173+
- **You have full control over the input** – For example, the value comes from internal code you trust, not external user input.
174+
- **The type is guaranteed by program logic** – The linter simply cannot prove what you know to be true.
175+
- **A crash on type mismatch is desirable** – If the variable somehow has the wrong type, a crash is better than silent corruption or undefined behaviour.
176+
177+
178+
**Example:**
179+
180+
```python
181+
def process_data(data: dict[str, object]) -> list[int]:
182+
# The linter cannot infer that 'values' is always a list of ints,
183+
# but you know from the API contract that this is true.
184+
values = data.get("values", [])
185+
186+
# This assert helps mypy/pyright understand the type,
187+
# and provides a runtime sanity check.
188+
assert isinstance(values, list)
189+
190+
# Now the linter knows 'values' is a list
191+
return [int(x) for x in values]
192+
```
193+
194+
But still:
195+
- **Document why the `assert` is safe** – Include a comment explaining that you are aware of the security implications and why the assertion will not fail in production.
196+
- **Consider using `typing.cast` for type narrowing only** – If you need zero runtime overhead and are absolutely certain of correctness, `cast` is an alternative (but provides no runtime protection).
197+
198+
```python
199+
from typing import cast
200+
201+
# No runtime check, but helps the type checker
202+
values = cast(list, data.get("values", []))
203+
```
204+
205+
Or like stated in the Python manual, just do:
206+
207+
```python
208+
def greet(name: str) -> None:
209+
assert_type(name, str) # OK, inferred type of `name` is `str`
210+
assert_type(name, int) # type checker error
211+
```
212+
## More information
213+
214+
* [The assert statement - Python Documentation](https://docs.python.org/3/reference/simple_stmts.html#the-assert-statement)
215+
* [The dangers of assert in Python](https://snyk.io/blog/the-dangers-of-assert-in-python/)
216+
* [Feature: Python assert should be consider harmful](https://community.sonarsource.com/t/feature-python-assert-should-be-consider-harmful/38501) But note that Sonar did not implement this check.
217+
* [CVE-2017-1000433](https://nvd.nist.gov/vuln/detail/CVE-2017-1000433) and see the related [issues/451](https://github.com/IdentityPython/pysaml2/issues/451)
218+
* [Advisory: pysaml2 Improper Authentication vulnerability](https://github.com/advisories/GHSA-924m-4pmx-c67h)
219+
220+
* [Rethinking Python Asserts in SAST](https://nocomplexity.com/python-asserts/)

‎constructs/binding.md‎

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# The `socket.bind()` Method
2+
3+
:::{caution}
4+
The Python construct `s.bind()` is dangerous from a security perspective if not used with extreme care.
5+
:::
6+
7+
## Security concerns
8+
9+
When using `s.bind()`, your application opens network sockets, which expands its attack surface. Additional protective measures are often required — and these typically extend beyond the Python code alone. Relying solely on code-level controls is seldom sufficient.
10+
11+
Detection of the `bind()` construct in code requires further inspection to determine the associated risks. This inspection can only be performed in full context, taking into account the environment where the code will be executed (for example, network architecture, firewall rules, and user privileges).
12+
13+
## Preventive measures
14+
15+
Before detailing specific measures, a critical caution applies:
16+
17+
:::{caution}
18+
Port bindings **SHOULD NOT** be hardcoded. Where possible, bind to ports dynamically (for example, by asking the operating system for an available ephemeral port). However, note that dynamic assignment does not eliminate security risks — it only reduces configuration brittleness.
19+
:::
20+
21+
When multiple sockets are allowed to bind to the same port, other services on that port may be hijacked or spoofed. You should prevent another application from binding to the same address on an unprivileged port and potentially stealing its UDP packets or TCP connections.
22+
23+
At a minimum, a strong authorisation mechanism is required. A **zero-trust network policy** is preferred.
24+
25+
**Ensure additional measures are taken** when communicating using raw sockets in Python.
26+
27+
### Preventive measures (beyond code changes)
28+
29+
| Area | Recommended actions |
30+
|------|---------------------|
31+
| **Firewall configuration** | Configure a firewall to explicitly allow incoming connections *only from trusted sources* or specific IP ranges. Block all other incoming connections to the bound port. |
32+
| **Authentication and authorisation** | Implement strong authentication and authorisation mechanisms for your application. Do not leave it open for anyone to connect. |
33+
| **Input validation** | Sanitise and validate all user inputs to prevent common vulnerabilities such as SQL injection, cross-site scripting (XSS), and command injection. |
34+
| **Least privilege** | Ensure the application runs with the minimum necessary privileges. Never bind to privileged ports (below 1024) unless absolutely required, and drop privileges after binding. |
35+
| **Error handling** | Implement robust error handling that does not expose sensitive system information (for example, stack traces or file paths). |
36+
| **Logging and monitoring** | Log access attempts and suspicious activities. Monitor these logs for anomalies. |
37+
| **Zero-trust and network segmentation** | For critical services, consider placing them in separate network segments to limit lateral movement if one part of your network is compromised. |
38+
39+
## Example
40+
41+
Binding sockets to all network interfaces can be done in several ways. The example below creates a socket bound to all available interfaces on port 8080, with IPv6 dual-stack support when available:
42+
43+
```python
44+
import socket
45+
46+
addr = ("", 8080)
47+
48+
if socket.has_dualstack_ipv6():
49+
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
50+
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
51+
else:
52+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
53+
54+
s.bind(addr)
55+
s.listen()
56+
```
57+
58+
59+
### Some Security considerations for this example
60+
61+
- Binding to `""` (all interfaces) exposes the service on every network interface, including public-facing ones. This broadens the attack surface significantly.
62+
- Port `8080` is a common HTTP alternative port and is frequently scanned by attackers.
63+
- Consider binding explicitly to `"127.0.0.1"` (localhost) if the service is only needed locally.
64+
- `s.listen()` is called with default backlog size (platform-dependent, typically around 128). An attacker could exhaust the connection backlog with a SYN flood or slowloris-style attack, causing denial of service.
65+
- The code calls `s.listen()` but there is no authentication, authorisation, or input validation shown for incoming connections. Any client that can reach the port can interact with the service. This could lead to data leakage, command injection, or denial of service depending on what the server does after accepting connections.
66+
67+
68+
**Safer alternative (localhost only):**
69+
70+
```python
71+
import socket
72+
73+
# Bind only to localhost — not accessible from other machines
74+
addr = ("127.0.0.1", 8080)
75+
s = socket.create_server(addr)
76+
```
77+
78+
## More information
79+
80+
- [Python `socket` module documentation](https://docs.python.org/3/library/socket.html)
81+
- [CWE-305: Missing Authentication for Critical Function](https://cwe.mitre.org/data/definitions/305.html)
82+
- [CWE-668: Exposure of Resource to Wrong Sphere](https://cwe.mitre.org/data/definitions/668.html)
83+
- [OWASP: Network Segmentation](https://owasp.org/www-community/Network_Segmentation)
84+
- [Zero Trust Architecture (NIST SP 800-207)](https://csrc.nist.gov/pubs/sp/800/207/final)
85+
86+

0 commit comments

Comments
 (0)