diff --git a/extensions/piagentui-server.ts b/extensions/piagentui-server.ts index 62a3944..a0f8a31 100644 --- a/extensions/piagentui-server.ts +++ b/extensions/piagentui-server.ts @@ -3,7 +3,7 @@ import { WebSocketServer, WebSocket } from 'ws' import * as http from 'node:http' import * as fs from 'node:fs' import * as path from 'node:path' -import { randomUUID } from 'node:crypto' +import { randomBytes, randomUUID } from 'node:crypto' import { validateBearerToken, preventPathTraversal, @@ -721,7 +721,8 @@ export default function (pi: ExtensionAPI) { } function generateBearerToken(): string { - return Array.from({ length: 4 }, () => Math.random().toString(36).substring(2)).join('-') + // CSPRNG: the token is the only credential guarding the local API. + return randomBytes(24).toString('base64url') } const settingsStore = createPiAgentUiSettingsStore(resolvePiAgentUiSettingsPaths()) @@ -2065,7 +2066,12 @@ export default function (pi: ExtensionAPI) { if (!fs.existsSync(piAgentDir)) { fs.mkdirSync(piAgentDir, { recursive: true }) } - fs.writeFileSync(discoveryPath, JSON.stringify({ port: localPort, token: localToken }, null, 2), 'utf8') + // The file holds the API token: keep it readable by the owner only (mode is ignored on Windows). + fs.writeFileSync(discoveryPath, JSON.stringify({ port: localPort, token: localToken }, null, 2), { + encoding: 'utf8', + mode: 0o600, + }) + fs.chmodSync(discoveryPath, 0o600) console.log(`[PiAgentUi] Wrote connection info to: ${discoveryPath}`) } catch (err) { console.error('[PiAgentUi] Failed to write discovery file:', err)