claude-remote-cli 3.9.0 → 3.9.2

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.
@@ -26,8 +26,7 @@ export async function verifyPin(pin, hash) {
26
26
  return false;
27
27
  }
28
28
  }
29
- // Legacy bcrypt hashes: require PIN reset
30
- console.warn('[auth] Legacy bcrypt PIN hash detected. Delete pinHash from config and restart to set a new PIN.');
29
+ // Legacy bcrypt hashes are migrated at startup; if one reaches here, reject it
31
30
  return false;
32
31
  }
33
32
  export function isRateLimited(ip) {
@@ -56,6 +55,9 @@ export function clearRateLimit(ip) {
56
55
  export function generateCookieToken() {
57
56
  return crypto.randomBytes(32).toString('hex');
58
57
  }
58
+ export function isLegacyHash(hash) {
59
+ return !!hash && !hash.startsWith('scrypt:');
60
+ }
59
61
  export function _resetForTesting() {
60
62
  attemptMap.clear();
61
63
  }
@@ -1,3 +1,4 @@
1
+ import crypto from 'node:crypto';
1
2
  import fs from 'node:fs';
2
3
  import http from 'node:http';
3
4
  import os from 'node:os';
@@ -160,8 +161,21 @@ async function main() {
160
161
  catch (err) {
161
162
  console.warn('Analytics disabled: failed to initialize:', err instanceof Error ? err.message : err);
162
163
  }
164
+ if (config.pinHash && auth.isLegacyHash(config.pinHash)) {
165
+ console.log('Migrating legacy PIN hash to scrypt. You will need to set a new PIN.');
166
+ delete config.pinHash;
167
+ saveConfig(CONFIG_PATH, config);
168
+ }
163
169
  if (!config.pinHash) {
164
- const pin = await promptPin('Set up a PIN for claude-remote-cli:');
170
+ let pin;
171
+ if (process.stdin.isTTY) {
172
+ pin = await promptPin('Set up a PIN for claude-remote-cli:');
173
+ }
174
+ else {
175
+ pin = crypto.randomInt(100000, 999999).toString();
176
+ console.log(`No interactive terminal detected. Generated PIN: ${pin}`);
177
+ console.log('Change it by deleting pinHash from your config file and restarting interactively.');
178
+ }
165
179
  config.pinHash = await auth.hashPin(pin);
166
180
  saveConfig(CONFIG_PATH, config);
167
181
  console.log('PIN set successfully.');
@@ -1,6 +1,6 @@
1
1
  import { test } from 'node:test';
2
2
  import assert from 'node:assert';
3
- import { hashPin, verifyPin, isRateLimited, recordFailedAttempt, generateCookieToken, _resetForTesting, } from '../server/auth.js';
3
+ import { hashPin, verifyPin, isLegacyHash, isRateLimited, recordFailedAttempt, generateCookieToken, _resetForTesting, } from '../server/auth.js';
4
4
  test('hashPin returns scrypt hash with expected format', async () => {
5
5
  _resetForTesting();
6
6
  const hash = await hashPin('1234');
@@ -77,6 +77,17 @@ test('hashPin produces unique salts', async () => {
77
77
  assert.strictEqual(await verifyPin('1234', hash1), true);
78
78
  assert.strictEqual(await verifyPin('1234', hash2), true);
79
79
  });
80
+ test('isLegacyHash returns true for bcrypt hashes', () => {
81
+ assert.strictEqual(isLegacyHash('$2b$10$abcdefghijklmnopqrstuuABCDEFGHIJKLMNOPQRSTUVWXYZ012'), true);
82
+ assert.strictEqual(isLegacyHash('$2a$10$someotherbcrypthashvalue'), true);
83
+ });
84
+ test('isLegacyHash returns false for scrypt hashes', async () => {
85
+ const hash = await hashPin('1234');
86
+ assert.strictEqual(isLegacyHash(hash), false);
87
+ });
88
+ test('isLegacyHash returns false for empty string', () => {
89
+ assert.strictEqual(isLegacyHash(''), false);
90
+ });
80
91
  test('generateCookieToken returns non-empty string', () => {
81
92
  _resetForTesting();
82
93
  const token = generateCookieToken();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-remote-cli",
3
- "version": "3.9.0",
3
+ "version": "3.9.2",
4
4
  "description": "Remote web interface for Claude Code CLI sessions",
5
5
  "type": "module",
6
6
  "main": "dist/server/index.js",