codedictate is a Flask-based dictation solution using OpenAI Whisper for speech recognition. The project shows solid foundational architecture but has significant security gaps (hardcoded secrets, missing authentication), insufficient test coverage, and several dead code paths.
Some dependencies are outdated with known vulnerabilities.
Think of this report as a guide, not a grade. Every hint includes a concrete next step — often a single AI prompt is enough.
Ranked by importance — work through them from the top
A single function handles audio decoding, chunking, Whisper API calls, post-processing, punctuation, and database writes — all in 380 lines.
Virtually untestable, extremely error-prone to modify. Any bug fix can have unintended side effects.
"Refactor transcribe_and_process() in server/transcribe.py into 5 smaller functions: decode_audio(), chunk_audio(), call_whisper(), postprocess_text(), save_transcription(). Wire them together in a pipeline function."
def transcribe_and_process(audio_file, user_id, language="de"):
# ... 380 lines of nested logic
# audio decoding, chunking, API calls, text cleanup, DB writesFunctions over 50 lines are a smell. Over 100 is dangerous. Over 300 is a maintenance nightmare. Split along responsibility boundaries.
The entire project has only 2 tests in a single test file. Core functionality like upload, transcription, and authentication is untested.
Any change can silently break existing functionality. Refactoring becomes a gamble.
"Set up pytest with pytest-flask. Create test files: tests/test_api.py (endpoint tests), tests/test_transcribe.py (transcription logic), tests/test_models.py (database operations). Target 60% coverage minimum."
# tests/test_transcribe.py — ENTIRE test suite:
def test_whisper_returns_text():
assert transcribe("hello.wav") != ""
def test_empty_audio():
assert transcribe("empty.wav") == ""Test coverage below 40% means you are flying blind. Prioritize testing critical paths: auth, payment, data persistence.
Existing tests connect to the same database as production because no test configuration exists.
Tests can modify or delete production data. An accidental test run can destroy real user data.
"Create tests/conftest.py with a test database fixture using SQLite in-memory. Update tests to use the fixture instead of importing from server.config directly."
# tests/test_transcribe.py
from server.config import DATABASE_URL # same as production!
from server.models import db
def test_save_transcription():
db.session.add(...) # writes to production DB!Tests must never touch production databases. Use separate test databases, fixtures, and cleanup.
API calls to the Whisper service have no timeout, no retry, and no specific error handling. A 500 or timeout crashes the entire request handler.
Transient API errors cause complete transcription failure. Users lose their recording without an error message.
"Wrap Whisper API calls in server/transcribe.py with tenacity retry decorator: @retry(stop=stop_after_attempt(3), wait=wait_exponential(min=1, max=10)). Add timeout=30 to requests."
response = openai.audio.transcriptions.create(
model=WHISPER_MODEL,
file=audio_chunk,
language=language
) # no timeout, no retry, no error handlingAll external API calls need timeout, retry, and error handling. Assume the network will fail.
The Flask SECRET_KEY has a hardcoded fallback "dev-key-change-me" that gets used in production when the environment variable is not set.
With a known secret key, attackers can sign session cookies and gain admin access.
"In server/config.py, change SECRET_KEY to raise an error if not set: SECRET_KEY = os.environ["FLASK_SECRET_KEY"] # no fallback, must be set."
SECRET_KEY = os.getenv("FLASK_SECRET_KEY", "dev-key-change-me") # predictable!Never provide default values for security-critical configuration. Fail loudly instead of running insecurely.
Flask 2.2.3 is vulnerable to session cookie manipulation (CVE-2023-30861). The current version is 3.1.x.
Attackers can manipulate session cookies and impersonate other users.
"In requirements.txt, update Flask from 2.2.3 to 3.1.0. Review the Flask 3.0 migration guide for breaking changes. Run tests after update."
# requirements.txt
Flask==2.2.3 # CVE-2023-30861: session cookie vulnerability
Werkzeug==2.2.3 # also outdated, update togetherRun pip-audit or safety check regularly. Pinned versions require active maintenance to stay secure.
The OpenAI API key is hardcoded directly in the source code and gets committed to the repository with every push.
Attackers can extract the API key from git history and make API calls at your expense.
"Replace the hardcoded OPENAI_API_KEY in server/config.py with os.environ.get("OPENAI_API_KEY") and add a .env.example file."
OPENAI_API_KEY = "sk-proj-abc123def456ghi789"Never commit API keys or secrets to version control. Use environment variables or a secrets manager.
User input is directly interpolated into a SQL query without parameterization or escaping.
Attackers can execute arbitrary SQL commands, steal data, or drop the entire database.
"In server/models.py line 87, replace the f-string SQL query with a parameterized SQLAlchemy query using bindparams or ORM methods."
db.execute(f"SELECT * FROM transcriptions WHERE user_id = '{user_id}' AND title LIKE '%{search}%'")Always use parameterized queries. Never interpolate user input into SQL strings.
The three most important next steps
You're on the right track. Every fix makes your code better for you and for AI. Keep going!
These findings are less urgent but remain fully documented
AI assistants pack everything into one big function. Works until you change something — then everything breaks. If a function has >50 lines or >3 nesting levels, ask AI to split it.
Five nested try/except blocks make the control flow nearly impossible to follow.
Errors get caught at the wrong level, leading to silent failures and hard-to-find bugs.
"In server/transcribe.py starting at line 128, flatten the 5 nested try/except blocks by extracting each into a separate function that raises specific exceptions."
try:
try:
try:
result = whisper.transcribe(chunk)
except APIError:
try:
result = whisper.transcribe(chunk, model="base")
except:
...Flatten nested error handling by extracting functions. Each function handles one concern and its specific errors.
app.py imports models.py, and models.py imports app.py for the db instance. This is worked around with delayed imports, making the code fragile.
Any restructuring can lead to ImportError. The code is hard to test because import order is critical.
"Create server/db.py exporting the SQLAlchemy db instance. Update server/app.py and server/models.py to import from server/db.py instead of each other."
# server/app.py
from server.models import User, Transcription
# server/models.py
from server.app import db # circular!Circular imports indicate poor module boundaries. Extract shared dependencies into a separate module.
Configuration values are spread across config.py, app.py, models.py, transcribe.py, whisper_api.py, and setup.py with sometimes conflicting defaults.
Inconsistent configuration leads to hard-to-reproduce bugs, especially between development and production.
"Consolidate all configuration into server/config.py with Development/Production/Testing classes. Update all other files to import from config."
# config.py: WHISPER_MODEL = "medium"
# transcribe.py: MODEL = os.getenv("MODEL", "small") # conflicts!
# whisper_api.py: DEFAULT_MODEL = "base" # another conflict!Configuration should live in one place. A single source of truth eliminates configuration drift.
In vibe coding, testing is the only guarantee. Every new prompt can break old code. Golden rule: write a test proving current state works BEFORE asking AI to change anything.
Not a single API endpoint is tested with HTTP requests. Neither upload, transcription, nor admin routes have integration tests.
Routing errors, wrong HTTP status codes, and serialization issues are only discovered in production.
"Create tests/test_api.py using Flask test client. Test all routes: GET /health, POST /upload, POST /transcribe, GET /transcriptions, /admin/* with both valid and invalid inputs."
# No integration tests exist. Example of what should be:
# def test_upload_invalid_file(client):
# response = client.post("/upload", data={"audio": (BytesIO(b"not audio"), "test.exe")})
# assert response.status_code == 400Every API endpoint needs at least a happy-path and an error-case integration test using the framework test client.
Test data is created inline without reusable fixtures. Every new test must write its own setup code.
Duplicated setup code leads to inconsistent test data and makes tests hard to maintain.
"Create tests/conftest.py with fixtures: app (Flask test app), client (test client), db_session (test database), sample_audio (test audio file). Create tests/factories.py for User and Transcription factories."
# Current: no fixtures, each test duplicates setup
def test_something():
app = create_app() # duplicated
db.create_all() # duplicated
user = User(email="test@test.com") # duplicatedGood test infrastructure (fixtures, factories, helpers) pays for itself within weeks by making tests easy to write and maintain.
Dead code accumulates from AI sessions — old approaches left behind. It confuses both you and future AI prompts. Keep code clean: what's not needed gets deleted.
server/whisper_api.py is not imported by any other module. The Whisper integration is done directly in transcribe.py.
Dead module confuses new developers and gets accidentally modified during refactoring.
"Delete server/whisper_api.py — it is not imported anywhere. Run grep -r "whisper_api" to confirm no references exist."
# server/whisper_api.py — 180 lines, imported by nobody
class WhisperClient:
def __init__(self, api_key, model="medium"):
...
def transcribe(self, audio_path, language="de"):
...Dead code is not free. It costs attention, creates confusion, and can introduce bugs when accidentally modified.
14 imported modules or functions are never used: json, sys, re in app.py, hashlib and hmac in auth.py, among others.
Unused imports slow down startup, increase memory footprint, and obscure real dependencies.
"Run ruff check --select F401 --fix server/ to auto-remove all unused imports. Then run ruff check --select I --fix server/ to sort remaining imports."
import json # unused
import sys # unused
import re # unused
from flask import Flask, request, jsonify, redirect # redirect unusedUse an auto-formatter (ruff, autoflake) to catch unused imports. Configure as a pre-commit hook to prevent accumulation.
AI repeats itself between prompts. Inconsistent naming, duplicate functions. Each issue is small but together code becomes unmaintainable. Check regularly.
except: without a specific exception catches everything including SystemExit, KeyboardInterrupt, and MemoryError.
Process cannot be cleanly terminated. Severe system errors are swallowed and go unnoticed.
"In server/transcribe.py, replace all bare except: clauses with except Exception as e: and add logging.exception("...") calls."
try:
result = process_audio(chunk)
except: # catches EVERYTHING
result = "" # silently returns empty stringNever use bare except:. Always catch specific exceptions or at minimum except Exception.
AI-generated code often contains security holes — hardcoded keys, missing validation, SQL concatenation. These are invisible: the code "works" but is like an unlocked front door. For every AI-generated code, check input validation and whether secrets ended up in the source.
Admin routes (/admin/users, /admin/stats) are accessible without any authentication whatsoever.
Anyone can view user data, delete accounts, and modify system settings.
"Add a @require_admin decorator to all /admin/* routes in server/app.py. Implement JWT-based authentication in server/auth.py."
@app.route("/admin/users")
def admin_users():
users = User.query.all()
return jsonify([u.to_dict() for u in users])Every admin endpoint must require authentication and authorization. Defense in depth means checking at every layer.
The application starts with debug=True, enabling the interactive debugger and code reload in production.
The Werkzeug debugger allows remote code execution. Attackers can run arbitrary Python code on your server.
"In server/app.py line 312, replace app.run(debug=True) with app.run(debug=os.environ.get("FLASK_DEBUG", "0") == "1")."
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)Never enable debug mode in production. It exposes an interactive debugger that allows arbitrary code execution.
A further 29 lower-priority hints were reviewed but not written out individually — they would have padded this report without making it more useful.
Your code probably has similar spots. AI agents find them for you — usually in under an hour.
Get a report for your project — EUR 1947 findings in this project. How many does yours have?
One-time EUR 19 incl. VAT · no subscription · report usually within an hour by e-mail