← Zpět na zprávu

Auctify / Auctea / Folio — codebase review, consolidated verdict

Adopter-side review for the auction house. Two open-source Rails engines were cloned with full history and read: auctify (the auction domain, HEAD a1b60f0, 2024-11-15) and folio (the CMS, HEAD 71084c9, 2026-09-14, VERSION 7.7.2). The production app, Auctea, is closed and combines both; it is not in the clone, so every finding states what was proved from public code and what still depends on Auctea's private configuration.

Four detailed reviews sit beside this file: review-auction-domain.md, review-auctify-security.md, review-folio.md, review-quality.md. This document is the verdict layer, and it marks which findings were verified by hand for this summary.

Scores, 0–10

AreaScoreBasis
Auction-domain correctness (auctify)4Sound arithmetic, no lock on the bid path
Security (auctify)3No authorisation layer in the engine; money-ceiling bypass
Security (folio)4Unauthenticated download controller; authorisation is opt-in
Maintainability (folio)6Long methods and single-author concentration at scale
Test strength — auctify / folio4 / 7auctify has an excellent bid test, no concurrency test
Code health — auctify / folio7 / 6auctify is small and clean; folio is large and normal
Documentation — auctify / folio3 / 9auctify README example raises; folio has 29 doc files
Release discipline — auctify / folio2 / 7auctify: 3 tags, no release in 24 months
Project health — auctify / folio1 / 8auctify has no CI ever and does not install on a modern Rails

Two different objects. folio is a mature project: continuous integration, a release every three weeks for years, breaking changes documented in advance, 19 contributors this year. auctify is a well-written 2 500-line engine that has been left alone since mid-2022: 8 commits in four years, bus factor 1, no CI in its entire history.

The findings that matter, and what is verified

1. The bid path takes no lock — VERIFIED, and it is the important one

app/models/auctify/sale/auction.rb:158 — bid! opens a transaction and takes no row lock. There is no lock!, no with_lock, no SELECT … FOR UPDATE, no lock_version column, no unique index on the bid table anywhere in the repository. BidsAppender reads the current bid set (bids_appender.rb:123), computes the minimum next bid (:98) and the winner in Ruby from that snapshot. Under PostgreSQL READ COMMITTED, two bids that arrive together both read the same snapshot and both commit. The only advisory lock in the codebase guards auction closing (app/jobs/auctify/bidding_closer_job.rb:19), not bidding.

Consequence for the house: at the closing second of a lot at a few million CZK, two near- simultaneous bids can both be accepted at the same price, or one can overwrite the other's price. That is a disputed hammer and a possible refund or lawsuit. There is no concurrency test in the suite that would catch a regression here.

Caveat. Auctea is closed. If Auctea wraps every bid in its own with_advisory_lock — the gem already depends on with_advisory_lock and uses it for closing, so the team knows the tool — this risk is handled outside the public engine and does not apply. This one question decides the severity, and only Auctea's code answers it. Ask the vendor: is bid submission serialised, and where.

2. A tie is won by the later bid — VERIFIED

app/models/auctify/bid.rb:7 orders by price: :desc, id: :desc. Two bidders reaching the same price: the row inserted second wins. Auction convention is that the first bid at a price holds. Low money impact, but wrong by the rules of a saleroom.

3. Commission is computed with a Float — VERIFIED

app/models/auctify/sale/base.rb:96 and :106 — percent 0.01 price. Prices themselves are decimal(12,2) (db/migrate/20210223152139_create_auctify_bids.rb:7) and clean; this Float is only on the commission path. It cannot move a hammer price. It makes invoice totals carry binary noise, so they stop tying out against a spreadsheet. Fix is one token: BigDecimal("0.01").

4. auctify's engine ships scaffold controllers with no authorisation — VERIFIED IN CODE, NOT LIVE

Auctify::ApplicationController (app/controllers/auctify/application_controller.rb:1-8) has only protect_from_forgery — no authentication, no authorisation. config/routes.rb:5-7 mounts resources :bids, :bidder_registrations, :sales as full CRUD, and bids_controller.rb:61 permits registration_id (the actor). Read literally, anyone could bid as anyone and delete the winning bid.

But: I probed the live site. GET /auctify/bids, /auctify/bidder_registrations, /auctify/sales all return 404 on www.acb.cz. Auctea does not mount these routes. The flaw is real in the gem and would be catastrophic if the routes were exposed; on acb.cz they are not. This is the clearest example of why the engine's score and the live site's exposure are different numbers.

