@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 CHANGED
@@ -174,7 +174,7 @@ dsh-kanban:
174
174
 
175
175
  ## 🧪 Тестирование и верификация
176
176
 
177
- Запуск полного набора из более чем 710 юнит- и интеграционных тестов:
177
+ Запуск полного набора из более чем 720 юнит- и интеграционных тестов:
178
178
 
179
179
  ```bash
180
180
  npm test
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.15",
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",