Skip to content

Commit 9910d24

Browse files
Merge pull request #24 from Frostbite1536/claude/audit-repository-LupYN
Claude/audit repository lup yn
2 parents 89f216a + 04e43c3 commit 9910d24

135 files changed

Lines changed: 4466 additions & 1694 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/settings.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"hooks": {
3+
"SessionStart": [
4+
{
5+
"matcher": "",
6+
"hooks": [
7+
{
8+
"type": "command",
9+
"command": "pip install -e '.[api,mcp,dev]' --quiet 2>/dev/null || pip install -e '.[dev]' --quiet 2>/dev/null || true"
10+
}
11+
]
12+
}
13+
]
14+
}
15+
}

.env.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,8 @@ KALSHI_PRIVATE_KEY_PATH=kalshi_private_key.pem
1313
MANIFOLD_API_KEY=manifold_your_api_key_here
1414

1515
# FastAPI settings (for the local web app)
16-
SECRET_KEY=change-this-secret-key-in-production
16+
# SECRET_KEY is auto-generated in dev mode. For production, generate a strong key:
17+
# python -c "import secrets; print(secrets.token_urlsafe(64))"
18+
# SECRET_KEY=your-secure-random-string-here
1719
DATABASE_URL=sqlite:///./data/prediction_analyzer.db
20+
# ENVIRONMENT=production # Uncomment to enforce SECRET_KEY requirement

.flake8

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[flake8]
2+
max-line-length = 100
3+
extend-ignore = E203, W503
4+
exclude =
5+
.git,
6+
__pycache__,
7+
build,
8+
dist,
9+
*.egg-info,
10+
venv,
11+
env
12+
per-file-ignores =
13+
__init__.py:F401

.github/workflows/ci.yml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version: ["3.9", "3.10", "3.11", "3.12"]
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Set up Python ${{ matrix.python-version }}
20+
uses: actions/setup-python@v5
21+
with:
22+
python-version: ${{ matrix.python-version }}
23+
24+
- name: Cache pip
25+
uses: actions/cache@v4
26+
with:
27+
path: ~/.cache/pip
28+
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }}
29+
restore-keys: |
30+
${{ runner.os }}-pip-${{ matrix.python-version }}-
31+
32+
- name: Install dependencies
33+
run: |
34+
if [[ "${{ matrix.python-version }}" == "3.9" ]]; then
35+
pip install -e ".[api,dev]"
36+
else
37+
pip install -e ".[api,mcp,dev]"
38+
fi
39+
40+
- name: Run tests
41+
run: |
42+
if [[ "${{ matrix.python-version }}" == "3.9" ]]; then
43+
pytest --cov=prediction_analyzer --cov-report=xml -q --ignore=tests/mcp
44+
else
45+
pytest --cov=prediction_analyzer --cov=prediction_mcp --cov-report=xml -q
46+
fi
47+
48+
- name: Upload coverage
49+
if: matrix.python-version == '3.12'
50+
uses: actions/upload-artifact@v4
51+
with:
52+
name: coverage-report
53+
path: coverage.xml
54+
55+
lint:
56+
runs-on: ubuntu-latest
57+
58+
steps:
59+
- uses: actions/checkout@v4
60+
61+
- name: Set up Python
62+
uses: actions/setup-python@v5
63+
with:
64+
python-version: "3.12"
65+
66+
- name: Install dependencies
67+
run: pip install -e ".[dev]"
68+
69+
- name: Check formatting (black)
70+
run: black --check prediction_analyzer prediction_mcp tests
71+
72+
- name: Lint (flake8)
73+
run: flake8 prediction_analyzer prediction_mcp

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ chart_*.png
5757
pro_chart_*.html
5858
enhanced_chart_*.html
5959
global_dashboard.html
60+
charts_output/
61+
62+
# Type checking
63+
.mypy_cache/
6064

6165
# SQLite database
6266
*.db

ARCHITECTURE.md

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ class MarketProvider(ABC):
317317
|----------|------|-----------|------------|
318318
| Limitless | `X-API-Key` header | Page-based (`page` param) | Native (API provides PnL) |
319319
| Polymarket | None (public API, wallet as query param) | Timestamp window narrowing | FIFO calculator |
320-
| Kalshi | RSA-PSS per-request signing | Cursor-based (`cursor` param) | Position endpoint + distribution |
320+
| Kalshi | RSA-PSS per-request signing (key cleared from memory after use) | Cursor-based (`cursor` param) | Position endpoint + distribution |
321321
| Manifold | `Authorization: Key ...` header | Cursor-based (`before` param) | FIFO calculator |
322322

