Part of #6.
config/cloud.ts states that the release mounts no framework routes and runs no migrations. Both halves are false, and because the comment says otherwise nobody has had a reason to look.
The exposure is small, and that is a fact about which models the framework ships rather than about anything this repository decided. Worth fixing while it is still small.
What is there today
config/cloud.ts#L42-L43: "So the release mounts no framework routes (STACKS_DEFAULT_ROUTES: 'none') and runs no migrations."STACKS_DEFAULT_ROUTES: 'none'is set (config/cloud.ts#L33), and it does what it says: the route loader skips the default route bundles, which is why the auth, dashboard and dashboard-api surfaces are absent.- It does not cover the ORM.
importRoutes()in@stacksjs/router/dist/stacks-router.jsloads the registry first, then imports@stacksjs/orm/routeswith no gate on it at all. That module scansstorage/framework/defaults/app/Modelsandapp/Modelsand registers index, show, store, update, destroy and bulk-delete for every model carryinguseApi. - 87 framework models carry it, so the production API mounts several hundred CRUD routes:
/api/products,/api/orders,/api/customers,/api/usersand the rest. - The api site's start command is
bun node_modules/@stacksjs/actions/dist/serve/api.js(config/cloud.ts#L156), which is one of the entry points that callsimportRoutes(). - Migrations do run.
preStartincludesbun node_modules/@stacksjs/buddy/dist/cli.js migrate(config/cloud.ts#L140), and there are 234 tracked migrations, so the tables those handlers query exist rather than erroring.
What is and is not exposed
Most of it is behind auth. Counting the useApi blocks of the 87 models:
| declared middleware | models |
|---|---|
['auth'] | 50 |
none, which defaults to ['auth'] | 12 |
['auth', 'team'] | 6 |
{ read: ['auth'], write: ['auth', 'role:admin'] } | 4 |
{ read: ['auth'], write: ['auth'] } | 2 |
{ read: [], write: ['auth'] } | 13 |
The last row is public. Those 13 are catalogue models a storefront is meant to read without a login: /api/products, /api/product-variants, /api/product-categories, /api/product-manufacturers, /api/product-units, /api/tags, /api/authors, /api/releases, /api/tax-rates, /api/shipping-zones, /api/shipping-methods, /api/shipping-rates, /api/loyalty-rewards.
In Uplink those tables are empty. Uplink sells one product through Stripe Checkout and keeps nothing in the commerce tables.
Uplink's own three models register nothing. app/Models/License.ts, app/Models/Conversation.ts and app/Models/Run.ts have no useApi, so license keys, customer emails, Stripe ids, prompts and replies are reachable only through /api/license/check and /api/billing/portal, which both require the key. Nothing sensitive is on an open route. That is the reason this is not urgent.
Proposal
- Correct the comment at
config/cloud.ts#L42-L43. It is what makes this invisible, and it is wrong twice: routes are mounted, and migrations do run. - Decide the surface deliberately.
config/cloud.ts#L111already carvesbypassPaths: ['/api/', '/_stacks/']out of the CDN, so the proxy in front of the api site is where an allowlist of the three real endpoints fits, and it needs no framework change. - Separately, raise upstream that
STACKS_DEFAULT_ROUTES: 'none'does not cover the ORM route import. A flag named that way not covering the largest source of routes is a trap every Stacks app will hit.
Requirements
- The three real endpoints must keep working.
/api/license/checkis what every installed Mac calls at launch,/api/billing/webhookis what Stripe posts to, and/api/billing/portalis the customer portal link. - Do not add per-model overrides. Shadowing 87 framework models under
app/Models/to turnuseApioff would work and would be 87 files to maintain against every framework upgrade. - Say what the verdict was. If several hundred mounted CRUD routes over empty tables is judged acceptable, the comment should say that instead, so the next person does not re-derive it.
Acceptance
config/cloud.tsdescribes what the release actually mounts and whether it migrates.- Whatever is decided, a request to one of the 13 public catalogue paths on the deployed host returns what the decision says it should.
- The three real endpoints still answer.