Skip to content

feat(vault-sync): opt-in symlink following#17

Open
LinuxIsCool wants to merge 2 commits intoregen-prodfrom
feature/vault-sync-follow-symlinks
Open

feat(vault-sync): opt-in symlink following#17
LinuxIsCool wants to merge 2 commits intoregen-prodfrom
feature/vault-sync-follow-symlinks

Conversation

@LinuxIsCool
Copy link
Copy Markdown

Summary

  • Adds VAULT_SYNC_FOLLOW_SYMLINKS env flag (default: false) to allow vault sync to follow symlinks in the Shared/ folder
  • Enables symlinking external files (journal entries, research docs) into the shared folder without copying — single source of truth
  • Zero behavioral change without the flag — fully backward compatible

Motivation

When sharing files between KOI-net peers, users currently must copy files into ~/Documents/Notes/Shared/. This creates stale duplicates when the source file is updated elsewhere (e.g., a journal entry managed by another tool).

Symlink support lets users ln -s /path/to/source.md ~/Documents/Notes/Shared/source.md and have vault sync read through the symlink to the target content. The sync protocol uses the symlink name as the relative path, not the target path — so peers see the file at the expected location.

Safety guards

Guard Purpose
Disabled by default Zero change for existing deployments
Path.resolve(strict=True) Catches circular and broken symlinks (raises OSError)
Blocked path prefixes Prevents resolving into /etc, /root, /proc, /sys
Outbound-only Incoming files never overwrite existing symlinks (apply-side guard preserved)
rel_path from symlink name Peer sees the link name, not the host filesystem structure

Changes

api/vault_sync.py (1 file, +61 -8):

  • New constant: FOLLOW_SYMLINKS from VAULT_SYNC_FOLLOW_SYMLINKS env var
  • New function: _resolve_if_symlink(path, vault_root) — safe resolution with guards
  • Reconcile scan (~line 711): follows symlinks for hash comparison
  • Main scan loop (~line 918): follows symlinks for file discovery, stat, read, hash
  • Apply guard (~line 1125): comment added, behavior unchanged — incoming files never clobber symlinks

Usage

# In config/personal.env (or export before starting the API):
VAULT_SYNC_FOLLOW_SYMLINKS=true

# Then symlink any file into the shared folder:
ln -s ~/.claude/local/journal/legion/2026/03/16/entry.md ~/Documents/Notes/Shared/entry.md

Test plan

  • Existing vault sync tests pass (all 44 — requires darrenzal DB role for full suite)
  • Module imports cleanly with FOLLOW_SYMLINKS=false (default)
  • Module imports cleanly with VAULT_SYNC_FOLLOW_SYMLINKS=true
  • Symlinked .md file in Shared/ is picked up by scan when flag is enabled
  • Broken symlink is skipped (no crash)
  • Circular symlink is skipped (no crash)
  • Symlink to /etc/passwd is blocked
  • Incoming file does not overwrite existing symlink at same path
  • Without flag, symlinks are skipped (backward compat)

🤖 Generated with Claude Code

LinuxIsCool and others added 2 commits March 16, 2026 21:37
…SYMLINKS

Adds a new env flag VAULT_SYNC_FOLLOW_SYMLINKS (default: false) that enables
vault sync to follow symlinks in the Shared/ folder to their target files.

This allows users to symlink external files (e.g., journal entries, research
docs) into the shared folder without copying, keeping a single source of truth.

Safety guards:
- Disabled by default — zero behavioral change without the flag
- Uses Path.resolve(strict=True) to catch circular/broken symlinks
- Blocks resolved paths under /etc, /root, /proc, /sys
- Only affects outbound scanning — incoming files never overwrite symlinks
- Relative path in the sync protocol uses the symlink name, not the target

Three scan sites updated:
- reconcile scan (line ~711): reconcile hash comparison
- main scan loop (line ~918): file discovery + read + hash
- apply guard (line ~1125): comment-only, behavior preserved

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ly_forget guard

Two critical fixes from code review:

1. Replace blocklist with allowlist for symlink target validation.
   Previously blocked only /etc, /root, /proc, /sys — insufficient.
   Now requires resolved target to be under vault_root, $HOME, or
   explicitly configured VAULT_SYNC_SYMLINK_ALLOWED_ROOTS (colon-separated).

2. Add symlink guard to _apply_forget(). Previously, incoming FORGET
   events could delete user-managed symlinks via Path.unlink(). Now
   skips symlinks with a warning log, consistent with _apply_new_or_update.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant