Conversation
Bumps and [picomatch](https://github.com/micromatch/picomatch). These dependencies needed to be updated together. Updates `picomatch` from 2.3.1 to 2.3.2 - [Release notes](https://github.com/micromatch/picomatch/releases) - [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md) - [Commits](micromatch/picomatch@2.3.1...2.3.2) Updates `picomatch` from 4.0.3 to 4.0.4 - [Release notes](https://github.com/micromatch/picomatch/releases) - [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md) - [Commits](micromatch/picomatch@2.3.1...2.3.2) --- updated-dependencies: - dependency-name: picomatch dependency-version: 2.3.2 dependency-type: indirect - dependency-name: picomatch dependency-version: 4.0.4 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…-2026-33672) Bumps and [picomatch](https://github.com/micromatch/picomatch). These dependencies needed to be updated together. Updates `picomatch` from 4.0.3 to 4.0.4 - [Release notes](https://github.com/micromatch/picomatch/releases) - [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md) - [Commits](micromatch/picomatch@4.0.3...4.0.4) Updates `picomatch` from 2.3.1 to 2.3.2 - [Release notes](https://github.com/micromatch/picomatch/releases) - [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md) - [Commits](micromatch/picomatch@4.0.3...4.0.4) --- updated-dependencies: - dependency-name: picomatch dependency-version: 4.0.4 dependency-type: indirect - dependency-name: picomatch dependency-version: 2.3.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/cache](https://github.com/actions/cache) from 5.0.3 to 5.0.4. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](actions/cache@cdf6c1f...6682284) --- updated-dependencies: - dependency-name: actions/cache dependency-version: 5.0.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [github/gh-aw](https://github.com/github/gh-aw) from 0.61.0 to 0.63.1. - [Release notes](https://github.com/github/gh-aw/releases) - [Changelog](https://github.com/github/gh-aw/blob/main/CHANGELOG.md) - [Commits](github/gh-aw@9758a19...f0a8321) --- updated-dependencies: - dependency-name: github/gh-aw dependency-version: 0.63.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces a standalone “AgentRC Readiness Scanner” web surface: a static frontend SPA served by a small Express backend that proxies into @agentrc/core to clone and scan repos, with optional report sharing and Azure Container Apps deployment assets.
Changes:
- Added a vanilla JS frontend that routes by URL, triggers scans, and renders CLI-style HTML reports.
- Added an Express backend with scan/config/report routes, rate limiting, storage, and unit tests.
- Added Docker, Azure (Bicep) infrastructure, and dedicated CI/CD workflows; updated root tooling configs to ignore
webapp/**.
Reviewed changes
Copilot reviewed 36 out of 37 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| webapp/frontend/index.html | Frontend shell + scan form + inline theme bootstrap. |
| webapp/frontend/src/main.css | Full styling for the web report and UI. |
| webapp/frontend/src/api.js | Frontend HTTP client for config/scan/share/report fetch. |
| webapp/frontend/src/app.js | Frontend orchestration: routing, scanning, progress/error UI, theme toggle. |
| webapp/frontend/src/report.js | HTML report renderer mirroring CLI structure, includes share UI + service info. |
| webapp/frontend/src/repo-location.js | Repo/shared-report URL parsing + history sync helpers. |
| webapp/frontend/tests/repo-location.test.js | Vitest tests for frontend URL parsing helpers. |
| webapp/docker-compose.yml | Local Docker Compose for the webapp container. |
| webapp/.env.example | Example env config for local dev/deploy. |
| webapp/backend/package.json | Backend package definition, scripts, deps. |
| webapp/backend/vitest.config.js | Backend vitest configuration. |
| webapp/backend/src/server.js | Express app factory + runtime config + static serving + CSP/helmet. |
| webapp/backend/src/middleware/rate-limiter.js | Rate limiting for scan and report endpoints. |
| webapp/backend/src/middleware/error-handler.js | Centralized error mapping to HTTP responses. |
| webapp/backend/src/routes/config.js | /api/config route for frontend runtime config. |
| webapp/backend/src/routes/scan.js | /api/scan route: validate + run scan. |
| webapp/backend/src/routes/report.js | /api/report share + retrieve routes. |
| webapp/backend/src/services/scanner.js | Orchestrates clone + readiness scan + temp dir lifecycle + concurrency + sweeper. |
| webapp/backend/src/services/storage.js | File-based (or in-memory) shared report persistence + TTL cleanup. |
| webapp/backend/src/services/report-validator.js | Normalization/validation of shared report payloads prior to persistence. |
| webapp/backend/src/utils/url-parser.js | SSRF-protected GitHub-only repo URL parsing/validation. |
| webapp/backend/src/utils/cleanup.js | Temp directory creation/removal + stale sweep. |
| webapp/backend/tests/routes.test.js | API route tests with mocked core + temp cleanup. |
| webapp/backend/tests/url-parser.test.js | Unit tests for SSRF-protected repo URL parsing. |
| webapp/backend/tests/storage.test.js | Unit tests for storage (memory mode). |
| webapp/backend/tests/report-validator.test.js | Unit tests for shared report validation/normalization. |
| webapp/backend/tests/cleanup.test.js | Unit tests for temp dir create/remove behavior. |
| Dockerfile.webapp | Multi-stage container image build for backend+frontend. |
| infra/webapp/main.bicep | Azure Container Apps + optional Storage/AppInsights infra definition. |
| infra/webapp/main.bicepparam | Example production parameters for the Bicep template. |
| infra/webapp/main.json | ARM template output (compiled from Bicep). |
| .github/workflows/webapp-ci.yml | Dedicated webapp CI (backend tests + docker build + Trivy). |
| .github/workflows/webapp-cd.yml | Dedicated webapp CD (build/push, scan, deploy, smoke test). |
| eslint.config.js | Root ESLint ignores updated to skip webapp/**. |
| vitest.config.ts | Root vitest excludes updated to skip webapp/**. |
| .prettierignore | Root prettier ignores updated to skip webapp/ and Dockerfile.webapp. |
Files not reviewed (1)
- webapp/backend/package-lock.json: Language not supported
| el.innerHTML = ` | ||
| <div class="hero-level ${levelClass}">${level}</div> | ||
| <div class="hero-info"> | ||
| <div class="hero-name">${report.repo_url ? `<a href="${esc(report.repo_url)}" target="_blank" rel="noopener">${esc(repoLabel)}</a>` : esc(repoLabel)}</div> | ||
| <div class="hero-subtitle">Level ${level}: ${esc(name)} — ${totalPassed} of ${totalChecks} checks passing</div> |
There was a problem hiding this comment.
report.repo_url is interpolated directly into an <a href=...> attribute. Even with HTML escaping, this still allows unsafe schemes like javascript: (and potentially data:) if a shared report contains a malicious repo_url, leading to XSS when viewing shared reports. Validate/whitelist the URL (e.g., only https://github.com/<owner>/<repo>), or construct the link via DOM APIs after verifying protocol/host, and render as plain text when invalid.
| return ` | ||
| <div class="ai-criterion"> | ||
| <div class="ai-criterion-icon ${c.status}">${c.status === "pass" ? "✓" : "✗"}</div> | ||
| <div class="ai-criterion-text"> | ||
| <div class="ai-criterion-title">${icon} ${esc(c.title)}</div> | ||
| <div class="ai-criterion-reason">${c.status === "pass" ? "Detected" : esc(c.reason || "")}</div> | ||
| </div> |
There was a problem hiding this comment.
Untrusted status values from the report are injected into CSS class attributes (e.g., ${c.status}, status-${s.status}) without sanitization. Because shared reports can be user-supplied via the share endpoint, a crafted status containing quotes/whitespace can break out of the attribute and inject HTML/JS (stored XSS). Whitelist allowed statuses (e.g., pass/fail/skip) and map everything else to a safe default before inserting into class names, or avoid innerHTML and set classList programmatically with validated tokens.
| // Validate string lengths to prevent abuse | ||
| for (const c of criteria) { | ||
| if (c.title && c.title.length > MAX_STRING_LEN) { | ||
| throw new ReportValidationError("Criteria title too long."); | ||
| } | ||
| if (c.reason && c.reason.length > MAX_STRING_LEN) { | ||
| throw new ReportValidationError("Criteria reason too long."); | ||
| } | ||
| } | ||
|
|
||
| const normalized = { | ||
| generatedAt, | ||
| isMonorepo, | ||
| apps: Array.isArray(apps) ? apps : [], | ||
| pillars, | ||
| levels, | ||
| achievedLevel, | ||
| criteria, | ||
| extras: Array.isArray(extras) ? extras : [] | ||
| }; | ||
|
|
||
| if (areaReports) normalized.areaReports = areaReports; | ||
| if (policies) normalized.policies = policies; | ||
| if (repo_url) normalized.repo_url = String(repo_url).slice(0, 500); | ||
| if (repo_name) normalized.repo_name = String(repo_name).slice(0, 200); | ||
| if (typeof durationMs === "number" && Number.isFinite(durationMs)) normalized.durationMs = durationMs; |
There was a problem hiding this comment.
normalizeSharedReportResult truncates repo_url/repo_name but does not validate that repo_url is a safe HTTPS GitHub URL or that nested fields like criteria[].status are constrained to expected values. Since these reports are persisted and later rendered into HTML, add strict validation/normalization (scheme/host allowlist for repo_url, enum validation for status, and reject/strip unexpected strings) to prevent stored XSS and other abuse.
| // Build clone URL | ||
| const baseUrl = `https://github.com/${owner}/${repo}.git`; | ||
| const cloneUrl = token | ||
| ? `https://x-access-token:${token}@github.com/${owner}/${repo}.git` | ||
| : baseUrl; | ||
|
|
||
| // Clone | ||
| try { | ||
| await cloneRepo(cloneUrl, tempDir, { | ||
| shallow: true, | ||
| timeoutMs | ||
| }); | ||
| } catch (err) { | ||
| if (err.message?.includes("timed out") || err.message?.includes("timeout")) { | ||
| throw new CloneTimeoutError(); | ||
| } | ||
| throw new GitCloneError(`Failed to clone repository: ${err.message || "unknown error"}`); | ||
| } |
There was a problem hiding this comment.
The clone token is embedded directly into the clone URL (https://x-access-token:<token>@github.com/...). If cloneRepo/git throws an error that includes the remote URL, the token can leak via the propagated error message and server logs. Prefer using @agentrc/core helpers (buildAuthedUrl + setRemoteUrl) and always sanitize error messages before returning them, and reset the repo remote to the non-credential URL immediately after cloning (as core batch does) to minimize exposure.
| "scripts": { | ||
| "start": "node src/server.js", | ||
| "dev": "node --env-file-if-exists=../.env --watch src/server.js", | ||
| "test": "vitest run" | ||
| }, | ||
| "dependencies": { | ||
| "@agentrc/core": "file:../../packages/core", | ||
| "cors": "^2.8.5", | ||
| "express": "^4.21.2", | ||
| "express-rate-limit": "^7.5.0", | ||
| "helmet": "^8.1.0" | ||
| }, | ||
| "devDependencies": { | ||
| "tsx": "^4.21.0", | ||
| "vitest": "^3.1.1" | ||
| } |
There was a problem hiding this comment.
@agentrc/core exports TypeScript entrypoints (e.g., ./src/index.ts). start/dev run plain node src/server.js without --import tsx (or another TS loader), so the backend will fail at runtime when it imports @agentrc/core/services/*. Update the scripts to run with the TS loader, and consider moving tsx from devDependencies to dependencies since it's required in production (CMD node --import tsx ...).
| @@ -0,0 +1,50 @@ | |||
| import { randomUUID } from "node:crypto"; | |||
There was a problem hiding this comment.
Unused import: randomUUID is imported but never referenced in this module. Remove it to avoid lint failures and keep the utility minimal.
| import { randomUUID } from "node:crypto"; |
| export async function scanGitHubRepo( | ||
| owner, | ||
| repo, | ||
| { | ||
| token, | ||
| branch, | ||
| timeoutMs = DEFAULT_CLONE_TIMEOUT_MS, | ||
| maxConcurrent = DEFAULT_MAX_CONCURRENT | ||
| } = {} |
There was a problem hiding this comment.
scanGitHubRepo accepts a branch option (and the route reads branch from the request body) but the branch is never used (no checkout after clone). Either implement branch support (e.g., use @agentrc/core/services/git.checkoutBranch after cloning, with validation) or remove the parameter from the API to avoid misleading clients.
| /** | ||
| * Normalize a repo reference: trim, lower owner, return "owner/repo" or null. | ||
| */ | ||
| export function normalizeRepoReference(value) { | ||
| const ref = parseGitHubReference(value); | ||
| return ref || null; | ||
| } |
There was a problem hiding this comment.
Doc/behavior mismatch: the comment says this function lowercases the owner, but the implementation returns parseGitHubReference unchanged. Either implement the normalization (e.g., lowercase owner component) or update the comment to match current behavior.
| build-and-scan: | ||
| runs-on: ubuntu-latest | ||
| needs: test-backend |
There was a problem hiding this comment.
A frontend test file is added under webapp/frontend/tests, but the Webapp CI workflow only installs/runs backend tests and there is no frontend package/test step. As-is, these tests will never run in CI and can silently rot; add a workflow job to execute the frontend test suite (or relocate/run them as part of an existing test command) so they provide real coverage.
| build-and-scan: | |
| runs-on: ubuntu-latest | |
| needs: test-backend | |
| test-frontend: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: "24" | |
| cache: npm | |
| cache-dependency-path: webapp/frontend/package-lock.json | |
| - name: Install root dependencies | |
| run: npm ci | |
| - name: Install frontend dependencies | |
| run: cd webapp/frontend && npm ci | |
| - name: Run frontend tests | |
| run: cd webapp/frontend && npm test | |
| build-and-scan: | |
| runs-on: ubuntu-latest | |
| needs: [test-backend, test-frontend] |
| app.use( | ||
| helmet({ | ||
| contentSecurityPolicy: { | ||
| directives: { | ||
| defaultSrc: ["'self'"], | ||
| scriptSrc: ["'self'", "'unsafe-inline'"], | ||
| styleSrc: ["'self'", "'unsafe-inline'"], | ||
| imgSrc: ["'self'", "data:"], | ||
| connectSrc: ["'self'"] | ||
| } | ||
| } |
There was a problem hiding this comment.
The CSP explicitly allows 'unsafe-inline' for both script-src and style-src, which significantly weakens the protection (especially given the app uses innerHTML rendering). Consider moving the inline theme bootstrap script into an external module and tightening CSP (nonce/hashes or no inline) so CSP can actually mitigate XSS risks.
…ify docker-compose context
…dling for status, impact, and effort
…d lowercase formatting
…entions for Azure deployment
…owed signal status in report rendering
Bumps @typescript-eslint/eslint-plugin 8.57.1→8.57.2, @typescript-eslint/parser 8.57.1→8.57.2, @vitest/coverage-v8 4.1.0→4.1.1, typescript 5.9.3→6.0.2, vitest 4.1.0→4.1.1
…ry smoke tests - Image was pushed to GHCR but Bicep pulls from ACR added az acr import steps - Security scan lacked GHCR auth added docker/login-action + packages:read - Smoke test had no retries after cold-start restarts added retry loop
* feat(readiness): add APM awareness to ai-tooling pillar Add three new readiness criteria to the ai-tooling pillar that detect APM (Agent Package Manager) usage in repositories: - apm-config (level 2): detects apm.yml manifest presence - apm-locked-deps (level 3): detects apm.lock.yaml (skipped if no config) - apm-ci-integration (level 4): scans CI workflows for microsoft/apm-action or apm audit/install commands Criteria are ordered by level within the pillar (L2 → L3 → L4). Implementation lives in the monolithic readiness.ts (the active source of truth used by the build and tests). Closes microsoft#91 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update packages/core/src/services/readiness.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…crosoft#94) After generating instructions and configs, agentrc init now detects APM and offers to initialize it: - APM installed + no apm.yml: interactive prompt to run `apm init --yes` - APM installed + apm.yml exists: skip (already set up) - APM not installed: light tip suggesting APM with link to repo - --yes/--json mode: skip APM step entirely (non-interactive) This respects tool boundaries — agentrc never writes apm.yml directly, it delegates to `apm init` which handles auto-detection of project metadata. The prompt follows the same @inquirer/prompts pattern already used throughout the init command. Closes microsoft#93 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds a 'Works with APM' section explaining how agentrc-generated instructions integrate with APM for distribution, sharing, and governance across teams. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
) - Parse chat.instructionsFilesLocations, chat.agentFilesLocations, and chat.agentSkillsLocations from .vscode/settings.json and *.code-workspace - Extend stripJsonComments with trailing comma support for JSONC - Fix file-based instructions without areas reporting as pass - Delete monolithic readiness.ts, activate modular readiness/ directory - Add 14 new tests (11 VS Code locations + 3 JSONC trailing commas) - Reject path traversal, absolute paths, and repo root in location settings Co-authored-by: Harald Kirschner <digitarald@gmail.com>
… and criteria arrays fix(report): update report rendering logic to handle edge cases in passed and total values style(progress): replace progress bar with spinner for better UX during repository cloning fix(config): remove appInsightsConnectionString from public config response fix(Dockerfile): ensure core package symlink is recreated after removal fix(bicep): disable admin user for Azure Container Registry and add AcrPull role assignment chore(package-lock): update dependencies and remove unnecessary dev dependencies
fix: resolve frontend path using fileURLToPath for better compatibility fix: enhance theme toggle functionality to handle localStorage errors gracefully
- Changed the start script to run the bundled server from the dist directory. - Added a build script to bundle the application using esbuild. - Introduced a new esbuild configuration file to handle the bundling of the server. - Updated dependencies to include esbuild and adjusted the location of @agentrc/core.
…a fields to prevent XSS fix(report): use safe number handling for app and area summaries in report rendering fix(Dockerfile): include node_modules from deps for backend build
fix(cleanup): streamline error handling in removeTempDir function
…e naming fix(scanner): encode GitHub token in clone URL to prevent issues with special characters
AgentRC Readiness Scanner — Web Surface
A lightweight web application that exposes AgentRC readiness scanning through a browser interface. The webapp leverages
@agentrc/coreservices directly — importingcloneRepofrom@agentrc/core/services/gitandrunReadinessReportfrom@agentrc/core/services/readiness— to be as minimally invasive and standalone as possible. No core logic is duplicated; the webapp is a thin HTTP + UI layer on top of the existing engine.Live App
https://agentrc-webapp.redsand-b56d7588.eastus.azurecontainerapps.io
What's Included
webapp/backend/webapp/frontend/infra/webapp/.github/workflows/webapp-ci.yml.github/workflows/webapp-cd.ymlDockerfile.webapptsxfor@agentrc/coreTypeScript imports at runtime.Architecture
The report renderer matches the CLI
readiness --htmloutput format — same pillars, maturity levels, and color coding.Root CI Compatibility
The webapp is designed to be standalone, but the root CI workflows (ESLint, Prettier, vitest) automatically pick up all files in the repo. Since root
npm installdoesn't installwebapp/backend/node_modules, webapp files would fail in root CI. These minimal changes to root config files ensure the root CI ignores the webapp directory (the webapp has its own dedicated CI workflow):eslint.config.js"webapp/**"to globalignores@typescript-eslint/parserwithparserOptions.project: "./tsconfig.json". Webapp's plain JS files aren't in the root TS project and would fail parsing.vitest.config.tsexclude: ["webapp/**", ...]totestconfigwebapp/backend/tests/*.test.jsbut can't resolve their imports (express,cors, etc.) since those deps live only inwebapp/backend/node_modules.tsconfig.json.prettierignorewebapp/andDockerfile.webappAll root CI checks pass: Lint & Format, Typecheck, Build & Verify, and all 3 test matrices (Node 20/22, Ubuntu/Windows).
Repository Setup Required
To deploy your own instance, configure these GitHub repository secrets (in a
productionenvironment):AZURE_CLIENT_IDAZURE_TENANT_IDAZURE_SUBSCRIPTION_IDGH_TOKEN_FOR_SCANreposcope for private repos, or just public access)The Azure AD app needs Contributor role on the subscription and a federated credential for
repo:<owner>/<repo>:environment:production.Key Design Decisions
webapp/,infra/webapp/, and workflow files@agentrc/coreimports — no wrapper, no abstraction layer; the scanner service calls core functions directlydocker-compose upfor local dev, CD pipeline for AzurecontainerStartupStrategyparameter (currently set tokeep-warm)