An E2EE web app for managing monetary assets.
Go to file
Madeline 9b9d9b8aef
ci/woodpecker/push/create-release Pipeline was successful Details
ci/woodpecker/push/deploy_prod Pipeline was successful Details
ci: Create release on push
2023-11-21 19:08:08 -07:00
.github/workflows feat: Auto-close GitHub PRs 2023-11-17 13:45:20 -07:00
.vscode Vitest (#76) 2023-10-23 22:51:07 -06:00
.woodpecker ci: Create release on push 2023-11-21 19:08:08 -07:00
public Move to Codeberg (#81) 2023-11-13 00:59:45 +00:00
scripts ci: Create release on push 2023-11-21 19:08:08 -07:00
server fix: Passwords can get LONG (#85) 2023-11-21 04:12:22 +00:00
src Move to Codeberg (#81) 2023-11-13 00:59:45 +00:00
styles fix: Move footer out of the way when content demands it (#79) 2023-10-31 05:42:13 +00:00
.eslintrc Cleanup deps (#78) 2023-11-06 22:35:56 -07:00
.gitignore Move to Codeberg (#81) 2023-11-13 00:59:45 +00:00
.npmrc Vercel (#20) 2022-10-18 13:50:41 -06:00
.prettierignore Generate front-end REST interface (#26) 2022-11-09 20:54:27 -07:00
.prettierrc.json Svelte (#1) 2022-08-05 13:36:10 -06:00 Move to Codeberg (#81) 2023-11-13 00:59:45 +00:00 Important meta info (#48) 2023-01-29 11:41:46 -07:00 Move to Codeberg (#81) 2023-11-13 00:59:45 +00:00
FUNDING.yml Create FUNDING.yml (#33) 2022-06-08 01:20:06 +00:00
LICENSE License (#4) 2022-01-25 00:57:14 +00:00 Move to Codeberg (#81) 2023-11-13 00:59:45 +00:00 Move to Codeberg (#81) 2023-11-13 00:59:45 +00:00
cors.json Download, decrypt, and present attachments 2021-11-11 11:00:12 -07:00
index.html Move to Codeberg (#81) 2023-11-13 00:59:45 +00:00
package-lock.json fix: Rebuild package.json files (#82) 2023-11-16 00:42:00 +00:00
package.json ci: Create release on push 2023-11-21 19:08:08 -07:00
redocly.yaml Move to Codeberg (#81) 2023-11-13 00:59:45 +00:00
tsconfig.base.json Cleanup deps (#78) 2023-11-06 22:35:56 -07:00
tsconfig.json Cleanup deps (#78) 2023-11-06 22:35:56 -07:00 Automated releases (#12) 2022-09-09 11:31:23 -06:00
tsconfig.test.json Cleanup deps (#78) 2023-11-06 22:35:56 -07:00
vite.config.ts Cleanup deps (#78) 2023-11-06 22:35:56 -07:00
vitest.config.ts Cleanup deps (#78) 2023-11-06 22:35:56 -07:00
vitestSetup.ts Cleanup deps (#78) 2023-11-06 22:35:56 -07:00

Recorded Finance

A Svelte app for managing monetary assets. All data is encrypted client-side and stored on a server that you control.

This project is undergoing rapid development and should be considered experimental. Use it at your own risk. 🤙

Keep an eye on for breaking changes as they happen.

Alternative Projects

There are many open-source balance keepers out there, but none I've found that I quite like. A few are listed here.

The Goal

The aim of this project is to be a cross-platform and portable place to keep personal financial records. There will always be a way to self-host the storage server, and I intend for this project to always be open-source.



To run the app in your browser, you'll need one of the following browsers and versions:

  • Chrome >=87
  • Firefox >=78
  • Safari >=14
  • Edge >=88

(I've not tested any of these boundaries, but Vite.js recommends them.)

Developing for this project requires Node 16.5 and NPM v7 or above. You can check what versions you have installed by running npm -v and node -v:

$ npm -v && node -v

Compile and Run the Server

See the server's README for info on that.

Compile and Run the Client

  • Clone the repository
  • Create a .env file at the root of the project, like the one shown below:
# .env

# Where your server lives (optional for Vercel, required with Express)
VITE_PLATFORM_SERVER_URL={your storage server URL}:40850

# Enables the "Login" menu item (optional, defaults to "true")

# Enables the "signup" behaviors (optional, defaults to "false")

# Optional if the back-end runs on Express, required with Vercel
VITE_PUBNUB_SUBSCRIBE_KEY={your subscribe key from PubNub}

If you're hosting the storage server on the same machine that hosts the Recorded Finance web client, do NOT use localhost for the VITE_PLATFORM_SERVER_URL. You must set this to a URL that clients—that is, web browsers—can use to access your back-end.

Using localhost for this will cause clients to try themselves as the storage server, and that's usually not what you want.

$ cd recorded-finance       # Be in the root directory
$ npm ci                      # Install dependencies
$ npm run build:client:quick  # Compile the client
$ npm run dev:client          # Start a local webserver

Note: The build script injects your .env values at build time. If you must change .env, remember to re-build the client.

The webserver will print a URL in your terminal to paste into your browser. It should look something like Give that a go, and you're off to the races!

I recommend you deploy the client (the contents of the recorded-finance/dist folder) on a webserver like nginx.

DO NOT FORGET your ACCOUNT ID or PASSWORD. If you do, your data is irretrievably lost. That data is encrypted, and can only be retrieved using those details. You have been warned. :)



This project is entirely open source. Do with it what you will. If you're willing to help me improve this project, consider filing an issue.

See for ways to contribute.


Some questions I've asked myself while developing this. You might have these questions too!

Why use cookies?

JavaScript is not a safe spot to store cookies. Nascent versions of our front-end client stored the user's login token in a JavaScript variable. I later learned a better way: don't handle the token myself, let the browser handle it with standard cookie security APIs.

Our storage server still responds to successful login requests with the token in the response body, but the server also now asks requesting clients to set the token as a cookie with the following attributes:

  • HttpOnly - According to MDN: "A cookie with the HttpOnly attribute is inaccessible to the JavaScript Document.cookie API; it's only sent to the server. For example, cookies that persist in server-side sessions don't need to be available to JavaScript and should have the HttpOnly attribute. This precaution helps mitigate cross-site scripting (XSS) attacks." (emphasis mine)
  • Secure - Not set if the server is running on localhost. According to MDN: "A cookie with the Secure attribute is only sent to the server with an encrypted request over the HTTPS protocol."

If you create your own client to use against our hosted storage server, please don't mishandle the auth token. If you dig back in our git history to find and use a version that uses the old JavaScript-based auth method, know that you may be getting into avoidable security vulnerabilities.

Why disclose cookies?

I've heard that GDPR doesn't care about "session" cookies, and therefore don't need to be disclosed.

While our cookies indeed deal with the user's login "session," defines "session cookies" as cookies that "are temporary and expire once you close your browser (or once your session ends)." Since our cookies persist between browser sessions, I need to disclose them.

Secure cookies are also hidden to most browsers' devtools. (I might post screenshots later of what I mean by this). This means that most users won't see our cookies on their browser. However, our cookies are not set with the Secure attribute if the login request comes from an HTTP source, such as localhost. These cookies will appear in some browsers' devtools. To avoid confusion, and as a point of principle, I want to make clear to savvy users what's happening here.

How do releases work?

The manual way is complicated: add a version entry to, straighten out the not-yet-valid URLs in the changelog footer, update package.json and package-lock.json (the latter using npm i), then merge the PR, then copy the changelog entry to cut a new Release and tag using Codeberg's UI. The changelog's version links now point to the relevant newly-created tags.

I've missed some steps before. For example, version 0.9.0 didn't originally have a tag, so related comparison links were broken. Not ideal. Since we use Keep a Changelog, we can automate most of our release steps, as follows:

  1. I create a version entry in If I'm ready to merge to main but not yet ready to cut the release, I call the version "Unreleased", and the tooling ignores that version.
  2. When I'm ready to cut the release, I rename "Unreleased" to the next SemVer-appropriate number.
  3. I run npm run release, which fixes the changelog's footer links and the version fields in package.json and package-lock.json.
  4. I push a PR.
    • The CI (Continuous Integration) bots check that there's a new version in the changelog, and if so, check that I've run npm run release on the branch. (The usual CI checks also occur.)
  5. I merge the PR.
    • The CD (Continuous Deployment) bots dispatch a new git tag and Forgejo Release using the content of the

Once the release is tagged and deployed, it's up to server maintainers (including me) to pull down the latest changes. I might do something about that later.


Google Analytics is spoopy as heck, and even illegal in the EU. Right now, we don't have any analytics at all. We're considering respectful options:


I have a long wishlist for this project. In no particular order:

  • Move repo to our own GitHub org
  • Switch to Codeberg
  • Move repo to our own Forgejo instance
  • Separate the "website" from the "app" to make self-hosting easier
  • Standard API evolution protocol (deprecation and obsoleting of old API versions)
  • API v1
  • Detailed documentation webpage (protocols, API, etc.)
  • SDK for third-party tools to participate in our hosted E2EE data access without needing to re-implement the correct protocols directly
  • Logo
  • Mobile apps (?)