MemPalace/mempalace · warning · ValueError
refusing to export: {label} is a symbolic link ({path!r}). R
Error message
refusing to export: {label} is a symbolic link ({path!r}). Remove the symlink or choose a different output path. What it means
Raised by _reject_symlink before any export write when the target path itself is a symbolic link. This is defense-in-depth against symlink attacks: a pre-placed symlink at the export destination would redirect writes to wherever it points (system directories, other users' files). Exporter refuses and tells the user to remove the link or pick another path; the same caution mirrors the miner's input-side checks.
Source
Thrown at mempalace/exporter.py:38
from .palace import get_collection
def _safe_path_component(name: str) -> str:
"""Sanitize a string for use as a directory/file name component."""
name = re.sub(r'[/\\:*?"<>|]', "_", name)
name = name.strip(". ")
return name or "unknown"
def _reject_symlink(path: str, label: str) -> None:
"""Refuse to write into a path that is itself a symlink.
Defense-in-depth: a pre-placed symlink at the export target would
redirect writes to wherever it points (e.g., system directories).
Mirrors the miner's input-side caution.
"""
if os.path.islink(path):
raise ValueError(
f"refusing to export: {label} is a symbolic link ({path!r}). "
f"Remove the symlink or choose a different output path."
)
def _safe_open_for_write(path: str, mode: str, encoding: str = "utf-8"):
"""Open a file for writing, refusing to follow a symlink at the target path.
On POSIX (O_NOFOLLOW available) the open itself fails with ELOOP if path is
a symlink — closing the TOCTOU window between an islink check and the open.
On platforms without O_NOFOLLOW (Windows), pre-checks ``os.path.islink``,
which is narrower than no check at all.
"""
o_nofollow = getattr(os, "O_NOFOLLOW", 0)
if o_nofollow:
flags = os.O_WRONLY | os.O_CREAT | o_nofollow
flags |= os.O_APPEND if "a" in mode else os.O_TRUNC
try:View on GitHub (pinned to 06cb6987f0)
Solutions
- Replace the symlink with a real directory: rm <link> && mkdir <dir> (or bind-mount if you need the target location)
- Or export to a fresh plain directory and copy/sync afterward: export to ./export && rsync -a ./export/ ~/Dropbox/export/
- Check for symlinks first: find <output_dir> -type l
- If you need files to appear at the linked location, symlink in the other direction (link name -> real export dir) after export completes
Example fix
# before
ln -s ~/Dropbox/mempalace-export ~/export-target
export_palace(palace_path, output_dir=os.path.expanduser('~/export-target'))
# after
rm ~/export-target
mkdir -p ~/export-target
export_palace(palace_path, output_dir=os.path.expanduser('~/export-target'))
rsync -a ~/export-target/ ~/Dropbox/mempalace-export/ Defensive patterns
Strategy: validation
Validate before calling
import os
def export_path_is_safe(output_dir: str) -> bool:
p = os.path.expanduser(output_dir)
return not os.path.islink(p) and all(not os.path.islink(os.path.join(r, d))
for r, ds, fs in os.walk(p) for d in ds) if os.path.exists(p) else not os.path.islink(p) Try / catch
try:
export_palace(palace_path, output_dir=out)
except ValueError as e:
if "refusing to export" in str(e):
os.unlink(out); os.mkdir(out) # after investigating where the link pointed
export_palace(palace_path, output_dir=out) Prevention
- Export into a plain directory managed by mempalace, then sync copies elsewhere
- Avoid symlinked sync-folder roots (Dropbox/iCloud) as export targets
- Audit with find <out> -type l before scheduled exports
When it happens
Trigger: Calling export_palace(palace_path, output_dir) (or a per-wing/room export path) where output_dir or one of the wing/room file paths is a symlink — e.g. ~/mempalace-export symlinked to a Dropbox/www directory, or a leftover link created by a previous tool.
Common situations: Users symlinking the export dir into a synced folder (Dropbox/iCloud) or a web root; shared machines where an attacker pre-plants links; dotfiles managers that manage directories as symlinks; a previous export run left a symlink behind.
Related errors
- refusing to write: {path!r} is a symbolic link.
- update requires at least one of documents, metadatas, embedd
- qdrant marker is unreadable: {marker_path}
- LLM_ENDPOINT must use http:// or https:// (got scheme {schem
- {field_name} contains invalid path characters
AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15).
Data as JSON: /api/errors/379581d7842bcc83.
Report an issue: GitHub.