Skip to content

Bun 1.4.4 for CVE-2026-111497, SECRET_KEY rotation, Dokploy hosting - #21

Merged
lobbystack merged 28 commits into
mainfrom
books
Oct 23, 2026
Merged

lobbystack merged 28 commits into
mainfrom
books

Conversation

@lobbystack

Copy link
Copy Markdown
Owner

Security fixes after Railway's October 21 incident, plus the Books and hosting work that was on this branch.

Security

  • Bun 1.4.4 (4b5ef5d). Bun before 1.4.4 bundles a Brotli decoder with an out-of-bounds write (CVE-2026-111497). fetch() decompresses response bodies, and hosted workspaces choose their own alert webhook URLs, so a workspace could answer with a crafted body. The Dockerfile pins oven/bun:1.4.4-alpine and packageManager pins bun@1.4.4.
  • scripts/rotate-secret-key.ts (3e7b48f). It re-encrypts connection credentials and alert channel secrets from OLD_SECRET_KEY to SECRET_KEY in one transaction. --dry-run rolls back, and a rerun skips values already under the new key. Railway held the production SECRET_KEY, so it needs rotating.

Also on this branch

  • Hosting moves from Railway to Dokploy behind Cloudflare. CI runs the checks, publishes the image, then calls the Dokploy deploy webhook.
  • Books: fee math, POST exports, a re-sync migration.
  • The app, dev server and scripts run on Bun.
  • Blog posts, SEO and integration pages, and onboarding and login changes.

Before merging

  • The image name comes from github.repository, so it becomes ghcr.io/lobbystack/openprofit. Dokploy still pulls ghcr.io/morencyr/openprofit:latest.
  • This repo has no DOKPLOY_DEPLOY_WEBHOOK secret, so the deploy step fails until someone adds it.
  • Several links still point to morencyr/openprofit.

Testing

  • Built the image and ran it with a data volume. bun --version reports 1.4.4, the landing page, /login and /sitemap.xml return 200, and magic-link sign-in works.
  • Ran the rotation script on a seeded PGlite database: dry run, real run, rerun, and a wrong old key, which aborts.

lobbystack and others added 28 commits October 19, 2026 11:23
An optional select on the workspace form. The answer goes out as
heard_from on workspace_created, so signups can be counted per channel
once paid ads start. The page only asks when PostHog is configured.
An optional select on the workspace form. The answer goes out as
heard_from on workspace_created, so signups can be counted per channel
once paid ads start. The page only asks when PostHog is configured.
…140bb

Login and onboarding: Google logo, terms line, two hints removed
Revenue vs profit for a SaaS, an open-source TrustMRR alternative with
verified profit, AI token cost per user with October 2026 prices, and
what Stripe, Polar, Paddle and Lemon Squeezy leave from one sale.
Titles follow DataForSEO volumes; competitor and fee facts cite the
providers' own pages, checked 2026-10-19.
Headings and sentences no longer open with Wh- words, inanimate
subjects and passives name the actor, adverbs and lazy extremes are
gone, and the fees post now says Stripe leaves you the most per sale.
The running costs post prices one stack at three sizes with October
2026 plan prices. The alerts post compares hard limits and budget
alerts at ten providers from their docs, and says OpenProfit's spike
alert reports a day late.

Markdown tables now scroll inside their own box, so the wide tables
in the alerts and fees posts no longer widen the page on a phone.
The homepage title reads "OpenProfit: Open-source profit tracking for
developers", so "profit" stands as its own word again. The footer link to
/open says "Profit database" instead of "Open profit", which sent 51
internal links with the brand phrase to the leaderboard.

The homepage JSON-LD adds a WebSite node (name OpenProfit, alternate names
"Open Profit" and openprofit.dev) and sameAs links to GitHub and npm on the
app. /llms.txt lists the docs, integrations, comparisons and blog posts for
AI agents.
…ms-full.txt

- Integration pages link to the matching blog posts, and the books and
  public-pages docs link the posts they explain. /open links the TrustMRR
  comparison.
- Nav and footer Docs links go to /docs/connectors instead of the /docs
  redirect. The footer adds Benchmarks (/data), and its column titles are
  h2 so pages keep an unbroken heading outline.
- Posts use the wordings people search: the Stripe books post names the
  Stripe Connector by QuickBooks, the tax post names the T2125 and
  Schedule C, and the token post's price section names Claude and OpenAI
  API pricing. Post title tags drop the " · OpenProfit" suffix.
- Descriptions over 155 characters are trimmed, and the changelog's is
  longer. Comparison pages show when they were last updated.