323323
### FIFO PnL Calculator (`providers/pnl_calculator.py`)
@@ -331,19 +331,24 @@ Responsible for loading and normalizing trade data from multiple sources:
331331
- **Supported formats**: JSON, CSV, XLSX
332332
- **Auto-detection**: Uses ProviderRegistry to detect file format from field signatures
333333
- **Timestamp parsing**: Handles Unix epochs (seconds/milliseconds), RFC 3339, ISO 8601
334-
- **Unit conversion**: Converts API micro-units (6 decimals) to standard units
334+
- **Unit conversion**: Converts API micro-units using `USDC_DECIMALS` (1,000,000) constant
335335
- **Field mapping**: Maps various API field names to internal format
336+
- **NaN/Infinity sanitization**: `sanitize_numeric()` replaces NaN → 0.0, Inf → ±`INF_CAP`
336337

337338
Key functions:
338339
- `load_trades(file_path)`: Main entry point -- auto-detects provider format
339340
- `save_trades(trades, file_path)`: Save trades to JSON
340341
- `_parse_timestamp(value)`: Robust timestamp parsing
342+
- `sanitize_numeric(value)`: Guards against NaN/Infinity for JSON serialization
343+
344+
Key constants:
345+
- `INF_CAP = 999999.99` — shared ceiling for infinite values across the codebase
341346

342347
### PnL Calculator (`pnl.py`)
343348

344349
Calculates profit/loss metrics:
345350

346-
- `calculate_pnl(trades)`: Returns DataFrame with cumulative PnL
351+
- `calculate_pnl(trades)`: Returns DataFrame with cumulative PnL (uses `Decimal` accumulation to avoid float drift)
347352
- `calculate_global_pnl_summary(trades)`: Aggregate statistics with currency separation -- top-level totals use real-money currencies (USD/USDC) only; play-money (MANA) reported separately under `by_currency`; also includes `by_source` breakdown
348353
- `calculate_market_pnl_summary(trades)`: Per-market statistics
349354
- `calculate_market_pnl(trades)`: Breakdown by market
@@ -356,6 +361,11 @@ Metrics calculated:
356361
- Total invested/returned
357362
- Per-currency and per-source breakdowns
358363

364+
**Numeric precision notes:**
365+
- Cumulative PnL is computed using `decimal.Decimal` accumulation, then stored back as `float`.
366+
- Infinite values (e.g. profit factor with zero losses) are capped at `INF_CAP` (999999.99), defined in `trade_loader.py` and shared across the codebase.
367+
- DB monetary columns use `Numeric(18,8)` to reduce rounding in storage.
368+
359369
### Filters (`filters.py` + `trade_filter.py`)
360370

361371
Advanced filtering capabilities:
@@ -404,12 +414,14 @@ Four chart types with different use cases:
404414

405415
### MCP Server (`prediction_mcp/`)
406416

407-
Model Context Protocol server providing 18 tools across 7 modules:
417+
Model Context Protocol server implementing all three MCP primitives:
408418

409419
- **Transport**: stdio (Claude Code) or HTTP/SSE (web agents)
410420
- **State**: In-memory session with optional SQLite persistence
411421
- **Multi-source**: Session tracks multiple provider sources simultaneously
412-
- **Tools**: data (4), analysis (5), filter (1), chart (2), export (1), portfolio (4), tax (1)
422+
- **Tools**: 18 tools across 7 modules — data (4), analysis (5), filter (1), chart (2), export (1), portfolio (4), tax (1)
423+
- **Resources**: Dynamic resources exposing session state — `prediction://trades/summary`, `prediction://trades/markets`, `prediction://trades/filters`
424+
- **Prompts**: 3 prompt templates — `analyze_portfolio` (with risk/performance/tax focus), `compare_periods`, `daily_report`
413425

414426
Key features:
415427
- `fetch_trades` tool accepts `provider` parameter with auto-detection
@@ -421,11 +433,15 @@ Key features:
421433

422434
REST API with JWT authentication:
423435

424-
- Trade upload with auto-detection of provider format
436+
- Trade upload with auto-detection of provider format (10 MB upload limit)
425437
- Source-based filtering (`?source=polymarket`)
426-
- `/trades/providers` endpoint listing available providers
438+
- `/trades/providers` endpoint listing available providers (requires authentication)
427439
- CSV/JSON export with source and currency fields
428-
- SQLAlchemy models include `source` and `currency` columns
440+
- SQLAlchemy models use `Numeric(18,8)` for monetary columns (price, shares, cost, pnl)
441+
- Security headers middleware (X-Frame-Options, X-Content-Type-Options, HSTS, etc.)
442+
- Per-IP rate limiting with key eviction (bounded memory; single-process only)
443+
- SECRET_KEY auto-generated in dev mode; must be explicitly set for production
444+
- Minimum password length: 8 characters
429445

430446
## User Interfaces
431447

