Skip to content

Commit d9c0bb8

Browse files
jongioCopilot
andauthored
fix: MQ quality audit — 12 critical/high fixes, dead code removal, docs corrections (#176)
Comprehensive MAX QUALITY audit (15-step, 4-wave pipeline) identifying and fixing security vulnerabilities, race conditions, goroutine leaks, and code health issues. Critical fixes (4): - WebSocket readMessage returns sentinel error instead of nil on normal closure - Stop() reordered — close HTTP server before nilling shared fields - Pipeline: atomic stopped flag prevents send-on-closed-channel panic - build.sh ldflags updated for Version package path High fixes (8): - Path traversal prefix check hardened with filepath.Separator - Goroutine leak: WaitGroup closes output channel when readers finish - Secret redaction for env var display in info and MCP tools - Context threading through service orchestration for Key Vault - Buffered stopCleanup channel prevents goroutine hang - errors.As replaces direct type assertion for wrapped errors - ParsePortSpec returns errors instead of silently producing port 0 - Version moved to internal/version — skills no longer imports cmd Dead code removed (~844 lines net): - 4 unused packages, 36 unused constants, unused exports Documentation corrections: - README: Go version 1.26.0->1.26.1, MCP tool count 10->12 - CONTRIBUTING: Go prerequisite 1.25->1.26.1, project structure rewritten Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3b06f3a commit d9c0bb8

34 files changed

Lines changed: 172 additions & 1016 deletions

CONTRIBUTING.md

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Thank you for your interest in contributing to azd-app! This document provides g
88

99
Before contributing, ensure you have the following installed:
1010

11-
- **Go**: 1.25 or later
11+
- **Go**: 1.26.1 or later
1212
- **Node.js**: 20.0.0 or later
1313
- **npm**: 10.0.0 or later
1414
- **PowerShell**: 7.4 or later (recommended: 7.5.4 for full compatibility)
@@ -17,7 +17,7 @@ Before contributing, ensure you have the following installed:
1717

1818
You can verify your versions:
1919
```bash
20-
go version # Should be 1.25+
20+
go version # Should be 1.26.1+
2121
node --version # Should be v20.0.0+
2222
npm --version # Should be 10.0.0+
2323
pwsh --version # Should be 7.4+ or 7.5.4
@@ -188,18 +188,30 @@ Then create a Pull Request on GitHub.
188188
## Project Structure
189189

190190
```
191-
src/
192-
├── cmd/ # Command implementations
193-
├── internal/
194-
│ ├── detector/ # Project detection logic
195-
│ ├── installer/ # Dependency installation
196-
│ └── runner/ # Project execution
197-
└── types/ # Shared types
198-
199-
tests/
200-
└── projects/ # Test fixtures
201-
202-
docs/ # Documentation
191+
cli/
192+
├── src/
193+
│ ├── cmd/app/commands/ # CLI command implementations
194+
│ └── internal/
195+
│ ├── azure/ # Azure integration (credentials, logs)
196+
│ ├── config/ # Configuration loading
197+
│ ├── dashboard/ # Web dashboard server
198+
│ ├── detector/ # Project detection logic
199+
│ ├── docker/ # Docker/container support
200+
│ ├── executor/ # Process execution
201+
│ ├── healthcheck/ # Service health monitoring
202+
│ ├── installer/ # Dependency installation
203+
│ ├── logging/ # Structured logging
204+
│ ├── monitor/ # Service state monitoring
205+
│ ├── notifications/ # Desktop notifications
206+
│ ├── orchestrator/ # Service lifecycle orchestration
207+
│ ├── portmanager/ # Port allocation
208+
│ ├── runner/ # Project execution
209+
│ ├── service/ # Service management core
210+
│ └── workspace/ # Workspace utilities
211+
├── dashboard/ # React dashboard (Vite)
212+
├── tests/projects/ # Test fixture projects
213+
└── magefile.go # Build tasks (mage)
214+
web/ # Astro documentation site
203215
```
204216

205217
## Adding a New Command

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ One command starts all services, manages dependencies, and provides real-time mo
1515
[![Go Reference](https://pkg.go.dev/badge/github.com/jongio/azd-app/cli.svg)](https://pkg.go.dev/github.com/jongio/azd-app/cli)
1616
[![govulncheck](https://img.shields.io/badge/govulncheck-passing-brightgreen)](https://github.com/jongio/azd-app/actions/workflows/govulncheck.yml)
1717
[![golangci-lint](https://img.shields.io/badge/golangci--lint-enabled-blue)](https://github.com/jongio/azd-app/actions/workflows/ci.yml)
18-
[![Go Version](https://img.shields.io/badge/go-1.26.0-blue)](https://go.dev/)
18+
[![Go Version](https://img.shields.io/badge/go-1.26.1-blue)](https://go.dev/)
1919
[![Platform Support](https://img.shields.io/badge/platform-linux%20%7C%20macOS%20%7C%20windows-lightgrey)](https://github.com/jongio/azd-app)
2020

2121
<br />
@@ -153,9 +153,9 @@ azd app run
153153

154154
azd app includes a Model Context Protocol (MCP) server that connects your running application to AI assistants like GitHub Copilot.
155155

156-
**10 AI Tools Available:**
157-
- **Observability**: `get_services`, `get_service_logs`, `get_project_info`
158-
- **Operations**: `run_services`, `stop_services`, `restart_service`, `install_dependencies`
156+
**12 AI Tools Available:**
157+
- **Observability**: `get_services`, `get_service_logs`, `get_service_errors`, `get_project_info`
158+
- **Operations**: `run_services`, `stop_services`, `start_service`, `restart_service`, `install_dependencies`
159159
- **Configuration**: `check_requirements`, `get_environment_variables`, `set_environment_variable`
160160

161161
Ask Copilot things like:
@@ -186,7 +186,7 @@ Ask Copilot things like:
186186

187187
<div align="center">
188188

189-
| 10+ MCP Tools | <5 min Setup | 100% Open Source | Works with Copilot |
189+
| 12 MCP Tools | <5 min Setup | 100% Open Source | Works with Copilot |
190190
|:-------------:|:------------:|:----------------:|:------------------:|
191191
| Full AI integration | Quick start | MIT License | GitHub Copilot ready |
192192

cli/build.ps1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ else {
190190
}
191191

192192
$APP_PATH = "github.com/jongio/azd-app/cli/src/cmd/app/commands"
193+
$VERSION_PATH = "github.com/jongio/azd-app/cli/src/internal/version"
193194

194195
# Loop through platforms and build
195196
foreach ($PLATFORM in $PLATFORMS) {
@@ -223,7 +224,7 @@ foreach ($PLATFORM in $PLATFORMS) {
223224
$env:GOOS = $OS
224225
$env:GOARCH = $ARCH
225226

226-
$ldflags = "-s -w -X '$APP_PATH.Version=$env:EXTENSION_VERSION' -X '$APP_PATH.BuildTime=$BUILD_DATE' -X '$APP_PATH.Commit=$COMMIT'"
227+
$ldflags = "-s -w -X '$VERSION_PATH.Version=$env:EXTENSION_VERSION' -X '$APP_PATH.BuildTime=$BUILD_DATE' -X '$APP_PATH.Commit=$COMMIT'"
227228

228229
go build `
229230
"-ldflags=$ldflags" `

cli/build.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ else
169169
fi
170170

171171
APP_PATH="github.com/jongio/azd-app/cli/src/cmd/app/commands"
172+
VERSION_PATH="github.com/jongio/azd-app/cli/src/internal/version"
172173

173174
# Loop through platforms and build
174175
for PLATFORM in "${PLATFORMS[@]}"; do
@@ -186,7 +187,7 @@ for PLATFORM in "${PLATFORMS[@]}"; do
186187
# Delete the output file if it already exists
187188
[ -f "$OUTPUT_NAME" ] && rm -f "$OUTPUT_NAME"
188189

189-
LDFLAGS="-s -w -X '$APP_PATH.Version=$EXTENSION_VERSION' -X '$APP_PATH.BuildTime=$BUILD_DATE' -X '$APP_PATH.Commit=$COMMIT'"
190+
LDFLAGS="-s -w -X '$VERSION_PATH.Version=$EXTENSION_VERSION' -X '$APP_PATH.BuildTime=$BUILD_DATE' -X '$APP_PATH.Commit=$COMMIT'"
190191

191192
# Set environment variables for Go build
192193
GOOS=$OS GOARCH=$ARCH go build \

cli/src/cmd/app/commands/core_helpers.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"os"
88
"path/filepath"
9+
"strings"
910

1011
"github.com/jongio/azd-app/cli/src/internal/cache"
1112
"github.com/jongio/azd-app/cli/src/internal/detector"
@@ -544,3 +545,20 @@ func handleNoProjectsCase(searchRoot string, serviceFilter []string) error {
544545
}
545546
return nil
546547
}
548+
549+
// redactSecretValue masks the value of environment variables whose names
550+
// suggest they contain secrets (passwords, tokens, keys, connection strings).
551+
// Values with 4 or fewer characters are fully masked.
552+
func redactSecretValue(key, value string) string {
553+
upper := strings.ToUpper(key)
554+
sensitivePatterns := []string{"PASSWORD", "SECRET", "TOKEN", "KEY", "CONNECTION_STRING", "CONNECTIONSTRING"}
555+
for _, pattern := range sensitivePatterns {
556+
if strings.Contains(upper, pattern) {
557+
if len(value) <= 4 {
558+
return "***"
559+
}
560+
return value[:2] + "***" + value[len(value)-2:]
561+
}
562+
}
563+
return value
564+
}

cli/src/cmd/app/commands/info.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func printInfoJSON(projectDir string, services []*serviceinfo.ServiceInfo, azure
105105
// Include environment variables related to this service
106106
if strings.HasPrefix(envKeyUpper, serviceName+"_") ||
107107
strings.HasPrefix(envKeyUpper, "SERVICE_"+serviceName+"_") {
108-
svc.EnvironmentVars[envKey] = envValue
108+
svc.EnvironmentVars[envKey] = redactSecretValue(envKey, envValue)
109109
}
110110
}
111111
}
@@ -229,7 +229,7 @@ func printInfoDefault(projectDir string, services []*serviceinfo.ServiceInfo, az
229229
cliout.Newline()
230230
cliout.Info(" Environment Variables:")
231231
for key, value := range envVars {
232-
cliout.Item(" %s = %s", key, value)
232+
cliout.Item(" %s = %s", key, redactSecretValue(key, value))
233233
}
234234
}
235235
}

cli/src/cmd/app/commands/mcp.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"time"
1414

1515
"github.com/azure/azure-dev/cli/azd/pkg/azdext"
16+
internalversion "github.com/jongio/azd-app/cli/src/internal/version"
1617
"github.com/mark3labs/mcp-go/mcp"
1718
"github.com/mark3labs/mcp-go/server"
1819
"github.com/spf13/cobra"
@@ -118,7 +119,7 @@ This server complements azd's core MCP capabilities:
118119

119120
// Build MCP server using the azdext SDK builder
120121
// Server name follows azd extension naming convention: {namespace}-mcp-server
121-
builder := azdext.NewMCPServerBuilder("app-mcp-server", Version).
122+
builder := azdext.NewMCPServerBuilder("app-mcp-server", internalversion.Version).
122123
WithRateLimit(burstSize, float64(maxToolCallsPerMinute)/60.0).
123124
WithInstructions(instructions).
124125
WithResourceCapabilities(false, true). // subscribe=false, listChanged=true

cli/src/cmd/app/commands/mcp_resources.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func newAzureYamlResource() server.ServerResource {
5252
}
5353

5454
// Ensure resolved path is still within the validated directory
55-
if !strings.HasPrefix(resolvedPath, validatedDir) {
55+
if !strings.HasPrefix(resolvedPath, validatedDir+string(filepath.Separator)) && resolvedPath != validatedDir {
5656
return nil, fmt.Errorf("azure.yaml path escapes project directory")
5757
}
5858

cli/src/cmd/app/commands/run.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ func runServicesFromAzureYaml(ctx context.Context, azureYamlPath string, runtime
137137
}
138138

139139
// runAzdMode runs services in azd mode with individual service orchestration.
140-
func runAzdMode(_ context.Context, azureYamlPath, azureYamlDir string) error {
140+
func runAzdMode(ctx context.Context, azureYamlPath, azureYamlDir string) error {
141141
cwd, err := os.Getwd()
142142
if err != nil {
143143
return fmt.Errorf("failed to get current directory: %w", err)
@@ -179,7 +179,7 @@ func runAzdMode(_ context.Context, azureYamlPath, azureYamlDir string) error {
179179
}
180180

181181
// Execute and monitor services
182-
return executeAndMonitorServices(runtimes, cwd, azureYaml, azureYamlDir)
182+
return executeAndMonitorServices(ctx, runtimes, cwd, azureYaml, azureYamlDir)
183183
}
184184

185185
// showNoServicesMessage displays a message when no services are defined.
@@ -238,7 +238,7 @@ func detectServiceRuntimes(services map[string]service.Service, azureYamlDir, ru
238238
}
239239

240240
// executeAndMonitorServices starts services and monitors them until interrupted.
241-
func executeAndMonitorServices(runtimes []*service.ServiceRuntime, cwd string, azureYaml *service.AzureYaml, azureYamlDir string) error {
241+
func executeAndMonitorServices(ctx context.Context, runtimes []*service.ServiceRuntime, cwd string, azureYaml *service.AzureYaml, azureYamlDir string) error {
242242
// Create logger
243243
logger := service.NewServiceLogger(runVerbose)
244244
logger.LogStartup(len(runtimes))
@@ -250,7 +250,7 @@ func executeAndMonitorServices(runtimes []*service.ServiceRuntime, cwd string, a
250250
}
251251

252252
// Orchestrate services with dependency ordering
253-
result, err := service.OrchestrateServices(runtimes, azureYaml.Services, envVars, logger, runRestartContainers)
253+
result, err := service.OrchestrateServices(ctx, runtimes, azureYaml.Services, envVars, logger, runRestartContainers)
254254
if err != nil {
255255
return fmt.Errorf("service orchestration failed: %w", err)
256256
}

cli/src/cmd/app/commands/version.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
package commands
22

33
import (
4+
internalversion "github.com/jongio/azd-app/cli/src/internal/version"
45
coreversion "github.com/jongio/azd-core/version"
56
"github.com/spf13/cobra"
67
)
78

8-
// Version is set at build time via -ldflags.
9-
var Version = "dev"
10-
119
// BuildTime is set at build time via -ldflags.
1210
var BuildTime = "unknown"
1311

@@ -18,7 +16,7 @@ var Commit = "unknown"
1816
var VersionInfo = coreversion.New("jongio.azd.app", "azd app")
1917

2018
func init() {
21-
VersionInfo.Version = Version
19+
VersionInfo.Version = internalversion.Version
2220
VersionInfo.BuildDate = BuildTime
2321
VersionInfo.GitCommit = Commit
2422
}

0 commit comments

Comments
 (0)