mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-10 01:39:25 +00:00
fix(server): skip Unix-only permission check on Windows for server token (#1135)
This commit is contained in:
parent
b0b2aee8c5
commit
bf51fb7a10
4 changed files with 26 additions and 8 deletions
6
.changeset/fix-windows-token-permission.md
Normal file
6
.changeset/fix-windows-token-permission.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
"@moonshot-ai/server": patch
|
||||
"@moonshot-ai/kimi-code": patch
|
||||
---
|
||||
|
||||
Fix the local server failing to start on Windows after the first run because the persistent token file's synthesized mode was rejected as too permissive.
|
||||
|
|
@ -54,7 +54,11 @@ export async function writePrivateFile(
|
|||
|
||||
export async function readPrivateFile(filePath: string): Promise<Buffer> {
|
||||
const info = await stat(filePath);
|
||||
if ((info.mode & 0o077) !== 0) {
|
||||
// Windows does not have Unix-style permission bits; libuv synthesises the
|
||||
// mode from the read-only attribute, so a private writable file is reported
|
||||
// as 0o666 and a read-only one as 0o444. The ACL-based security model is
|
||||
// different, so this check only makes sense on POSIX systems.
|
||||
if (process.platform !== 'win32' && (info.mode & 0o077) !== 0) {
|
||||
throw new PrivateFileTooPermissiveError(filePath, info.mode & 0o777);
|
||||
}
|
||||
return readFile(filePath);
|
||||
|
|
|
|||
|
|
@ -48,7 +48,10 @@ export async function createTokenStore(homeDir: string): Promise<TokenStore> {
|
|||
}
|
||||
// Changed: re-read, but refuse a too-permissive file and never let an
|
||||
// empty/partial read clobber the last good token.
|
||||
if ((st.mode & 0o077) !== 0) {
|
||||
// Skip the check on Windows: fs.stat mode is synthesised from the
|
||||
// read-only attribute and does not reflect real ACLs, so it would always
|
||||
// appear too permissive and prevent legitimate token reloads.
|
||||
if (process.platform !== 'win32' && (st.mode & 0o077) !== 0) {
|
||||
return cache.token;
|
||||
}
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -44,14 +44,14 @@ describe('privateFiles', () => {
|
|||
expect(statSync(join(tmpDir, 'nested', 'dir')).mode & 0o777).toBe(0o700);
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === 'win32')('round-trips string content through readPrivateFile', async () => {
|
||||
it('round-trips string content through readPrivateFile', async () => {
|
||||
const p = join(tmpDir, 'secret');
|
||||
await writePrivateFile(p, 's3cr3t-value');
|
||||
const buf = await readPrivateFile(p);
|
||||
expect(buf.toString('utf8')).toBe('s3cr3t-value');
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === 'win32')('round-trips Buffer content through readPrivateFile', async () => {
|
||||
it('round-trips Buffer content through readPrivateFile', async () => {
|
||||
const p = join(tmpDir, 'bin');
|
||||
const data = Buffer.from([0, 1, 2, 254, 255]);
|
||||
await writePrivateFile(p, data);
|
||||
|
|
@ -59,7 +59,7 @@ describe('privateFiles', () => {
|
|||
expect(buf.equals(data)).toBe(true);
|
||||
});
|
||||
|
||||
it('readPrivateFile throws on a 0644 file', async () => {
|
||||
it.skipIf(process.platform === 'win32')('readPrivateFile throws on a 0644 file', async () => {
|
||||
const p = join(tmpDir, 'leaky');
|
||||
writeFileSync(p, 'x', { mode: 0o644 });
|
||||
chmodSync(p, 0o644);
|
||||
|
|
@ -84,7 +84,7 @@ describe('tokenStore', () => {
|
|||
await b.dispose();
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === 'win32')('reuses the same persistent token across stores in one home dir', async () => {
|
||||
it('reuses the same persistent token across stores in one home dir', async () => {
|
||||
const home = join(tmpDir, 'home');
|
||||
const a = await createTokenStore(home);
|
||||
const token = a.getToken();
|
||||
|
|
@ -123,7 +123,7 @@ describe('tokenStore', () => {
|
|||
expect(existsSync(store.tokenPath)).toBe(true);
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === 'win32')('re-reads the token after the file is rewritten (live rotation)', async () => {
|
||||
it('re-reads the token after the file is rewritten (live rotation)', async () => {
|
||||
const home = join(tmpDir, 'home');
|
||||
const store = await createTokenStore(home);
|
||||
const original = store.getToken();
|
||||
|
|
@ -142,11 +142,16 @@ describe('tokenStore', () => {
|
|||
});
|
||||
|
||||
describe('persistentToken', () => {
|
||||
it.skipIf(process.platform === 'win32')('loadOrCreateServerToken generates once and reuses thereafter', async () => {
|
||||
it('loadOrCreateServerToken generates once and reuses thereafter', async () => {
|
||||
const home = join(tmpDir, 'home');
|
||||
const a = await loadOrCreateServerToken(home);
|
||||
const b = await loadOrCreateServerToken(home);
|
||||
expect(a).toBe(b);
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === 'win32')('writes server.token with mode 0600', async () => {
|
||||
const home = join(tmpDir, 'home');
|
||||
await loadOrCreateServerToken(home);
|
||||
expect(statSync(join(home, 'server.token')).mode & 0o777).toBe(0o600);
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue