Feb 27, 2026·5 min read·23 visits
Actual Budget versions prior to 26.2.1 fail to verify file ownership on synchronization endpoints. Any authenticated user can read or overwrite another user's financial data by supplying the target's budget ID in API requests.
A critical authorization flaw exists in Actual Budget's synchronization server, specifically affecting multi-user deployments. The vulnerability allows authenticated users to access, modify, or delete budget files belonging to other users due to missing ownership verification checks in the sync endpoints. This effectively constitutes an Insecure Direct Object Reference (IDOR) where knowledge of a file's UUID is sufficient to grant full access, bypassing intended isolation between users.
Actual Budget is a local-first personal finance application that supports a server component for syncing data across devices. In multi-user configurations, the server acts as a central repository for multiple distinct user accounts. CVE-2026-27638 identifies a Missing Authorization vulnerability (CWE-862) within this synchronization layer.
The core issue lies in the API endpoints responsible for handling budget file operations, such as downloading, uploading, and syncing changes. While the application correctly enforces authentication—requiring a valid session token to interact with the API—it failed to implement resource-level authorization. Consequently, the server did not verify that the authenticated user was the owner of the budget file specified in the request.
The vulnerability stems from a reliance on authentication middleware without complementary object-level permission checks. The server utilizes validateSessionMiddleware to ensure that incoming requests originate from a logged-in user. However, once this check passed, the endpoints trustingly accepted the fileId (typically provided via the X-Actual-File-Id header or JSON body) as the target resource.
In packages/sync-server/src/app-sync.ts, handlers for routes like /sync, /download-user-file, and /upload-user-file would retrieve file metadata from the database based on the provided ID. The logic proceeded to perform the requested operation (read/write) immediately after retrieval. Crucially, the code did not compare the owner field of the retrieved file record against the userId associated with the active session. This omission created a direct path for Insecure Direct Object Reference (IDOR), where the identifier alone acted as the authorization token.
The following comparison highlights the absence of authorization logic in the vulnerable version and the explicit checks introduced in the patch.
Vulnerable Logic (Conceptual Representation):
Before the fix, endpoints would look up a file and immediately return it or process it, assuming that possession of the ID implied permission.
// Vulnerable handler example
app.post('/download-user-file', async (req, res) => {
const fileId = req.headers['x-actual-file-id'];
// DANGER: No check to see if req.userId owns fileId
const file = await db.getFile(fileId);
if (file) {
res.send(file.content);
}
});Patched Logic (Commit 9966c024):
The fix introduces a centralized helper function requireFileAccess which is now invoked at the start of every sync endpoint. This function explicitly validates ownership or administrative privileges.
// New authorization helper
function requireFileAccess(file, userId) {
// 1. Direct ownership check
if (file.owner === userId) {
return null; // Access granted
}
// 2. Admin override for system tasks
if (isAdmin(userId)) {
return null;
}
// 3. Shared access check (new feature support)
if (UserService.countUserAccess(file.id, userId) > 0) {
return null;
}
// Default deny
return 'file-access-not-allowed';
}
// Patched handler usage
app.post('/download-user-file', async (req, res) => {
const fileId = req.headers['x-actual-file-id'];
const file = await db.getFile(fileId);
// Explicit authorization gate
const accessError = requireFileAccess(file, req.userId);
if (accessError) {
return res.status(403).send(accessError);
}
res.send(file.content);
});This change ensures that even if an attacker guesses a valid fileId, the server rejects the request if the database record for that file lists a different owner.
To exploit this vulnerability, an attacker requires a valid account on the target Actual Budget instance. The attack does not require administrative privileges, only a standard user session.
Step 1: Reconnaissance
The attacker must obtain the fileId (a UUID) of a target budget. While UUIDs are generally not enumerable, they may be leaked through shared logs, exposed in client-side errors, or obtained via social engineering. In some configurations or older versions, file IDs might be predictable or sequentially generated (though Actual uses UUIDs by default).
Step 2: Execution The attacker crafts a malicious HTTP request to the synchronization API. For example, to exfiltrate a victim's budget:
POST /download-user-file HTTP/1.1
Host: budget.example.com
X-Actual-Token: <attacker-session-token>
X-Actual-File-Id: <victim-budget-uuid>
Content-Type: application/json
{}Step 3: Impact The server processes the request using the attacker's valid session but the victim's file ID. Without the ownership check, the server returns the victim's full budget database. Conversely, an attacker could upload a corrupted database to the same ID, destroying the victim's data.
The impact of this vulnerability is significant for multi-user deployments, affecting the confidentiality and integrity of financial data.
Confidentiality: An attacker can download complete budget files, exposing sensitive transaction histories, bank balances, and financial habits of other users.
Integrity: An attacker can modify or overwrite budget files. This allows for the injection of fraudulent transactions, deletion of valid records, or complete corruption of the budget database, rendering it unusable for the legitimate owner.
Availability: The /delete-user-file endpoint is also affected. An attacker could permanently delete other users' budget files, causing data loss if no backups exist.
The CVSS v3.1 score is 7.1 (High), driven by the high Integrity impact and the low complexity required to execute the attack once a file ID is known.
The primary remediation is to update the Actual server software.
Patching:
Update to Actual version 26.2.1 or later. This version includes the requireFileAccess logic and database migrations to ensure all existing files have correctly assigned owners.
Configuration Workarounds: If immediate patching is not feasible, administrators should restrict the instance to single-user mode. This vulnerability specifically impacts multi-user configurations where isolation between accounts is assumed. By disabling open registration and ensuring only trusted users have accounts, the risk is mitigated, although the underlying code flaw remains.
Database Audit:
Post-update, administrators should verify that the migration script (1763873600000-backfill-files-owner.js) successfully populated the owner column for all records in the files table. Orphaned files (files with NULL owners) should be manually assigned or audited.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Actual Actual Budget | < 26.2.1 | 26.2.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-862 |
| Vulnerability Type | Missing Authorization / IDOR |
| CVSS v3.1 | 7.1 (High) |
| Attack Vector | Network |
| Privileges Required | Low (Authenticated User) |
| Impact | Data Exfiltration & Integrity Loss |
Grav CMS prior to version 1.7.53 and 2.0.0-rc.8 is vulnerable to an unauthenticated remote denial of service (DoS) vulnerability. By supplying crafted query parameters with extremely large dimensions to image assets, remote unauthenticated attackers can force the server to allocate massive amounts of system memory, leading to kernel Out-Of-Memory (OOM) termination of web worker processes.
CVE-2026-53657 is a local privilege escalation vulnerability in Lima (lima-vm/lima) affecting versions prior to 2.1.3 when configured with the QEMU driver. The guest agent daemon, running as root, creates its communication socket `/run/lima-guestagent.sock` with world-writable permissions (0777). This allows unprivileged local users to command the agent to establish arbitrary tunnels, including to privileged local UNIX sockets (like D-Bus). Because the target daemon authenticates the incoming connection using the credentials of the root-owned guest agent (via SO_PEERCRED), unprivileged users can perform root operations, resulting in complete guest VM compromise.
An unauthenticated Denial of Service vulnerability exists in the s2n-quic library's CryptoStream reassembler due to a lack of buffer limits on out-of-order cryptographic frames. An attacker can transmit a crafted CRYPTO frame with an extremely high offset and nominal payload, forcing the receiver to execute unbounded memory allocations and causing service crashes.
A JNDI Injection and Deserialization Gadget vulnerability exists in mchange-commons-java prior to version 0.6.0. The com.mchange.v2.naming.JavaBeanObjectFactory component permits arbitrary class instantiation and setter invocation, allowing attackers to perform Server-Side Request Forgery (SSRF) and remote class loading.
SurrealDB versions supporting element-level SELECT permissions on arrays are vulnerable to a logical authorization bypass. Due to an index-shifting error during array filtration, restricted elements can skip permission checks and leak to unauthorized record users.
CVE-2026-12243 is a path traversal vulnerability in the Natural Language Toolkit (NLTK) version 3.9.4. The flaw exists because the input validation routine fails to account for percent-encoded directory traversal sequences like '..%2f' before passing them to urllib.request.url2pathname(), which decodes them into active traversal sequences.