Skip to content

[Improve] Harden DCG binary installation - #1060

Open
navedmerchant wants to merge 14 commits into
mainfrom
feat/dcg-binary-service
Open

[Improve] Harden DCG binary installation#1060
navedmerchant wants to merge 14 commits into
mainfrom
feat/dcg-binary-service

Conversation

@navedmerchant

@navedmerchant navedmerchant commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

What changed

Completes the DCG binary-service safeguards with macOS temporary-directory support, an explicit skipped ZIP-only test on non-Windows CI, and coverage for trusted cross-host GitHub release redirects.

Why this change was made

DCG must run with the minimum viable environment and install predictably across supported platforms and GitHub's release-asset redirect flow. Closes #1056.

Impact

DCG installation and execution are more reliable across platforms while keeping extension-host secrets out of the child process environment.

Related PRs

Summary by CodeRabbit

  • New Features

    • Added a destructive-command guard that evaluates commands and returns allow or deny decisions, including optional explanations and rule identifiers.
    • Added automatic installation of the guard binary with platform support checks, secure downloads, checksum validation, and archive extraction.
    • Added safeguards for restricted environments, timeouts, excessive output, invalid responses, and process failures.
  • Tests

    • Added comprehensive coverage for installation, downloads, validation, platform handling, command execution, and failure scenarios.

@navedmerchant navedmerchant added the enhancement New feature or request label Jul 30, 2026
@navedmerchant navedmerchant added the enhancement New feature or request label Jul 30, 2026
@navedmerchant navedmerchant self-assigned this Jul 30, 2026
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Added the destructive-command-guard service. It selects and installs pinned platform binaries, validates downloads and archives, and runs the binary with restricted environment variables, output limits, timeout handling, and typed decision parsing.

Changes

Destructive command guard service

Layer / File(s) Summary
Pinned binary installation
src/services/destructive-command-guard/constants.ts, src/services/destructive-command-guard/manager.ts, src/services/destructive-command-guard/__tests__/manager.spec.ts
Defines pinned archive metadata and checksums. Resolves supported platforms, validates trusted downloads, verifies checksums, extracts ZIP or TAR.XZ archives, reuses installed binaries, and deduplicates concurrent installation attempts.
Bounded DCG execution
src/services/destructive-command-guard/runner.ts, src/services/destructive-command-guard/__tests__/runner.spec.ts
Runs the DCG binary without a shell. Filters environment variables, enforces output and timeout limits, validates JSON decisions, handles process failures, and returns typed allow or deny results.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant ensureDcgInstalled
  participant ManagedBinaryInstaller
  participant runDcg
  participant DCGBinary
  Caller->>ensureDcgInstalled: request DCG binary
  ensureDcgInstalled->>ManagedBinaryInstaller: install or reuse pinned binary
  ManagedBinaryInstaller-->>Caller: binary path
  Caller->>runDcg: command and working directory
  runDcg->>DCGBinary: spawn with sanitized environment
  DCGBinary-->>runDcg: bounded JSON decision
  runDcg-->>Caller: typed allow or deny result
Loading

Possibly related PRs

Suggested labels: awaiting-review

Suggested reviewers: edelauna

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the purpose and impact but omits the required test procedure and pre-submission checklist. Add the template sections for test steps, verification details, checklist status, and documentation impact.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: hardening DCG binary installation.
Linked Issues check ✅ Passed The changes address issue #1056 through pinned archives, secure installation, runner safeguards, concurrency handling, and focused tests.
Out of Scope Changes check ✅ Passed The changed constants, manager, runner, and focused tests are directly related to the DCG binary service and runner scope.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dcg-binary-service

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/services/destructive-command-guard/__tests__/manager.spec.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

src/services/destructive-command-guard/__tests__/runner.spec.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

src/services/destructive-command-guard/constants.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

  • 2 others

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.34884% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/services/destructive-command-guard/runner.ts 92.15% 0 Missing and 4 partials ⚠️

📢 Thoughts on this report? Let us know!

@navedmerchant
navedmerchant force-pushed the feat/dcg-binary-service branch from 5c460bd to baade5f Compare July 30, 2026 04:50
@github-actions github-actions Bot added the awaiting-review PR changes are ready and waiting for maintainer re-review label Jul 30, 2026

@edelauna edelauna left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good - just had 2 comments for increased test coverage.

Comment thread src/services/destructive-command-guard/__tests__/runner.spec.ts
Comment thread src/services/destructive-command-guard/__tests__/manager.spec.ts Outdated
@github-actions github-actions Bot added awaiting-author PR is waiting for the author to address requested changes and removed awaiting-review PR changes are ready and waiting for maintainer re-review labels Jul 31, 2026
Comment thread src/services/destructive-command-guard/constants.ts
@navedmerchant
navedmerchant force-pushed the feat/dcg-binary-service branch from baade5f to e1a0c39 Compare August 1, 2026 17:17
@github-actions github-actions Bot added awaiting-author PR is waiting for the author to address requested changes and removed awaiting-author PR is waiting for the author to address requested changes labels Aug 1, 2026

