docs(session-persistence): tighten SQLite permission prose

This commit is contained in:
Tianyi Cui
2026-07-19 11:37:41 +08:00
parent 7ba5b95d8e
commit 85fc58943e
4 changed files with 20 additions and 15 deletions

View File

@@ -656,10 +656,11 @@ Requires: `sessions`
export interface Config {
/**
* Filesystem path to the SQLite database file. The special value `:memory:`
* opens an in-process database (tests). Missing directories and the database
* are created with owner-only permissions; existing path modes are preserved.
* Parent directories writable by another principal are outside the backend's
* database-integrity boundary.
* opens an in-process database (tests). On filesystems with POSIX modes,
* missing directories and databases are created owner-only; existing path
* modes are preserved. Filesystem setup errors other than an existing database
* fail initialization. The backend does not protect integrity when another
* principal can replace the database entry in its parent directory.
*/
path: string
/**
@@ -682,7 +683,7 @@ export interface Config {
export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist'
```
Source: [`packages/session-persistence/session-persistence-sqlite/src/index.ts:53`](../packages/session-persistence/session-persistence-sqlite/src/index.ts)
Source: [`packages/session-persistence/session-persistence-sqlite/src/index.ts:54`](../packages/session-persistence/session-persistence-sqlite/src/index.ts)
## `@deepseek-ai/dsh-session-query`

View File

@@ -10,7 +10,9 @@ A SQLite durable session-persistence backend — a second `SessionPersistence` i
Each `SessionEvent` maps 1:1 onto a row in an `events` table `(session_id, seq, type, time, data, source_event_seqs, surface_op)``data` is the event payload as JSON text, so the row shape is the event verbatim (including `assistant/chunk`, keeping `seq` contiguous). The two `TEXT` columns `source_event_seqs` and `surface_op` are nullable; they store the event's optional surface-metadata fields (see [session surface](../../../docs/rfc/implemented/architecture/2026-06-18-session-surface.md)). Out-of-log metadata (`SessionHeader`) lives in a `sessions` row. A `sessions` row is written only by the first `append` — its existence is the lazy-materialization signal (`list` reports exactly the sessions that have a row), so no separate column is needed.
The repository's Node range supports unflagged `node:sqlite`. The database enables foreign keys and uses the configured journal mode (`wal` by default; use a rollback mode where WAL shared-memory files are unsuitable). On filesystems with POSIX modes, missing directories are created as `0700` and a missing database is exclusively created as `0600` before SQLite opens it, causing new WAL sidecars to inherit owner-only access. Existing directories, database files, and sidecars keep their modes; ordinary filesystem access errors still fail initialization. This default prevents incidental exposure through the process umask; it does not protect database integrity when another principal can modify entries in an existing parent directory. `PRAGMA user_version` stores the table-layout version; databases with any other version are rejected because this unreleased format has no migrations.
The repository's Node range supports unflagged `node:sqlite`. The database enables foreign keys and uses the configured journal mode (`wal` by default; use a rollback mode where WAL shared-memory files are unsuitable). `PRAGMA user_version` stores the table-layout version; databases with any other version are rejected because this unreleased format has no migrations.
On filesystems with POSIX modes, the backend creates missing directories as `0700` and exclusively creates a missing database as `0600` before SQLite opens it. New WAL sidecars receive the database's owner-only mode. Existing directories, database files, and sidecars keep their modes; filesystem setup errors other than an existing database fail initialization. These defaults prevent incidental exposure through the process umask, but do not protect database integrity when another principal can replace the database entry in its parent directory.
## Contract semantics over rows

View File

@@ -36,9 +36,10 @@ function surfaceBindings(event: SessionEvent): [string | null, string | null] {
}
/**
* Create a missing database owner-only while preserving an existing file's
* mode. `DatabaseSync` cannot adopt this handle, so a parent directory writable
* by another principal is outside the backend's database-integrity boundary.
* Exclusively create a missing database file with owner-only permissions.
* Existing files retain their modes, and errors other than `EEXIST` propagate.
* `DatabaseSync` reopens by path, so this does not protect integrity when
* another principal can replace the database entry in its parent directory.
*/
async function createDatabaseFile(path: string): Promise<void> {
try {
@@ -53,10 +54,11 @@ async function createDatabaseFile(path: string): Promise<void> {
export interface Config {
/**
* Filesystem path to the SQLite database file. The special value `:memory:`
* opens an in-process database (tests). Missing directories and the database
* are created with owner-only permissions; existing path modes are preserved.
* Parent directories writable by another principal are outside the backend's
* database-integrity boundary.
* opens an in-process database (tests). On filesystems with POSIX modes,
* missing directories and databases are created owner-only; existing path
* modes are preserved. Filesystem setup errors other than an existing database
* fail initialization. The backend does not protect integrity when another
* principal can replace the database entry in its parent directory.
*/
path: string
/**

View File

@@ -390,7 +390,7 @@ describe('SessionPersistenceSqlite: durability and crash semantics', () => {
})
describe('SessionPersistenceSqlite: edge cases', () => {
it('creates a new database and WAL sidecars owner-only without changing an existing directory mode', async () => {
it('creates a new database and WAL sidecars with owner-only modes without changing its parent mode', async () => {
if (process.platform === 'win32') return
const path = await freshDbPath()
const dir = dirname(path)
@@ -421,7 +421,7 @@ describe('SessionPersistenceSqlite: edge cases', () => {
await fiber.dispose()
})
it('surfaces database pre-creation errors independently of process privileges', async () => {
it('surfaces an invalid database path during pre-creation', async () => {
const path = await freshDbPath()
const b = await backend(`${path}\0`)