@goodandready/dsh-kanban 0.2.15 → 0.2.16
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/README.ru.md +1 -1
- package/lib/store.js +49 -3
- package/package.json +1 -1
package/README.ru.md
CHANGED
package/lib/store.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { DatabaseSync } from 'node:sqlite'
|
|
15
15
|
import { randomUUID } from 'node:crypto'
|
|
16
|
-
import { mkdirSync, copyFileSync, readdirSync, unlinkSync, statSync } from 'node:fs'
|
|
16
|
+
import { mkdirSync, copyFileSync, readdirSync, unlinkSync, statSync, chmodSync, existsSync } from 'node:fs'
|
|
17
17
|
import { join } from 'node:path'
|
|
18
18
|
import { keyBetween } from './order.js'
|
|
19
19
|
import { DEFAULT_BOARDS, normalizeKind } from './config.js'
|
|
@@ -159,13 +159,48 @@ function rowToTask(row) {
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Обеспечить безопасные права доступа к каталогу хранилища и файлам базы.
|
|
164
|
+
* Каталог и подкаталог бэкапов: 0700 (rwx------)
|
|
165
|
+
* Файлы базы, WAL, SHM и файлы бэкапов: 0600 (rw-------)
|
|
166
|
+
*
|
|
167
|
+
* @param {string} dir каталог хранилища
|
|
168
|
+
*/
|
|
169
|
+
export function secureStorePermissions(dir) {
|
|
170
|
+
try {
|
|
171
|
+
if (existsSync(dir)) {
|
|
172
|
+
try { chmodSync(dir, 0o700) } catch { /* best-effort chmod on dir */ }
|
|
173
|
+
const entries = readdirSync(dir, { withFileTypes: true })
|
|
174
|
+
for (const entry of entries) {
|
|
175
|
+
const fullPath = join(dir, entry.name)
|
|
176
|
+
if (entry.isFile()) {
|
|
177
|
+
try { chmodSync(fullPath, 0o600) } catch { /* best-effort chmod on file */ }
|
|
178
|
+
} else if (entry.isDirectory() && entry.name === 'backups') {
|
|
179
|
+
try { chmodSync(fullPath, 0o700) } catch { /* best-effort chmod on backups dir */ }
|
|
180
|
+
try {
|
|
181
|
+
const backupFiles = readdirSync(fullPath, { withFileTypes: true })
|
|
182
|
+
for (const bFile of backupFiles) {
|
|
183
|
+
if (bFile.isFile()) {
|
|
184
|
+
try { chmodSync(join(fullPath, bFile.name), 0o600) } catch { /* best-effort chmod on backup file */ }
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} catch { /* ignore readdir error on backups */ }
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
/* best-effort permission hardening */
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
162
196
|
/**
|
|
163
197
|
* Открыть хранилище задач.
|
|
164
198
|
* @param {{dir: string}} options каталог, в котором лежит файл базы
|
|
165
199
|
* @returns {object} интерфейс хранилища
|
|
166
200
|
*/
|
|
167
201
|
export function openStore({ dir }) {
|
|
168
|
-
mkdirSync(dir, { recursive: true })
|
|
202
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 })
|
|
203
|
+
secureStorePermissions(dir)
|
|
169
204
|
const db = new DatabaseSync(join(dir, 'kanban.db'))
|
|
170
205
|
db.exec('PRAGMA journal_mode = WAL')
|
|
171
206
|
db.exec('PRAGMA foreign_keys = ON')
|
|
@@ -226,6 +261,9 @@ export function openStore({ dir }) {
|
|
|
226
261
|
db.exec('PRAGMA synchronous = NORMAL')
|
|
227
262
|
try { db.exec('PRAGMA optimize') } catch { /* pragma optimize best-effort */ }
|
|
228
263
|
|
|
264
|
+
// Повторно обеспечиваем права на созданные/изменённые файлы базы, WAL и SHM
|
|
265
|
+
secureStorePermissions(dir)
|
|
266
|
+
|
|
229
267
|
const q = {
|
|
230
268
|
boards: db.prepare('SELECT * FROM boards ORDER BY position'),
|
|
231
269
|
insertTask: db.prepare(`INSERT INTO tasks
|
|
@@ -309,7 +347,8 @@ export function openStore({ dir }) {
|
|
|
309
347
|
|
|
310
348
|
backupDatabase() {
|
|
311
349
|
const backupsDir = join(dir, 'backups')
|
|
312
|
-
mkdirSync(backupsDir, { recursive: true })
|
|
350
|
+
mkdirSync(backupsDir, { recursive: true, mode: 0o700 })
|
|
351
|
+
try { chmodSync(backupsDir, 0o700) } catch { /* ignore */ }
|
|
313
352
|
const stamp = new Date().toISOString().replace(/[:.]/g, '-')
|
|
314
353
|
const backupName = `kanban-backup-${stamp}.db`
|
|
315
354
|
const dest = join(backupsDir, backupName)
|
|
@@ -317,6 +356,7 @@ export function openStore({ dir }) {
|
|
|
317
356
|
db.exec('PRAGMA wal_checkpoint(TRUNCATE)')
|
|
318
357
|
} catch { /* wal checkpoint may not be strictly required */ }
|
|
319
358
|
copyFileSync(join(dir, 'kanban.db'), dest)
|
|
359
|
+
try { chmodSync(dest, 0o600) } catch { /* ignore */ }
|
|
320
360
|
|
|
321
361
|
// Ротация: сохраняем только последние 10 бэкапов
|
|
322
362
|
try {
|
|
@@ -661,8 +701,14 @@ export function openStore({ dir }) {
|
|
|
661
701
|
}
|
|
662
702
|
},
|
|
663
703
|
|
|
704
|
+
securePermissions() {
|
|
705
|
+
secureStorePermissions(dir)
|
|
706
|
+
},
|
|
707
|
+
|
|
664
708
|
close() {
|
|
709
|
+
try { secureStorePermissions(dir) } catch { /* ignore */ }
|
|
665
710
|
db.close()
|
|
711
|
+
try { secureStorePermissions(dir) } catch { /* ignore */ }
|
|
666
712
|
},
|
|
667
713
|
}
|
|
668
714
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-kanban",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.16",
|
|
4
4
|
"description": "Visual Kanban board for DeepSeek Harness: Gitea and GitHub-backed tasks, workflow columns, and a dedicated agent session per task.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|