@edelauna edelauna left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😅 have a couple additional comments related to the logic used for the guard.

Comment thread src/services/destructive-command-guard/manager.ts Outdated
Comment thread src/services/destructive-command-guard/manager.ts
Comment thread src/services/destructive-command-guard/manager.ts Outdated
Comment thread src/services/destructive-command-guard/manager.ts
Comment thread src/services/destructive-command-guard/runner.ts Outdated
Comment thread src/services/destructive-command-guard/runner.ts
Comment thread src/services/destructive-command-guard/runner.ts Outdated
Comment thread src/services/destructive-command-guard/__tests__/manager.spec.ts
Comment thread src/services/destructive-command-guard/__tests__/manager.spec.ts
Comment thread src/services/destructive-command-guard/__tests__/manager.spec.ts
@github-actions github-actions Bot added awaiting-author PR is waiting for the author to address requested changes and removed awaiting-author PR is waiting for the author to address requested changes labels Aug 2, 2026
edelauna
edelauna previously approved these changes Aug 2, 2026

@edelauna edelauna left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good - approving with some minor nits, which could also always be addressed later if we want.

Comment thread src/services/destructive-command-guard/runner.ts Outdated
Comment thread src/services/destructive-command-guard/__tests__/manager.spec.ts Outdated
Comment thread src/services/destructive-command-guard/__tests__/manager.spec.ts
@github-actions github-actions Bot removed the awaiting-author PR is waiting for the author to address requested changes label Aug 2, 2026
@github-actions github-actions Bot removed the awaiting-review PR changes are ready and waiting for maintainer re-review label Aug 2, 2026
@zoomote zoomote Bot changed the title [Feat] Add the Destructive Command Guard binary service [Improve] Harden DCG binary installation Aug 2, 2026
@zoomote

zoomote Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Addressed all current unresolved review feedback in 5f3854a. DCG now preserves TMPDIR, covers trusted release redirects, and reports the ZIP-only scenario as skipped on non-Windows CI.

@github-actions github-actions Bot added the awaiting-review PR changes are ready and waiting for maintainer re-review label Aug 2, 2026
Base automatically changed from feat/managed-binary-infrastructure to main August 4, 2026 02:38
@navedmerchant
navedmerchant dismissed edelauna’s stale review August 4, 2026 02:38

The base branch was changed.

@github-actions github-actions Bot added awaiting-author PR is waiting for the author to address requested changes and removed awaiting-review PR changes are ready and waiting for maintainer re-review labels Aug 4, 2026
@github-actions github-actions Bot removed the awaiting-author PR is waiting for the author to address requested changes label Aug 4, 2026
@navedmerchant

Copy link
Copy Markdown
Contributor Author

Merging, base branch merged, code has not changed

@navedmerchant
navedmerchant enabled auto-merge August 4, 2026 03:23

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
src/services/destructive-command-guard/__tests__/runner.spec.ts (1)

122-132: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a case for signal-terminated exits.

The tests cover exit code 2 but never a non-null signal. runner.ts line 61 treats signal || (code !== 0 && code !== 1) as a failure, so a signal-terminated run with code 0 must still reject. Add a case that emits close with a signal to lock that branch.

🧪 Proposed test
+	it("rejects runs terminated by a signal", async () => {
+		const child = createChild()
+		useChild(child)
+
+		const result = runDcg("/dcg", "echo test", "/workspace")
+		child.stdout.write(JSON.stringify({ schema_version: 1, decision: "allow" }))
+		child.emit("close", 0, "SIGKILL")
+
+		await expect(result).rejects.toThrow("DCG evaluation failed")
+	})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/destructive-command-guard/__tests__/runner.spec.ts` around lines
122 - 132, Add a test alongside “rejects non-DCG exit statuses with stderr” that
emits the child’s close event with a non-null signal and exit code 0, then
assert the runDcg promise rejects. Reuse createChild, useChild, and the existing
result setup to cover the signal failure branch in the runner.
src/services/destructive-command-guard/__tests__/manager.spec.ts (3)

244-247: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the silent early return with skipIf.

Line 247 returns when the current platform is supported. Vitest then reports a passing test with zero assertions on every supported platform. The ZIP test at line 332 already uses it.skipIf. Use the same pattern here, and at line 263, so CI output states that the test was skipped.

♻️ Proposed change
-	it("warns when the current platform is unsupported", async () => {
-		const platformKey = `${process.platform}-${process.arch}`
-		const info = DCG_ARCHIVES[platformKey]
-		if (!info) return
+	const currentPlatformKey = `${process.platform}-${process.arch}`
+
+	it.skipIf(!DCG_ARCHIVES[currentPlatformKey])("warns when the current platform is unsupported", async () => {
+		const platformKey = currentPlatformKey
+		const info = DCG_ARCHIVES[platformKey]!
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/destructive-command-guard/__tests__/manager.spec.ts` around
lines 244 - 247, Replace the silent `if (!info) return` early return in the
platform warning test with Vitest’s `it.skipIf` pattern, and apply the same
change to the related test around `DCG_ARCHIVES` at line 263. Preserve the
existing test bodies while ensuring supported platforms are reported as skipped
rather than passing with zero assertions.

