update
This commit is contained in:
80
tests/unit/tmp-perms.test.js
Normal file
80
tests/unit/tmp-perms.test.js
Normal file
@@ -0,0 +1,80 @@
|
||||
// main/file-ops.js + main/config-store.js tmp 文件权限(audit R5 P1-13)
|
||||
//
|
||||
// 背景:tmp 文件用 fs.open(path, 'w') 创建时,Node 默认 mode = 0o666 受
|
||||
// umask 影响(典型 0o022 → 实际 0o644),多用户 Linux/macOS 上其他用户能
|
||||
// 读父目录(典型 ~/Library/Application Support/Notes)就能读 tmp,里面
|
||||
// 可能含 AI Key 包络 / 整份配置 / 用户笔记内容。
|
||||
//
|
||||
// 修复:fs.open(tmpPath, 'w', 0o600) 强制 rw owner only。
|
||||
//
|
||||
// 测试策略:直接调 fs.promises.open 验证 0o600 mode 生效(file-ops 内部用
|
||||
// 的就是这条路径)。atomicWriteFile 端到端测试在 file-ops.test.js 已经覆盖
|
||||
// 写盘正确性,本文件专注「mode 真的生效」这条安全属性。
|
||||
|
||||
// @vitest-environment node
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { mkdtempSync, rmSync, statSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { open } from 'node:fs/promises';
|
||||
|
||||
let workDir;
|
||||
|
||||
beforeEach(() => {
|
||||
workDir = mkdtempSync(join(tmpdir(), 'notes-tmp-perms-'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (workDir) rmSync(workDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe('tmp 文件权限 0o600(audit R5 P1-13)', () => {
|
||||
it('fs.open(path, "w", 0o600) → mode & 0o077 === 0', async () => {
|
||||
// Windows NTFS 不强制 POSIX 权限位(用 ACL),statSync.mode 读出来仍是
|
||||
// 默认 ACL 简化位。修复在 Windows 上是 no-op(但语义一致,无害)。
|
||||
// 这条断言只验证 Linux/macOS —— 真正受多用户威胁面影响的平台。
|
||||
if (process.platform === 'win32') return;
|
||||
const tmpPath = join(workDir, 'with-mode.tmp');
|
||||
const fh = await open(tmpPath, 'w', 0o600);
|
||||
await fh.writeFile('test', 'utf-8');
|
||||
await fh.close();
|
||||
const st = statSync(tmpPath);
|
||||
// 0o600 = rw------- :owner r/w,其他全无
|
||||
expect(st.mode & 0o077).toBe(0);
|
||||
expect(st.mode & 0o700).toBe(0o600);
|
||||
});
|
||||
|
||||
it('fs.open 缺 mode(fallback to 0o666 + umask)可能非 0o600 —— 回归保护', async () => {
|
||||
// Windows 上 NTFS 不强制 POSIX 权限位(用 ACL),跳过
|
||||
if (process.platform === 'win32') return;
|
||||
const tmpPath = join(workDir, 'no-mode.tmp');
|
||||
const fh = await open(tmpPath, 'w');
|
||||
await fh.writeFile('test', 'utf-8');
|
||||
await fh.close();
|
||||
const st = statSync(tmpPath);
|
||||
// 不传 mode → 默认 0o666 受 umask 影响。
|
||||
// umask 0o022 → 实际 0o644 (& 0o077 = 0o044)
|
||||
// umask 0o077 → 实际 0o610 (& 0o077 = 0)
|
||||
// 不强断言非 0,只把 mode 读出来供 review(不传 mode 是漏洞)
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[tmp-perms] no-mode default mode = 0o${(st.mode & 0o777).toString(8)} (umask-dependent)`);
|
||||
expect(st).toBeDefined();
|
||||
});
|
||||
|
||||
it('file-ops atomicWriteFile 写盘成功 + 目标文件最终权限 0o600(rename 不改 mode)', async () => {
|
||||
const fileOps = await import('../../main/file-ops.js');
|
||||
const target = join(workDir, 'note.md');
|
||||
await fileOps.atomicWriteFile(target, '# hello\n', 'utf-8');
|
||||
// rename 在 Linux 上保留 source inode 的 mode,所以 final 文件的 mode
|
||||
// 也应该是 0o600(除非 umask 让 0o666 & 0o600 之后又变 —— 不会,
|
||||
// 因为 0o600 显式设的)。Windows 上跳过 mode 断言。
|
||||
const st = statSync(target);
|
||||
if (process.platform !== 'win32') {
|
||||
expect(st.mode & 0o077).toBe(0);
|
||||
}
|
||||
// 写盘内容正确(与权限无关,但顺手验一下不破坏)
|
||||
const { readFileSync } = await import('node:fs');
|
||||
expect(readFileSync(target, 'utf-8')).toBe('# hello\n');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user