- JSON-LD: breadcrumbs on docs, integration, comparison and blog pages,
  FAQPage from an integration page's common questions, TechArticle on docs.
- The sitemap gives blog URLs a lastmod. /llms-full.txt joins the markdown
  of every page /llms.txt lists.
Each page now says which lines OpenProfit reads, how to create the
narrowest key, how costs map to products, and what the provider's API
leaves out, with common questions and links to the related blog post and
docs. Claims come from the connector code and the providers' own docs.
Vercel's FOCUS charges endpoint accepts at most one year per request.
The connector asked for the whole 730-day backfill in one call (366
days on the free plan), so first and full syncs could fail. It now
requests 365-day chunks and merges the lines.

Cloudflare's billable-usage endpoint returns nothing unless the range
includes a subscription's billing cycle anchor day. The 3-day reread
usually missed it, so regular syncs stored no usage after the first.
The connector now reads the anchor from billable-usage/info, starts
the request at the last anchor day on or before the range, and drops
lines before the range. A 4xx from either call still falls back to
billing history.
Vercel's FOCUS charges endpoint accepts at most one year per request.
The connector asked for the whole 730-day backfill in one call (366
days on the free plan), so first and full syncs could fail. It now
requests 365-day chunks and merges the lines.

Cloudflare's billable-usage endpoint returns nothing unless the range
includes a subscription's billing cycle anchor day. The 3-day reread
usually missed it, so regular syncs stored no usage after the first.
The connector now reads the anchor from billable-usage/info, starts
the request at the last anchor day on or before the range, and drops
lines before the range. A 4xx from either call still falls back to
billing history.
- bun.lock replaces pnpm-lock.yaml, migrated with the same versions.
  Workspaces move to package.json.
- Vite runs under `bun --bun`. Scripts run with `bun` instead of tsx.
- Nitro builds with the `bun` preset. The Docker image is
  oven/bun:1.3-alpine and starts `bun .output/server/index.mjs`.
- CI installs Bun with oven-sh/setup-bun. The CLI package tests run
  with `bun test`; the published CLI still targets Node 20.
- postinstall relinks node_modules/.bin/intent: @tanstack/devtools-vite
  ships a stale `intent` bin that Bun links in its place.
- Payment fees are gross minus refunds minus net, so a provider's
  balance moves by net. Stripe's other charges and lost disputes count
  as payment fees.
- A yearly flat cost's 12 months add up to the yearly amount.
- The export summary keeps a group's bank and other entries apart.
- The journal export takes POST, since a whole-workspace export closes
  the month.
- With no country set, the tax report is a plain profit and loss, and a
  USD workspace exports US dates.
- Changing the base currency reconverts gross, refunds and payouts, and
  reopens exported months.
- Onboarding asks for the incorporation date. Equipment must be a
  one-time cost.
- The books read each span once. Unquoted Bank of Canada days are held
  for an hour.
- Polar sync drops old-format lines across the whole reread window.
- 0012 re-syncs the whole history of Stripe and Paddle connections and of
  every connection in a CAD workspace. It sets last_synced_at to 1, not
  null, so the sync skips first-sync sub-unit mapping and analytics.
Conflicts:
- CLAUDE.md: main's intro and Books notes, with commands for Bun.
- src/routes/index.tsx: main's version, with the WebSite schema.
- src/routes/onboarding.tsx: main's trimmed form, plus the books
  "Incorporated on" date field without a hint.

src/lib/app.ts takes main's version: the "heard from" lists landed twice,
once from each branch's copy of the same commit. .cta.json names Bun.
The currency section says a reopened month exports in full again, so you
delete the earlier import first. The changelog tells self-hosters who
override the container command to use bun.
CI runs the checks before publishing the image, then calls the Dokploy
deploy webhook. CLIENT_IP_HEADER reads the client's address from a CDN
header such as cf-connecting-ip. Docs, privacy and security pages name
OVH and Cloudflare in place of Railway.
scripts/rotate-secret-key.ts decrypts connection credentials and the
alert channels' Slack and webhook values with OLD_SECRET_KEY and
encrypts them with SECRET_KEY, in one transaction. --dry-run rolls
back. Values the new key already opens are skipped, so reruns are safe.
encrypt and decrypt take an optional key for it.
Bun before 1.4.4 bundles a Brotli decoder with an out-of-bounds write.
fetch() decompresses response bodies, and hosted workspaces choose
their alert webhook URLs, so a workspace could answer with a crafted
body. The image now pins oven/bun:1.4.4-alpine instead of the 1.3 tag,
and packageManager pins the same version for CI.
@lobbystack
lobbystack merged commit 3d9f034 into main Oct 23, 2026
1 check passed
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.

2 participants