From b8838603dc52392f241896a79b5e5622447e0b69 Mon Sep 17 00:00:00 2001 From: Jon Gallant <2163001+jongio@users.noreply.github.com> Date: Mon, 2 Mar 2026 13:03:29 -0800 Subject: [PATCH 1/3] Security hardening and code quality improvements MQ + Hack dual-model analysis (Opus 4.6 + Codex 5.3) findings and fixes: HIGH: - HTTPS downgrade via redirect following in update-registry.js (CWE-319) - No artifact domain validation - added ALLOWED_ARTIFACT_HOST allowlist (CWE-20) - Response size DoS - added 5MB size limit on artifact downloads (CWE-400) MEDIUM: - Weak checksum acceptance (MD5/SHA1) rejected in validate-registry.js (CWE-328) - Missing CSP headers on website (CWE-16) - Regex injection in update-readme-versions.js - added escapeRegExp (CWE-1333) - Removed unused code in validate-registry.js DRY refactor: extracted compareSemver to shared scripts/lib/semver.js module. Updated dependencies, pinned astro@5.17.3 (5.18.0 Windows build bug). Added azd-rest to install/uninstall/watch scripts. Updated SECURITY.md with GitHub Security Advisories link. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- SECURITY.md | 2 +- docs/archive/security-audit-2025-07-14.md | 556 ++++++++++++++++++++++ package.json | 10 +- pnpm-lock.yaml | 150 ++++-- scripts/install-all.ps1 | 3 +- scripts/lib/semver.js | 49 ++ scripts/uninstall-all.ps1 | 3 +- scripts/update-readme-versions.js | 18 +- scripts/update-registry.js | 79 ++- scripts/validate-registry.js | 41 +- scripts/watch-all.ps1 | 3 +- src/components/ExtensionShowcase.astro | 4 +- src/pages/index.astro | 1 + 13 files changed, 824 insertions(+), 95 deletions(-) create mode 100644 docs/archive/security-audit-2025-07-14.md create mode 100644 scripts/lib/semver.js diff --git a/SECURITY.md b/SECURITY.md index 6a33afb..d7c0f86 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -13,7 +13,7 @@ We release patches for security vulnerabilities for the following versions: If you discover a security vulnerability, please do the following: 1. **Do NOT** open a public issue -2. Email security details to: [your-email@example.com] +2. Use [GitHub Security Advisories](https://github.com/jongio/azd-extensions/security/advisories/new) to privately report a vulnerability 3. Include: - Description of the vulnerability - Steps to reproduce diff --git a/docs/archive/security-audit-2025-07-14.md b/docs/archive/security-audit-2025-07-14.md new file mode 100644 index 0000000..bcf98be --- /dev/null +++ b/docs/archive/security-audit-2025-07-14.md @@ -0,0 +1,556 @@ +# Security Audit: azd-extensions + +**Date**: 2025-07-14 +**Scope**: Full adversarial review of azd-extensions codebase +**Methodology**: STRIDE threat model, OWASP Top 10 (2021), CWE-mapped vulnerability analysis +**Auditor**: SecOps Agent (dual-model analysis) + +--- + +## Executive Summary + +The azd-extensions codebase is a **static website + extension registry** deployed to GitHub Pages. It fetches registry data from external GitHub repositories, aggregates it, and serves it as a JSON file alongside a showcase website. The attack surface is concentrated in three areas: (1) the CI/CD pipeline that builds and publishes the registry, (2) the registry aggregation scripts that fetch and trust external data, and (3) the GitHub Actions supply chain. + +**Overall risk**: MEDIUM. No CRITICAL vulnerabilities found. The most significant risks are supply chain integrity issues in the CI/CD pipeline and missing security headers on the deployed site. + +### Finding Summary + +| Severity | Count | Status | +|----------|-------|--------| +| CRITICAL | 0 | -- | +| HIGH | 3 | Remediation recommended | +| MEDIUM | 5 | Remediation recommended | +| LOW | 4 | Backlog / informational | + +--- + +## Phase 1: Attack Surface Enumeration + +### Entry Points + +| # | Entry Point | Type | Trust Level | +|---|-------------|------|-------------| +| 1 | GitHub Pages static site | Web (HTML/JS/CSS) | Public, unauthenticated | +| 2 | `public/registry.json` | REST endpoint (static JSON) | Public, unauthenticated | +| 3 | GitHub Actions `repository_dispatch` | API trigger | Authenticated (PAT) | +| 4 | GitHub Actions `schedule` (cron) | Timer trigger | Automated | +| 5 | GitHub Actions `workflow_dispatch` | Manual trigger | Repo collaborator | +| 6 | External registry sources (raw.githubusercontent.com) | HTTP fetch | Semi-trusted (upstream repos) | +| 7 | PowerShell scripts (local dev) | Local execution | Developer workstation | +| 8 | Extension submission issue template | GitHub Issues | Public, unauthenticated | + +### Trust Boundaries + +``` +[Public Internet] --> [GitHub Pages CDN] --> [Static HTML + registry.json] + ^ + | (build-time) +[GitHub Actions Runner] --> [fetch from upstream repos] --> [aggregate] --> [deploy] + ^ + | +[Upstream Extension Repos] -- (4 external registry.json files, semi-trusted) +``` + +--- + +## Phase 2: STRIDE Threat Model + +### S - Spoofing + +| Component | Threat | Mitigation Status | +|-----------|--------|-------------------| +| Registry sources | Attacker compromises upstream repo (e.g., jongio/azd-app) and injects malicious registry entries | **PARTIAL** - Checksums verified but no signature verification | +| CI/CD dispatch | Attacker sends forged `repository_dispatch` to trigger malicious builds | **OK** - Requires PAT with repo scope | +| Static site | Attacker spoofs the GitHub Pages domain | **OK** - GitHub enforces HTTPS for *.github.io | + +### T - Tampering + +| Component | Threat | Mitigation Status | +|-----------|--------|-------------------| +| Registry JSON | Attacker tampers with registry.json in transit | **OK** - Served over HTTPS | +| Registry JSON | Attacker tampers with upstream registry data before aggregation | **GAP** - No cryptographic signing of registry entries (see Finding SEC-02) | +| CI/CD pipeline | Attacker modifies build artifacts between build and deploy | **OK** - Same job, no artifact handoff between jobs | +| Checksums | Registry contains checksums but azd CLI verification depends on client implementation | **PARTIAL** - SHA-256 checksums present | + +### R - Repudiation + +| Component | Threat | Mitigation Status | +|-----------|--------|-------------------| +| Registry updates | No audit trail of what changed between registry versions | **PARTIAL** - Git history shows commits by github-actions[bot] | +| Extension submissions | Issue template allows public submissions with no verification | **OK** - Manual review process | + +### I - Information Disclosure + +| Component | Threat | Mitigation Status | +|-----------|--------|-------------------| +| Static site | Source maps or debug info exposed | **OK** - Production build strips debug | +| Error messages | Scripts log URLs and error details to CI logs | **LOW RISK** - Public repo, no secrets in logs | +| Astro version disclosure | `meta[name=generator]` exposes Astro v5.18.0 | **INFORMATIONAL** (see Finding SEC-10) | + +### D - Denial of Service + +| Component | Threat | Mitigation Status | +|-----------|--------|-------------------| +| Registry update script | Upstream source returns extremely large JSON, exhausting CI runner memory | **GAP** - No size limit on fetched data (see Finding SEC-05) | +| HEAD request validation | Attacker-controlled URL causes slow response, blocking CI pipeline | **PARTIAL** - 10s timeout exists | +| GitHub Pages | DDoS against static site | **OK** - GitHub CDN handles this | + +### E - Elevation of Privilege + +| Component | Threat | Mitigation Status | +|-----------|--------|-------------------| +| Publish workflow | `contents: write` permission allows pushing to main | **ACCEPTABLE** - Required for auto-commit pattern | +| Local PowerShell scripts | Scripts run as current user, modify `~/.azd/config.json` | **ACCEPTABLE** - Local dev tooling | + +--- + +## Phase 3: OWASP Top 10 (2021) Coverage + +| # | Category | Status | Details | +|---|----------|--------|---------| +| A01 | Broken Access Control | **N/A** | Static site, no auth required, no protected resources | +| A02 | Cryptographic Failures | **PASS** | SHA-256 checksums on artifacts, HTTPS enforced for artifact URLs | +| A03 | Injection | **PASS** | No XSS vectors found (see detailed analysis below) | +| A04 | Insecure Design | **PASS** | Registry aggregation design is sound; validation pipeline exists | +| A05 | Security Misconfiguration | **FINDING** | Missing CSP, X-Frame-Options, and other security headers (SEC-04) | +| A06 | Vulnerable Components | **PASS** | `pnpm audit` reports 0 vulnerabilities; dependency-review action in CI | +| A07 | Identification & Auth Failures | **N/A** | No authentication in static site | +| A08 | Software & Data Integrity Failures | **FINDING** | GitHub Actions not pinned to SHA (SEC-01); no registry signing (SEC-02) | +| A09 | Security Logging & Monitoring | **N/A** | Static site on GitHub Pages - no server-side logging capability | +| A10 | Server-Side Request Forgery | **PASS** | URLs are hardcoded constants, not user-controlled (see analysis below) | + +--- + +## Phase 4: Detailed Findings + +### SEC-01: GitHub Actions Not Pinned to Commit SHA [HIGH] + +- **CWE**: CWE-829 (Inclusion of Functionality from Untrusted Control Sphere) +- **CVSS**: 8.1 (High) +- **STRIDE**: Tampering, Elevation of Privilege +- **OWASP**: A08 Software and Data Integrity Failures +- **Classification**: [PROPOSE] + +**Files affected**: +- `.github/workflows/ci.yml` lines 19, 22, 25 +- `.github/workflows/publish.yml` lines 28, 31, 37, 45, 82, 85, 91 +- `.github/workflows/codeql.yml` lines 29, 32, 37, 40 +- `.github/workflows/dependency-review.yml` lines 12, 15 +- `.github/workflows/spellcheck.yml` lines 15, 18 + +**Description**: All 17 GitHub Actions `uses:` references use mutable version tags (e.g., `actions/checkout@v4`, `pnpm/action-setup@v4`) instead of immutable commit SHAs. A compromised or hijacked action tag could inject malicious code into the CI/CD pipeline. + +**Attack scenario**: An attacker who compromises the `pnpm/action-setup` repository (or any third-party action) can push malicious code under the existing `v4` tag. The next CI run would execute the attacker's code with `contents: write`, `pages: write`, and `id-token: write` permissions -- enough to push malicious registry entries and deploy them to GitHub Pages. + +**Why this matters for a registry**: The `publish.yml` workflow has `contents: write` and commits directly to `main`. A compromised action could: +1. Modify `public/registry.json` to point artifact URLs at malicious binaries +2. Push the change to `main` +3. Deploy the poisoned registry to GitHub Pages + +Users who then install extensions would download and execute attacker-controlled binaries. + +**Recommended fix**: Pin all actions to full commit SHAs with version comments: +```yaml +# Before +- uses: actions/checkout@v4 +# After +- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 +``` + +--- + +### SEC-02: Registry Data Integrity - No Cryptographic Signing [HIGH] + +- **CWE**: CWE-345 (Insufficient Verification of Data Authenticity) +- **CVSS**: 7.5 (High) +- **STRIDE**: Tampering, Spoofing +- **OWASP**: A08 Software and Data Integrity Failures +- **Classification**: [CONSULT] + +**File**: `scripts/update-registry.js` lines 50-58, `public/registry.json` + +**Description**: The registry aggregation script fetches registry data from 4 upstream GitHub repositories and trusts it without cryptographic verification. The upstream repos are all owned by the same account (`jongio`), but there is no signature verification (e.g., GPG-signed commits, Sigstore cosign, or a manifest signature). + +**Attack scenario**: If an attacker gains write access to any upstream repo (e.g., `jongio/azd-app`), they can modify that repo's `registry.json` to point artifact URLs at malicious binaries with valid-looking checksums. The aggregation script would happily incorporate these entries. The checksum values in the registry are self-reported by the upstream repo -- the build pipeline does not independently compute them. + +**Current mitigations** (partial): +- Artifact URLs must start with `https://` (line 115-116) +- Placeholder checksums (all zeros) are rejected (lines 121-127) +- HEAD requests verify URLs return 200 (lines 136-162) + +**Gap**: None of these prevent an attacker who controls an upstream repo from providing a valid HTTPS URL to a malicious binary with a real SHA-256 checksum for that malicious binary. + +**Recommended approach**: Consider one or more of: +1. Pin upstream registry sources to specific commit SHAs or use signed tags +2. Implement a manual approval gate for registry changes (PR-based flow instead of auto-commit) +3. Add Sigstore/cosign verification for release artifacts + +--- + +### SEC-03: Publish Workflow Auto-Commits to Main Without PR Review [HIGH] + +- **CWE**: CWE-862 (Missing Authorization) +- **CVSS**: 7.2 (High) +- **STRIDE**: Tampering, Elevation of Privilege +- **OWASP**: A08 Software and Data Integrity Failures +- **Classification**: [CONSULT] + +**File**: `.github/workflows/publish.yml` lines 69-79 + +**Description**: The publish workflow runs on a schedule (daily cron) and on `repository_dispatch`. It fetches external data, writes it to `public/registry.json`, commits directly to `main`, and pushes -- all without human review. + +```yaml +git config user.name "github-actions[bot]" +git config user.email "github-actions[bot]@users.noreply.github.com" +git add public/registry.json README.md +git commit -m "chore: update extension registry" +git push +``` + +**Attack scenario**: Combined with SEC-01 and SEC-02, this creates a fully automated attack chain: +1. Compromise upstream repo -> poisoned registry data +2. Wait for cron or trigger `repository_dispatch` -> publish workflow runs +3. Poisoned data auto-committed to main and deployed -> malicious registry live + +**Current mitigations**: The `validate-registry.js` script runs before commit, but it only checks structural validity (semver order, platform coverage, URL reachability, checksum presence) -- not whether the data is authentically from the expected source. + +**Recommended fix**: Consider: +1. Create registry update PRs instead of direct pushes (allows review) +2. Add branch protection rules requiring approvals for changes to `public/registry.json` +3. Add a diff summary to CI logs showing exactly what changed + +--- + +### SEC-04: Missing Security Headers on Static Site [MEDIUM] + +- **CWE**: CWE-1021 (Improper Restriction of Rendered UI Layers) +- **CVSS**: 5.3 (Medium) +- **STRIDE**: Information Disclosure, Spoofing +- **OWASP**: A05 Security Misconfiguration +- **Classification**: [INFORM] + +**Files affected**: `astro.config.mjs`, no `_headers` file, no security headers configuration + +**Description**: The deployed GitHub Pages site has no Content Security Policy (CSP), no X-Frame-Options, no X-Content-Type-Options, no Referrer-Policy, and no Permissions-Policy headers. GitHub Pages does set some baseline headers (HTTPS, HSTS), but application-level headers are absent. + +**Impact**: +- **No CSP**: The page uses inline `