mirror of
https://github.com/actualbudget/actual.git
synced 2026-11-09 10:45:04 -06:00
future-buffer
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
42e9d79d4a |
Merge commit from fork
* [AI] Reject invalid password inputs before hashing - Require non-empty strings at password validation, hashing and verification boundaries so JSON objects, arrays and other scalar types cannot reach Argon2's buffer conversion. - Add regressions for malformed passwords on fresh and configured servers, including assertions that rejected input never reaches Argon2. Advisory: https://github.com/actualbudget/actual/security/advisories/GHSA-3jjw-2hq7-mmm2 * [AI] Require admin permission for EnableBanking configuration - Require administrator permission to configure EnableBanking, even when credentials have not been configured yet. - Check permission before validating credentials with the provider or persisting secrets, and disable configuration controls for non-admins. - Test unauthenticated, BASIC and ADMIN requests against empty and existing configuration, preserving secrets and avoiding provider calls on denial. Advisories: https://github.com/actualbudget/actual/security/advisories/GHSA-hfxx-qc34-gqcw * [AI] Neutralize BOM-prefixed CSV formulas - Detect spreadsheet formula triggers after leading BOM characters and prepend an apostrophe before the original text. - Protect textual CSV headers and scalar values as well as ordinary strings. - Preserve numeric output, including negative amounts, and cover repeated BOMs, formula triggers, quoting and Unicode text in regression tests. Advisories: https://github.com/actualbudget/actual/security/advisories/GHSA-7jvc-qfpw-85g2 * [AI] Remove ambiguous Commerzbank payee matching - Split Commerzbank payees into non-empty whitespace/slash-delimited tokens and escape their literal text before constructing the cleanup expression. - Use consuming separators that cannot overlap the literal tokens, avoiding ambiguous matching on long runs of spaces or slashes. - Require separators between payee tokens: concatenated words in notes are no longer stripped. Allow 20 seconds for the hostile-case subprocess, including Node and Vite startup on slower CI runners. - Skip matching for empty payees while preserving normal note cleanup. - Exercise hostile payees, including the report's spaced-word reproducer, in a subprocess with a hard timeout; retain the existing bank fixtures. Advisories: https://github.com/actualbudget/actual/security/advisories/GHSA-p34v-m2gp-w3jm * [AI] Reject unsafe IPv6 address classes in SSRF checks - Allow public unicast addresses by default. The private-network option additionally permits private, loopback, unique-local and carrier-grade NAT addresses. - Permit the well-known NAT64 prefix only for embedded public IPv4 destinations; private and metadata addresses remain blocked in both modes. - Reject other IPv6 transition, multicast, reserved and link-local classes in both modes, and classify IPv4-mapped addresses by the embedded IPv4 address. - Cover literal URLs, DNS answers, redirects and both SimpleFIN fetch paths, including NAT64, 6to4 and Teredo addresses, with synthetic upstreams. Advisories: https://github.com/actualbudget/actual/security/advisories/GHSA-97j5-6g5r-42v5 * [AI] Exclude callback secrets from ordinary server logs - Log request method, pathname and response status without query strings, and restrict serialized metadata to the same safe fields. - Keep full OpenID and banking provider messages, response bodies, session identifiers and error stacks behind existing actual-sensitive namespaces. - Apply the same opt-in handling to shared banking errors, proxy target URLs and unhandled rejections; do not forward raw streaming errors to Express's ordinary error logger. - Stop logging stored secret values, including with debugging enabled. - Document targeted sensitive debugging and private support exchanges. - Test ordinary and opt-in output, query and serialized metadata leaks, provider failures, session URLs, streaming errors and secret storage. - Preserve provider format sequences by passing SimpleFIN response text as a debug format argument, rather than part of the format string. Advisories: https://github.com/actualbudget/actual/security/advisories/GHSA-ppcp-hx2h-fm35 * [AI] Revoke password sessions when credentials change - Update the password hash and revoke all password-authenticated sessions in one transaction, including password changes through the local CLI. - Issue a fresh token for each successful password login instead of reusing an existing session token. - Recheck the verified password hash before creating a session so an in-flight login cannot restore access after a credential change. - Correct the UI's claim that other sessions remain signed in, and test multiple sessions, CLI resets, transaction rollback and login/reset races. This addresses session revocation and token reuse. Requiring the current password for remote changes is covered by the following commit. Advisories: https://github.com/actualbudget/actual/security/advisories/GHSA-7363-3qq5-2frj https://github.com/actualbudget/actual/security/advisories/GHSA-4wgp-48c6-v9w3 * [AI] Require the current password for remote password changes - Require currentPassword alongside the replacement password for remote changes, preserving the administrator and password-authentication checks. - Verify the current credential, rate-limit attempts and reject a change if the verified password hash changes before the update. - Collect the current password in the client and handle failure to sign in after a successful change. Keep the local reset command available without the old password. - Test that a session token alone cannot change credentials and that failed requests preserve the existing password and sessions. - Clear the redundant success notice when signing in with the new password fails. Advisories: https://github.com/actualbudget/actual/security/advisories/GHSA-4wgp-48c6-v9w3 * [AI] Restrict Electron protocol launches and network file reveals - Allow only parsed HTTP/HTTPS URLs through external-opening IPC, new-window handling and navigation, addressing GHSA-7pf2-w24j-p6h2. - Also reject network/UNC paths in the separate file-reveal handler. A note such as [View receipt](//host/share/invoice.pdf) reaches showItemInFolder without passing through openExternal. On Windows, resolving that share can disclose NTLM authentication responses to an attacker-controlled server where network and authentication policies permit it. This additional flow was not demonstrated in the advisory. - Accept native absolute local paths without adding file-URL conversion. - Test protocol filtering and network-path rejection without invoking OS handlers. The note-to-file-reveal flow is code-confirmed; an actual Windows authentication exchange has not been reproduced. The following commit addresses the advisory's separate internal-origin classification finding. Advisory: https://github.com/actualbudget/actual/security/advisories/GHSA-7pf2-w24j-p6h2 Additional finding references: https://github.com/electron/electron/blob/v43.4.0/shell/common/platform_util_win.cc#L270-L340 https://learn.microsoft.com/en-us/windows-server/storage/file-server/smb-ntlm-blocking - Exercise Windows path semantics on all test platforms and exclude generated Electron tests and Vitest configuration from the packaged application. * [AI] Classify Electron app URLs exactly and block redirects - Replace substring checks with exact app://actual scheme/host matching and the existing development origin, http://localhost:3001. - Use the classification for browser handoffs and focus/log script execution so remote URLs containing app:// or localhost: are not mistaken for app pages. - Preserve the existing global renderer-navigation block and add redirect blocking there, without duplicating it in the main-window handlers. - Keep ordinary IPC registration unchanged; broad sender validation is outside this URL-classification fix. - Test misleading hosts, ports and URL substrings. Exercise ordinary clicks, new-window links and explicit browser IPC in Electron, asserting the exact HTTP/HTTPS URLs handed to the browser without opening real OS handlers. Exercise a local HTTP redirect to check the app stays put. Advisory: https://github.com/actualbudget/actual/security/advisories/GHSA-7pf2-w24j-p6h2 - Retain startup logs until an internal page finishes loading, then forward them to DevTools; cover startup delivery in the Electron navigation suite. * [AI] Choose import destinations from trusted local context - Allocate fresh local IDs for cloud downloads and local archive imports, ignoring destination IDs supplied in archived metadata. - Pass an explicit local destination for replacement downloads and verify its cloud-file association. Do not require matching sync group IDs, so downloading the reset version of the same cloud file remains possible. - Reject server metadata describing a different cloud file and close the budget before an authorized replacement. - Retain existing import metadata compatibility, collision error handling, nested archive layouts and backups; do not require a budget name. - Test targeted collisions, local recovery imports with obsolete IDs or missing optional metadata, mismatched cloud associations and replacement after a sync ID reset using real local files and databases. Advisory: https://github.com/actualbudget/actual/security/advisories/GHSA-7fr5-fjj8-hwjq - Reuse the matching local budget for API downloads after a sync reset, report cloud-file mismatches in API/CLI errors, and document local IDs. * [AI] Revoke sessions on sign-out and API shutdown Revoke only the supplied session on explicit sign-out. Clear local credentials if the server cannot be reached, and revoke password-created API sessions during shutdown without revoking caller-supplied tokens. Cover isolated and repeated revocation, invalid tokens, offline sign-out, borrowed API tokens and shutdown after a close failure. Session expiry policy is unchanged. Follow-up to: https://github.com/actualbudget/actual/security/advisories/GHSA-7363-3qq5-2frj |
||
|
|
8ad218b7d9 |
[AI] api: make the browser build work in consumer production bundles (#8289)
* api: make the browser build work in consumer production bundles
The browser build shipped `new Worker(new URL("./worker.js", import.meta.url))`
in dist/browser.js. A consumer bundler (Vite/Rollup) recognizes that as a worker
entry and re-bundles the already-prebuilt worker.js from scratch, which desyncs
the absurd-sql/connection RPC so `init` throws a structured-clone error. It only
worked in dev and in the package's own e2e because both serve dist verbatim.
Make the browser build fully self-contained instead:
- Inline the worker into browser.js and spawn it from a Blob URL, so consumer
bundlers never see a worker entry to re-bundle. The worker is built as an IIFE
(classic worker), mirroring the web app's kcab.worker.
- Embed the sql.js wasm and the default filesystem data (migrations + default
DB) into the worker so it performs no PUBLIC_URL asset fetches:
- loot-core sqlite gains an opt-in `setWasmBinary` (dormant for web/node);
- the worker installs a scoped fetch shim serving the embedded data/* files
from a sentinel base URL.
- Share the asset-collection logic between the Node disk copy and the embedded
build via scripts/embedded-assets.mjs so the two never drift.
Result: `import '@actual-app/api'` + `init()` works in any bundler with zero
config; only COOP/COEP headers remain required (SharedArrayBuffer).
Adds an e2e (e2e/consumer + e2e/global-setup.mjs) that builds a real consumer
app for production and boots it under COOP/COEP — coverage the verbatim-dist
harness can't provide. Docs note the build is self-contained and that
cross-origin isolation is still required.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* [AI] api: address review feedback (guard DOM lookup, revoke blob URL on worker failure)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* [AI] api: address review nitpicks (drop BodyInit cast, reword release note)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* [AI] api: build the browser worker via Vite ?worker&inline
Replace the hand-rolled worker inlining (a custom Vite plugin that read the
prebuilt worker.js off disk and inlined it as a string, plus a second build
config and a build-ordering dependency) with Vite's native `?worker&inline`
import. This collapses the two browser build configs into one, removes the
`virtual:actual-worker-code` module and the manual Blob URL spawn, and drops
the worker-before-facade build step.
Vite's `?worker&inline` sub-build doesn't receive vite-plugin-node-polyfills'
global-shim injection (the plugin writes it to `build.rollupOptions`, but the
worker sub-build reads `worker.rollupOptions`), so the worker crashed on
`process is not defined`. Patch the plugin to also emit the injection on
`worker.rollupOptions` — additive and backwards compatible (the web app build,
including loot-core's nodePolyfills worker, is unaffected).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* [AI] api: embed browser assets via Vite, drop the embedded-assets script
Replace scripts/embedded-assets.mjs with Vite-native asset imports in the
worker entry: `?inline` for the sql.js wasm and default DB, `import.meta.glob`
(`?raw`) for the migrations. The worker builds the default-filesystem wire
format from those at module load, so the embedded set comes straight from
loot-core and can't drift.
The Node build only needs migrations + the default DB on disk (it reads them at
runtime); the old script also wrote sql-wasm.wasm and the data/ fetch tree,
which are dead now that the browser worker is self-contained. Replace
writeEmbeddedAssetsToDist with a small closeBundle copy of just those two.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* [AI] api: simplify embedded data map; drop loot-core comment churn
Merge the worker's binData/textData into a single dataFiles map (Response
accepts both bytes and strings), collapsing the fetch shim's two lookups into
one. Revert the unrelated comment edits to loot-core's backend-worker.ts and
fs/index.ts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* [AI] api: rename e2e consumer-dist to consumer/dist; drop a prose comment
Use the standard `dist` directory name for the consumer fixture's build output
(now e2e/consumer/dist), and remove the verbose comment from index.browser.ts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* [AI] loot-core: own the default-filesystem assets; consumers stop reaching in
Add packages/loot-core/default-filesystem.mjs as the single source of truth for
loot-core's runtime assets (sql.js wasm, default DB, migrations) and the
data-file-index wire format, exported as @actual-app/core/default-filesystem.
- @actual-app/api: the browser worker embeds them via the actual-embedded-assets
Vite plugin (which calls collectEmbeddedAssets), replacing the relative
?inline/?raw/import.meta.glob reaches into ../loot-core; the Node build copies
migrations + default DB using the helper's paths. Drops the direct
@jlongster/sql.js devDependency (loot-core resolves the wasm).
- @actual-app/web: stagePublicData copies from the helper's paths instead of
hardcoding loot-core's tree and the sql.js wasm location.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* [AI] loot-core: scan migrations once when collecting embedded assets
collectEmbeddedAssets() read the migrations directory twice — once in its
own loop and once via buildDataFileIndex(). List the names once and pass
them through, keeping the index wire-format defined in a single place.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
8f27e177dc |
[AI] api: add browser build (#8166)
* [AI] api: add browser build
Continuation of #7545 (and #8143, which moved the browser Web Worker
bootstrap into loot-core). This adds the browser-compatible build of
@actual-app/api: the full loot-core stack runs inside a Web Worker and a
thin main-thread facade exposes the same public surface as the Node entry.
loot-core:
- Add platform/client/backend-worker.ts: createBackendWorker(worker)
centralizes absurd-sql's main-thread initBackend + __absurd:* message
filtering, and is exported for the api to reuse.
- Refactor browser-preload's start.ts and worker-bridge.ts to route through
it (drops the duplicated __absurd:* filtering; terminate() now also clears
listeners). Behavior is preserved.
api:
- browser-worker.ts: Web Worker entry owning real loot-core, speaking
loot-core's {id,name,args} backend protocol; wraps fetch to map the
.js -> .js.data migration names.
- index.browser.ts: main-thread facade that spawns the worker.
- browser/rpc.ts: main-thread <-> worker RPC bridge (uses createBackendWorker).
- browser/lib-stub.ts: stubs lib.send so methods.ts routes over postMessage.
- vite.browser.config.mts / vite.browser-worker.config.mts and asset bundling
in vite.config.mts (sql-wasm.wasm + data/ + data-file-index.txt); package.json
build chain, browser export, and absurd-sql/npm-run-all/vite-plugin-node-polyfills deps.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] Rename release note to match PR number 8166
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] api: move browser worker plumbing into loot-core
Address review feedback: the browser worker logic shouldn't live in the api
package since it's generic "loot-core as a browser npm package" plumbing, not
api-specific. Resolve it via loot-core's existing conditional-export system
instead of a vite alias.
- loot-core: add server/main.api-browser.ts (worker-routing `lib` that
delegates to the existing platform/client/connection), selected via a new
`api-browser` export condition on "./server/main". Move the worker entry to
server/api-browser-worker.ts.
- api: the facade (index.browser.ts) now reuses loot-core's client connection
via a minimal global.Actual.getServerSocket shim and spawns the worker built
from loot-core's entry. Deletes browser-worker.ts, browser/rpc.ts and
browser/lib-stub.ts (~270 lines) in favor of the shared loot-core code.
- The facade build selects the impl with resolve.conditions: ['api-browser']
instead of resolve.alias, removing the enforce-boundaries lint exception.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] api: simplify browser build cleanups
Post-review cleanups (no behavior change):
- backend-worker.ts: fix stale doc comment referencing the removed
packages/api/browser/rpc.ts (the consumer is now index.browser.ts).
- api-browser-worker.ts: drop the redundant `endsWith('/data-file-index.txt')`
disjunct (subsumed by `endsWith('data-file-index.txt')`) and the no-op
`input as RequestInfo | URL` re-casts on the fetch pass-throughs.
- index.browser.ts: replace the single-field `state` holder with a plain
module-level `let backend`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] api: drop the createBackendWorker wrapper, revert browser-preload churn
Now that the facade rides on loot-core's platform/client/connection (which
already ignores absurd-sql's `__`-prefixed internal messages), the only thing
the main thread still needs is absurd's one-line initBackend. So:
- Replace the 60-line createBackendWorker wrapper (onMessage/postMessage/
terminate/listener filtering) with a tiny initBrowserBackend(worker) helper.
- Revert browser-preload/start.ts and worker-bridge.ts to their merged state —
they keep importing absurd's initBackend directly, so this PR no longer
refactors just-merged files and the loot-core change is purely additive.
- Facade holds a plain Worker handle and calls worker.terminate() on shutdown.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] api: trim verbose comments in the browser build
Keep only the load-bearing "why" notes (the absurd-sql init, the fetch/.js.data
workaround, the global.Actual shim, the api-browser condition); drop protocol
diagrams, usage examples, and restated context. No code/behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] api: drop redundant absurd-sql dependency
No api source imports absurd-sql; it's reached only through @actual-app/core
(the facade's initBrowserBackend and the bundled worker), which declares it.
It's bundled into the build output, so it isn't a runtime dependency of the
published package either. Bundles are byte-for-byte unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] api: drop the api-browser condition, inject the worker into the connection
Review feedback: the api-browser export condition fought loot-core's idiom
(browser IS the default platform; node/"api" is the override) and hid a bug —
the condition-swapped lib only had `send`, so api.utils.amountToInteger was
silently undefined in the browser build.
Replace the condition with a plain transport seam:
- methods.ts no longer imports server/main; it sends through api/send.ts,
which the Node entry wires to in-process lib.send and the browser entry
wires to loot-core's client connection. utils.ts re-exports
amountToInteger/integerToAmount from shared/util directly (fixes the bug).
- loot-core's client connection init() now accepts an optional injected
socket; connectBackendWorker(worker) installs absurd-sql's bridge and
connects in one call. This removes the forged global.Actual shim and the
facade's `define: { global: 'globalThis' }` workaround. Desktop's argless
init() path is unchanged.
- Delete main.api-browser.ts, the "./server/main" conditional export, and
the facade's resolve.conditions.
The 4 loot-core snapshot failures (main.test.ts / transfer.test.ts) are
pre-existing on the branch without these changes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] api: route methods through the client connection, slim the facade
Review feedback: get rid of the _setSend seam and move the worker init logic
into loot-core.
- methods.ts now imports `send` from loot-core's client connection. The node
build resolves it via the existing `api` condition to a new in-process
implementation (platform/client/connection/index.api.ts — previously a dead
re-export of the electron client), matching the platform idiom: browser is
the default, node/"api" overrides. api/send.ts is deleted and the node
entry (index.ts) reverts to master byte-for-byte.
- loot-core's startBackendWorker(worker, config, assetsBaseUrl) now owns the
whole worker boot (absurd-sql bridge + connection.init + server init RPC),
so the browser entry just spawns the worker and calls it.
- Trims: single chained build script (drops the npm-run-all dep), copyFileSync
instead of the linkOrCopy helper, drop the worker entry's error listeners
(browsers already log worker uncaughts to DevTools).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] api: trim prose comments to the essential ones
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] fix client connection stalling when no sends are queued at connect
Found while smoke-testing the browser api build in a real consumer app: the
client connection only nulled messageQueue (switching from queueing to direct
postMessage) when the queue was non-empty at 'connect'. The desktop app always
has queued sends by then, so it never hits this; the api facade connects
before sending anything, so every later send queued forever and init() hung.
Drop the queue on connect unconditionally.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] fix client connection dropping api/* error replies
Found via the browser smoke test: opening an e2e-encrypted budget without the
key surfaced as "undefined is not an object" instead of an error. The server
connection forwards api/* handler failures as {type:'reply', id, error}, but
the web client connection only read `result`, silently resolving undefined for
every failed api/* call. Reject when the reply carries an error, matching the
server's envelope (and the old api rpc behavior). The electron client has the
same latent gap; left untouched here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] api: move the worker entry back into the api package
Review feedback: browser-worker.ts is the api package's build entry, not
generic loot-core code — move it back (it now imports loot-core through the
public @actual-app/core exports instead of #-subpaths).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] api: drop the manifest rewrite, align shutdown, trim comments
The data-file-index.txt manifest now lists migrations under their real .js
names (the worker's fetch shim already redirects those to the .js.data files
on disk), so the manifest-rewriting half of the shim is gone. The worker init
payload carries config and assetsBaseUrl as separate fields instead of
smuggling a private key into the config object, browser shutdown() mirrors
the node facade, and the client connection's queue flush drops a redundant
branch.
* [AI] fix rejected handler promises poisoning close-budget
runHandler only removed a method's promise from runningMethods when it
fulfilled, so any handler rejection left a stale promise in the set. The next
close-budget then failed: flushRunningMethods' Promise.all rejected with that
old, unrelated error before closeBudget even ran. Remove settled promises on
rejection too, and flush with Promise.allSettled since rejected methods
already reported their error to their caller.
* [AI] api: add a browser e2e test
A Playwright test loads the built browser bundle in chromium from a static
COOP/COEP server and exercises the full stack: worker boot, the rejected-error
contract, budget import, transaction read-back, and IndexedDB persistence
across a page reload. A dedicated workflow runs it in CI on api/loot-core
changes.
* [AI] api: drop the worker fetch shim; strip .data names in loot-core fs
The .js.data scheme was spread across three coordinated pieces: the build
renamed migrations on disk, the manifest listed names that didn't exist, and
a global fetch monkey-patch in the worker un-lied at runtime. Now the
manifest lists the true on-disk names and populateDefaultFilesystem (the only
fetcher of these files) strips the .data suffix when naming the virtual file
— the fetch patch is gone and the suffix becomes a small documented fs
convention. Also: e2e static server uses a single stat and drops a dead MIME
entry.
* [AI] fix electron client connection dropping api/* error replies
Both server connection variants post api/* handler failures as
{type: 'reply', id, error}, but only the web client rejected on it
(
|