Skip to content

Latest commit

 

History

History
88 lines (59 loc) · 5.92 KB

File metadata and controls

88 lines (59 loc) · 5.92 KB

Storage Backends — Security Model

Three concerns drive the storage abstraction's security design:

  1. Credentials must never live on disk in config.
  2. Path traversal must be impossible — agents writing through the adapter cannot escape the configured root.
  3. Concurrent writes must not corrupt files.

1. Credentials are env-var-only

.aiwg/storage.config is checked into git on most projects. It would be wrong to put secret or apiKey there — even one accidental commit pushes the credential into history.

The schema (https://aiwg.io/schemas/storage.config.v1.json) actively rejects credential-named properties at every nesting depth. The forbidden property names are:

token, password, secret, apiKey, api_key,
accessKey, accessKeyId, secretAccessKey

Loading a storage.config containing any of these throws an error pointing at the offending property path. This catches both the obvious case ({ "backends": { "memory": { "token": "…" } } }) and the sneaky case ({ "backends": { "memory": { "extras": [{ "apiKey": "…" }] } } }).

aiwg doctor runs the same recursive walk on the loaded config as defense-in-depth — schema validation alone can be bypassed by hand-rolled config edits.

Where credentials do come from

Each backend documents its env-var (and optional OS-keychain) requirements:

Backend Env var(s)
fs none
obsidian none (file-system access governed by OS permissions)
logseq LOGSEQ_API_TOKEN (only when useApi: true)
notion NOTION_API_TOKEN (planned, #959)
anythingllm ANYTHINGLLM_API_KEY (planned, #960)
fortemi governed by AIWG MCP server config, not by storage.config; legacy storage only, not Fortemi Core index/search
s3 AWS default credential chain (planned, #962)
webdav AIWG_WEBDAV_USER + AIWG_WEBDAV_PASSWORD or AIWG_WEBDAV_TOKEN (planned, #963)

aiwg storage list-backends reports each backend's status. aiwg doctor does a reachability probe and reports clearly when a required env var is unset.

2. Path traversal rejection

Every adapter validates subsystem-relative paths at the boundary. The same five rules apply across fs, obsidian, and logseq:

Rejected pattern Example Why
.. segments ../etc/passwd Escapes subsystem root
Leading / /etc/passwd Absolute path
Leading ~ ~/secrets.md Home expansion
Backslashes a\\b.md Windows separator (POSIX bug)
Empty "" Ambiguous

Additionally, file-shaped backends refuse to touch the host application's config directory:

  • obsidian rejects any path resolving into <vault>/.obsidian/ — for read, write, delete, AND list (skipped during walk).
  • logseq rejects any path resolving into <graph>/logseq/.

These rejections are tested per-adapter; see test/unit/storage/{fs,obsidian,logseq}.test.ts.

3. Atomic writes

Three places use atomic write-then-rename (or O_APPEND) semantics:

  • fs adapter append() — uses Node's fs.appendFile which opens with O_APPEND. The kernel guarantees atomicity for writes ≤ PIPE_BUF (4096 bytes on Linux), which means concurrent appenders interleave at line granularity rather than racing read-then-write. (#976)
  • aiwg activity-log append — prefers adapter.append when available; falls back to read-then-write only on backends that don't expose append.
  • sandbox-registry identity store — writes to <path>.tmp.<pid> first, then renames onto the live path. SIGINT during save can no longer leave a half-written JSON file. (#969)

Async backends (Notion, AnythingLLM, Fortemi) have their own concurrency models and don't expose append(). The read-then-write fallback is harmless on them since they'd serialize requests at the API layer anyway.

Defense in depth — aiwg doctor

aiwg doctor runs four checks on .aiwg/storage.config:

  1. Schema validation — JSON Schema v1 conformance.
  2. Credential walk — recursively rejects any property named in the forbidden list above.
  3. Reachability probe — best-effort ping per backend (e.g., obsidian --version, GET /api/v1/system for AnythingLLM, vault existence for obsidian).
  4. Roots existence — checks each roots.<subsystem> path actually exists for fs backends.

Failures print the property path, the issue, and a remediation hint. Run aiwg doctor after every storage.config edit and at the start of long sessions.

What this does not protect against

  • An attacker with write access to your storage.config can redirect AIWG writes anywhere on the filesystem (within the configured root rules). Treat storage.config like any other build-config file — review changes in PRs.
  • A misconfigured external backend (wrong vault path, wrong workspace ID) writes to the wrong place. AIWG can't validate semantic correctness — only structural. Run aiwg storage test <subsystem> after any config change.
  • Backend-side authentication misconfiguration (e.g., an Obsidian sync client also writing to the same vault folder) is outside AIWG's scope.

Reporting issues

Security concerns about the storage system: file an issue at https://git.integrolabs.net/roctinam/aiwg/issues with the security label, or follow the project's responsible-disclosure process.