5. The maximum-bid ceiling is bypassable — VERIFIED IN CODE

app/services/auctify/bids_appender.rb — the ceiling check can be avoided by sending max_price alongside price. On the public engine this needs the API bid path, which does authenticate (api/v1/auctions_controller.rb). Confirm against Auctea's bid endpoint.

6. Folio: unauthenticated download controller — VERIFIED IN CODE, LIVE EXPOSURE UNVERIFIED

app/controllers/folio/downloads_controller.rb runs on bare ActionController::Base, no auth filter, finds a file by slug and redirects to its S3 URL (config/routes.rb:343, GET /download/:slug). The review claims numeric slugs enumerate the whole file table across tenants. That part is SUSPECTED and partly overstated: file.rb:453 deliberately forces a non-numeric slug to prevent slug/id collision, so a file cannot have a numeric slug. Enumeration by primary key could still work through FriendlyId's own PK fallback, which I did not run. I did not probe file enumeration on the live site, because retrieving files that may belong to another party is the attack, not a review. Confirm internally: is /download/:id reachable, and are files scoped to a site.

7. Folio: authorisation is opt-in, not enforced — VERIFIED

grep -rn check_authorization app lib test returns nothing. CanCanCan's check_authorization, which raises when an action finishes without an authorize! call, is not enabled. A new console action that forgets authorisation fails open. There is no default_scope for tenant isolation (app/models/folio/current.rb:98 maps host→site; scoping is per-query by_site). Whether this produces a real cross-tenant read depends on the association graph and on whether acb.cz truly shares one Folio deployment with the other auction houses — which I cannot see from outside.

8. auctify pins Rails 6.1 and does not install on a modern Rails — VERIFIED

auctify.gemspec — spec.add_dependency "rails", "~> 6.1.4" (i.e. >= 6.1.4, < 6.2). Folio runs Rails 8.0.4.1. The two cannot be installed in one app. The highest Rails constraint on any of the 25 branches is ~> 6.1. So the public auctify is not the engine running acb.cz — Auctea runs a private fork. Rails 6.1 is past end of security support. The MIT engine you could legally take is a 2023-era snapshot on an EOL framework, not the live code.

The Folio review's more severe cross-tenant claims — treat as leads, not facts

review-folio.md reports several CRITICAL cross-tenant findings: a site admin granting themselves admin on another tenant (ability.rb:71 + users_controller.rb:174), a transports controller that exports any record of any tenant (transports_controller.rb:42), order_scope reaching unscoped via send, an embed system that runs editor <script> on the site origin. Each is a plausible reading of real code, and each is marked in that review with the console check that would confirm it. Two things hold them back from being called live vulnerabilities:

They are worth handing to whoever runs Auctea as a checklist. They are not, on this evidence, confirmed exposures on acb.cz.

Where the code is genuinely good

Is it a mature project, and should the house keep it

Folio: yes, mature. auctify: no, dormant. The auction engine is well-built and then abandoned; the CMS is actively maintained by one company with a bus factor near one.

Keep it. Do not replace it, and do not take it in-house. The reasoning:

  1. The one finding that could cost real money — the unlocked bid path — is likely already handled

inside Auctea (the closing path uses the very lock the bid path lacks). Confirm it with one question to the vendor rather than assuming the worst.

  1. The most alarming security findings are in the public engine and are **not exposed on the live

site** (the scaffold routes 404) or are conditional on a deployment you cannot see.

  1. Taking the MIT code in-house means adopting a Rails-6.1 auction engine that does not install on a

modern Rails, plus a 45 000-line CMS that one company on earth understands. That is more risk, not less.

The exit-cost correction. Earlier I estimated an open-source exit at 2,0–2,75 M CZK partly because "you inherit a working, tested bid engine for free". That was too optimistic: the public auctify is a 2023 snapshot on an EOL Rails and needs porting before it boots. Realistic exit, open-source route, is now 2,4–3,6 M CZK, and you would own two Rails upgrade treadmills you did not write.

The four questions to put to the vendor — each has a numeric answer

  1. Is bid submission serialised (row lock or advisory lock), and where? (Decides finding 1.)
  2. Is GET /download/:id reachable on the deployment, and are Folio files scoped per site?
  3. Does acb.cz share a Folio deployment with the other auction houses, or run its own?
  4. What Rails and Ruby versions does the live Auctea run, and when were they last patched?