File size: 4,335 Bytes
4e23b01
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

import { afterAll, beforeAll, describe, expect, it } from 'vitest';

import { type RunningServer, startServer } from '../src/start';
import { TEST_HOST_IDENTITY } from './helpers/hostIdentity';

describe('server-v2 exposure hardening hooks', () => {
  let server: RunningServer | undefined;
  let home: string | undefined;

  beforeAll(async () => {
    home = await mkdtemp(join(tmpdir(), 'kimi-server-v2-exposure-'));
    server = await startServer({ hostIdentity: TEST_HOST_IDENTITY, host: '127.0.0.1', port: 0, homeDir: home, logLevel: 'silent' });
  });

  afterAll(async () => {
    if (server !== undefined) {
      await server.close();
      server = undefined;
    }
    if (home !== undefined) {
      await rm(home, { recursive: true, force: true });
      home = undefined;
    }
  });

  it('rejects a disallowed Host header with 40301', async () => {
    const res = await server!.app.inject({
      method: 'GET',
      url: '/api/v1/healthz',
      headers: { host: 'evil.com' },
    });
    expect(res.statusCode).toBe(403);
    const body = res.json() as Record<string, unknown>;
    expect(body['code']).toBe(40301);
  });

  it('allows the default loopback Host header', async () => {
    const res = await server!.app.inject({ method: 'GET', url: '/api/v1/healthz' });
    expect(res.statusCode).toBe(200);
  });

  it('echoes CORS headers for a same-origin request', async () => {
    const res = await server!.app.inject({
      method: 'GET',
      url: '/api/v1/healthz',
      headers: { origin: 'http://localhost:80', host: 'localhost:80' },
    });
    expect(res.statusCode).toBe(200);
    expect(res.headers['access-control-allow-origin']).toBe('http://localhost:80');
  });

  it('refuses to bind non-loopback hosts without TLS opt-out', async () => {
    await expect(
      startServer({ hostIdentity: TEST_HOST_IDENTITY, host: '0.0.0.0', port: 0, homeDir: home, logLevel: 'silent' }),
    ).rejects.toThrow(/Refusing to bind 0\.0\.0\.0/);
  });

  it('sets security headers on a non-loopback bind without HSTS', async () => {
    const alt = await startServer({
      hostIdentity: TEST_HOST_IDENTITY,
      host: '0.0.0.0',
      port: 0,
      homeDir: home,
      logLevel: 'silent',
      insecureNoTls: true,
    });
    try {
      const res = await alt.app.inject({ method: 'GET', url: '/api/v1/healthz' });
      expect(res.statusCode).toBe(200);
      expect(res.headers['x-content-type-options']).toBe('nosniff');
      expect(res.headers['referrer-policy']).toBe('no-referrer');
      expect(res.headers['content-security-policy']).toBe(
        "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; form-action 'self'; base-uri 'none'; frame-ancestors 'self'",
      );
      expect(res.headers['strict-transport-security']).toBeUndefined();
    } finally {
      await alt.close();
    }
  });

  it('does not set security headers on a loopback bind', async () => {
    const res = await server!.app.inject({ method: 'GET', url: '/api/v1/healthz' });
    expect(res.statusCode).toBe(200);
    expect(res.headers['x-content-type-options']).toBeUndefined();
    expect(res.headers['referrer-policy']).toBeUndefined();
    expect(res.headers['content-security-policy']).toBeUndefined();
    expect(res.headers['strict-transport-security']).toBeUndefined();
  });

  it('does not register shutdown or terminal routes on non-loopback by default', async () => {
    const alt = await startServer({
      hostIdentity: TEST_HOST_IDENTITY,
      host: '0.0.0.0',
      port: 0,
      homeDir: home,
      logLevel: 'silent',
      insecureNoTls: true,
    });
    try {
      const token = alt.authTokenService.getToken();
      const shutdown = await alt.app.inject({
        method: 'POST',
        url: '/api/v1/shutdown',
        headers: { authorization: `Bearer ${token}` },
      });
      expect(shutdown.statusCode).toBe(404);

      const terminals = await alt.app.inject({
        method: 'GET',
        url: '/api/v1/sessions/missing/terminals',
        headers: { authorization: `Bearer ${token}` },
      });
      expect(terminals.statusCode).toBe(404);
    } finally {
      await alt.close();
    }
  });
});