Help us shape the future of Go! Take the 2026 Go Developer Survey

vault

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 17, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package vault provides vault scanning and configuration loading.

Index

Constants

View Source
const ConfigRelPath = ".vaultmind/config.yaml"

ConfigRelPath is the vault's type registry, relative to the vault root, in slash form. Its presence is what makes a directory a vault.

The marker used to be the .vaultmind/ directory alone, which is wrong in the one place it matters most: the model cache lives at ~/.vaultmind/models, so on any machine that has downloaded BGE-M3 weights a bare-directory test answers "yes" for the home directory. A registry is written by `init` (or by `index` when a vault is explicitly named); a cache never carries one.

Variables

View Source
var ErrEscapesVault = errors.New("path escapes vault")

ErrEscapesVault reports a path that resolves outside the vault root.

A sentinel rather than a typed error because the callers have incompatible contracts: plan and mutation must answer with their own `path_traversal` error codes, index and cmdutil return plain errors, and a shared error type would force one of them to leak the other's vocabulary. errors.Is is the whole interface.

Functions

func ResolveInside added in v0.6.0

func ResolveInside(vaultRoot, rel string) (string, error)

ResolveInside joins rel onto vaultRoot and returns the cleaned absolute path, or ErrEscapesVault if the result would land outside the vault.

This is the CONFINEMENT check, and it is deliberately not the symlink check (see SkipSymlink). They answer different questions and neither implies the other: `notes.md -> ~/.zshrc` passes this function — notes.md really is inside the vault — and writing to it still lands outside. Anything that opens a path built from vault content or vault config wants both.

What it catches is `..` traversal. `filepath.Join` re-roots an absolute second argument inside the first (Join("/v", "/etc/passwd") is "/v/etc/passwd"), so an absolute rel is contained rather than escaping — but it is also silently not the path the operator wrote, which is its own problem; reject those at the point they are configured, not here.

The vault root itself is allowed: some callers resolve "." to mean the root.

func SkipSymlink(root, path string, d fs.DirEntry) (rel string, skip bool)

SkipSymlink reports whether a walked directory entry must not be followed.

Every walk over vault content needs this, and each one that skipped it was a hole. filepath.WalkDir does not descend into directory symlinks, but it DOES hand back a file symlink named *.md, and the stdlib calls that take a path follow it:

  • os.ReadFile turns an untrusted vault into a READ primitive — `secrets.md -> ~/.ssh/id_rsa` was hashed, parsed, stored in FTS, embedded, and returned by `ask`.
  • os.WriteFile turns it into a WRITE primitive — `notes.md -> ~/.zshrc` was rewritten in place by the wikilink healer.

The rule is the LINK, not the destination: an entry is skipped whatever it points at, including a target inside the vault. Resolving first and confining after would need EvalSymlinks, whose answer can change between the check and the call, and would let one file be handled under two paths — the duplicate-id class that took three fixes to close.

This is NOT the confinement check. "Stays under the vault root" and "is not a link" are different predicates, and passing the first says nothing about the second: the mutator confines every path it writes and would still have overwritten ~/.zshrc, because notes.md really is inside the vault. Keep them separate; merging them is how one of the two quietly disappears.

d.Type() comes from Lstat, so it describes the link itself rather than its target — no extra syscall, and no window between deciding and acting.

Callers must REPORT what they skip, on their result rather than in a log line: from the outside, a file passed over and a file that was never there look identical. That is why the vault-relative path comes back from this call rather than being each caller's job — four hand-rolled copies of the same filepath.Rel fallback is how they drift.

func ValidSegment added in v0.6.0

func ValidSegment(s string) bool

ValidSegment reports whether s is safe to use as a SINGLE path segment — letters, digits, underscore, dash, and nothing else.

For values that name a directory or file component rather than a path: a note's `type` from its own frontmatter, a section key from its markers. Those are attacker-controlled in any vault you did not write yourself, and they get joined into a path. ResolveInside would catch the resulting escape, but a whitelist is the better answer one level up: a type called "../../../etc" is not a legal type in the first place, whatever it resolves to, and rejecting it names the real problem instead of a path error.

Types

type Config

type Config struct {
	Vault  VaultConfig        `yaml:"vault"`
	Types  map[string]TypeDef `yaml:"types"`
	Git    GitPolicyConfig    `yaml:"git"`
	Index  IndexConfig        `yaml:"index"`
	Memory MemoryConfig       `yaml:"memory"`
	Schema SchemaConfig       `yaml:"schema"`
}

Config represents the full .vaultmind/config.yaml

func LoadConfig

func LoadConfig(vaultRoot string) (*Config, error)

LoadConfig reads .vaultmind/config.yaml from the vault root. Returns defaults if the config file doesn't exist.

type GitPolicyConfig

type GitPolicyConfig struct {
	Policy map[string]string `yaml:"policy"`
}

GitPolicyConfig holds git policy overrides.

type IndexConfig

type IndexConfig struct {
	DBPath string `yaml:"db_path"`
}

IndexConfig holds indexing settings.

type MemoryConfig

type MemoryConfig struct {
	AliasMinLength           int     `yaml:"alias_min_length"`
	TagOverlapThreshold      float64 `yaml:"tag_overlap_threshold"`
	ContextPackDefaultBudget int     `yaml:"context_pack_default_budget"`
}

MemoryConfig holds memory engine settings.

type ScanResult added in v0.6.0

type ScanResult struct {
	Files []ScannedFile

	// SkippedSymlinks holds vault-relative paths of *.md symlinks that were not
	// followed. Callers are expected to REPORT these, not drop them: from the
	// outside, a skipped note and a missing note look identical.
	SkippedSymlinks []string
}

ScanResult is what a walk found: the notes to index, and what was passed over for a reason the caller has to answer for.

A struct rather than extra return values because "skipped, and why" is a growing category — and a plain []ScannedFile let a whole class of skip go unmentioned, which is how a note vanishes from an index in silence.

func Scan

func Scan(vaultRoot string, excludes []string) (ScanResult, error)

Scan walks the vault directory and returns all .md files, excluding directories that match any of the exclude patterns.

Symlinks are never followed — see the guard below for why.

type ScannedFile

type ScannedFile struct {
	RelPath string    // Vault-relative path (e.g., "concepts/act-r.md")
	AbsPath string    // Absolute filesystem path
	ModTime time.Time // Last modification time
}

ScannedFile represents a discovered .md file in the vault.

type SchemaConfig

type SchemaConfig struct {
	Aliases map[string][]string `yaml:"aliases"`
}

SchemaConfig holds per-vault schema settings beyond the type registry. Aliases let migrating users keep their existing frontmatter field names (e.g. `last_updated`) while vaultmind validates against canonical names (e.g. `updated`). The map is canonical → list of aliases. Aliasing is non-destructive: vaultmind never rewrites frontmatter to normalize field names; the alias and the canonical are equivalent at validation only.

type TypeDef

type TypeDef struct {
	Required []string `yaml:"required" json:"required"`
	Optional []string `yaml:"optional" json:"optional"`
	Statuses []string `yaml:"statuses" json:"statuses"`
	Template string   `yaml:"template" json:"template"`
}

TypeDef defines a note type in the registry.

type VaultConfig

type VaultConfig struct {
	Exclude []string `yaml:"exclude"`
}

VaultConfig holds vault scanning settings.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL