# Security audit — user_service.py (with semgrep skill)

## Scan record
- Rulesets run: p/security-audit, p/secrets, p/python, https://github.com/trailofbits/semgrep-rules, https://github.com/elttam/semgrep-rules, https://github.com/apiiro/malicious-code-ruleset
- Failed/skipped: none failed, none skipped; partial: https://github.com/elttam/semgrep-rules (partial: true, exit 2 — some rules failed to compile, 0 findings), https://github.com/apiiro/malicious-code-ruleset (partial: true, exit 2 — some rules failed to compile, 1 finding)
- Findings from scanner: 5

## Findings
### F1: Hardcoded live Stripe API key
- Severity: Critical
- Line: 11
- Why it is exploitable: A live Stripe secret key (`STRIPE_API_KEY`, `sk_live_...`) is committed in source code and reused as a Bearer credential in `billing_request`. Anyone with read access to the code/repo can extract it and make authenticated Stripe API calls as the account — creating charges/refunds and accessing payment data — until the key is revoked.
- Fix: Revoke/rotate the leaked key immediately (treat it as compromised), remove it from source and history, and load a restricted Stripe key from an environment variable or secret manager at runtime; never commit live credentials.

### F2: MD5 used as a password hash
- Severity: High
- Line: 25
- Why it is exploitable: `hash_password` stores passwords as an unsalted MD5 hex digest (`hashlib.md5(password.encode()).hexdigest()`), used by `create_user` and checked by `verify_login`. MD5 is extremely fast and collision-broken, so a leaked `users` table can be brute-forced / rainbow-tabled / GPU-cracked in a short time, recovering users' plaintext passwords for credential stuffing.
- Fix: Replace MD5 with a dedicated, salted, slow password hash — Argon2id or bcrypt (or stdlib `hashlib.scrypt` / `hashlib.pbkdf2_hmac` with a unique per-password random salt and a high work factor) — and re-hash passwords on next successful login.

### F3: Code injection via eval() on user input
- Severity: Critical
- Line: 54
- Why it is exploitable: `calculate()` returns `eval(expression)`, and the `POST /calc` route passes `req["expression"]` straight into it. Any caller can submit arbitrary Python (e.g. importing `os` and running commands) and achieve remote code execution with the service process's privileges, not just arithmetic.
- Fix: Remove `eval()` entirely. Parse the expression with `ast.parse(..., mode="eval")` and evaluate only a whitelist of nodes (numeric literals, `+ - * /` etc.), or use `ast.literal_eval` / a dedicated safe arithmetic parser; never execute user-supplied code.

## Summary line
Findings: 3
