Changelog
What's new on SkinBox. The platform ships changes continuously —
this page highlights the user-visible ones. For the latest version,
check /api/version. System status lives at
/status.html.
May 3, 2026 — latest
Site reliability + UX sweep
- POLISHCookie-aware health checks, currency consistency across listings + cart, stack-aware Sell Items grouping, freshness chip on grid cards, and a soft-fade on nav active state.
May 1, 2026
SEO + PWA + share URL fixes · HSTS · /watchlist 401 silenced · browser-search routing · seller-stall analytics
- FIXPublic sitemap was emitting
http://localhost:8082/...URLs. Search engines crawling skinbox.market would have indexed broken URLs.SitemapControllernow derives the base URL from the actual request viaX-Forwarded-Proto+X-Forwarded-Host(Cloudflare). Local dev still emits localhost URLs; public visitors get https://skinbox.market. Same fix applied toOpenGraphController(Discord / Facebook / Twitter shares of /item/{id}, /stall/{id}, /faq, /loadout/{id} all leaked localhost canonical + og:url tags). - FIXEmail links + Stripe redirects. Outbound verification emails, suspension notices, wallet alerts, and Stripe deposit success/cancel redirects all used
APP_PUBLIC_URL, which was hardcoded to localhost in run-local.sh. Real users on skinbox.market got broken localhost links. Now defaults to${PUBLIC_URL:-https://skinbox.market}. - FIXBrowser-search bar UX. OpenSearch descriptor pointed `?q={searchTerms}` at
/(the marketing landing) — typing "blue hat" in the URL bar landed on the hero ignoring the query. Updated to/market?q={searchTerms}+ added an SPA-side redirect for legacy/?q=...links so the user actually sees filtered results. - FIXPWA manifest.
start_url+ Marketplace shortcut both pointed to/(marketing hero). Returning users tapping the installed app now land on/marketgrid directly. - SECURITYHSTS now active on the public site. Pre-fix,
Strict-Transport-Securityonly emitted whenSECURITY_HSTS=trueenv was set, andrun-local.shsets it tofalse(correct for local HTTP dev). The same container also serves https://skinbox.market via Cloudflare tunnel — those requests were silently missing HSTS.CorrelationIdFilternow also checksX-Forwarded-Proto: httpsso HTTPS visitors get HSTS regardless of the env flag. - POLISH/watchlist 401 noise gone.
WatchlistModalwas unconditionally firingfetchWatchlistAlerts()on mount, which 401's for anon users. Now gated onme?.id. Network panel + console error stream stay clean for sign-out browsing. - POLISHaria-label on filter controls.
.discount-selecton /market now identifies as "Minimum discount % vs Steam Market price"; affiliate email field on /affiliate as "Affiliate program contact email". 16 of 16 routes audited; 0 a11y issues. - NEWSeller-stall analytics tab.
/api/listings/my-stall/analytics+ new "Analytics" tab in MyStall: per-active-listing view count, my-price-vs-floor delta, and 30-day item-level demand count (one bulk GROUP BY for the whole stall). Lazy-loaded; opens up a price-strategy surface sellers had to eyeball before.
May 1, 2026 — earlier
Sessions never expire (365d TTL, persist across restarts) · branded error pages · NUL-byte 500 fix · /sell + /wallet H1 a11y
- NEWSessions effectively never expire and now survive container restarts. Re-enabled Spring Session JDBC with a 365-day TTL on all three layers — Tomcat servlet session timeout, Spring Session row TTL (
@EnableJdbcHttpSession(maxInactiveIntervalInSeconds = 365 * 24 * 60 * 60)), and the browser cookie'sMax-Age=31536000so the cookie itself sticks across browser closes. Verified live: sameSBOX_SESSIONcookie issued beforedocker restartstill returns 200 after the restart. Closes the standing complaint about being signed out mid-grind. - FIXThe session-cookie sanitizer was hardcoded to scan a cookie named
SESSION, but the actual Tomcat cookie isSBOX_SESSION— so corrupt cookies (with embedded NUL bytes) sailed past the filter and hit Postgres directly, producinginvalid byte sequence for encoding "UTF8": 0x00500s on every authenticated request. Sanitizer now checks both names and runs a defence-in-depth pass over every inbound cookie (control bytes scrubbed in place for non-session cookies; session cookies dropped + cleared on the response so the browser stops replaying the bad value). Without this fix, the JDBC re-enable above would immediately re-trigger the 500 cascade. - SECURITYReplaced Spring's default Whitelabel error page with a branded
GlobalErrorController.text/htmlrequests get a small SkinBox-themed page (no framework strings, no/errorpath leak);application/json+ every/api/**path get a clean envelope{ status, error, message, path, timestamp }with status-code-specific user-readable copy (no raw exception messages — those leak SQL/stack to anonymous visitors). Disabled Whitelabel viaserver.error.whitelabel.enabled: false+ strippedinclude-message/stacktrace/binding-errors/exceptionin both yml profiles. - POLISHEvery dedicated route (
/sell,/wallet,/profile,/cart, etc.) now ships a real<h1>page heading.InfoModal's title is now<h1 class="info-modal-header">(was a styled<div>);/wallet's custom layout gets avisually-hiddenH1 so screen readers + SEO crawlers see the page heading without altering the visual layout. Page-heading audit went from 1/10 routes lacking a heading to 0/10.
April 30, 2026
QA sweep · listing-count sync · sort price-asc fix · reflexive legal redirects · footer column re-balance
- FIXSort by price (and any sort that relies on API order) was silently re-ordered alphabetically by
item.idafter dedup — the dedup map used a plain object whoseObject.values()iterates integer-string keys in numeric order. Switched to aMapso insertion order is preserved. Now?sort=price_asccorrectly puts $1.08 first instead of $1.47. - FIX"X listings found" count text on the marketplace toolbar used the raw API
listings.length(40) instead of the post-dedup/post-filterdedupedListings.length. Click "% Deals" and the chip pool narrows to 17 — the count now says "17", not "40". - FIXHome rail "Limited" tab dead-ended to "0 listings found" — there are no Limited-rarity items in the catalog. Swapped to "Off-Market", which has 14 live listings.
- NEW12 reflexive legal-doc redirects so common typed URLs land on the right doc —
/trade-safety,/refunds,/disclaimer,/acceptable-use,/responsible-disclosure, plus shorthands (/safety,/risk,/aup,/security,/tos,/eula,/refund-policy) all 302 to/legal/doc.html. - POLISHFooter Legal column was cramping "Responsible Disclosure" onto two lines. Re-balanced the 5-column grid (
1.4fr 0.85fr 0.85fr 0.95fr 1.25fr) and addedwhite-space: nowrapto all footer links so nothing wraps awkwardly.
April 30, 2026 — earlier
CSFloat 1:1 home redesign · marketing landing split · grid card chrome · /db hero banner
- NEW
/is now a true marketing landing — pure hero + tab rail + 6-card preview strip + trust strip, no marketplace grid. The visitor lands, scans the live featured listings, and clicks "Visit Marketplace →" (or any tab) to enter/marketfor the full grid. Mirrors csfloat.com's home/market split. - NEWHero card stack on home: top card pulls a live featured listing, two stack-back cards peek to the right with rotation/translate offsets, every card carries the full csfloat chrome (rarity-tinted top + bottom stripes, magnifier zoom cue, decorative wear bar with deterministic-thumb position, online status row, green USD chip, "Buy now / Bargain / cart" decorative button trio).
- NEW"Top Deals · Newest Items · Limited" tab rail under the hero. Each tab navigates to
/market?sort=…with the matching filter pre-applied. URL re-read on popstate so the deep-link actually lands on the filtered grid (was previously rewritten by the in-memory state). - NEWTrust strip below the hero —
● 40 live listings · 2% platform fee · 🔒 Steam OpenID auth · ✓ Stripe secured payments. Live-listings dot pulses softly to signal a live marketplace. - NEWEvery grid card (on /market and /watchlist) now ships the full csfloat chrome: magnifier zoom icon at bottom-right, green→yellow→red rarity-band bar with a thumb position derived from rarity + listing id, the rarity tier name + listing id printed beneath (s&box has no float values — see
s&box vs CSFloat), "● Online" / "Offline" + verified-blue check status row, green USD$chip after the price, recolored green discount chip. Cards lift + image-scale on hover. - NEW
/dbhero banner —[s&box] Database · [80 Skins indexed across the workshop]block sits above the search controls, mirrors csfloat's "/db" hero treatment with corner tick marks and a centered stat chip. - FIX
/item/{id}wear bar gradient direction — was inverted (red on left, green on right); now reads green Factory-New → red Battle-Scarred to match csfloat's standard wear axis. - FIXHome featured pool stays stable when filter tabs are clicked — dedicated
/api/listings?sort=price_desc&limit=8fetch on mount caches a separatehomeFeaturedarray so the hero stack never blanks out during filter narrowing. - POLISHAll
'/?…'deep-links throughout the SPA (auction-only chip, rarity badge, breadcrumb Market crumb, sort-by-discount card, etc.) re-pointed to'/market?…'so the home/market split is consistent everywhere. - POLISHCard price row collapsed to a single line —
flex-wrap: nowrapon.grid-price+white-space: nowrapon.grid-freshso price + USD chip + Steam icon + green discount chip + "Listed Xd ago" all fit cleanly without wrapping mid-word.
April 2026 — earlier
Inline seller ratings · trust signals · perf sweep · Profile overflow banners · staff-panel rate limits
- NEWInline seller ratings on every listing. Each row in the marketplace grid + the Active Listings table on /item now carries a "★ 4.7" chip when the seller has reviews — populated server-side via a single GROUP BY across every seller in the response (no per-seller round-trip). Buyers see seller reputation at a glance without opening the stall page first. CSFloat-1:1.
- NEWDate-range filter on every personal CSV export.
/api/profile/trades.csv,/api/profile/bids.csv,/api/profile/offers.csv, and/api/listings/my-stall/sold.csvnow accept optionalfromandtoquery params (epoch ms, inclusive). Lets users pull a quarterly slice for tax bookkeeping without piping through a full-year file. Backward-compatible — calls without the params still export everything. - NEWHomepage "Marketplace at a glance" strip expanded from 4 to 10 trust signals: active listings + seller count ("41 from 12 sellers"), 24h volume + sale count ("$84 · 3 sales"), 7d volume + count, price range (floor → ceiling), live auctions, and a "Last sale Xm ago" green liveness chip when a real buyer just closed a trade. Anon visitors read a much fuller "is this marketplace alive" signal.
- NEWSeller stall hero carries "Last listed Xh ago" + green "Last sold Xh ago" chips — listing activity and sales activity tell different stories (a seller who relists but never sells vs one who moves inventory routinely). Auto-hide on stale / missing values so dormant stalls stay quiet.
- NEWSelf-visit "Manage stall" affordance: signed-in users visiting their own
/stall/{id}URL see an accent-styled one-click jump to/me/stall. Previously they saw a muted-but-identical visitor view with no management hook. - NEW404 page recovery rail — lands users who followed a dead link on their last-viewed items so they can resume where they left off.
- NEWProfile → Trades / Buy Orders / MyStall Sold / MyStall Active / Inventory tabs all surface a subtle overflow banner ("Showing most recent 200 of 1,842 trades · Use ⇣ CSV for the full history") when the server's display cap trims the payload. Silent for normal users.
- NEWNav bell poll is 10× lighter — mount + 25s poll now hit
/api/notifications/unread-count(a cheap indexed COUNT) instead of shipping the full 100-row notification list. Full list lazy-fetches only when the dropdown opens. - POLISHEvery create-a-row user endpoint now carries a sanity cap: Cart 50 · SavedSearch 10 · Watchlist 500 · UserBlock 100 · ApiKey 20 · Loadout 50 · BuyOrder 200 (active) · Offer 100 (pending) · SellerFollow 200. Bounded DB growth per account.
- POLISHEvery user-history fetch (trades, offers, buy orders, inventory, MyStall Sold, MyStall Active, public stall, watchlist alerts, trade-chat messages, price-history chart, admin withdraw / disputed-deposit queues) pages at the SQL layer instead of hydrating every historical row and trimming in memory.
- SECURITYRate-limit coverage expanded to
/api/admin,/api/csr, broader/api/support,/api/wallet/*,/api/unsubscribe. Compromised staff / CSR / unsubscribe sessions can no longer exfiltrate CSVs or scan user tables at 1000 req/s. - SECURITYDead chat-panel CSS (
.chat-panel,.chat-reopen,--chat-w, padding references) fully removed following the panel's retirement. Restored a missing.toggle-switchclass that three settings toggles depended on — the cleanup commit had silently broken them. - FIXSitemap no longer hydrates the entire catalogue into memory before slicing — paged JPQL query with SQL-level LIMIT keeps the generator safe at 50M-row scale.
- FIXUnsubscribe endpoint swapped full-table scan for an indexed
findByEmailIgnoreCase. Token-gated but anonymously reachable, so a scanner was forcing a scan per hit. - FIXRegression where the seller stall's "away mode" derivation hydrated every row twice just to call
.size()— now a single indexedcountActiveBySeller. Same fix applied to everyfindBy…().size()anti-pattern in the codebase.
April 2026 — prior
Security hardening · CSV injection fix · trailing-slash sweep · wallet leak fix
- NEW♿ Full WAI-ARIA tablist semantics on every tab strip: Profile, Wallet, Offers, Admin, CSR. Arrow keys cycle tabs,
Home/Endjump to first/last, screen readers announce "Tab 3 of 9, selected" instead of raw button labels. - NEWMarketplace sidebar filters become keyboard-navigable
radiogroups — category and rarity filters now work for keyboard-only users + announce selected state to screen readers. - NEWKeyboard-accessible user chip: the nav avatar (user menu trigger) now has
role="button",aria-haspopup,aria-expanded, and Enter/Space keydown. Menu closes on Esc. - NEWClickable table rows on the marketplace list view are now focusable (
tabIndex) and open the item detail on Enter or Space. Same for TrendCards on the home page rails. - NEWLoadout Favorite button reflects state (
♥ Favoritedvs♡ Favorite) via a newfavoritedflag on the loadout detail response — no more guessing whether you've already starred something. - NEWMobile keypads: every money / quantity input across the SPA carries
inputMode="decimal"or"numeric"so iOS Safari and Android surface the right keyboard (no QWERTY for $12.50). - NEWEvery toggle-style filter chip (tx type, buy-order status, trade role, review stars, sell rarity, etc.) now carries
aria-pressedso SRs announce "pressed" alongside the label. - POLISHPersonalised toasts across every confirmation path: buy-order cancel names the item + freed funds, offer accept/counter names the item + amount, review post renders star glyphs, staff bans/role-changes cite the target user + concrete policy shift.
- POLISHWithdrawal reject / dispute clear / stall removal / trade force-cancel all moved from blocking
window.prompt()to the sharedReasonDrawerprimitive with char counter + Ctrl+Enter submit + aria-haspopup on the trigger. - POLISHRecent-searches dropdown gets a
Clear allaffordance for users with 2+ saved queries. Notifications empty-state splits four ways (search-zero / unread-zero / type-filter-zero / brand-new) with per-branch CTAs. - POLISHSkip-to-main link finally moves keyboard focus (prior pattern only scrolled into view; the next Tab fired from the skip-link again). CSP-safe delegated handler in
main.js. - FIXSilent render bug on the cart-empty and watchlist-empty "Recently viewed" pills — they read
it.thumb/it.pricebut the writer storedimageUrl/lowestPrice, so thumbnails + price chips never appeared. - FIXNested
<main>element (invalid HTML, confused screen readers' landmark navigation) — the content region is now a semantic<div>, and the search container gainsrole="search". - FIXAccount-deletion request / cancel no longer does a jarring
location.reload(); it re-fetches the profile in place and toasts the SLO. - SECURITYDropped explicit
hibernate.dialectproperty — Hibernate 6+ auto-detects from the driver and emitsHHH90000025when specified unnecessarily. Now lets the ORM pick future PG-specific subclasses without a config push. - NEWKeyboard parity on every remaining clickable row + tile: Database page, support ticket list, staff ticket queue + reported-listing rows, sell-page Steam and platform inventory grids, buy-order item picker, loadout cards, and loadout slot pickers. Each carries a concrete
aria-labelnaming the target (e.g. "List Wizard Hat for sale" or "Open ticket #42 — Refund request") plus Enter/Space activation. - FIXMarketplace grid-card floor price now matches the real cheapest listing. 39 of 40 listed items had drifted up to $1.74 below the true MIN(active listings.price) because the Steam-Market price sync was clobbering the platform floor on every cycle. Card-advertised floor is now pinned to the listings table; Steam's market floor is a fallback only used when the item has zero active listings.
- FIXItem detail modal no longer lies about listing state —
items.is_listedwas stuck attrueon 40 catalogue rows that had never been listed on sboxmarket. Flyway V54 reconciled against the real listings table; the default + the SCMM import path now start atfalsesoupdateItemFloorPriceis the sole source of truth. - NEWSaved searches now round-trip every toolbar filter —
minDiscountPct,dealsOnly,newOnly,affordableOnly, andlistingType(ALL / BUY_NOW / AUCTION). Pre-fix, saving a "Hats · ≥20% off · AUCTIONs" preset silently stored only the category; re-applying showed a much broader view. Server-sideLISTING_MATCHnotifier also now respects all 5 extra fields so match alerts are real matches. - POLISH"Hot right now" homepage rail no longer vanishes on cold-start marketplaces. When no sales exist in the 7-day window it falls back to most-watched → most-viewed (both projected to the cheapest active listing per item) so the rail always has curated content.
- POLISHFooter "Catalog updated X ago" badge now reflects the live Steam-Market sync timestamp instead of the retired SCMM one, which meant the badge was permanently hidden since the sync change.
- SECURITYCSRF cookie shipped with explicit
SameSite=Lax— previously relied on browser defaults (footgun for security-critical cookies). Session + CSRF cookies are now symmetric. - SECURITYOWASP CSV Injection defence on every
.csvexport. Formula-trigger first chars (= + - @ \t \r) are prefixed with'so Excel / LibreOffice / Sheets render as text, not formula — 10 endpoints covered (wallet transactions, admin withdrawals / tickets / audit / fraud / disputes / trades / users / per-user listings + transactions, buy orders, my-stall sold + active, profile offers, watchlist). SharedCsvUtil.safeCellensures future CSVs inherit the guard. - SECURITY
/api/wallet+/api/wallet/transactionsno longer leak the persisted demo wallet to anonymous callers. Signed-out users get a zeroed snapshot (id:null, balance:0) instead of the demo wallet's live state. - SECURITY
/api/notificationswrites (read-all,clear-read,delete-batch) added to the 20-writes-per-10s rate-limit bucket. Previously unbounded — an authenticated attacker could thrash the DB with UPDATE / DELETE scans. - SECURITYOPTIONS preflight requests no longer count against the write rate-limit bucket. Cross-origin callers (e.g. the Valuer browser extension) no longer 429 on preflight before the real request runs.
- FIXEvery public URL now resolves cleanly with or without a trailing slash.
/item/1/,/stall/1/,/help/,/faq/,/profile/,/api/health/,/api/ready/,/api/version/all returned 404 before; Spring Boot 3's PathPatternParser doesn't auto-match the trailing slash variant. Inbound links with accidental trailing slashes (crawler bookmarks, copy-paste) now resolve. - FIX
/item/99999+/stall/99999+/loadout/<PRIVATE>now return real HTTP 404 instead of 200 with the generic site template. Google drops phantom URLs from its index instead of preserving them indefinitely. SPA client-side 404 modal still renders for real users. - NEWSitemap now carries
<lastmod>tags on every dynamic URL (items fromcreatedAt, stalls fromlastSyncedAt, loadouts fromupdatedAt). Google + Bing use this as a strong crawl-scheduling signal — previously they had to re-crawl every URL at thechangefreqcadence. - NEWWallet withdraw form gets a "Max" preset that one-taps the full withdrawable balance (clamped to the $10k per-request cap).
- POLISH
INSUFFICIENT_BALANCEerror body now carries structured{required, available, shortfall}details so the frontend toast renders "Top up $40.00 to complete this purchase" instead of string-parsing the human message. Cart checkout aggregates per-row shortfalls for accurate deposit pre-fill. - FIXDatabaseModal + LoadoutLabModal search inputs debounced 300ms. Each keystroke previously fired a
/api/databaseor/api/loadouts/publicround-trip — 10-char queries = 10 round-trips + 10 LIKE scans + 10 rate-limit tokens. Under real typing speed the page was consistently hitting the 429 ceiling.
April 2026
Profile analytics + trust signals
- NEWCapital-exposure summary strips on Profile → Buy Orders, Active Bids, and Offers — see your total potential outlay across standing positions at a glance instead of hand-summing rows.
- NEWPast Bids sub-tab gains a win-rate + wins-spend + lost-bids summary: "72% win rate (13/18), $4,521 spent on wins."
- NEWWallet hero now shows a spending summary (7d / 30d / lifetime) mirroring the seller-earnings strip on /me/stall.
- NEWCounterparty reputation chips on the Offers tab — sellers see buyer's completed-trade count + star rating on incoming offers; buyers see seller's star rating on outgoing offers. Sort incoming offers by buyer reputation.
- NEWOutgoing offer rows now show To @sellerName instead of the uninformative "Your offer".
- NEWView-count chip on MyStall listing rows — pair with watcher-count to read "lots of views, no stars" (price/desc issue) vs "stars but stagnant views" (needs re-promotion).
- NEWOne-click Bump button on near-expiring buy orders resets the 30-day auto-expire without opening the edit flow.
- NEWReopen resolved support tickets with one click — the thread continues instead of forcing a brand-new ticket.
- NEWContact Seller and Save Search now use accessible inline drawers (role="dialog", screen-reader support, Ctrl+Enter to submit) instead of native
window.promptdialogs. - SECURITYInvalid trade-offer URLs on Mark Sent now reject with a clear error — previously silently dropped, which could hide phishing URLs during dispute triage.
- FIXPath-variable parsing errors (e.g.
/api/items/%20) now return 400 with anINVALID_PATH_VARIABLEcode instead of a generic 500. - POLISHHEAD requests on rate-limited endpoints now use the safe-method budget (same as GET), fixing misleading X-RateLimit headers on health probes and link-preview bots.
- POLISHPer-viewer private endpoints (saved-searches, follows, steam, loadouts, reviews, watchlist) gain explicit
no-storeCache-Control so intermediate caches can't leak across users.
April 2026
Polish sprint · loadouts + cart + cache
- NEWLoadout slots can now be locked — click the on a filled slot and Generate keeps it intact while reshuffling the rest.
- NEWCart rows and the confirm dialog now show each item's seller, linked to their stall. A multi-seller cart also shows "N sellers — expect N separate trade offers" so you're not surprised by multiple inbound Steam offers.
- NEWMarketplace grid now paginates past 100 listings with a "↓ Load more" button. Same filter state, appends to the existing set — no infinite scroll trap.
- NEWAuction Buy Now is now gated on your Steam trade URL, matching Buy / Offer / Bid / Cart. A bidder without a trade URL gets a tooltip-explained disabled state instead of a surprise error.
- NEWWithdraw review now shows a concrete arrival date ("by Mon, Apr 22") computed as today + 2 business days, weekend-aware and locale-formatted.
- NEWDaily deposit / withdraw caps hit? A "Request a raise →" link opens a pre-filled PAYMENT ticket with your used/cap numbers baked into the body.
- NEWFees & Pricing footer link jumps straight to the FAQ's 2%-platform-fee answer. Report a Bug footer link pre-fills a support ticket with a full bug-report scaffold.
- NEWLogout now confirms with a toast and bounces you to /market if you were on a private page, so the URL bar matches what you see.
- NEWCtrl/⌘+K works from every page — if the search input isn't mounted, we bounce to /market and focus it for you.
- POLISHEvery marketplace rail and nav badge now pauses polling while its tab is hidden. A user with 50 backgrounded SkinBox tabs no longer hammers the server for zero visual benefit.
- POLISHPublic read endpoints (listings, items, buy-orders aggregates, database) now carry explicit Cache-Control headers so CDN + browser caches can absorb the hottest-items traffic.
- POLISHPost-deploy, the SPA shell and JS bundles revalidate via 304 on every load — no more "I see stale code for 4 hours after a deploy."
- SECURITYPrivate routes (/profile, /wallet, /admin, …) now ship with
noindex, nofollowmeta — defense-in-depth if a crawler ignores robots.txt. - FIXrobots.txt Disallow lines now match the real router paths (/me/stall, /buy-orders) — the old rules protected non-existent paths.
- FIXAdmin can now remove TOS-violating loadouts directly from the Loadout Lab (previously the API endpoint existed but had no button).
April 2026
Developer platform + export surface
- NEWFind a seller by name — debounced dropdown under the Top Sellers rail with ★ rating, verified badge, and keyboard nav (↑↓ Enter Esc).
- NEWAPI keys gain an RW / RO scope picker in Profile → Developers. RO keys can fetch data but can't buy, sell, or move funds.
- NEWBearer-token auth goes live:
Authorization: Bearer sbx_live_…now authenticates/api/*for bots and extensions, with CSRF bypass on validated tokens. - NEWComplete your download-your-data set: wallet transactions, my-stall sold + active listings, offer history, and now watchlist export as CSV.
- NEWMachine-readable security disclosure at
/.well-known/security.txtwith a full policy page at /legal/responsible-disclosure.html. - NEWReadiness probe at
/api/readyseparate from liveness/api/health— load balancers now route away from pods with a broken DB pool. - NEWSupport ticket form spells out response-time SLOs (≤4h for trade + payment, ≤24h elsewhere).
- NEWWithdrawal pending chip shows age + ETA ("requested 2h ago, typically arrives in 1-2 business days"); flips to an "open a support ticket" prompt if it's past SLO.
- NEWAdmin dashboard shows 30-day rolling chargeback count alongside active — trend signal for card-fraud surges.
- SECURITYEvery API key mint now fires a security-alert email to the account owner (mirrors sign-in / 2FA-reset / wallet-frozen alerts).
- SECURITYPer-user API key ceiling (20 active max) — a compromised session can't silently mint an army of long-lived tokens.
- FIXStall pages get SEO-indexed from day one: sitemap now includes every seller with at least one active listing, not just those with closed sales.
- FIXClient-side crashes (ErrorBoundary catches, global
window.error+ unhandled promise rejections) now forward to the server so ops can see production issues. - FIXCase-insensitive sort / category / rarity / listingType params — shared links like
?category=hatsno longer silently return an empty grid. - POLISHSitemap + CSV exports are gzip-compressed (~95% bandwidth win on crawler fetches).
- POLISHLegal pages reference the real
@skinbox.marketsupport / abuse / privacy inboxes instead of the placeholder.exampledomain.
April 2026
CSFloat-manual parity sprint
- NEWAffiliate Program page at /affiliate with hero banner, platform requirements, and apply CTA.
- NEWAccount Standing gauge on the Profile hero (Excellent / Good / Poor / At Risk / Banned). Collapses your trade + review history into a glance-sized status.
- NEWProfile → Trades progress bar is now a 6-node stepper (Seller → Accepts → Sends → Receives → Verified → Buyer) matching CSFloat's layout.
- NEWTransactions ledger has a month-picker chip — narrow to a specific month with one click.
- NEWItem share button uses
navigator.share— opens the OS share sheet (Discord, Messages, AirDrop, X, etc.) on devices that support it. - NEWDynamic theme — accent hue drifts through the day (cool morning → warm evening).
- NEW"Why SkinBox" trust strip on the homepage for anonymous visitors (Auctions · Bargains · No 7-day Steam hold).
- NEWAuction panel surfaces your auto-bid cap with an inline cancel link, so retracting the cap no longer needs a profile-page detour.
- NEWActive Bids tab now shows a live Ends countdown + top-bid gap chip per row.
- NEWBuyers can delete their own reviews; profile has a "Given" sub-tab alongside Received.
- NEW"Verified buyer" chip on every review — every rating is trade-anchored.
- NEWCounter / Raise actions inline on the Offers queue (no round trip to item page).
- NEW"Ending Soonest" sort option on the marketplace; auctions bubble up by closing time.
- NEWItem price-check chip ("Good deal" / "High price") based on the 30-day range.
- NEW"Last N sold · median" price chip on the sell form so sellers anchor on real clearing prices.
- NEWSell form warns when your asking price beats the Steam Community Market price.
- NEWDatabase page has a "Listed only" filter toggle.
- NEW⌘/Ctrl + K focuses search from anywhere (works even while typing in another input).
- FIXTrade cancel now returns the listing to the seller's inventory for relisting — previously the item stayed in the refunded buyer's "platform inventory" as a phantom.
- FIXDispute now pushes a TRADE_DISPUTED notification to the counterparty — previously they only found out hours later from staff.
- FIXSeller-pending trades auto-cancel + refund the buyer after 3 days of seller silence (was indefinite escrow hold).
- FIX
Item.totalSold+Item.isListednow stay in sync with real platform sales — "Most Traded" and "Listed only" sorts read accurate data. - FIXPrice history now records every real sale, not just external market sync — sparkline works on a fresh deployment.
- POLISHError-boundary page rewritten as a friendly "Something went wrong" screen with Reload / Go home / Contact support buttons and a collapsed technical-details toggle.
April 2026
SEO + social-preview overhaul
- NEWPlatform-wide "Just Sold" ticker on the homepage — social proof from live activity.
- NEWDynamic
sitemap.xmlthat lists every item detail page for search engines. - NEWPer-item and per-stall OG tags — sharing an item link into Discord / Twitter shows the actual skin + price.
- NEWSaved-search presets — name a filter combo and re-apply in one click.
- NEWQuick-filter chips (Under $5 / $20 / $50, Premium, Limited-only).
- POLISHBrowser tab titles now describe the current page and refine to the item name on item detail.
- FIXCart drops sold-while-waiting listings on page load with a single toast, instead of failing at checkout.
April 2026
Moderation + staff tooling
- NEWUsers can Report a listing or report a seller — reports route to a dedicated admin queue.
- NEWAdmin can dismiss reports without cancelling (reviewers notified), or force-cancel and notify all reporters of the action.
- NEWAdmin can grant/revoke CSR role, reset 2FA for a locked-out user, and filter the user list by role chip.
- NEWAudit log gains a free-text search over summary + names.
April 2026
Profile + wallet upgrades
- NEWExport your trade + offer + transaction history as CSV for tax and accounting.
- NEW30-day credits/debits/net summary above the transactions ledger.
- NEWCart → Watchlist shortcut, Copy Steam ID, Copy item link, account-age trust badge.
- NEWEmail-verification nag banner (7-day dismiss cooldown).
- NEWSelf-cancel pending withdrawals without waiting for admin rejection.
April 2026
Marketplace + discovery
- NEWBiggest-discount sort option + "−X%" chips on listing rows below Steam Market price.
- NEWMarketplace filter state is URL-shareable — copy the bar and send the exact same view to a friend.
- NEWSell page shows live take-home breakdown (price − 2% fee) with floor-relative sell-time guidance.
- NEWRecently-viewed + top-sellers + top-deals + auctions-ending-soon rails on the homepage.
April 2026
Security + infrastructure
- SECURITYCSRF double-submit-cookie on every write endpoint; session cookies HttpOnly + SameSite-strict in prod.
- SECURITYRate limiting on write (20/10s) and read (120/10s) surfaces.
- SECURITYCSP covering every CDN origin; HSTS opt-in; body-size caps.
- NEWSystem status page at /status.html — probes core endpoints every 30s.
- FIXDocker HEALTHCHECK now respects SERVER_PORT (was hard-coded to 8080).