Skip to content

Commit ae63f48

Browse files
committed
v2.13.1 — security hardening, YubiKey filter, reader debug, TigerData_ prefix
Security (from the 2026-07-19 review): closed two stored-XSS holes reachable from a friend's product links/attachments (safeHref at all href sites), gated shell.openExternal + added a will-navigate lock (isSafeExternalUrl), and fixed img:get SSRF (public http(s) only, hostname resolved, redirects re-validated, timeout). Fixes: a YubiKey is no longer enumerated as an RFID reader (nfc-pcsc CCID interface skipped by name — untested on hardware, patch to follow if needed); Snapmaker/FlashForge/Creality connections no longer leak on account switch; _checkLowStockNotifs no longer re-scans per product; the Add-Product NFC Data preview fills on open; calibration step badges align at the top. Changed: new chipless spools mint a TigerData_ id prefix (both prefixes accepted forever, no migration; generalised across mobile/Hub/Function); stock-value tile shows its HT/TTC qualifier on the price line; "NFC Data" label → "TigerData", rack "Subtitle" → "Description", list name/occasion capped at 40. Added: length-limit shake feedback on every maxlength field; a debug-only reader readout in the TigerPOD modal (replaces the redundant "I own a TigerPOD" toggle; hasPod is telemetry-only, auto-set on first scan); .ttag / reader-selection / review briefs; partner-packaging assets. Docs: URL fields documented as attacker-controlled across the ecosystem; llms.txt Firestore map completed; docs:check now validates the map and image paths. Note: ships the TigerData_ minting while mobile build 16 adoption is still rolling — accepted for this release; the failure mode is a wrong tier badge on an un-updated tester, not data loss.
1 parent aabc882 commit ae63f48

23 files changed

Lines changed: 377 additions & 56 deletions

