Aximar is a desktop application that opens user-provided notebook files and executes their contents via a local Maxima process. This document describes the threat model, known attack surfaces, and current mitigations.
The primary threat is a user opening an .axm notebook file from an untrusted source. Notebook files are JSON containing cell inputs and metadata. Opening a notebook does not automatically execute any cells — the user must explicitly run them. This is the most important mitigation and must be preserved.
However, social engineering ("run all cells to see the result") can still lead to exploitation once cells are executed.
Maxima is a full programming environment with access to the host system. The system() function executes arbitrary shell commands. There is no sandbox around the Maxima process.
Impact: A cell containing system("rm -rf ~")$ would execute if the user runs it.
Current mitigation: A safety gate detects dangerous function calls (system, batch, batchload, writefile, appendfile, closefile, stringout, with_stdout, save, store, and unknown load() targets) before execution. The behaviour depends on context:
- GUI: Untrusted cells containing dangerous functions show an inline approve/abort bar. Trusted cells (previously user-edited or approved) execute without prompting. "Run All" stops at cells needing approval.
- MCP connected mode: Untrusted cells return a
pending_approvalstatus so the GUI user can approve them.evaluate_expression(no cell) always blocks dangerous calls. - MCP headless mode: Dangerous calls are blocked by default. The
--allow-dangerousCLI flag opts in. - Smart
load()detection:load("distrib")is allowed when the argument matches a known Maxima package; unknown targets are flagged. - Trust model: Cells start untrusted. User editing in the GUI makes them trusted. MCP editing or loading from disk resets trust.
The Docker backend provides additional sandboxing:
--network none: no network access from the container--memory 512m: memory limit prevents resource exhaustion- Non-root user (
maxima) inside the container - Volume mount restricted to a dedicated temp directory
- Custom seccomp profile: GCL (the Lisp runtime used by Ubuntu's Maxima package) calls
personality(ADDR_NO_RANDOMIZE | READ_IMPLIES_EXEC)which Docker's default seccomp profile blocks. Rather than disabling seccomp entirely, a custom profile based on Docker's default is used that adds only the three specificpersonalityargument values GCL requires (0x40000, 0x400000, 0x440000). All other seccomp restrictions remain in effect.
Possible future mitigations:
- Run Maxima in a restricted sandbox (e.g. seccomp on Linux, sandbox-exec on macOS)
- Display untrusted-file warnings when opening notebooks not created by the user
Even within a Tauri app, XSS is dangerous because JavaScript runs in the app's WebView context and can invoke Tauri IPC commands.
File: src/components/CellOutput.tsx
Plot output SVGs originate from files that Maxima writes during plot operations. The Rust parser reads SVG files from paths extracted from Maxima's text output (src-tauri/src/maxima/parser.rs).
Current mitigation:
- SVG content is sanitized via
sanitizeSvg()which strips dangerous elements (<script>,<foreignObject>,<iframe>), event handler attributes, and data URIs - Sanitized SVGs are rendered as
<img src="blob:...">— the<img>context natively blocks all script execution and event handlers - SVG file paths are canonicalized and validated: must have a
.svgextension and reside within the system temp directory (is_safe_svg_path()inparser.rs) - CSP blocks inline scripts as an additional layer
Files: src/components/KatexOutput.tsx, src/components/MathText.tsx
Current mitigation: KaTeX is configured with trust: false in both components, which blocks macros like \href{javascript:...}{text} that could otherwise execute JavaScript when clicked.
File: src/components/MarkdownCell.tsx
Markdown cells are rendered with react-markdown, which sanitizes HTML by default. This is currently safe, but the lack of explicit sanitization configuration means a future library update changing defaults could introduce a vulnerability.
Recommendation: Explicitly configure rehype-sanitize to make the safe behaviour intentional rather than incidental.
File: src-tauri/src/maxima/parser.rs
The parser extracts file paths from Maxima's text output using a regex and reads them with fs::read_to_string.
Current mitigation: The is_safe_svg_path() function canonicalizes extracted paths (resolving symlinks and ..) and validates that they have a .svg extension and reside within the system temp directory. For the Docker backend, the Docker host temp directory (aximar-docker) is also allowed.
File: src-tauri/src/commands/plot.rs
The write_plot_svg Tauri command accepts a file path and writes content to it. The UI gates this behind a save dialog.
Current mitigation: The backend rejects paths containing .. segments (directory traversal) and enforces a .svg extension.
When enabled, the embedded MCP server listens on a local TCP port (default 127.0.0.1:19542) and exposes tools that can read/write notebook cells, evaluate Maxima expressions, and manage sessions.
Impact: An unauthenticated attacker on the same machine (or on the network, if the listen address is changed to 0.0.0.0) could execute arbitrary Maxima expressions, including system() calls, leading to RCE.
Current mitigation:
- Bearer token authentication. Every HTTP request must include an
Authorization: Bearer <token>header. The token is a cryptographically random 256-bit hex string generated on first launch, stored in the app config, and visible/regenerable in Settings. Requests without a valid token receive HTTP 401. - Localhost binding. The default listen address is
127.0.0.1, which restricts access to the local machine. Users can change this in Settings but should understand the risk of binding to a non-loopback address. - The token is compared in constant-length string comparison (both sides are always 64 hex chars), though timing attacks are low-risk for a localhost service.
Limitations:
- The token is stored in plaintext in the app config JSON file. Anyone with read access to the user's config directory can extract it.
- There is no TLS — the token is sent in cleartext over HTTP. This is acceptable for localhost but would be insecure over a network.
- There is no rate limiting on authentication failures.
- Large notebooks: No size limits on notebook JSON. A multi-gigabyte file could exhaust memory during parsing.
- Expensive Maxima expressions: Cell execution has a configurable timeout (default 30 seconds, max 600 seconds) enforced via
tokio::time::timeoutin the protocol layer. However, a user can set a long timeout, and expressions can still consume significant CPU within the allowed window. - Rapid evaluation requests: No rate limiting on the
evaluate_expressionTauri command.
These are low-severity for a desktop application since the user can kill the process.
| Surface | Trigger | Impact | Status | Mitigation |
|---|---|---|---|---|
system() in Maxima |
User runs cell | RCE | Mitigated | Safety gate detects dangerous functions; untrusted cells require approval. MCP headless blocks by default. |
| SVG rendering | User runs plot cell | XSS / IPC access | Mitigated | SVG sanitized via sanitizeSvg() then rendered as <img src="blob:...">. The <img> context natively blocks all script execution and event handlers. Sanitization is defence-in-depth. CSP blocks inline scripts as an additional layer. |
| SVG file path reading | User runs plot cell | Arbitrary file read | Mitigated | Path canonicalized and validated: must have .svg extension and reside within the system temp directory. |
| KaTeX trust mode | User runs cell producing LaTeX | XSS via \href |
Mitigated | trust set to false in both KatexOutput and MathText. |
write_plot_svg IPC |
User clicks "Save SVG" | Arbitrary file write | Mitigated | Backend enforces .svg extension and rejects paths containing .. segments. |
| MCP server (connected mode) | MCP enabled in Settings | RCE via Maxima | Mitigated | Bearer token auth (256-bit random token). Localhost-only by default. |
| Notebook JSON parsing | User opens file | DoS (memory) | No mitigation | — |
| Maxima execution time | User runs cell | DoS (CPU) | Partial | Configurable eval timeout (default 30s, max 600s). |
- Never auto-execute. Opening a notebook must never run any cell. This is the most important safety property.
- Sanitize all rendered output. Anything originating from Maxima or notebook files must be treated as untrusted before rendering in the WebView.
- Restrict file access. File reads and writes initiated by the backend should be scoped to expected directories.
- Gate dangerous operations. Detect and require approval for cells containing known-dangerous Maxima functions (
system,batch,writefile, etc.) before execution.