Jun 19, 2026·8 min read·4 visits
A critical vulnerability in Anki's local HTTP media server allows malicious websites to perform cross-origin directory traversal attacks, resulting in silent local file exfiltration from vulnerable browsers such as Firefox.
The local media server (mediasrv.py) in Anki up to and including version 25.09.2 fails to validate incoming HTTP requests. The server does not validate the Origin header, enabling cross-origin requests. Additionally, several endpoints suffer from directory traversal vulnerabilities. Combined, these flaws permit an unauthenticated remote attacker to exfiltrate arbitrary files from a local file system when a user visits a malicious website.
Anki desktop applications bundle a local Flask-based web server (mediasrv.py) running on the loopback interface (127.0.0.1). This web server serves internal UI assets, user media files, and application-specific templates necessary for rendering interactive flashcards. Because this server exposes a local network port to the system, it establishes an attack surface accessible by other processes on the same system, as well as sandboxed environments such as web browsers.
The security model of local loopback servers relies on browser-enforced boundary security to prevent external sites from interacting with local endpoints. This boundary is defined by the Same-Origin Policy (SOP) and Cross-Origin Resource Sharing (CORS) rules. In affected versions of Anki (up to and including version 25.09.2), multiple implementation weaknesses in mediasrv.py break these boundary guarantees. Specifically, a combination of deficient CORS validation (CWE-346) and improper restriction of file path access (CWE-22) allows external actors to abuse the local server.
An attacker can exploit these weaknesses by hosting a malicious website that targets the vulnerable local endpoints. When a user runs Anki and visits the attacker-controlled webpage, the attacker can leverage the victim's browser as a network pivot to access the local web server. The local server processes unauthorized cross-origin requests, allowing the attacker to bypass path restrictions, read sensitive local files, and exfiltrate them to a remote destination.
The primary root cause resides within the origin-checking mechanics of qt/aqt/mediasrv.py. The server previously configured its Cross-Origin Resource Sharing policy using the third-party flask_cors library, specifying origins as "127.0.0.1". However, this setup did not strictly validate incoming HTTP Origin headers from cross-origin requests, enabling remote domains to craft valid requests that bypass local access limits.
Furthermore, the internal validation for the Host header was insufficient and fragile. The code validated the Host header by ensuring it started with local prefixes containing trailing colons, such as 127.0.0.1:. This check failed to handle requests that omitted the port, and did not validate the companion Origin header. In certain browsers that do not implement Private Network Access (PNA), such as Mozilla Firefox, cross-origin HTTP requests are allowed to proceed to localhost without pre-flight validation, meaning the server must implement strict manual origin verification.
The second core failure lies in the resolution of file paths. In the _builtin_data endpoint, incoming path arguments were appended using unsafe path joining: aqt_data_path() / ".." / path. The code executed read_bytes() directly on the resolved path without verifying if the requested resource remained within the intended directory boundaries. Additionally, the standard local file request handler implemented a substring-based prefix check (fullpath.startswith(directory)) which is vulnerable to prefix-matching bypasses when directories share common name prefixes.
To understand the path traversal and CORS validation weaknesses, we examine the vulnerable code implementation alongside the corrected logic implemented in version 25.09.3. The original implementation allowed arbitrary directory navigation due to a lack of proper base-path canonicalization and substring-matching flaws.
# Vulnerable implementation of directory check
directory = os.path.realpath(directory)
path = os.path.normpath(path)
fullpath = os.path.abspath(os.path.join(directory, path))
# protect against directory transversal
if not fullpath.startswith(directory):
return _text_response(
HTTPStatus.FORBIDDEN, f"Path for '{directory} - {path}' is a security leak!"
)The flaw in fullpath.startswith(directory) allows an attacker to navigate to sibling directories that share a prefix. For instance, if the authorized directory is /home/user/app, a requested path of ../app_backup/conf.json resolves to /home/user/app_backup/conf.json, which starts with /home/user/app and erroneously passes the validation. The patched version resolves this by appending the platform-specific directory separator (os.sep) to the base directory before performing the validation check:
def ensure_safe_path(base_dir: str | Path, path: str | Path) -> str:
base_dir = os.path.realpath(base_dir)
path = os.path.normpath(path)
fullpath = os.path.abspath(os.path.join(base_dir, path))
# The patch appends os.sep to ensure strict directory containment
if not fullpath.startswith(base_dir + os.sep):
raise UnsafePathException(path)
return fullpathIn addition, the flask_cors dependency was completely removed, replaced by manual extraction and comparison of the Origin and Host headers. A shadowing bug was also fixed in commit 858e5689d0e4fd24f74856c7e8f245412694a219, where the generator expression (f"{host}:" for host in _LOCALHOST_HOSTS) shadowed the outer host variable. The corrected logic utilizes an explicit loop parameter h to preserve the outer variable context:
# Patched Host and Origin validation
if os.environ.get("ANKI_API_HOST") != "0.0.0.0":
host = request.headers.get("Host", "").lower()
origin = request.headers.get("Origin", "").lower()
allowed_hosts = tuple(f"{h}:" for h in _LOCALHOST_HOSTS)
if not any(host.startswith(h) for h in allowed_hosts):
logger.warning("denied non-local host: %s", host)
abort(403)
if origin and not is_localhost_origin(origin):
logger.warning("denied non-local origin: %s", origin)
abort(403)Exploiting this vulnerability requires zero administrative privileges and can be achieved entirely through a user-interaction vector. The attack relies on the victim visiting a malicious or compromised web page while the Anki application is running in the background. The malicious page executes client-side JavaScript within the user's browser context to perform a local network scan.
The script first attempts to locate the exact TCP port of mediasrv by executing rapid asynchronous fetch requests to localhost across a range of common ports. Once the correct port is identified, the exploit issues a request to the _builtin_data endpoint with a path-traversal sequence. In a vulnerable browser environment such as Firefox, which does not block public-to-local requests, the browser forwards the request to http://127.0.0.1:<port>/_builtin/../../../../etc/passwd or an equivalent system file path.
The local server parses the payload, performs the unsafe directory resolution, reads the file, and sends the contents back in the HTTP response. Because the server fails to reject non-local origins, the browser allows the calling JavaScript context to read the response payload. The script then encodes the file data (e.g., via Base64) and transmits it to an attacker-controlled remote logging server, achieving full local file exposure.
The impact of this vulnerability is rated High with a CVSS v4.0 score of 8.7. Successful exploitation results in unauthorized, arbitrary read access to the victim's local file system. An attacker can access sensitive documents, configuration files, SSH keys, API tokens, browser session databases, and other credentials stored under the user's home directory.
The severity is moderated by the user's choice of browser. Modern Chromium-based browsers enforce Private Network Access (PNA) specifications, which block or prompt for local resource requests initiated from public internet origins. Consequently, users running Chrome, Microsoft Edge, or Brave are substantially protected. However, users utilizing Firefox or older versions of Safari are highly vulnerable, as these browsers lack PNA controls and will transparently execute and return the cross-origin requests.
Because the server runs with the same privileges as the logged-in user, the file traversal capability is bound by the user's operating system permissions. It does not directly provide remote code execution (RCE) on its own. However, if an attacker can read sensitive application configurations or writable startup scripts, they may be able to chain this file read primitive into complete system compromise.
The recommended and primary remediation strategy is upgrading the local Anki installation to version 25.09.3 or higher. This version deprecates the generic flask_cors library configuration in favor of strict, manual validation of both the Host and Origin headers against localhost patterns. Furthermore, the path validation is refactored to enforce strict directory containment using canonical paths.
If an immediate upgrade is not possible, users should employ localized mitigation strategies to minimize exposure. The most effective workaround is utilizing a web browser that implements full Private Network Access (PNA) security policies to prevent public websites from reaching localhost. Users should avoid running the Anki desktop application in the background while browsing untrusted web pages on browsers that lack PNA support.
From a development perspective, secure coding practices require that loopback servers always implement explicit origin verification. Relying on default CORS library configurations or simple substring checks is highly error-prone. Base directories must always be canonicalized, and path validations must enforce boundaries by including directory separator boundaries, ensuring that input paths cannot navigate outside the intended scope.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
aqt Anki | <= 25.09.2 | 25.09.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-346, CWE-22 |
| Attack Vector | Network |
| CVSS v4.0 | 8.7 |
| Impact | Arbitrary Local File Read |
| Exploit Status | Proof of Concept (PoC) available |
| First Patched Version | 25.09.3 |
The product does not properly validate the origin of a request, or improperly restricts access to system directories.
SurrealDB versions 3.0.0 through 3.1.4 contain an information exposure vulnerability (CWE-203) where the query planner optimizes sorted queries using indexes on fields with field-level SELECT restrictions. Because the query planner performs index-based sorting before enforcing permission-based redaction, unauthorized users can observe the physical order of returned rows to deduce the relative values of protected fields.
A security vulnerability exists in SurrealDB's streaming query planner where streaming graph edge traversals or reverse-reference traversals bypass field-level SELECT permissions. This vulnerability allows an authenticated database user with valid, low-privileged credentials holding table-level SELECT permissions to bypass field-level access controls and read highly confidential or restricted fields.
An authenticated denial-of-service vulnerability in SurrealDB allows remote attackers with query privileges to crash the server process. The issue arises from uncontrolled recursion during the compilation, serialization, or deallocation of exceptionally deep Abstract Syntax Trees (ASTs). While the iterative Pratt parser successfully handles long flat sequences of binary operators without triggering recursion limits, the resulting AST structure causes stack overflow in downstream recursive tree-walking components.
The http4k-security-digest module within the http4k library fails to validate HTTP Digest Access Authentication nonces by default. Due to an always-true nonce verifier lambda implementation, applications using default configurations do not enforce session freshness or uniqueness. This design flaw allows remote attackers to perform replay attacks, gaining unauthorized access to protected endpoints by intercepting and retransmitting valid authorization headers.
CVE-2026-11769 is a directory traversal vulnerability affecting the Grafana Operator before version 5.24.0. An authenticated attacker with basic namespace privileges can deploy a crafted GrafanaDashboard or GrafanaLibraryPanel custom resource to read sensitive local files. This enables the extraction of the service account token of the operator manager, resulting in cluster-wide privilege escalation.
CVE-2026-53725 is a critical sensitive information disclosure vulnerability in Parse Server (versions 9.8.0 to < 9.9.1-alpha.5). When Multi-Factor Authentication (MFA) is enabled and standard read permissions on the _User class are restricted via Class-Level Permissions (CLPs), the /login and /verifyPassword endpoints improperly fall back to returning the raw database row upon a failed mock re-fetch request. This behavior leaks plaintext MFA TOTP secrets, recovery codes, and fields designated as protected, enabling attackers with compromised user passwords to bypass multi-factor authentication controls entirely.