MechBay is a single-user (or small household) desktop application. There is no authentication system — anyone who can reach the web server is treated as the owner. Security priorities are: protecting the local database from accidental data loss, preventing unsafe input from corrupting data, keeping secrets out of source control, and not accidentally exposing the app to the network.
Read this file before touching: CSRF configuration, file upload handling, input validation, structured logging, proxy / networking settings, or secret key management.
MechBay has no login system. This is intentional for a local desktop tool.
Consequences:
- Never add a multi-user feature without also adding authentication and per-user data isolation.
- Do not expose the development server (
main.py) to a public or shared network. It binds to127.0.0.1only. - The Docker / Waitress production server binds to
0.0.0.0:5000. Apply firewall rules or reverse-proxy ACLs if deploying in a shared environment.
Flask-WTF CSRFProtect is initialised in create_app() and protects all POST/PUT/DELETE routes automatically.
- CSRF token is injected into every page via
<meta name="csrf-token" content="{{ csrf_token() }}">inbase.html. - AJAX requests must include the token:
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
fetch(url, {
method: 'POST',
headers: { 'X-CSRFToken': csrfToken, 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});- CSRF validation is disabled only when
TESTING=Truein test config, so the test client can POST without tokens. - Never disable
WTF_CSRF_ENABLEDin non-test configurations.
SECRET_KEY signs Flask sessions and CSRF tokens. Rules:
- Never commit a real secret key to source control.
- If
SECRET_KEYis not set in the environment, the app generates a random ephemeral key on startup and logs a warning. This is acceptable for local desktop use but means sessions are invalidated on every restart. - In any persistent deployment (Docker, shared server), set
SECRET_KEYvia environment variable or.envfile. WTF_CSRF_SECRET_KEYdefaults toSECRET_KEYif not set separately.
Validate at route boundaries before passing values to services.
- Always convert ID parameters with
int()wrapped intry/except (TypeError, ValueError). Return 400 on failure. - Strip and validate string inputs (name fields, etc.) at the route level.
- Services assume inputs are already validated; do not add route-level validation inside service functions.
- Do not trust hidden form fields for access control, roles, or pricing.
File menu routes accept .mechbay, .mbforce, and legacy .json inventory exports via /files/upload/* and native open dialogs.
- Maximum upload size: 10 MB (
MAX_CONTENT_LENGTH = 10 * 1024 * 1024inConfig). Flask enforces this and returns 413 automatically. - Parse uploads in a temporary file or memory; do not trust client filenames for security decisions beyond display labels.
- Validate that uploaded JSON is well-formed before processing; catch
json.JSONDecodeErrorand return 400. inventory_project_serviceandforce_servicemust handle malformed or unexpected JSON fields gracefully.
TRUST_PROXY_HEADERS defaults to False. Set it to True only when:
- The app is behind a trusted reverse proxy (nginx, Caddy, etc.)
- That proxy strips any client-supplied forwarded headers and sets its own
When enabled, ProxyFix is applied with x_for=1, x_proto=1, x_host=1, x_prefix=1. Setting this incorrectly allows clients to spoof their IP address.
MechBay uses structlog for all logging.
Do log:
- Application startup event (
app_startup) with version, debug flag, DB URL (not credentials) - Database rollback events
- 400/404 errors at WARNING
- 500 errors at ERROR with traceback
Do not log:
- Passwords or tokens
- Full connection strings containing credentials
- Personal data or miniature inventory contents in error messages
- Request/response bodies
Error handlers return generic messages to the browser; detailed context stays server-side.
- SQLite database stored at
%APPDATA%\MechBay\mechbay.db(Windows desktop) or/data/mechbay.db(Docker). - The directory is created automatically if it does not exist.
- There is no Alembic migration history. Schema changes require manual SQL or DB recreation.
- All queries go through SQLAlchemy ORM — no string-concatenated SQL.
- Use
session_scope()for all DB access; never execute raw SQL strings with user input.
- Dependencies are pinned in
uv.lock. Do not leave version constraints floating. - Keep the
uv.lockfile committed to source control for reproducible installs. - Review new dependencies before adding them; prefer well-maintained packages for non-trivial functionality.