‎CHANGELOG.md‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,37 @@ Versions follow [Semantic Versioning](https://semver.org/).
55

66
---
77

8+
## v2.13.1 — 2026-07-21
9+
10+
### Added
11+
12+
- **Length-limit feedback.** A `[maxlength]` field silently swallowed the over-the-limit keystroke; it now shakes (`fieldBump` keyframe) so the rejection is seen — the desktop analogue of a phone's haptic "no". One delegated `beforeinput` listener + `bumpField()` covers every `[maxlength]` input/textarea (present and future), firing only on a genuinely rejected insert (accounts for selected text a keystroke/paste would replace). Honours `prefers-reduced-motion` (a `--danger-soft` border flash instead of movement) — `renderer/inventory.js`, `renderer/css/00-base.css`.
13+
- **Debug-only reader readout** in the TigerPOD modal (admin/`state.debugEnabled` only), replacing the removed "I own a TigerPOD" toggle. Shows the readers the app currently treats as **active** (name + chip UID or "empty") and the account TigerPOD telemetry (`current` vs `max` reader count, `hasPod`). Reads live state and repaints on reader plug/unplug, card present/removed, and telemetry hydration (`hydratePodSignal`) — `renderer/inventory.js`.
14+
- **`docs/TTAG-EXPORT-BRIEF.md`, `docs/READER-SELECTION-BRIEF.md`, `docs/REVIEW-BRIEF.md`** — frozen implementation/audit briefs (`.ttag` inventory backup format; per-reader active/inactive selection panel; standing read-only review scope). `docs/reviews/` now keeps one permanent file per code review.
15+
- **`assets-src/img/partners/`** — retail-box photos from eSun, SUNLU and ROSA3D whose spools carry the TigerTag badge (masters only, not bundled).
16+
17+
### Changed
18+
19+
- **New chipless spools mint a `TigerData_` id prefix** (was `CLOUD_`). The prefix is a discriminator, not a display value: rather than migrate existing docs, `_isChiplessId` now accepts **both** prefixes forever. The same both-prefixes generalisation was applied across the ecosystem — mobile (`main_inventory.dart`), Hub (`lib/data/inventory.ts`), the stats Cloud Function (`functions/index.js`) — and documented (`llms.txt`, backend README). Studio has one discriminator point (`normalizeRow`) and one mint point (`_adpCloudId`).
20+
- **The stock-value tile shows its HT/TTC qualifier on the price line** (`236.60 € excl. tax`), not under the label. The figure is wrapped in `.sb-stat-num` so the count-up tween no longer wipes the suffix; the qualifier has its own colour so the up/down tint never tints it.
21+
- **The Add-Product "NFC Data" preview label is now "TigerData"**; the rack **"Subtitle" field is now "Description"** (both value-only, 9 locales). **List name/occasion capped at 40** characters (were 80/60).
22+
- **Documented that URL fields are attacker-controlled** (`buyUrl`, `attachments[].url`, inventory `Link*`, `publicLists` items) — the Firestore rules cannot filter string contents, so every client scheme-checks at render; written into `docs/firestore-schema.md`, the backend README and the third-party integration guide, and the mobile app + Hub `cleanUrl` were aligned. README gained a *"A sandbox, not the product"* section; `llms.txt` Firestore map completed (24 collections) and `docs:check` now validates it plus image paths.
23+
24+
### Fixed
25+
26+
- **Two stored-XSS holes reachable from another user's data** (2026-07-19 review). `esc()` escapes HTML but never validated the URL scheme, so `javascript:`/`file://`/`smb://` passed through friend-supplied product links and attachments untouched. New `safeHref()` (composes `_looksLikeUrl` + `esc`) collapses any non-http(s) value to `#`, applied at all 8 `href` sites. In the main process `isSafeExternalUrl()` gates every `shell.openExternal`, plus a `will-navigate` lock — a renderer foothold can no longer hand the OS a `file://` or leave the app origin.
27+
- **`img:get` SSRF with readback** — it fetched a Firestore-sourced image URL (incl. a friend's `photoURL`) with no validation/timeout and served the body back. Now refuses non-public destinations (resolves the hostname first; rejects loopback/RFC1918/link-local/CGNAT/IPv6-ULA), re-validates each redirect hop, 8 s deadline.
28+
- **A YubiKey (or other Yubico security key) is no longer treated as an RFID reader** — over USB it exposes a PC/SC CCID interface, so `nfc-pcsc` enumerated it like an ACR122U. The reader-registration gate skips names matching `yubico`/`yubikey` (kept narrow; the reader-selection panel is the general control).
29+
- **Printer connections no longer leak on account switch / sign-out** — `unsubscribePrinters` now sweeps Snapmaker, FlashForge and Creality (was Elegoo/Bambu/Anycubic only), stopping the Creality camera with its socket.
30+
- **`_checkLowStockNotifs` no longer re-scans the inventory per product** on every render (now one `_stockCountByKey()` pass).
31+
- **The Add-Product NFC Data preview is filled on open** (was empty until the first field change — the refresh ran before the section un-hid). **Calibration "How to measure" step badges sit at the top of every card** (`align-items: flex-start` + a 2px optical nudge) regardless of caption wrap.
32+
33+
### Removed
34+
35+
- **The "I own a TigerPOD" toggle** and its `tigerPodOwn*` i18n keys / `.tigerpod-own-card` CSS. `hasPod` is telemetry-only, auto-set on the first successful scan, so the manual toggle was redundant; the auto-set and the 1-or-2-reader write path are untouched.
36+
37+
---
38+
839
## v2.13.0 — 2026-07-19
940

1041
### Added

‎FEATURES.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Tiger Studio Manager — Feature Catalogue
22

3-
Tiger Studio Manager is the Electron desktop companion to the TigerTag ecosystem: a filament-inventory manager built around RFID/NFC-tagged spools ("TigerTag" chips), a fully-digital "TigerData" tier, live 3D-printer integration across six brands, physical storage/rack management, a Firebase-backed social layer (friends, shareable wishlists, public profiles), and companion hardware (TD1S color sensor, TigerScale, TigerPOD dual-reader stand). This document catalogues every **shipped** feature, grouped by domain, current as of **v2.13.0**. Per-version release detail lives in `CHANGELOG.md`; forward-looking / in-progress work lives in `ROADMAP.md`.
3+
Tiger Studio Manager is the Electron desktop companion to the TigerTag ecosystem: a filament-inventory manager built around RFID/NFC-tagged spools ("TigerTag" chips), a fully-digital "TigerData" tier, live 3D-printer integration across six brands, physical storage/rack management, a Firebase-backed social layer (friends, shareable wishlists, public profiles), and companion hardware (TD1S color sensor, TigerScale, TigerPOD dual-reader stand). This document catalogues every **shipped** feature, grouped by domain, current as of **v2.13.1**. Per-version release detail lives in `CHANGELOG.md`; forward-looking / in-progress work lives in `ROADMAP.md`.
44

55
---
66

‎WORKLOG.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Added
44

5+
- **Debug-only reader readout in the TigerPOD modal** (admin/debug mode) — in place of the removed ownership toggle, it shows what the app currently treats as an **active** reader (name + whether a chip is on it), plus the account's TigerPOD telemetry (lifetime max reader count, `hasPod`). Reads live state (`state.nfcReaders`, `state.nfcCardPresent`, `_telRfidMax`) and **refreshes in real time** — the reader plug/unplug and card present/removed handlers re-render it while the modal is open (guarded so it's a no-op when closed), and it also repaints when the telemetry read lands — `hydratePodSignal` is a fire-and-forget read at connect, so a modal opened before it returned showed the pre-hydration defaults (max 0, hasPod false) until now. Shows current vs max reader count side by side. Hidden entirely for non-debug users. Admin/dev surface — not user-facing — `renderer/inventory.js`, `renderer/inventory.html`, `renderer/css/60-modals.css`
6+
7+
- **Reader-selection panel brief** — `docs/READER-SELECTION-BRIEF.md`, the frozen spec for a per-reader active/inactive panel: the set of active readers is what the app considers usable (burn flow, card-present, "no reader" prompt all key off it). Smart defaults fold the YubiKey filter in (security keys default off with the reason shown, real readers default active), choices persist per reader, and it handles the two-identical-ACR122U case. Ships the immediate YubiKey fix now; the panel is the general control — `docs/READER-SELECTION-BRIEF.md`
8+
59
- **`.ttag` export/import brief** — `docs/TTAG-EXPORT-BRIEF.md`, the frozen spec for backing up inventory materials to a `.ttag` file so they live outside the cloud. A `.ttag` is a verbatim copy of the Firestore doc(s), `records[]` of 1→N (one spool / a twin / the whole inventory, no branching); twins are atomic (exporting one always exports both) and re-paired from `twin_tag_uid` in the data; import validates then previews then accepts, with restore (same account, verbatim) vs import (other account, ids regenerated + twin refs remapped) modes. Frozen identifiers (`.ttag`, UTI `io.tigertag.ttag`, ProgID `TigerTag.ttag`, content marker), and a Phase 1 (export + file-picker import, testable in dev) / Phase 2 (OS association, needs a build) / Phase 3 (whole-inventory) split. The signed TigerTag+ backup (a separate `rfidList` collection, not the inventory doc) rides along in a required `rfidBackups` map so the backup is complete. A multi-material export asks grouped (one file, all records) vs individual (one file per material); a twin is 2 records but one material, so it stays one file and counts as individual — `docs/TTAG-EXPORT-BRIEF.md`
610

711
- **Length-limited fields now shake when they reject a keystroke** — a `maxlength` field used to swallow the over-the-limit character in silence; on desktop the equivalent of a phone's haptic "no" is a quick horizontal shake, so the rejection is seen. One delegated `beforeinput` listener covers **every** `[maxlength]` input and textarea in the app (13 today, from the 3-char friend-code boxes to the 500-char list message), present or added later, so nothing is wired per field. It fires only on a genuinely rejected insert — a paste or keystroke that would overflow, accounting for any selected text it would replace. Honours `prefers-reduced-motion`: a single danger-tinted border flash instead of movement. Shared `bumpField()` helper + `fieldBump` keyframe, reusable for any future hard-limit feedback — `renderer/inventory.js`, `renderer/css/00-base.css`
@@ -10,6 +14,8 @@
1014

1115
## Changed
1216

17+
- **The stock-value tile shows its HT/TTC qualifier next to the price, not under the label** — `236.60 € excl. tax` instead of `236.60 €` with `STOCK VALUE · excl. tax` below. It qualifies the figure, so it belongs on the figure's line. The number is wrapped in its own span so the count-up tween (which sets `.value` textContent) no longer wipes the suffix; the qualifier has its own muted colour so the up/down tint never turns it green/red — `renderer/inventory.js`, `renderer/css/00-base.css`
18+
1319
- **New chipless spools are minted with a `TigerData_` id prefix** (was `CLOUD_`), so an exported `.ttag` never shows a confusing `CLOUD_` id — the file carries the tier name the user knows. The prefix is a *discriminator*, not a display value: `isCloud = spoolId.startsWith("CLOUD_")` decided the whole chipless path. Rather than migrate (a Firestore doc-id rename is delete+recreate across every account) the discriminator now accepts **both** prefixes via a shared `_isChiplessId` — `CLOUD_` stays recognised forever for the docs already out there, `TigerData_` is used going forward. Studio has a single discriminator point (`normalizeRow`) and a single mint point (`_adpCloudId`), both updated. The filter chain is unchanged: `TigerData_` → `isCloud` → `protocol:"TigerCloud"` → shown as "TigerData", so it lands under the existing search selector. **This prefix is read by four codebases** — the same both-prefixes generalisation was applied to the mobile app (`main_inventory.dart`), the Hub (`lib/data/inventory.ts`) and the stats Cloud Function (`functions/index.js`, where `cloudCount` would otherwise under-count), plus the `llms.txt` / backend-README docs. **Deployment order matters**: a `TigerData_` spool created in Studio syncs to Firestore and is read by the phone / Hub / Function immediately, so their both-prefix updates must be live before the Studio release that flips the minting reaches users, or an old client reads a new spool as a real chip and shows the wrong tier — `renderer/inventory.js` (+ mobile, Hub, backend)
1420

1521
- **The Add-Product panel's "NFC Data" preview label is now "TigerData"** — value change across all 9 locales (it's a brand name, identical in every language). The key stays `addProductRfidDataLbl` — `renderer/locales/*`
@@ -30,6 +36,8 @@
3036

