Skip to content

Production hardening: CI, vault, CSV import, P&L correctness - #8

Merged
harshabala merged 14 commits into
mainfrom
harden/production
Sep 28, 2026
Merged

harshabala merged 14 commits into
mainfrom
harden/production

Conversation

@harshabala

Copy link
Copy Markdown
Owner

Summary

Production-hardening pass on MyTicker v0.5.0. One fix per commit; see CHANGELOG.md for the full list.

Correctness

  • Indian day P&L was a ~5-day change: Yahoo's chartPreviousClose on a 5-day range is the close before the first bar. Now uses the previous session's close.
  • INR and USD are never summed; a zero or missing previous close shows "—" instead of counting the whole position as day P&L.
  • Holdings that missed the latest poll are marked stale; the popup shows Live / Closed / Stale by data age.

Security

  • Finnhub key vault: PBKDF2 raised to 600k iterations with automatic migration; bad session material fails safe instead of stopping all prices.
  • Unlock codes can be 6–128 characters with a strength hint, and can be changed from Settings (atomic re-encryption).
  • CSV import hardened: prototype-named headers, non-finite/negative quantities, row/column caps, quoted newlines, Indian number formats.
  • Removed unused query2.finance.yahoo.com host permission; a test keeps host permissions equal to the hosts the code contacts.

Robustness

  • 12s timeout on every provider call; 429/5xx back off per provider (1 min doubling to 30), surviving service-worker restarts.
  • Clearing holdings during a refresh no longer resurrects them.

Tooling

  • npm test (89 tests) and npm run check, run by a new GitHub Actions workflow.
  • Removed the obsolete myticker-v0.2.0-browser-test/ copy and an unused dependency.

Test plan

  • npm test: 89/89
  • npm run check: green
  • CI green on this PR
  • Manual: load unpacked in Chrome, import the sample Zerodha CSV, confirm the tape, popup Live/Closed/Stale pill and "—" rendering
  • Manual: change the unlock code from Settings and confirm prices resume

harsha balakrishnan added 14 commits September 27, 2026 20:38
The extension ships a static SVG sprite (icons/phosphor-symbols.svg) and
never imports the React package, which also pulled react/react-dom as
peers into the lockfile. The project now has zero npm dependencies.
npm test runs every legacy test_fixtures script plus node:test files in
tests/ through node --test, so a non-zero exit from any script fails the
run. scripts/check-syntax.mjs replaces the README shell loop: it parses
classic content scripts as scripts and the rest as ES modules, and fails
when the manifest references a file that does not exist.
Pinned action SHAs, read-only token, no persisted credentials.
…session key

- New vault records use 600k PBKDF2-SHA256 iterations (OWASP 2023).
  310k records from v0.5.0 still decrypt and are re-wrapped on unlock.
- Validate record version, KDF cost, salt and IV length before use.
- A session key that no longer matches the stored vault threw inside the
  poll and stopped Yahoo and crypto prices too. It now drops the stale
  material and reports the vault as locked.
- vault-status reports a legacy plaintext key so Options asks for an
  unlock code instead of claiming the key is merely locked.
- Set chrome.storage.session access to TRUSTED_CONTEXTS explicitly.
- Time out the Finnhub test-connection request after 12s.
Only query1 is ever fetched. Add a test that the set of hosts in shipped
code equals host_permissions exactly, and that the manifest has no
externally_connectable or extra permissions.
- RFC 4180 parsing: quoted commas, doubled quotes, and quoted newlines no
  longer shift columns.
- Size (1 MB), row (5,000), column (64), cell (256 chars) and holdings
  (1,000) caps with a user-readable CsvLimitError.
- Rows are null-prototype objects; __proto__/constructor/prototype headers
  are dropped and column lookups use own properties only.
- parseBrokerNumber accepts Indian grouping (1,23,456.78), currency
  prefixes and accounting negatives; rejects Infinity, exponents and junk.
  Negative, zero, NaN and infinite quantities are skipped.