218-218: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document the remaining double assertions.

Lines 168, 200, 288, 292, and 310 explain each as unknown as cast with a comment. Lines 218, 352, and 355 use the same casts without a comment. Add the same short explanation for consistency.

As per coding guidelines: "Use double assertions only as a last resort and explain them with a comment."

Also applies to: 348-356

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/destructive-command-guard/__tests__/manager.spec.ts` at line
218, Add concise explanatory comments beside the remaining double assertions in
the test setup around mockSpawn and the lines also identified near 348–356.
Match the existing comments used at lines 168, 200, 288, 292, and 310, without
changing the assertions or test behavior.

Source: Coding guidelines


302-308: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Handle rejections in the async setImmediate callbacks.

writeFile runs inside an async callback that no one awaits. If writeFile rejects, the rejection is unhandled, the test hangs on firstInstallation, and the real cause is hidden. The same pattern exists at lines 363-370. Catch the error and fail the child process instead.

♻️ Proposed change
 			setImmediate(async () => {
-				if (executable === "tar") {
-					const stagingDir = args[args.indexOf("-C") + 1]
-					await writeFile(path.join(stagingDir, info.binary), "executable")
-				}
-				child.emit("close", 0)
+				try {
+					if (executable === "tar") {
+						const stagingDir = args[args.indexOf("-C") + 1]
+						await writeFile(path.join(stagingDir, info.binary), "executable")
+					}
+					child.emit("close", 0)
+				} catch (error) {
+					child.emit("error", error)
+				}
 			})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/destructive-command-guard/__tests__/manager.spec.ts` around
lines 302 - 308, Handle promise rejections in both async setImmediate callbacks
around the child process simulation, including the callback near the tar
executable setup and the matching callback later in the test. Wrap writeFile and
subsequent logic in try/catch, and on failure emit a nonzero child close/error
result so the awaiting installation rejects instead of hanging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/services/destructive-command-guard/__tests__/manager.spec.ts`:
- Around line 244-247: Replace the silent `if (!info) return` early return in
the platform warning test with Vitest’s `it.skipIf` pattern, and apply the same
change to the related test around `DCG_ARCHIVES` at line 263. Preserve the
existing test bodies while ensuring supported platforms are reported as skipped
rather than passing with zero assertions.
- Line 218: Add concise explanatory comments beside the remaining double
assertions in the test setup around mockSpawn and the lines also identified near
348–356. Match the existing comments used at lines 168, 200, 288, 292, and 310,
without changing the assertions or test behavior.
- Around line 302-308: Handle promise rejections in both async setImmediate
callbacks around the child process simulation, including the callback near the
tar executable setup and the matching callback later in the test. Wrap writeFile
and subsequent logic in try/catch, and on failure emit a nonzero child
close/error result so the awaiting installation rejects instead of hanging.

In `@src/services/destructive-command-guard/__tests__/runner.spec.ts`:
- Around line 122-132: Add a test alongside “rejects non-DCG exit statuses with
stderr” that emits the child’s close event with a non-null signal and exit code
0, then assert the runDcg promise rejects. Reuse createChild, useChild, and the
existing result setup to cover the signal failure branch in the runner.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b8ff35b7-5682-4638-aa3d-239408320348

📥 Commits

Reviewing files that changed from the base of the PR and between 7918f6b and a637dc6.

📒 Files selected for processing (5)
  • src/services/destructive-command-guard/__tests__/manager.spec.ts
  • src/services/destructive-command-guard/__tests__/runner.spec.ts
  • src/services/destructive-command-guard/constants.ts
  • src/services/destructive-command-guard/manager.ts
  • src/services/destructive-command-guard/runner.ts

@navedmerchant
navedmerchant requested a review from a team August 4, 2026 04:36
@github-actions github-actions Bot added the awaiting-author PR is waiting for the author to address requested changes label Aug 4, 2026
@navedmerchant
navedmerchant requested review from edelauna and removed request for a team August 4, 2026 04:37
@github-actions github-actions Bot added awaiting-review PR changes are ready and waiting for maintainer re-review and removed awaiting-author PR is waiting for the author to address requested changes labels Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting-review PR changes are ready and waiting for maintainer re-review enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add the Destructive Command Guard binary service and runner

2 participants