3137
## Fixed
3238

39+
- **A YubiKey (or other Yubico security key) is no longer treated as an RFID reader** — plugged in over USB it exposes a PC/SC CCID interface, so `nfc-pcsc` enumerated it exactly like an ACR122U and the app tried to use it as a filament reader (reported by a real user). The reader-registration gate now skips devices whose PC/SC name matches `yubico`/`yubikey`. Deliberately narrow (Yubico only) to avoid excluding a real reader with an unusual name; the general user-controlled mechanism is the reader-selection panel (`docs/READER-SELECTION-BRIEF.md`) — `services/nfc-process.js`
40+
3341
- **The Add-Product panel's NFC Data preview is now filled on open** (debug mode). The panel builds the preview whenever a field changes, but on open every one of those refreshes ran *before* the section was un-hidden, so each hit the hidden-section early-return and did nothing — the field stayed empty until the first manual change. `openAddProductPanel` now runs one refresh right after un-hiding the section, so the default product (Generic PLA Basic, 1000 g) renders straight away — `renderer/inventory.js`
3442

3543
- **The step badges in the calibration modal's "How to measure" strip now sit at the top of every card** — the head was `align-items: center`, so a caption that wrapped to two lines (steps 2 and 3, "Keep the cardboard core" / "Put it all on the scale") dropped its orange number badge to the vertical middle while step 1's one-line caption kept its badge at the top. Switched to `align-items: flex-start` so the badge pins to the first line regardless of wrap, plus a −2px nudge so the 18px badge's centre sits level with the 14px caption line rather than 2px below it. Verified the three badges land at delta 0.00 from the first-line centre — `renderer/css/60-modals.css`
@@ -43,4 +51,6 @@
4351

