From 9c56b9fe5b7802b5470332773c92f66c12fd3ed1 Mon Sep 17 00:00:00 2001 From: levineam Date: Fri, 14 Aug 2026 14:34:17 -0400 Subject: [PATCH] fix(secondbrain): validate journal dates before mutation --- .../adapters/obsidian/src/vault-storage-adapter.js | 5 +++++ .../tests/vault-storage-adapter-journal.test.js | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/modules/jarvos-secondbrain/adapters/obsidian/src/vault-storage-adapter.js b/modules/jarvos-secondbrain/adapters/obsidian/src/vault-storage-adapter.js index e448d684..d9fb34a5 100644 --- a/modules/jarvos-secondbrain/adapters/obsidian/src/vault-storage-adapter.js +++ b/modules/jarvos-secondbrain/adapters/obsidian/src/vault-storage-adapter.js @@ -22,6 +22,10 @@ const FLAGGED_HEADING = '## 📌 Flagged'; function todayDate() { return new Intl.DateTimeFormat('en-CA', { timeZone: getTimeZone(), year: 'numeric', month: '2-digit', day: '2-digit' }).format(new Date()); } +function validateJournalDate(date) { + if (typeof date !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(date)) throw new Error('Journal date must be YYYY-MM-DD'); + return date; +} function relativeToVault(vaultRoot, absolutePath) { const relative = path.relative(path.resolve(vaultRoot), path.resolve(absolutePath)).split(path.sep).join('/'); if (!relative || relative.startsWith('../') || path.isAbsolute(relative)) throw new Error('Journal path is outside the configured vault'); @@ -59,6 +63,7 @@ function createVaultStorageAdapter({ mutationService, vaultRoot = getVaultDir(), } return Object.freeze({ ensureJournal({ date = todayDate(), intentId } = {}) { + validateJournalDate(date); const journalPath = path.join(journalDir, `${date}.md`); const existed = fs.existsSync(journalPath); const vaultRelativePath = relativeToVault(vaultRoot, journalPath); diff --git a/modules/jarvos-secondbrain/tests/vault-storage-adapter-journal.test.js b/modules/jarvos-secondbrain/tests/vault-storage-adapter-journal.test.js index bd3d1ae9..8bfe85d9 100644 --- a/modules/jarvos-secondbrain/tests/vault-storage-adapter-journal.test.js +++ b/modules/jarvos-secondbrain/tests/vault-storage-adapter-journal.test.js @@ -87,6 +87,20 @@ test('missing journal creates before its acknowledged section transform', () => }); }); +test('journal dates reject path traversal before performing a mutation', () => { + withVault(({ root, journalDir }) => { + const service = fakeService(root); + const adapter = createVaultStorageAdapter({ mutationService: service, vaultRoot: root, journalDir }); + + assert.throws( + () => adapter.appendLineToJournalSection({ date: '../../outside/escape', heading: '## 💡 Ideas', line: '- New idea' }), + /Journal date must be YYYY-MM-DD/, + ); + assert.deepEqual(service.operations, []); + assert.equal(fs.existsSync(path.join(root, 'outside', 'escape.md')), false); + }); +}); + test('unavailable service never falls back to a raw journal write', () => { withVault(({ root, journalDir }) => { const service = fakeService(root, { unavailable: true });