Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3d3ecf1
feat(cli): add safe mode
DennisYu07 Jun 10, 2026
f4e2b3d
fix(test): add isSafeMode to test mocks and config stubs
DennisYu07 Jun 10, 2026
f69a198
fix(ui): hide memory toggles in safe mode
DennisYu07 Jun 10, 2026
c8fa93e
merge main and resolve conflict
DennisYu07 Jun 14, 2026
4b9706d
fix(cli,core): harden safe mode guards for permissions and extensions
DennisYu07 Jun 16, 2026
c57db62
Merge remote-tracking branch 'origin/main' into feat/safe-mode
DennisYu07 Jun 16, 2026
81bc81a
fix(test): add isSafeMode to config stub in gemini.test.tsx
DennisYu07 Jun 16, 2026
07ddcba
Merge branch 'main' into feat/safe-mode
yiliang114 Jun 16, 2026
10c130e
fix(cli,core): close safe-mode bypass gaps in context loading and chi…
DennisYu07 Jun 22, 2026
d4f4914
Merge branch 'main' into feat/safe-mode
DennisYu07 Jun 22, 2026
4193772
fix(test): add isSafeMode to directoryCommand mock config
DennisYu07 Jun 22, 2026
d15eb4b
Merge remote-tracking branch 'origin/main' into feat/safe-mode
DennisYu07 Jun 22, 2026
fe8bb42
fix(safe-mode): address code review feedback and improve consistency
DennisYu07 Jun 22, 2026
f1eddf4
Merge branch 'main' into feat/safe-mode
DennisYu07 Jun 22, 2026
5777de5
fix: rename safeMode.ts to safe-mode.ts for kebab-case convention
DennisYu07 Jun 22, 2026
19d4f66
Merge branch 'main' into feat/safe-mode
DennisYu07 Jun 22, 2026
666c3d2
Merge branch 'main' into feat/safe-mode
DennisYu07 Jun 22, 2026
a95f1b2
fix(safe-mode): close extension leak and restore bare-mode sleep prev…
DennisYu07 Jun 23, 2026
119eb33
fix(safe-mode): address review feedback on bare-mode extensions, env …
DennisYu07 Jun 23, 2026
b605f5e
Merge branch 'main' into feat/safe-mode
DennisYu07 Jun 23, 2026
5a7ff3f
Merge branch 'main' into feat/safe-mode
DennisYu07 Jun 23, 2026
5089059
Merge branch 'main' into feat/safe-mode
DennisYu07 Jun 23, 2026
d2615c3
Merge branch 'main' into feat/safe-mode
wenshao Jun 24, 2026
d68167e
merge: resolve conflicts between feat/safe-mode and main
qwen-code-dev-bot Jun 25, 2026
3e134b1
merge: resolve conflicts between feat/safe-mode and main
qwen-code-dev-bot Jun 29, 2026
d3af1e0
fix(core): add missing DEFAULT_SENSITIVE_SPAN_ATTRIBUTE_MAX_LENGTH to…
DennisYu07 Jun 29, 2026
06756d6
Merge branch 'main' into feat/safe-mode
DennisYu07 Jun 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions packages/cli/src/config/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2721,6 +2721,140 @@ describe('loadCliConfig with includeDirectories', () => {
});
});

describe('loadCliConfig safe mode', () => {
const originalArgv = process.argv;

beforeEach(() => {
vi.resetAllMocks();
vi.mocked(os.homedir).mockReturnValue('/mock/home/user');
vi.stubEnv('GEMINI_API_KEY', 'test-api-key');
vi.spyOn(process, 'cwd').mockReturnValue(
path.resolve(path.sep, 'home', 'user', 'project'),
);
});

afterEach(() => {
process.argv = originalArgv;
vi.unstubAllEnvs();
vi.restoreAllMocks();
});

it('should strip settings-sourced permissions in safe mode', async () => {
process.argv = ['node', 'script.js', '--safe-mode'];
const argv = await parseArguments();
const settings: Settings = {
permissions: {
allow: ['Bash(*)'],
ask: ['Edit'],
deny: ['ReadFile'],
autoMode: { hints: { allow: ['everything'] } },
},
tools: {
allowed: ['ShellTool'],
exclude: ['WriteFile'],
},
};
const config = await loadCliConfig(settings, argv, undefined, []);

expect(config.getPermissionsAllow()).toEqual([]);
expect(config.getPermissionsAsk()).toEqual([]);
// mergedDeny should not contain settings-sourced rules.
// Non-interactive mode exclusions (run_shell_command, monitor, edit,
// write_file) are expected since no prompt/interactive flag was passed.
expect(config.getPermissionsDeny()).not.toContain('WriteFile');
expect(config.getAutoModeSettings()).toEqual({});
});

it('should ignore settings-sourced coreTools in safe mode', async () => {
process.argv = ['node', 'script.js', '--safe-mode'];
const argv = await parseArguments();
const settings: Settings = {
tools: {
core: [ToolNames.WEB_FETCH],
},
};
const config = await loadCliConfig(settings, argv, undefined, []);

expect(config.getCoreTools()).toBeUndefined();
});

it('should ignore settings-sourced allowedTools in safe mode but keep argv ones', async () => {
process.argv = [
'node',
'script.js',
'--safe-mode',
'--allowed-tools',
'ReadFile',
];
const argv = await parseArguments();
const settings: Settings = {
tools: {
allowed: ['ShellTool'],
},
};
const config = await loadCliConfig(settings, argv, undefined, []);

expect(config.getPermissionsAllow()).toContain('ReadFile');
expect(config.getPermissionsAllow()).not.toContain('ShellTool');
});

it('should ignore settings-sourced approvalMode in safe mode', async () => {
process.argv = ['node', 'script.js', '--safe-mode'];
const argv = await parseArguments();
const settings = {
tools: {
approvalMode: 'yolo',
},
} as unknown as Settings;
const config = await loadCliConfig(settings, argv, undefined, []);

expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT);
});

it('should ignore settings-sourced disabled slash commands in safe mode', async () => {
process.argv = ['node', 'script.js', '--safe-mode'];
const argv = await parseArguments();
const settings: Settings = {
slashCommands: {
disabled: ['/review', '/bug'],
},
};
const config = await loadCliConfig(settings, argv, undefined, []);

expect(config.getDisabledSlashCommands()).toEqual([]);
});

it('should ignore settings-sourced disabledTools in safe mode', async () => {
process.argv = ['node', 'script.js', '--safe-mode'];
const argv = await parseArguments();
const settings: Settings = {
tools: {
disabled: ['WebFetch', 'ReadFile'],
},
};
const config = await loadCliConfig(settings, argv, undefined, []);

expect(config.getDisabledTools().size).toBe(0);
});

it('should respect safe mode via QWEN_CODE_SAFE_MODE env var', async () => {
vi.stubEnv('QWEN_CODE_SAFE_MODE', 'true');
process.argv = ['node', 'script.js'];
const argv = await parseArguments();
const settings: Settings = {
permissions: {
allow: ['Bash(*)'],
autoMode: { hints: { allow: ['everything'] } },
},
};
const config = await loadCliConfig(settings, argv, undefined, []);

expect(config.getPermissionsAllow()).toEqual([]);
expect(config.getAutoModeSettings()).toEqual({});
expect(config.isSafeMode()).toBe(true);
});
});

describe('loadCliConfig chatCompression', () => {
const originalArgv = process.argv;

Expand Down
Loading
Loading