Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ tools/wta/target*/
tools/wta/*.excalidraw
tools/wta/*.png

# Cargo-fuzz artifacts (corpus, crashes, build output)
tools/wta/fuzz/target/
tools/wta/fuzz/corpus/
tools/wta/fuzz/artifacts/
tools/wta/fuzz/Cargo.lock

# Claude Code local settings
.claude/settings.local.json

Expand Down
51 changes: 49 additions & 2 deletions build/pipelines/fuzz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,61 @@
buildEverything: true
keepAllExpensiveBuildOutputs: false

- stage: BuildWtaFuzzer
displayName: WTA Rust Fuzzer Build
dependsOn: []
condition: succeeded()
jobs:
Comment thread
PankajBhojwani marked this conversation as resolved.
- job: BuildRustFuzzer
pool:
${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
name: SHINE-INT-L
${{ else }}:
name: SHINE-OSS-L
steps:
- checkout: self
- bash: |
set -ex
msrustup toolchain install ms-nightly

Check failure

Code scanning / check-spelling

Unrecognized Spelling Error

msrustup is not a recognized word
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
cargo +ms-nightly install cargo-fuzz --locked
displayName: Install Rust nightly + cargo-fuzz
- bash: |
set -ex
cd tools/wta
cargo +ms-nightly fuzz build cmdline_builder \
--config .cargo/intelligent_terminal_feed.toml
Comment thread
PankajBhojwani marked this conversation as resolved.
Outdated
displayName: Build WTA fuzz targets
- bash: |
set -ex
# cargo-fuzz outputs to tools/wta/fuzz/target/<triple>/release/
cp tools/wta/fuzz/target/x86_64-pc-windows-msvc/release/cmdline_builder.exe \
$(Build.ArtifactStagingDirectory)/WtaCmdlineFuzzer.exe
Comment thread
PankajBhojwani marked this conversation as resolved.
Outdated
displayName: Stage fuzzer artifact
- task: PublishPipelineArtifact@1
displayName: Publish WTA fuzzer
inputs:
targetPath: $(Build.ArtifactStagingDirectory)/WtaCmdlineFuzzer.exe
artifactName: wta-fuzzer

- stage: Submit
displayName: Submit to OneFuzz
dependsOn: [Build]
dependsOn: [Build, BuildWtaFuzzer]
condition: succeeded()
jobs:
- job:
pool:
vmImage: 'ubuntu-latest'
steps:
- task: DownloadPipelineArtifact@2
displayName: Download artifacts
displayName: Download C++ fuzzer artifacts
inputs:
artifactName: build-x64-Fuzzing
downloadPath: $(Build.ArtifactStagingDirectory)
- task: DownloadPipelineArtifact@2
displayName: Download WTA fuzzer artifact
inputs:
artifactName: wta-fuzzer
downloadPath: $(Build.ArtifactStagingDirectory)
- task: UsePythonVersion@0
inputs:
versionSpec: '3.x'
Expand Down Expand Up @@ -77,3 +118,9 @@
env:
target_exe_path: $(Build.ArtifactStagingDirectory)/ProtocolFuzzer.exe
test_name: ProtocolServerParsing
- bash: |
onefuzz template libfuzzer basic --colocate_all_tasks --vm_count 1 --target_exe $target_exe_path --notification_config @./build/Fuzz/notifications-ado.json OpenConsole $test_name $(Build.SourceVersion) default
displayName: Submit OneFuzz Job — WtaCmdlineFuzzer
env:
target_exe_path: $(Build.ArtifactStagingDirectory)/WtaCmdlineFuzzer.exe
test_name: WtaCmdlineBuilder
4 changes: 4 additions & 0 deletions tools/wta/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ description = "Windows Terminal Agent — ACP TUI client / MCP tool server"
name = "wta"
path = "src/main.rs"

[lib]
name = "wta"
path = "src/lib.rs"

Comment thread
PankajBhojwani marked this conversation as resolved.
[dependencies]
agent-client-protocol = { version = "0.10", features = ["unstable_session_model"] }
tokio = { version = "1", features = ["full"] }
Expand Down
24 changes: 24 additions & 0 deletions tools/wta/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]

Check failure

Code scanning / check-spelling

Check File Path Error

wta is not a recognized word
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
name = "wta-fuzz"
version = "0.0.0"
publish = false
edition = "2021"

[package.metadata]
cargo-fuzz = true

[dependencies]
libfuzzer-sys = "0.4"
arbitrary = { version = "1", features = ["derive"] }

[dependencies.wta]
path = ".."

# Prevent this from interfering with workspaces
[workspace]
members = ["."]

[[bin]]
name = "cmdline_builder"
path = "fuzz_targets/cmdline_builder.rs"
doc = false
55 changes: 55 additions & 0 deletions tools/wta/fuzz/fuzz_targets/cmdline_builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation.

Check failure

Code scanning / check-spelling

Check File Path Error

wta is not a recognized word
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
// Licensed under the MIT license.
//
// Fuzz target for WTA's commandline builder.
//
// This exercises build_wt_commandline with arbitrary (command, args) pairs
// generated by the Arbitrary trait, looking for panics and correctness
// violations in the quoting/escaping logic.

#![no_main]

use arbitrary::Arbitrary;
use libfuzzer_sys::fuzz_target;
use wta::build_wt_commandline;

/// Structured fuzz input: a command string and a list of arguments.
#[derive(Arbitrary, Debug)]
struct FuzzInput {
command: String,
args: Vec<String>,
}

fuzz_target!(|input: FuzzInput| {
// Skip degenerate inputs that would make the test noisy
if input.command.is_empty() {
return;
}
if input.args.len() > 64 {
return;
}

let result = build_wt_commandline(&input.command, &input.args);

// Basic sanity checks — these catch escaping failures:

// 1. The result must start with the command
assert!(
result.starts_with(&input.command),
"Commandline doesn't start with command: {:?} -> {:?}",
input.command,
result
);
Comment thread
PankajBhojwani marked this conversation as resolved.
Outdated

// 2. The result must not be empty if the command is non-empty
assert!(!result.is_empty());

// 3. No null bytes should appear (would truncate the commandline)
if !input.command.contains('\0') && !input.args.iter().any(|a| a.contains('\0')) {
assert!(
!result.contains('\0'),
"Null byte injected into commandline: {:?}",
result
);
}
});
12 changes: 12 additions & 0 deletions tools/wta/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.

Check failure

Code scanning / check-spelling

Check File Path Error

wta is not a recognized word
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
// Licensed under the MIT license.
//
// Library target for WTA — exposes functions needed by fuzz targets
// and tests without pulling in the full binary's module tree.
//
// Only the pure-logic functions are re-exported here. Modules with
// runtime dependencies (wt_channel, app, protocol) stay in main.rs.

mod shell_fuzz;

pub use shell_fuzz::build_wt_commandline;
20 changes: 7 additions & 13 deletions tools/wta/src/shell/shell_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ use tokio::process::Command;

use super::wt_channel::WtChannel;

// Import the commandline builder from the shared fuzz-target module.
#[path = "../shell_fuzz.rs"]
mod shell_fuzz;
use shell_fuzz::build_wt_commandline;

Comment thread
PankajBhojwani marked this conversation as resolved.
Outdated
/// Configuration for creating a new terminal.
pub struct TerminalConfig {
pub command: String,
Expand Down Expand Up @@ -115,19 +120,8 @@ impl ShellManager {
let id = self.next_id();
let wt = self.wt()?;

Comment thread
PankajBhojwani marked this conversation as resolved.
// Build the commandline string: "command arg1 arg2 ..."
let mut cmdline = config.command.clone();
for arg in &config.args {
cmdline.push(' ');
// Quote args containing spaces
if arg.contains(' ') {
cmdline.push('"');
cmdline.push_str(arg);
cmdline.push('"');
} else {
cmdline.push_str(arg);
}
}
// Build the commandline string for WT pane creation
let cmdline = build_wt_commandline(&config.command, &config.args);

// Create a new tab in WT with the command
let mut params = serde_json::Map::new();
Expand Down
35 changes: 35 additions & 0 deletions tools/wta/src/shell_fuzz.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation.

Check failure

Code scanning / check-spelling

Check File Path Error

wta is not a recognized word
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
// Licensed under the MIT license.
//
// Pure functions extracted from shell_manager for fuzzing.
// This module is compiled into the library target only and has
// no dependencies on the binary-specific module tree.
Comment thread
PankajBhojwani marked this conversation as resolved.
Outdated

/// Build a commandline string from a command and its arguments for WT pane
/// creation. This is the string passed to `create_tab`'s `commandline` param.
///
/// # Security note
///
/// This function is a fuzz target — the quoting must be robust against
/// agent-supplied strings containing shell metacharacters.
Comment thread
PankajBhojwani marked this conversation as resolved.
Outdated
pub fn build_wt_commandline(command: &str, args: &[String]) -> String {
let mut cmdline = command.to_string();
for arg in args {
cmdline.push(' ');
// Quote args containing spaces or double quotes
if arg.contains(' ') || arg.contains('"') {
cmdline.push('"');
// Escape embedded double quotes by doubling them
for ch in arg.chars() {
if ch == '"' {
cmdline.push('"');
}
cmdline.push(ch);
}
cmdline.push('"');
} else {
cmdline.push_str(arg);
}
}
Comment thread
PankajBhojwani marked this conversation as resolved.
Outdated
cmdline
}
Loading