Skip to content
Open
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: 7 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## 2024-05-24 - Insecure Random String Generation

**Vulnerability:** Found the use of Python's built-in `random` module (`random.choices`) being used to generate sensitive data, specifically the root SSH password in `prepare.py` and internal system identifiers in `agent.py` and `helpers/guids.py`.

**Learning:** Developers frequently use `random` because it's familiar and convenient (e.g., `random.choices` accepts a `k` argument for length). However, the standard `random` module uses the Mersenne Twister, a deterministic PRNG designed for simulations, not security. Its outputs are entirely predictable if a small sample of prior outputs is known, making generated passwords or session tokens vulnerable to brute-force or prediction attacks.

**Prevention:** For any security-sensitive context (passwords, tokens, system IDs, session keys), always use the `secrets` module, which relies on the operating system's cryptographically secure pseudo-random number generator (CSPRNG, like `/dev/urandom`). Note that `secrets.choice()` does not have a `k` argument, so it must be implemented via a generator expression: `"".join(secrets.choice(chars) for _ in range(length))`.
4 changes: 2 additions & 2 deletions agent.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import asyncio, random, string, threading
import asyncio, secrets, string, threading

from collections import OrderedDict
from dataclasses import dataclass, field
Expand Down Expand Up @@ -135,7 +135,7 @@ def all():
@staticmethod
def generate_id():
def generate_short_id():
return "".join(random.choices(string.ascii_letters + string.digits, k=8))
return "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(8))

while True:
short_id = generate_short_id()
Expand Down
4 changes: 2 additions & 2 deletions helpers/guids.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import random, string
import secrets, string

def generate_id(length: int = 8) -> str:
return "".join(random.choices(string.ascii_letters + string.digits, k=length))
return "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(length))
4 changes: 2 additions & 2 deletions prepare.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from helpers import dotenv, runtime, settings
import string
import random
import secrets
import sys
from helpers.print_style import PrintStyle

Expand Down Expand Up @@ -33,7 +33,7 @@ def _retire_legacy_collabora_runtime() -> None:
# generate random root password if not set (for SSH)
root_pass = dotenv.get_dotenv_value(dotenv.KEY_ROOT_PASSWORD)
if not root_pass:
root_pass = "".join(random.choices(string.ascii_letters + string.digits, k=32))
root_pass = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(32))
PrintStyle.standard("Changing root password...")
settings.set_root_password(root_pass)

Expand Down