Security
wickworks is built to sit on a private network or behind your own proxy. Read this page before you publish its port anywhere other people can reach.
No authentication
wickworks has no authentication of any kind, on the REST API or on the MCP endpoint. Anyone who can reach the port can call every endpoint. There is no setting to turn auth on.
Bind the port to loopback unless something else needs it:
docker run --rm -p 127.0.0.1:8000:8000 psyb0t/wickworks:latest
To reach it from other machines, put a reverse proxy or a VPN in front that does the authentication, and keep wickworks itself on a private network. The WICKWORKS_TOKEN setting of the agent plugin exists for exactly this: it sends a bearer token for your proxy to check. wickworks ignores it.
The repository’s docker-compose.yml and make run publish the port on every interface. Add 127.0.0.1: to the port mapping on any host with a public interface.
MCP Host check is off
The MCP SDK can reject requests whose Host header is not on an allowlist, a protection against DNS rebinding attacks from a browser. wickworks turns it off, because a self-hosted service reached through arbitrary hostnames and proxies would otherwise reject every real deployment. The consequence: a web page open in a browser on the same machine could try to reach a wickworks bound to localhost through DNS rebinding. All it could do is run computations, since there is nothing to read or change, but it is another reason to keep wickworks off machines where you browse the web, or behind a proxy that checks Host.
What it can reach and store
- No outbound network. wickworks never fetches market data or calls another service. It computes on the bars in the request and returns numbers.
- No storage. It writes no files and keeps nothing between requests. There is no database, cache or log of request bodies. Restarting the container loses nothing.
- Logs. The service logs at
LOG_LEVEL. A failed computation logs the traceback with the request’ssymbolandtimeframe, but not the bars.
Input limits
Every request is bounded before any computation starts:
- At most
MAX_BARSbars (default 5000). More gets 413. symbolup to 128 characters,timeframeup to 32.- Every bar is checked: positive finite prices, consistent high and low, finite non-negative volume, strictly increasing times, no extra fields.
- Every indicator parameter is checked for name, type and range.
FastAPI parses and validates the whole body before the MAX_BARS check runs, so a very large body still costs memory and CPU to reject. There is no request body size limit in wickworks itself; set one in your proxy if untrusted clients can reach it.
A request that passes validation can still be expensive. SMC outputs walk the full history, and raising MAX_BARS raises the cost of one request. Rate-limit in the proxy if clients are untrusted.
Errors do not leak internals
A failure inside an indicator returns 500 with the fixed body {"detail": "indicator computation failed"}. The traceback goes to the server log, never to the client.
The container
The image runs as the unprivileged user wickworks, not root, and needs no capabilities, volumes or extra mounts. Base images and the uv installer are pinned by digest in the Dockerfile. Every published image is scanned with Grype in CI, and the findings go to the repository’s Security tab.
Dependencies
All Python dependencies are pinned exactly and locked in uv.lock. The lock is resolved with uv’s exclude-newer date, so a package version published after that date cannot enter the lock; see Development.