@knpkv/herdr-fleet 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/sqlite.js ADDED
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Private on-disk SQLite databases for Herdr stores.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Refuse, never repair.** A state directory or database file that other users
7
+ * could write is refused, and left exactly as found. Repairing it would hide that
8
+ * someone could already have changed it.
9
+ * - **Owner-only once accepted.** The state directory ends `0700` and the database
10
+ * files `0600`; an existing directory is checked before it is restricted, so a
11
+ * writable one is refused rather than quietly fixed.
12
+ * - **Only the last path component is checked for a symlink.** Symlinked ancestors
13
+ * (macOS `/var`, a Nix-managed home) are normal and allowed; a database or state
14
+ * directory that is itself a link is refused.
15
+ * - **Two layers.** {@link preparePrivateDatabasePath} and
16
+ * {@link securePrivateDatabaseFiles} only touch the file system, so any SQLite
17
+ * client can use them; {@link openPrivateSqlite} adds the `node:sqlite` connection.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * const opened = yield* openPrivateSqlite(path, {
22
+ * initialize: (database) => database.exec("CREATE TABLE IF NOT EXISTS jobs (id TEXT PRIMARY KEY)")
23
+ * })
24
+ * // after each write, WAL may have created -wal/-shm:
25
+ * yield* opened.secureFiles
26
+ * ```
27
+ *
28
+ * @module
29
+ */
30
+ import { Effect, FileSystem, Path, Schema } from "effect";
31
+ import { DatabaseSync } from "node:sqlite";
32
+ /** One step of opening or securing a private database failed; `operation` names the step. */
33
+ export class PrivateDatabaseError extends Schema.TaggedError()("PrivateDatabaseError", {
34
+ operation: Schema.String,
35
+ cause: Schema.Defect()
36
+ }) {
37
+ }
38
+ const failWith = (operation) => (cause) => new PrivateDatabaseError({ operation, cause });
39
+ const groupOrOtherWritable = (paths, mode) => paths.sep === "/" && (mode & 0o022) !== 0;
40
+ const databaseFiles = (path) => [path, `${path}-wal`, `${path}-shm`];
41
+ // Refuses `path` when it is itself a symlink — including a dangling one — or when
42
+ // its real path is not its real parent joined with its name. A component that does
43
+ // not exist yet is checked through its parent.
44
+ const verifyPathIdentity = (path, fileSystem, paths, operation) => fileSystem.readLink(path).pipe(Effect.matchEffect({
45
+ onFailure: (cause) => cause.reason._tag === "NotFound" || cause.reason._tag === "Unknown"
46
+ ? Effect.void
47
+ : Effect.fail(new PrivateDatabaseError({ cause, operation: `${operation}.readlink` })),
48
+ onSuccess: (target) => Effect.fail(new PrivateDatabaseError({ cause: { path, target }, operation }))
49
+ }), Effect.andThen(fileSystem.exists(path).pipe(Effect.mapError(failWith(`${operation}.exists`)))), Effect.flatMap((exists) => {
50
+ const parent = paths.dirname(path);
51
+ if (!exists) {
52
+ return parent === path ? Effect.void : verifyPathIdentity(parent, fileSystem, paths, operation);
53
+ }
54
+ return Effect.all({
55
+ realPath: fileSystem.realPath(path).pipe(Effect.mapError(failWith(`${operation}.realpath`))),
56
+ realParentPath: fileSystem.realPath(parent).pipe(Effect.mapError(failWith(`${operation}.parent-realpath`)))
57
+ }).pipe(Effect.flatMap(({ realParentPath, realPath }) => {
58
+ const expectedPath = paths.join(realParentPath, paths.basename(path));
59
+ return realPath === expectedPath
60
+ ? Effect.void
61
+ : Effect.fail(new PrivateDatabaseError({ cause: { expectedPath, path, realPath }, operation }));
62
+ }));
63
+ }));
64
+ /**
65
+ * Refuse `path` if it is itself a symlink or does not resolve to its real parent
66
+ * joined with its name; a missing component is checked through its parent.
67
+ * Failures carry `operation` (sub-steps append `.readlink`, `.exists`, …).
68
+ */
69
+ export const verifyPrivatePathIdentity = Effect.fn("PrivateDatabase.verifyPathIdentity")(function* (path, operation) {
70
+ const fileSystem = yield* FileSystem.FileSystem;
71
+ const paths = yield* Path.Path;
72
+ yield* verifyPathIdentity(path, fileSystem, paths, operation);
73
+ });
74
+ /**
75
+ * Make `path`'s directory and any existing database files safe to open, or refuse.
76
+ *
77
+ * Refuses a directory or file that is a symlink, the wrong type, or group/other
78
+ * writable, without changing it. Otherwise restricts the directory (created if
79
+ * missing) to `0700` and existing db/-wal/-shm files to `0600`.
80
+ */
81
+ export const preparePrivateDatabasePath = Effect.fn("PrivateDatabase.prepare")(function* (path) {
82
+ const fileSystem = yield* FileSystem.FileSystem;
83
+ const paths = yield* Path.Path;
84
+ const directory = paths.dirname(path);
85
+ yield* fileSystem.makeDirectory(directory, { recursive: true, mode: 0o700 }).pipe(Effect.mapError(failWith("open.directory")));
86
+ const directoryInfo = yield* fileSystem.stat(directory).pipe(Effect.mapError(failWith("open.directory.stat")));
87
+ if (directoryInfo.type !== "Directory" || groupOrOtherWritable(paths, directoryInfo.mode)) {
88
+ return yield* new PrivateDatabaseError({
89
+ cause: { directory, mode: directoryInfo.mode, type: directoryInfo.type },
90
+ operation: "open.directory.unsafe"
91
+ });
92
+ }
93
+ yield* verifyPathIdentity(directory, fileSystem, paths, "open.directory.path-identity");
94
+ yield* fileSystem.chmod(directory, 0o700).pipe(Effect.mapError(failWith("open.secureDirectory")));
95
+ yield* Effect.forEach(databaseFiles(path), (file) => verifyPathIdentity(file, fileSystem, paths, "open.path-identity").pipe(Effect.andThen(fileSystem.exists(file).pipe(Effect.mapError(failWith("open.file.exists")))), Effect.flatMap((exists) => {
96
+ if (!exists)
97
+ return Effect.void;
98
+ return fileSystem.stat(file).pipe(Effect.mapError(failWith("open.file.stat")), Effect.flatMap((info) => info.type !== "File" || groupOrOtherWritable(paths, info.mode)
99
+ ? Effect.fail(new PrivateDatabaseError({
100
+ cause: { file, mode: info.mode, type: info.type },
101
+ operation: "open.file.unsafe"
102
+ }))
103
+ : fileSystem.chmod(file, 0o600).pipe(Effect.mapError(failWith("open.file.secure")))));
104
+ })), { discard: true });
105
+ });
106
+ /**
107
+ * Set the database file and its `-wal`/`-shm` siblings, whichever exist, to `0600`.
108
+ *
109
+ * SQLite creates the siblings lazily, so call this after writes as well as after
110
+ * opening. A sibling that has become a symlink is refused rather than followed.
111
+ */
112
+ export const securePrivateDatabaseFiles = Effect.fn("PrivateDatabase.secureFiles")(function* (path) {
113
+ const fileSystem = yield* FileSystem.FileSystem;
114
+ const paths = yield* Path.Path;
115
+ yield* Effect.forEach(databaseFiles(path), (file) => verifyPathIdentity(file, fileSystem, paths, "secure.path-identity").pipe(Effect.andThen(fileSystem.exists(file).pipe(Effect.mapError(failWith("secure.exists")))), Effect.flatMap((exists) => exists ? fileSystem.chmod(file, 0o600).pipe(Effect.mapError(failWith("secure.chmod"))) : Effect.void)), { discard: true });
116
+ });
117
+ /**
118
+ * Prepare `path` (see {@link preparePrivateDatabasePath}), open it in WAL mode,
119
+ * initialize the schema and secure the files. The caller owns the returned
120
+ * connection and must close it.
121
+ */
122
+ export const openPrivateSqlite = Effect.fn("PrivateDatabase.openSqlite")(function* (path, options) {
123
+ const fileSystem = yield* FileSystem.FileSystem;
124
+ const paths = yield* Path.Path;
125
+ yield* preparePrivateDatabasePath(path);
126
+ const database = yield* Effect.try({
127
+ try: () => {
128
+ const database = new DatabaseSync(path);
129
+ try {
130
+ if (options.busyTimeoutMillis !== undefined) {
131
+ database.exec(`PRAGMA busy_timeout = ${options.busyTimeoutMillis}`);
132
+ }
133
+ options.initialize(database);
134
+ database.exec("PRAGMA journal_mode = WAL");
135
+ return database;
136
+ }
137
+ catch (error) {
138
+ database.close();
139
+ throw error;
140
+ }
141
+ },
142
+ catch: failWith("open.database")
143
+ });
144
+ const secureFiles = securePrivateDatabaseFiles(path).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem), Effect.provideService(Path.Path, paths));
145
+ yield* secureFiles.pipe(Effect.onError(() => Effect.sync(() => database.close())));
146
+ return { database, secureFiles };
147
+ });
148
+ //# sourceMappingURL=sqlite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../src/sqlite.ts"],"names":[],"mappings":"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"}