feat: add 12 new safe output tools with gh-aw alignment (#72-#79)#141
feat: add 12 new safe output tools with gh-aw alignment (#72-#79)#141jamesadevine wants to merge 7 commits intomainfrom
Conversation
Implements the remaining safe output features from GitHub issues #72-#79: - add-pr-comment (#72): Create comment threads on Azure DevOps pull requests with support for general and file-specific inline comments - link-work-items (#73): Create relationships between work items (parent/child, related, predecessor/successor, duplicate) - queue-build (#74): Trigger Azure DevOps pipeline/build runs with optional branch and template parameters - create-git-tag (#75): Create annotated git tags on commits in ADO repositories - add-build-tag (#76): Add tags to Azure DevOps builds for classification - create-branch (#77): Create new branches without immediately creating a PR - update-pr (#78): Update PR metadata (reviewers, labels, auto-complete, vote, description) - upload-attachment (#79): Upload file attachments to work items Each tool includes: - Params struct with validation - Config struct for front matter configuration - Executor with full ADO REST API implementation - Sanitize impl for security - Comprehensive unit tests All 8 tools are registered in the MCP server, Stage 2 executor dispatch, budget system, and write-permissions validation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Needs changes — solid foundation with good patterns throughout, but two correctness bugs and one meaningful security gap. Findings🐛 Bugs / Logic Issues1. // Line ~170
"{}/{}/_apis/git/repositories/{}/refs?filter=heads/main&api-version=7.1"The
2. let reviewer_url = format!(
"{}/{}/pullRequests/{}/reviewers/{}?api-version=7.1",
base_url, encoded_repo, self.pull_request_id,
utf8_percent_encode(reviewer, PATH_SEGMENT), // ← reviewer is an email
);
```
The `PUT .../reviewers/{reviewerId}` ADO endpoint expects a user **GUID/descriptor** as `reviewerId`, not an email address. Passing an email will consistently return a `404` or `400`. In practice the `add-reviewers` operation will always silently fail — every reviewer ends up in the `failed` list and the operation returns partial-success.
The fix requires an identity lookup step first:
```
GET (vssps.dev.azure.com/redacted)Then use the returned 🔒 Security Concerns3. The PR description highlights "vote restrictions to prevent unauthorized approvals," but looking at the actual default: impl Default for UpdatePrConfig {
fn default() -> Self {
Self {
allowed_votes: Vec::new(), // ← empty = all votes allowed
...
}
}
}And the enforcement: if !config.allowed_votes.is_empty() && !config.allowed_votes.contains(&vote_str.to_string()) {An empty Suggested fix: flip the default so that if config.allowed_votes.is_empty() {
return Ok(ExecutionResult::failure(
"vote operation requires allowed-votes to be configured"
));
}Or at minimum restrict
|
Bug fixes: - create-git-tag: resolve HEAD via repository's defaultBranch instead of hardcoding 'heads/main' (works with master, develop, etc.) - update-pr: resolve reviewer emails to GUIDs via VSSPS identity API before adding to PR (ADO reviewers endpoint requires user ID, not email) Security: - update-pr: require 'allowed-votes' to be explicitly configured for vote operation — empty list now rejects all votes to prevent accidental auto-approve by agents Improvements: - update-pr: make auto-complete merge options configurable via front matter (delete-source-branch, merge-strategy) instead of hardcoding squash+delete - add-pr-comment: validate file_path in Validate::validate() at Stage 1 (was only validated at Stage 2, giving no feedback to agent) - queue-build: sanitize template parameter keys and values to prevent ##vso[] command injection in build logs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: 8 new tools with solid overall structure, but two bugs in Findings🐛 Bugs / Logic Issues
🔒 Security Concerns
|
Adds 4 new safe output tools to align with gh-aw's PR review capabilities: - submit-pr-review: Submit PR review with decision (approve, request-changes, comment) and optional body/rationale text. Requires allowed-events config. - reply-to-pr-review-comment: Reply to existing review comment threads - resolve-pr-review-thread: Resolve/reactivate review threads (fixed, wont-fix, closed, by-design, active) - report-incomplete: Signal task could not complete due to infrastructure failure Enhancements to existing tools: - add-pr-comment: Add start_line param for multi-line code comments (gh-aw parity) - link-work-items: Set DEFAULT_MAX=5 (matches gh-aw link-sub-issue default) - queue-build: Set DEFAULT_MAX=3 (prevents pipeline queue flooding) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bug fixes: - update-pr: derive connectiondata URL from org_url instead of hardcoding dev.azure.com — supports vanity domains and national cloud endpoints - update-pr: derive VSSPS identity URL from org_url instead of hardcoding vssps.dev.azure.com — same environment portability fix - submit-pr-review: same connectiondata URL fix (derived from org_url) Security: - upload-attachment: add canonicalize() + prefix check after resolving file path to prevent symlink escape attacks (symlinks within workspace could previously be followed to read arbitrary files outside source_directory) Improvements: - add-pr-comment: sanitize structural fields (repository, status, file_path) with control-char stripping for defense-in-depth consistency Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/rust-review |
|
✅ Rust PR Reviewer completed successfully! |
🔍 Rust PR ReviewSummary: Previous review findings are resolved — one lingering bug in Findings🐛 Bugs / Logic Issues
|
- queue-build: drop equality arm from release/* wildcard matching so bare 'release' no longer matches 'release/*' — only 'release/...' does - update-pr: add warn! log when VSSPS URL derivation produces no change (surfaces the issue for visualstudio.com-hosted orgs at runtime) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Largely solid — good security model, consistent patterns — but one clear bug and a few concerns worth addressing before merge. Findings🐛 Bugs / Logic Issues
🔒 Security Concerns
|
Bug fix: - add-pr-comment: fix WontFix status mapped to 6 (should be 3). Unify status strings to kebab-case (active, fixed, wont-fix, closed, by-design) with CamelCase accepted for backwards compatibility. Security: - update-pr: reject add-reviewers for non-dev.azure.com org URLs instead of silently proceeding with a malformed VSSPS URL (*.visualstudio.com orgs) Improvements: - queue-build: sanitize branch field for defense-in-depth consistency - update-pr: document allow-list semantics asymmetry (operations=permissive, votes=secure) in UpdatePrConfig doc comment - create-branch: check config policy (allowed-repositories) before resolving repo alias, so operators see a policy error not a checkout-list error Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Solid implementation with good security defaults, a couple of correctness bugs worth fixing before merge. Findings🐛 Bugs / Logic Issues
The // BUG: this `?` fires BEFORE the config policy check below it
let repo_name = if repo_alias == "self" {
...
} else {
ctx.allowed_repositories
.get(repo_alias)
.cloned()
.context(format!("Repository alias '{}' not found...", repo_alias))? // hard error
};
// This block is never reached for an unknown alias
if !config.allowed_repositories.is_empty()
&& !config.allowed_repositories.contains(&repo_alias.to_string())
{
return Ok(ExecutionResult::failure(...));
}
The VSSPS URL is re-derived (and the equality check is re-evaluated) inside the // Currently inside the for loop
let vssps_base = trimmed_org.replace("://dev.azure.com/", "://vssps.dev.azure.com/");
if vssps_base == trimmed_org {
return Ok(ExecutionResult::failure(...));
}Move this to the top of 🔒 Security Concerns
Both files define an identical
The injection check is skipped for binary files (any file where
|
Bug fixes: - create-git-tag: check config allowed-repositories before resolving repo alias (same fix as create-branch — policy error before resolution error) - update-pr: hoist VSSPS URL derivation and validation out of the per-reviewer loop (avoids repeated work, fails fast before any API calls) Refactor: - Extract resolve_repo_name() to tools/mod.rs — was duplicated identically in update_pr.rs and submit_pr_review.rs Documentation: - add-pr-comment: clarify start_line must be strictly less than line - reply-to-pr-comment: explain parentCommentId=1 targets root comment - upload-attachment: document ##vso[] scan UTF-8 boundary behavior - mcp.rs: log warning on report-incomplete write failure Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Solid overall — the security model (config-driven allow-lists, required Findings🐛 Bugs / Logic Issues
🔒 Security Concerns
|
Summary
Implements all 8 remaining safe output feature requests (issues #72–#79), plus 4 additional tools and enhancements to align with gh-aw's PR review capabilities. Takes the project from 6 to 18 safe output tools.
New Tools — Feature Issues
add-pr-commentlink-work-itemsqueue-buildcreate-git-tagadd-build-tagcreate-branchupdate-prupload-attachmentNew Tools — gh-aw Alignment
submit-pr-reviewsubmit_pull_request_reviewreply-to-pr-review-commentreply_to_pull_request_review_commentresolve-pr-review-threadresolve_pull_request_review_threadreport-incompletereport_incompleteEnhancements (gh-aw parity)
add-pr-comment:start_lineparam for multi-line code commentslink-work-items:DEFAULT_MAX=5(matches gh-aw)queue-build:DEFAULT_MAX=3(prevents flooding)Per-tool structure
Each tool includes:
Validatetrait — input validationIntegration points updated
src/tools/mod.rs— module registration + re-exportssrc/mcp.rs— 12 new MCP tool handlers with sanitizationsrc/execute.rs— dispatch cases + budget registrationsrc/compile/common.rs—WRITE_REQUIRING_SAFE_OUTPUTSlistSecurity highlights
permissions.writerequired at compile time for all write toolssubmit-pr-review+update-pr vote: require explicitallowed-events/allowed-votes— empty config rejects all (prevents accidental auto-approve)queue-build: require explicitallowed-pipelines— empty rejects allupload-attachment: symlink escape protection viacanonicalize()+ prefix check,##vso[scanning, size limitsorg_urlcontext (supports vanity domains, national clouds)Test results
Closes #72, closes #73, closes #74, closes #75, closes #76, closes #77, closes #78, closes #79