W
File size: 3,012 Bytes
2b64d42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import { afterEach, describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { config } from '../src/config.js';
import { configureBindHost } from '../src/auth.js';
import { handleDashboardApi } from '../src/dashboard/api.js';
import { isLoopbackAddress } from '../src/dashboard/local-windsurf.js';

const originalDashboardPassword = config.dashboardPassword;
const originalApiKey = config.apiKey;

afterEach(() => {
  config.dashboardPassword = originalDashboardPassword;
  config.apiKey = originalApiKey;
  configureBindHost('127.0.0.1');
});

function fakeRes() {
  return {
    statusCode: 0,
    body: '',
    writeHead(status) { this.statusCode = status; },
    end(chunk) { this.body += chunk ? String(chunk) : ''; },
    json() { return this.body ? JSON.parse(this.body) : null; },
  };
}

describe('isLoopbackAddress (high-risk address parsing)', () => {
  it('accepts bracketed IPv6 and mapped IPv6 variants', () => {
    assert.equal(isLoopbackAddress('[::1]'), true);
    assert.equal(isLoopbackAddress('::ffff:127.0.0.1'), true);
    assert.equal(isLoopbackAddress('::ffff:7f00:1'), true);
  });

  it('rejects URL-encoded public-looking loopback candidates', () => {
    assert.equal(isLoopbackAddress('%3a%3a1'), false);
    assert.equal(isLoopbackAddress('7f%00:1'), false);
  });
});

describe('GET /accounts/import-local (security posture)', () => {
  it('rejects public binds even when dashboard secret is provided', async () => {
    config.dashboardPassword = 'dash-secret';
    configureBindHost('0.0.0.0');

    const res = fakeRes();
    await handleDashboardApi(
      'GET',
      '/accounts/import-local',
      {},
      { headers: { 'x-dashboard-password': 'dash-secret' }, socket: { remoteAddress: '127.0.0.1' } },
      res
    );

    assert.equal(res.statusCode, 403);
    assert.equal(res.json().error, 'ERR_LOCAL_IMPORT_NOT_AVAILABLE_PUBLIC_BIND');
  });

  it('rejects remote callers that are not loopback on local binds', async () => {
    config.dashboardPassword = '';
    config.apiKey = '';
    configureBindHost('127.0.0.1');

    const res = fakeRes();
    await handleDashboardApi(
      'GET',
      '/accounts/import-local',
      {},
      { headers: {}, socket: { remoteAddress: '192.168.1.10' } },
      res
    );

    assert.equal(res.statusCode, 403);
    assert.equal(res.json().error, 'ERR_LOCAL_IMPORT_LOOPBACK_ONLY');
  });

  it('does not leak absolute paths in discovery metadata', async () => {
    config.dashboardPassword = '';
    config.apiKey = '';
    configureBindHost('127.0.0.1');

    const res = fakeRes();
    await handleDashboardApi(
      'GET',
      '/accounts/import-local',
      {},
      { headers: {}, socket: { remoteAddress: '127.0.0.1' } },
      res
    );

    const r = res.json();
    assert.equal(Array.isArray(r.sources), true);
    for (const s of r.sources) {
      assert.equal(typeof s.path, 'string');
      assert.equal(/[/\\]/.test(s.path), false, `path leaked: ${s.path}`);
    }
  });
});