3.2 KiB
GHSA Draft: Arbitrary filesystem write via Obsidian export in agentmemory
Severity: Medium · CVSS 3.1: 6.5 (AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L)
CWE: CWE-22 — Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'), CWE-73 — External Control of File Name or Path
Affected versions: < 0.8.2
Patched version: 0.8.2
Summary
The POST /agentmemory/obsidian/export endpoint accepted a vaultDir parameter and passed it directly to mkdir and writeFile calls without any containment check. A caller could set vaultDir to any absolute path on the filesystem and agentmemory would create directories and write Markdown files there with the permissions of the process running the server.
# Example exploit payload (affected versions only)
curl -X POST http://localhost:3111/agentmemory/obsidian/export \
-H "Content-Type: application/json" \
-d '{"vaultDir": "/etc/cron.d"}'
The content written would be agentmemory's exported memories in Markdown format, but an attacker could craft specific memory content beforehand to plant arbitrary files.
Impact
When chained with advisory #03 (default 0.0.0.0 binding) or advisory #04 (unauthenticated mesh), an attacker on the local network could write arbitrary files to any filesystem location the agentmemory process had write access to.
Possible exploitation paths:
- Write to
~/.ssh/authorized_keys— SSH key injection - Write to
/etc/cron.d/*— cron job injection (if running as root) - Write to
~/.bashrcor shell rc files — code execution on next shell - Overwrite any file the process could write to
Patches
Fixed in 0.8.2:
- New
AGENTMEMORY_EXPORT_ROOTenvironment variable (default:~/.agentmemory) vaultDirnow goes throughresolveVaultDir()insrc/functions/obsidian-export.ts:- Resolves the path with
path.resolve - Checks
resolved === root || resolved.startsWith(root + path.sep) - Returns
nullif the check fails, and the endpoint returns{ success: false, error: "vaultDir must be inside AGENTMEMORY_EXPORT_ROOT" }
- Resolves the path with
- Default export is confined to
~/.agentmemory/vault - Tests added in
test/obsidian-export.test.tsfor both the custom-but-valid case and the rejection case
Known limitations
resolveVaultDir() performs lexical containment only — it does not call fs.realpathSync / fs.lstatSync. A pre-existing symlink under AGENTMEMORY_EXPORT_ROOT that points outside the root can still be written through. Users who allow untrusted processes to create files inside AGENTMEMORY_EXPORT_ROOT should additionally run agentmemory inside a sandbox that forbids symlink creation, or file a follow-up issue requesting symlink-aware containment.
Workarounds
Users on affected versions should:
- Disable the Obsidian export endpoint by setting
OBSIDIAN_AUTO_EXPORT=false(and avoid calling/agentmemory/obsidian/exportmanually) - Set
AGENTMEMORY_SECRETso the endpoint requires bearer auth - Upgrade to 0.8.2
References
Credit
@eng-pf