- Symbols must look like tickers; duplicates merge with a weighted cost.
- A missing or contradictory currency column no longer labels US stocks
  as INR: currency follows the quote source (.NS/.BO INR, else USD).
Correctness:
- Yahoo meta.chartPreviousClose is the close before the first bar of the
  requested range. With range=5d that is about five sessions ago, so every
  NSE/BSE 'day' P&L was really a five-day change. The previous close is now
  the last daily close before the regularMarketTime session (split-adjusted),
  falling back to meta.previousClose.
- Finnhub pc = 0 now means 'no previous close' instead of a zero baseline
  that turned the whole position value into day P&L.
- Quotes carry currency, provider timestamp and marketState (open/closed
  from Yahoo's regular trading period).

Robustness:
- Every provider request (CoinGecko and Binance had none) uses a 12s
  timeout; malformed JSON and HTTP errors yield no quote, never a throw.
- 429/5xx pause that provider with exponential backoff (1 min doubling to
  30 min, honouring Retry-After). State persists in pts_provider_backoff so
  a restarted service worker keeps honouring it.
- Console logs name the provider only, never symbols, URLs or keys.

Tests use recorded-shape fixtures in test_fixtures/providers; no real API
calls.
…live

- Day P&L needs a positive provider previous close. Missing or zero now
  yields null ('—' in the tape and popup) instead of either the whole
  position value (pc = 0) or a 15-minute change mislabelled as 'day'.
- Aggregates sum only positions that share a currency; INR + USD totals
  are null instead of a meaningless sum. aggregate.partial marks totals
  that exclude unpriced holdings. Quote currency beats a stale CSV value.
- Positions without a quote from the current poll are marked stale.
- describeFreshness (shared.js + content bridge) labels state Stale when
  flagged or not rewritten for max(5 min, 3 refresh intervals), Closed
  when every known market is shut, else Live. Popup pill and tape use it.
- Cent rounding is half-away-from-zero and never renders '-0.00'.
- mergePriceSnapshots drops non-positive prices and corrupt entries.
…dings

- staleWarning is also set when any holding missed this poll (partial
  provider outage, paused provider, locked Finnhub key).
- Re-read holdings before writing state; if they changed while quotes were
  in flight (import or clear), skip the write instead of restoring deleted
  positions. A poll requested mid-poll now runs afterwards, not dropped.
- Settings are migrated before use, displayCurrency comes from priced
  positions, and state records marketState and refreshMinutes.
- ProviderBackoff: a success racing a 429 in the same batch no longer
  cancels the pause.
- Integration tests load background.js against a chrome.* fake: alarms on
  restart, partial and total outages, persisted backoff, holdings race,
  storage failure, and message sender/type allowlisting.
PRIVACY lists the exact four hosts, the vault parameters, session-storage
scope, the backoff key, and what logs contain. README explains day P&L,
currency handling, freshness labels, CSV limits, and the new npm test and
npm run check commands.
The 6-character minimum is unchanged so existing vaults keep working.
Codes may now be any characters, including spaces, up to 128 characters,
and are used exactly as typed. Options shows a live, non-blocking hint
(weak / fair / strong) nudging towards 12+ characters or a passphrase.
The worker enforces the same bounds on create and unlock.
…yption

Options gains a 'Change unlock code' form (current code, new code with the
strength hint, confirmation). The worker's vault-change-code handler
decrypts with the current code, builds and verifies the new record in
memory, then replaces the vault in a single storage write only if it is
still the record it decrypted. A wrong current code, invalid new code,
failed verification, storage error, or concurrent replace leaves the old
vault intact. Old 310k-iteration vaults are upgraded in the same step.
A v0.2.0 snapshot of the extension (manifest 0.2.0, pre-vault code) that
was committed by accident in 5fccd64. Nothing references it: not the
manifest, scripts, tests, CI, or docs.
@harshabala
harshabala merged commit 4ad9baf into main Sep 28, 2026
4 checks passed
@harshabala
harshabala deleted the harden/production branch September 28, 2026 14:42
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