Skip to content

Commit 75e2d8f

Browse files
authored
Merge pull request #8 from DefectDojo/hardening/egress-defaults
chore(hardening): make telemetry opt-in and give the tips fetch a kill switch
2 parents e56b156 + 2144e42 commit 75e2d8f

5 files changed

Lines changed: 68 additions & 5 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@moonshot-ai/kimi-code': minor
3+
---
4+
5+
Telemetry is opt-in in this fork. It previously ran unless the config said
6+
`telemetry = false`, including when the config could not be read at all, so an
7+
install that never made a choice still reported. It now runs only when the
8+
config opts in; an unreadable config is treated as no consent rather than as
9+
consent. `KIMI_DISABLE_TELEMETRY` continues to work as a hard override.
10+
11+
`KIMI_CODE_NO_TIPS=1` disables the startup tips fetch, which was the one
12+
network call at launch with no way to turn it off — so an install configured
13+
for no outbound traffic still beaconed on every run.

‎apps/kimi-code/src/cli/telemetry.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ export function initializeCliTelemetry(options: InitializeCliTelemetryOptions):
5151
initializeTelemetry({
5252
homeDir: options.harness.homeDir,
5353
deviceId: options.bootstrap.deviceId,
54-
enabled: options.config.telemetry !== false,
54+
// Opt-in in this fork: telemetry only runs when the user turns it on.
55+
enabled: options.config.telemetry === true,
5556
appName: CLI_USER_AGENT_PRODUCT,
5657
version: options.version,
5758
uiMode: options.uiMode,
@@ -100,7 +101,8 @@ export function initializeServerTelemetry(
100101
initializeTelemetry({
101102
homeDir: bootstrap.homeDir,
102103
deviceId: bootstrap.deviceId,
103-
enabled: config.telemetry !== false,
104+
// Opt-in in this fork: telemetry only runs when the user turns it on.
105+
enabled: config.telemetry === true,
104106
appName: CLI_USER_AGENT_PRODUCT,
105107
version: options.version,
106108
uiMode: WEB_UI_MODE,

‎apps/kimi-code/src/tui/banner/banner-provider.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,14 @@ export function selectDisplayableBanner({
312312
return pickRandomCandidate(candidates, random);
313313
}
314314

315+
/**
316+
* `KIMI_CODE_NO_TIPS` disables the startup tips fetch. Same truthy values as
317+
* the other kill switches (`1` / `true` / `yes` / `on`).
318+
*/
319+
export function isTipsBannerDisabled(env: NodeJS.ProcessEnv = process.env): boolean {
320+
return ['1', 'true', 'yes', 'on'].includes((env['KIMI_CODE_NO_TIPS'] ?? '').trim().toLowerCase());
321+
}
322+
315323
export class BannerProvider {
316324
constructor(
317325
private readonly clientVersion: string,
@@ -322,6 +330,9 @@ export class BannerProvider {
322330
fetchImpl: typeof fetch = fetch,
323331
options: BannerProviderLoadOptions = {},
324332
): Promise<BannerState | null> {
333+
// The banner is the one startup fetch with no way to turn it off, which
334+
// makes an otherwise offline-configured install still beacon on every run.
335+
if (isTipsBannerDisabled()) return null;
325336
try {
326337
const controller = new AbortController();
327338
const timeout = setTimeout(() => {

‎apps/kimi-code/test/cli/telemetry.test.ts‎

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,16 +104,37 @@ describe('initializeServerTelemetry', () => {
104104
);
105105
});
106106

107-
it('degrades to enabled with no model when config is unreadable', async () => {
107+
it('stays disabled when the config does not opt in', async () => {
108+
mocks.loadRuntimeConfigSafe.mockReturnValue({ config: {}, fileError: undefined });
109+
const { initializeServerTelemetry } = await import('#/cli/telemetry');
110+
initializeServerTelemetry({ version: '1.2.3' });
111+
112+
expect(mocks.initializeTelemetry).toHaveBeenCalledWith(
113+
expect.objectContaining({ enabled: false }),
114+
);
115+
});
116+
117+
it('enables only when the config opts in', async () => {
118+
mocks.loadRuntimeConfigSafe.mockReturnValue({ config: { telemetry: true }, fileError: undefined });
119+
const { initializeServerTelemetry } = await import('#/cli/telemetry');
120+
initializeServerTelemetry({ version: '1.2.3' });
121+
122+
expect(mocks.initializeTelemetry).toHaveBeenCalledWith(
123+
expect.objectContaining({ enabled: true }),
124+
);
125+
});
126+
127+
it('degrades to disabled with no model when config is unreadable', async () => {
108128
mocks.loadRuntimeConfigSafe.mockReturnValue({
109129
config: {},
110130
fileError: new Error('bad toml'),
111131
});
112132
const { initializeServerTelemetry } = await import('#/cli/telemetry');
113133
initializeServerTelemetry({ version: '1.2.3' });
114134

135+
// Telemetry is opt-in, so a config we could not read is not consent.
115136
expect(mocks.initializeTelemetry).toHaveBeenCalledWith(
116-
expect.objectContaining({ enabled: true, model: undefined }),
137+
expect.objectContaining({ enabled: false, model: undefined }),
117138
);
118139
});
119140
});

‎apps/kimi-code/test/tui/banner/banner-provider.test.ts‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { describe, expect, it } from 'vitest';
1+
import { describe, expect, it, vi } from 'vitest';
22

33
import {
44
selectBannerState,
55
selectDisplayableBanner,
66
shouldDisplayBanner,
77
} from '#/tui/banner/banner-provider';
88
import type { BannerState } from '#/tui/types';
9+
import { BannerProvider } from '#/tui/banner/banner-provider';
910

1011
describe('selectBannerState', () => {
1112
const now = new Date('2026-06-15T12:00:00+08:00');
@@ -679,3 +680,18 @@ describe('selectDisplayableBanner', () => {
679680
expect(result).toMatchObject({ key: 'fallback-always', display: 'always' });
680681
});
681682
});
683+
684+
describe('tips banner kill switch', () => {
685+
it('skips the fetch entirely when KIMI_CODE_NO_TIPS is set', async () => {
686+
vi.stubEnv('KIMI_CODE_NO_TIPS', '1');
687+
try {
688+
const fetchImpl = vi.fn<typeof fetch>();
689+
const provider = new BannerProvider('1.0.0');
690+
691+
await expect(provider.load(fetchImpl)).resolves.toBeNull();
692+
expect(fetchImpl).not.toHaveBeenCalled();
693+
} finally {
694+
vi.unstubAllEnvs();
695+
}
696+
});
697+
});

0 commit comments

Comments
 (0)