@@ -625,6 +641,58 @@ pytest # Run all tests
625641
pytest --cov=prediction_analyzer # With coverage
626642
```
627643

644+
## Security Architecture
645+
646+
### Web API Security Layers
647+
648+
```
649+
Request → Rate Limiter → Security Headers → CORS → Auth (JWT) → Route Handler
650+
```
651+
652+
1. **Rate Limiting** (per-IP, in-memory sliding window)
653+
- Auth endpoints: 5 req/60s
654+
- General endpoints: 60 req/60s
655+
- Key eviction at 10,000 keys to bound memory
656+
- **Limitation**: Single-process only. For multi-worker deployments, replace with Redis-backed solution.
657+
658+
2. **Security Headers** (middleware on all responses)
659+
- `X-Content-Type-Options: nosniff`
660+
- `X-Frame-Options: DENY`
661+
- `Referrer-Policy: strict-origin-when-cross-origin`
662+
- `X-XSS-Protection: 1; mode=block`
663+
- `Permissions-Policy: geolocation=(), camera=(), microphone=()`
664+
- `Strict-Transport-Security` (HTTPS only)
665+
666+
3. **CORS**
667+
- Explicit origins list with credentials; wildcard without credentials
668+
- Methods restricted to `GET, POST, PUT, PATCH, DELETE, OPTIONS`
669+
- Headers restricted to `Authorization, Content-Type, Accept`
670+
671+
4. **Authentication**
672+
- JWT tokens with HS256 (symmetric), includes `exp`, `iat`, `iss`, `aud` claims
673+
- Passwords hashed with Argon2 (minimum 8 characters)
674+
- SECRET_KEY: auto-generated random key in dev; must be set via env var in production
675+
676+
5. **Upload Protection**
677+
- 10 MB file size limit enforced before processing
678+
- SHA-256 dedup prevents duplicate uploads
679+
- Temporary files cleaned up in `finally` block
680+
681+
### Provider Credential Security
682+
683+
- API keys are never logged, printed, or serialized
684+
- Kalshi RSA private key cleared from memory (`self._private_key = None`) after each `fetch_trades` call
685+
- `.env` and `*.pem` files excluded from version control via `.gitignore`
686+
- Polymarket wallet address is not logged (removed in security audit)
687+
688+
### Numeric Precision Invariants
689+
690+
- **DB storage**: `Numeric(18,8)` for all monetary columns (price, shares, cost, pnl)
691+
- **Cumulative PnL**: Computed via `decimal.Decimal` accumulation, not float `cumsum()`
692+
- **Infinity cap**: Unified to `INF_CAP = 999999.99` (defined in `trade_loader.py`)
693+
- **USDC conversion**: Uses named constant `USDC_DECIMALS = 1_000_000`
694+
- **NaN/Infinity sanitization**: Applied at serialization boundaries (JSON export, MCP responses)
695+
628696
## Design Principles
629697

630698
1. **Modularity**: Each component has a single responsibility

CHANGELOG.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [1.0.0] - 2026-03-10
9+
10+
### Added
11+
- Multi-provider support: Limitless Exchange, Polymarket, Kalshi, Manifold Markets
12+
- Provider auto-detection from API key prefix and file field signatures
13+
- FIFO PnL calculator for providers without native PnL (Polymarket, Manifold)
14+
- MCP server with 18 tools across 7 modules (stdio + SSE transports)
15+
- FastAPI web application with JWT authentication
16+
- SQLite session persistence for the MCP server
17+
- Interactive CLI menu system for novice users
18+
- Tkinter desktop GUI with provider selection
19+
- Four chart types: simple (matplotlib), pro (Plotly), enhanced, global dashboard
20+
- Advanced trading metrics: Sharpe, Sortino, drawdown, profit factor, streaks
21+
- Portfolio tools: open positions, concentration risk, drawdown analysis, period comparison
22+
- Tax reporting with FIFO/LIFO/average cost basis methods
23+
- CSV, XLSX, and JSON export
24+
- LLM-friendly error handling with recovery hints
25+
- Input validation with case normalization for LLM agents
26+
- NaN/Infinity sanitization across all serialization boundaries
27+
28+
### Security
29+
- Security headers middleware (X-Frame-Options, X-Content-Type-Options, HSTS, etc.)
30+
- Per-IP rate limiting with key eviction (5 req/min auth, 60 req/min general)
31+
- 10 MB file upload limit with SHA-256 deduplication
32+
- Argon2 password hashing (minimum 8 characters)
33+
- SECRET_KEY auto-generated in dev, required in production
34+
- Kalshi RSA private key cleared from memory after use
35+
- `Numeric(18,8)` for all DB monetary columns (replacing Float)
36+
- `decimal.Decimal` accumulation for cumulative PnL
37+
- CORS with restricted methods/headers
38+
- All endpoints authenticated (including `/trades/providers`)
39+
- API keys never logged; only env var names in error messages

0 commit comments

Comments
 (0)