4452
## Removed
4553

54+
- **The "I own a TigerPOD" toggle** in the TigerPOD modal, with its `tigerPodOwnLabel` / `tigerPodOwnSub` i18n keys and `.tigerpod-own-card` CSS. It was redundant: `hasPod` is a telemetry-only flag (`telemetry/studio.hasPod`) that **nothing in the app behaviourally depends on**, and it is auto-set on the first successful RFID read with readers — so a manual toggle changed nothing the next scan wouldn't re-set. The auto-set (and the whole 1-or-2-reader write path) is untouched — `renderer/inventory.html`, `renderer/inventory.js`, `renderer/css/60-modals.css`, i18n (2 keys × 9 locales)
55+
4656
## i18n

‎data/release-notes/v2.13.1.md‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
## Security
2+
3+
- Fixed a flaw where a link stored on a filament — a buy link or an attachment shared by a friend — could carry a dangerous, non-web address that ran when clicked. Every link is now checked, and only real web addresses (http/https) open.
4+
- Hardened how the app opens external links and how it fetches product images, so a page or image address coming from someone else's data can no longer reach anything on your machine or your local network.
5+
6+
## Fixed
7+
8+
- Fixed an issue where a YubiKey (or similar security key) was mistaken for an RFID reader when plugged in, because it presents a smart-card interface to the system. Security keys are now ignored.
9+
- Fixed a leak where switching accounts or signing out left Snapmaker, FlashForge and Creality printers still connected in the background.
10+
- Fixed an issue where the "NFC Data" preview in Add Product was empty until you changed a field.
11+
- Fixed the alignment of the step badges in the container "How to measure" guide when a step's caption ran onto two lines.
12+
- Made low-stock checks lighter, so large inventories update more smoothly.
13+
14+
## Changed
15+
16+
- Length-limited fields now give a quick shake when they're full, so you can tell your keystroke was rejected instead of silently ignored.
17+
- The **Stock value** now shows whether it's excluding or including tax right next to the figure.
18+
- Chipless spools created in Tiger Studio now use a cleaner **TigerData** identifier, and the "NFC Data" preview label reads **TigerData** to match.
19+
- The rack **Subtitle** field is now labelled **Description**, and list names and occasions are capped at a tidy length.
20+
21+
---
22+
23+
📖 **[Full changelog](https://github.com/TigerTag-Project/TigerTag-Studio-Manager/blob/main/CHANGELOG.md)** · ⬇️ Grab the installer for your OS from the **Assets** below (macOS `.dmg`, Windows `Setup .exe`, Linux `.AppImage`).

0 commit comments

Comments
 (0)