Skip to content

fix(security): bounds on stored data and csv escaping - #111

Merged
jplacht merged 3 commits into
mainfrom
fix/bcr-07-data-bounds-csv
Nov 3, 2026
Merged

jplacht merged 3 commits into
mainfrom
fix/bcr-07-data-bounds-csv

Conversation

@jplacht

@jplacht jplacht commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

What and why

Plan amounts, CX prices, empire state totals and several preferences had no upper bound. Values no game can produce reached storage as 500s (int64 overflow, NaN on Postgres), poisoned planet insights and the public material tracker, and let preferences and named versions grow without limit. Plan names went into CSV exports unescaped, so a shared plan named =1+1 ran as a formula in another user's spreadsheet. Now out-of-range input is a 400 with field errors, and CSV cells are never formulas. Findings SEC-13, SEC-14, SEC-15 of the backend code review.

Changes

  • Plan schema V1 caps amounts at 100_000, experts at 100 and lists at 500, far above anything the game allows, so stored plans keep validating. CX ticker values must be finite.
  • Empire state sync accepts known material tickers only, with p/c/d within ±1e9. This adds one query to that PATCH.
  • Planet insights skip stored plans with out-of-range amounts. This prevents both the median overflow and the orjson 500 on the public endpoint.
  • Preferences:
    • locale ≤16 chars, burnOrigin ≤100, visitation exclusions ≤500 tickers.
    • planOverrides takes at most 500 lowercase-UUID keys. Keys for plans the user doesn't own are dropped rather than rejected, because the frontend resends unsynced keys and a 400 would block every later preference save.
  • At most 20 named versions per plan. The 21st, by save or by rename, is a 400; nothing is dropped.
  • SafeCSVRenderer prefixes ' to text cells starting with = + - @, tab or CR, in every CSV export. As a result, COGC --- exports as '---.

How it was verified

  • QA PASS, first round:
    • ruff check, ruff format --check, ty check and pytest pass, before and after merging main.
    • The code review passed every acceptance criterion.
    • E2E on the Docker stack (main frontend on this backend):
      • Plan PUT/POST with an amount of 1e20 → 400. CX PUT with "nan" → 400. Unknown ticker → 400. Malformed planOverrides key → 400. 21st named version → 400.
      • A plan named =1+1 appears as '=1+1 in both plan CSVs.
      • Walked as perf_user_0 with no 400/500 or validation errors: plan edit and save, named save, rename, restore, save as copy, CX save, empire sync, plan preferences across a reload.
  • OpenAPI snapshot: only the new cap documentation (maxLength, maxItems, ±1e9).
  • Merged origin/main (bcr-05 token revocation), no conflicts.

Backend only, no migration, no frontend change needed.

🤖 Generated with Claude Code

jplacht and others added 2 commits November 3, 2026 12:00
Findings SEC-13, SEC-14, SEC-15 of the backend code review.

- Plan schema V1 caps amounts at 100_000, experts at 100 and lists at 500;
  CX ticker values must be finite. Out-of-range input is a 400, not a 500.
- Empire state sync accepts known material tickers only, within +-1e9.
- Planet insights skip stored plans with out-of-range amounts.
- Preferences: locale, burnOrigin and visitation exclusions capped;
  planOverrides takes at most 500 UUID keys and drops plans the user
  doesn't own. At most 20 named versions per plan.
- SafeCSVRenderer quotes text cells starting with = + - @ tab or CR in
  every CSV export.

Spec: specs/2026-11-02-bcr-07-data-bounds-csv.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codacy-production

codacy-production Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 23 complexity · 0 duplication

Metric Results
Complexity 23
Duplication 0

View in Codacy

🟢 Coverage 100.00% diff coverage · +0.02% coverage variation

Metric Results
Coverage variation ✅ +0.02% coverage variation (-1.00%)
Diff coverage ✅ 100.00% diff coverage

View coverage diff in Codacy

Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (4422433) 6809 6636 97.46%
Head commit (fd7d3dd) 6856 (+47) 6683 (+47) 97.48% (+0.02%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#111) 70 70 100.00%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

…x/bcr-07-data-bounds-csv

# Conflicts:
#	backend/tests/user/api/test_user_viewsets.py
@jplacht
jplacht merged commit 7a80933 into main Nov 3, 2026
9 checks passed
@jplacht
jplacht deleted the fix/bcr-07-data-bounds-csv branch November 3, 2026 11:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant