← Back to posts

Quality gates that actually run: verification and security in the agentic workflow

Part 4 of 7 in the Agentic (.NET) developer workflow series
  1. Turning your AI tool into your pair programming companion
  2. Dependency updates that understand your code
  3. Teaching your AI how to write tests with you
  4. Quality gates that actually run: verification and security in the agentic workflow
  5. Documentation as a first-class concern in your agentic workflow
  6. AI-driven usability testing: a think-aloud study with a team of AI testers
  7. Building and evolving your own AI development skills

Most code quality checks exist in three places: a CI pipeline that runs after you push, a mental checklist you may or may not remember, and post-commit hooks that hit you with a wall of failures right when you thought you were done. The agentic workflow collapses all of these into a single command that runs before the PR, covers both .NET and Angular, and pairs automated scanning with reasoning about what the results mean.

Note: The skills shown here reflect my stack and conventions at the time of writing. They improve over time as the workflow learns from daily use. Your project will have different tools, different security concerns, different quality bars. These are examples of what’s possible, not prescriptions. Fork them, adjust them, or use them as inspiration for your own.

If you’ve read part 1, you know that the workflow’s foundation is a persistent memory system and a skill library that gives your AI agent deep, project-specific context. This post covers two of the most practical skills in that library: /dev-verify and /dev-security.

Neither of these is magic. What they are is a clearly defined checklist that the agent executes consistently, combined with the reasoning capability to understand what the tool output means and fix the problems it finds. The skill defines the structure; the agent’s intelligence adds the depth.

What /dev-verify does

/dev-verify is the unified entry point for post-change verification. Run it after finishing a feature, before creating a PR, or any time you want confidence that the code is in a good state. It auto-detects which stacks are present by looking for *.csproj, *.sln, angular.json, or a package.json with @angular/core. In a project with both, it runs both. You can filter with dotnet or angular if you only want one.

The design choice worth noting: it collects all results before stopping. It doesn’t fix the first issue it finds and ask “what next?”. It runs every phase, builds a complete picture, then presents you with a numbered list and a fix menu.

VERIFICATION REPORT
═══════════════════

  .NET
  ────
  Build:       ✓ pass
  Analyzers:   ✗ 3 warnings
  Format:      ✗ 2 files
  Tests:       ✗ 1 failing
  Security:    ✓ pass

  Angular
  ───────
  Build:       ✓ pass
  TypeScript:  ✓ pass
  Lint:        ✗ 5 issues
  Tests:       ✓ pass (92% coverage)
  Security:    ✓ pass

  Issues
  ──────
  1. [dotnet] Analyzer warnings: CS8602 (null ref) in ProfileService.cs:42
  2. [dotnet] Format violations in ProfileEndpoints.cs
  3. [dotnet] Test failure: CreateProfile_WhenDuplicate_ShouldReturnConflict
  4. [angular] Lint: 5 ESLint issues in profile.component.ts

  ─────────────────────────────────
  Fix?  [a]ll  [1-4] pick  [s]kip

After you respond, it fixes the selected issues and re-runs only the phases that had problems. That re-check loop continues until everything passes or you explicitly skip.

.NET verification phases

The backend skill runs seven phases in sequence. Build comes first — if that fails, nothing else runs.

Phase 1: Build runs dotnet build --no-restore /bl:build.binlog. The binlog flag captures a binary build log for deeper diagnosis if the build fails with non-trivial MSBuild errors. After all phases complete, the binlog is cleaned up automatically (it can be large and contains path information).

Phase 2: Roslyn analyzers runs the build again with /p:TreatWarningsAsErrors=true. This catches every analyzer warning your project has configured — nullable reference violations, async method patterns, custom analyzers, anything that would be a warning under normal build but a CI failure under strict settings. The skill reports all of them and expects you to address the critical ones before proceeding.

Phase 3: Format check runs dotnet format --verify-no-changes. If the --fix flag is passed, it runs dotnet format directly. The check is against your .editorconfig rules, so it catches indentation, brace placement, using directive ordering — whatever your project has configured.

Phase 4: Test suite runs dotnet test --collect:"XPlat Code Coverage". It reports total tests, pass/fail counts, and coverage percentage. When a test fails, the AI agent reads the test, reads the code under test, and fixes the root cause — not the test itself. Format violations get auto-fixed; test failures require understanding why they’re failing.

Phase 4b: Aspire runtime health is optional and runs when a .NET Aspire AppHost is running. It checks resource health, scans console logs for exceptions and stack traces, and checks structured logs for error-severity entries. This catches the class of failures that unit tests miss entirely: DI registration errors, startup crashes, event store connection failures, projection catch-up errors. If Aspire isn’t part of the project or isn’t running, this phase is skipped.

The Aspire MCP server that provides these tools is configured automatically when you run /meta-bootstrap on a project with an Aspire AppHost. No manual setup needed, though you’ll need to restart your AI agent session after bootstrap for the MCP server to become available.

Phase 5: Security scan runs a targeted set of checks: secret grepping in tracked files, hardcoded connection strings, dotnet list package --vulnerable, and deprecated packages. This is a quick automated pass, not a full security review. The dedicated /dev-security skill (covered below) goes much deeper.

Phase 6: Diff review is where the AI agent reads each changed file and looks for the things that automated tools miss. Missing null checks. Unparameterized SQL. async methods without await. EF Core queries that fetch more than they should. New endpoints without authorization attributes. This phase is what distinguishes the skill from a shell script — it applies reasoning to what changed, not just measurements.

Angular verification phases

The frontend skill mirrors the backend’s structure: build first, then static analysis, then tests, then runtime checks.

Phase 1: Production build runs ng build --configuration production. This matters because AOT compilation catches template errors that the development build doesn’t. A component might work perfectly in dev mode and fail to compile for production. Finding that in verification beats finding it at deploy time.

Phase 2: TypeScript type check runs npx tsc --noEmit. Even with strict mode in tsconfig.json, the Angular build can sometimes succeed with type errors that the standalone type check catches. Reporting these separately makes it clear which phase flagged them.

Phase 3: ESLint and Prettier runs ng lint --max-warnings 0 and npx prettier --check. Zero warnings is the threshold. If Stylelint is configured, it runs that too. The --fix flag makes the skill auto-apply fixes for anything that can be fixed automatically.

Phase 4: Test suite uses Angular CLI’s ng test which delegates to Vitest 4.x. Coverage is collected and reported. The skill notes that tests should prefer @testing-library/angular queries (getByRole, getByText) over direct DOM access — and when fixing failing tests, it looks at how tests are written, not just whether they pass.

Phase 5: Service worker verification checks the ngsw-config.json is valid JSON, then confirms the built output actually contains ngsw-worker.js and ngsw.json. A missing service worker in the production build is the kind of thing that gets noticed at 3am when someone reports the app doesn’t work offline.

Phase 5b: Aspire runtime checks works the same as the backend equivalent: resource health for the frontend proxy, console log scan for Angular runtime errors (ExpressionChangedAfterItHasBeenCheckedError, NullInjectorError, NG0 errors, chunk load failures, CORS issues), and API structured log scan to catch 4xx/5xx responses that frontend requests are triggering.

Phase 6: Security scan checks for npm audit vulnerabilities, leftover console.log and debugger statements in source files (excluding specs), and hardcoded URLs or tokens outside environment files.

Phase 7: Diff review applies the same reasoning pass as the backend: uncaught Observable errors, any types that should be properly typed, missing takeUntilDestroyed on subscriptions, missing OnPush change detection where it would be appropriate, hardcoded strings that belong in i18n files.

Flags for different situations

Both stacks support the same flag set:

/dev-verify             # full run, auto-detect stacks
/dev-verify dotnet      # .NET only
/dev-verify angular     # Angular only
/dev-verify --quick     # build + type check only
/dev-verify --full      # all phases (default)
/dev-verify --fix       # skip the fix menu, auto-fix everything

--quick is useful when you want fast feedback during active development. --fix is useful when you trust the changes and want to just clean everything up. The default — full run with fix menu — is the right call before opening a PR.

What /dev-security covers

/dev-security is a separate skill from /dev-verify. The verification skill has a security scan phase, but that’s automated tooling: dotnet list package --vulnerable, npm audit, secret grepping. The security skill is a full OWASP Top 10:2025 review.

The distinction matters. An automated scan tells you whether a known-vulnerable package version is in your dependency tree. It can’t tell you whether your authentication flow is correctly structured, or whether your error handling is leaking internal state to clients. The security skill does both: it runs the automated scans and applies reasoning to the checklist.

Like /dev-verify, it auto-detects stacks and runs both in a mixed project. The output is grouped by severity: Critical, High, Medium, Low, Info. Each finding is tagged with its OWASP category.

SECURITY REVIEW — OWASP Top 10:2025
════════════════════════════════════

  Critical
  ────────
  1. [A04] Hardcoded connection string in appsettings.json:12
  2. [A05] String concatenation in SQL query — OrderRepository.cs:45

  High
  ────
  3. [A01] Missing auth on POST /api/admin/reset — AdminEndpoints.cs:28

  Medium
  ──────
  4. [A02] Security headers missing (X-Frame-Options, CSP)

  Info
  ────
  ✓ A06 Insecure Design: Rate limiting configured
  ✓ A08 Integrity: Anti-forgery tokens enabled

Security fixes work differently from verification fixes. Formatting and lint issues get auto-applied. Security changes always show the proposed change and ask before applying. You want to review a change that affects your auth flow before it goes in.

.NET security: what each category checks

The backend security skill maps its checklist directly to OWASP Top 10:2025 categories. Here’s what each one looks at.

A01 Broken Access Control: Are all endpoints explicitly requiring authorization? The skill checks for new endpoints that are missing .RequireAuthorization() and looks for role or policy checks on sensitive operations. The preferred pattern is opt-out, not opt-in: default everything to authenticated, then explicitly allow anonymous where needed.

A02 Security Misconfiguration: Are security headers configured? Is HTTPS enforced? Is debug mode disabled in production? The skill checks for X-Content-Type-Options, X-Frame-Options, CSP headers, and UseHttpsRedirection().

A03 Supply Chain: dotnet list package --vulnerable and --deprecated. The checklist also covers whether a lock file (packages.lock.json) is committed, whether package sources are restricted to trusted feeds, and whether any wildcard version ranges exist in .csproj files. Supply chain attacks typically target transitive dependencies, not direct ones, so this isn’t just about what you explicitly referenced.

A04 Cryptographic Failures: The skill checks for hardcoded connection strings, API keys, or passwords. The correct pattern is reading from IConfiguration:

// Bad: hardcoded connection string with credentials
var connectionString = "Server=prod-db;Password=secret123";

// Good: loaded from configuration
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");

// Fail fast if missing
if (string.IsNullOrEmpty(connectionString))
    throw new InvalidOperationException("DefaultConnection not configured");

For production, secrets belong in Azure Key Vault, not environment variables or appsettings.json.

A05 Injection: Are all queries parameterized? No string concatenation in ExecuteSqlRawAsync? The skill prefers ExecuteSqlInterpolatedAsync over ExecuteSqlRawAsync because interpolated parameters are safe; raw with concatenation is not. For projects using Event Sourcing with Azure Blob Storage, the focus shifts to blob key validation and event stream access control, since there’s no SQL to inject into.

A06 Insecure Design: Rate limiting. The skill checks whether AddRateLimiter is configured and whether expensive endpoints (search, AI calls, file uploads) have stricter limits than general API endpoints. 429 Too Many Requests responses should include a Retry-After header.

A07 Authentication Failures: Cookie configuration. HttpOnly, Secure, SameSite=Strict, session timeout. These are easy to get wrong and easy to forget to configure when setting up a new auth flow.

A08 Integrity Failures: Anti-forgery tokens for state-changing operations. CI/CD pipeline integrity checks.

A09 Logging and Alerting: Are passwords, tokens, or connection strings appearing in log statements? Are error responses showing stack traces to clients? The correct pattern is detailed logging server-side and generic errors to clients:

// Bad: stack trace exposed to client
catch (Exception ex)
{
    return Problem(detail: ex.ToString());
}

// Good: log the detail, return generic message
catch (Exception ex)
{
    _logger.LogError(ex, "Order creation failed for {OrderId}", orderId);
    return Problem("An error occurred. Please try again.");
}

A10 Exceptional Conditions: Empty catch blocks, missing try/catch on async methods, unhandled task exceptions, missing cancellation token propagation, no timeout policies on external HTTP calls. The skill checks whether a global exception handler (IExceptionHandler) is implemented and whether it returns Problem Details (RFC 7807) format.

Angular security: what each category checks

The frontend security skill targets the same OWASP categories but from the browser’s perspective.

A01 Broken Access Control: Are all protected routes covered by functional route guards? The checklist asks for CanActivateFn guards on every page that requires authentication, and role-based variants for pages requiring specific permissions.

A02 Security Misconfiguration: Source maps disabled in production ("sourceMap": false). CSP headers set server-side. No debug mode in production builds.

A03 Supply Chain: npm audit clean. Lock file (package-lock.json) committed and not regenerated carelessly. No postinstall scripts in dependencies that download external code. Subresource Integrity (SRI) on any CDN-loaded scripts.

A04 Cryptographic Failures: No secrets in Angular bundles. Everything in environment.ts is visible to anyone who opens the browser’s devtools. API keys belong on the server.

// environment.ts
export const environment = {
  apiUrl: 'https://api.example.com',  // OK — just a URL
  // apiKey: 'sk-xxx'                 // Never — visible in bundle
};

A05 Injection: Angular sanitizes template interpolation by default. The risk comes from bypassing it. The skill checks for bypassSecurityTrustHtml, bypassSecurityTrustUrl, or bypassSecurityTrustScript with user input, [innerHTML] bindings that could render unescaped HTML, and eval() or new Function() calls.

A06 Insecure Design: Forms validated with appropriate constraints (max length on text inputs, min/max on numbers, required on non-optional fields). Error states handled gracefully. Client-side validation is for UX; the server always re-validates.

A07 Authentication Failures: The skill checks for the __Host- cookie prefix (which enforces Secure, HttpOnly, SameSite=Strict at the browser level), OAuth2 flows using PKCE with S256 challenge, code_verifier stored in memory or sessionStorage only (not localStorage), and a functional HTTP interceptor that handles 401 redirects and 429 rate-limit UX.

A08 Integrity Failures: XSRF token configuration. Angular’s HttpClient reads the XSRF-TOKEN cookie and sends it as X-XSRF-TOKEN automatically — but the cookie has to be set server-side and the server has to validate the header. The skill checks both sides are configured. It also checks the service worker update strategy, since a stale service worker can serve outdated auth state.

A09 Logging and Alerting: No console.log or console.error in production code with sensitive data. Error messages shown to users are generic. No PII in localStorage or sessionStorage.

A10 Exceptional Conditions: Global error handler (ErrorHandler implementation). HTTP interceptor covering 401, 429, and 5xx responses. Observable chains with catchError. Signal-based async operations that handle error states, not just loading and success.

The frontend skill also includes a section specific to Angular signals, which is worth calling out. Signals exposed in templates are visible in Angular DevTools. The rule is simple: public signals and computed() values should never contain auth tokens, session state, or PII. Keep sensitive data in private methods.

// WRONG — token accessible in DevTools
authToken = computed(() => this.authService.session()?.token);

// CORRECT — sensitive data in private method
private getToken(): string | null {
  return this.authService.session()?.token ?? null;
}

Service worker security

The service worker section deserves specific attention because it’s an area where Angular’s power works against you if you’re not careful. Service workers cache assets and API responses for offline use. If your caching configuration is too broad, it can cache auth-sensitive responses that shouldn’t be stored at all.

The skill checks ngsw-config.json for data groups where auth endpoints are configured with the freshness strategy and short (or zero) maxAge. The performance strategy — cache first, network second — is appropriate for static assets, not for anything that contains user-specific data. The checklist also includes SW cache clearing on logout.

Running both together

The workflow is designed so that you run /dev-verify as your standard pre-PR gate. It includes a security scan phase, but that’s the automated part: vulnerable packages, secret grepping, hardcoded URLs. Run /dev-security separately when you’re implementing authentication, working with user input, creating new API endpoints, or preparing for a production deployment.

Both skills use the same flag pattern:

/dev-security             # full review, auto-detect stacks
/dev-security dotnet      # .NET only
/dev-security angular     # Angular only
/dev-security --checklist # print the pre-deployment checklist only
/dev-security --scan      # automated scans only, no code review
/dev-security --full      # checklist + scans + code review (default)

--checklist is useful before a deployment: it prints the full OWASP Top 10 checklist for both stacks so you can go through it manually. --scan is the automated-only path when you want a quick pass without the full reasoning.

What the skill can and can’t do

This is worth being direct about. The skill defines the checklist. It tells the AI agent what to look for, how to structure the output, what commands to run, and how to apply fixes. The AI agent’s reasoning is what turns dotnet list package --vulnerable output into an assessment of actual risk, or what decides whether a missing null check in a diff review is a critical issue or an acceptable pattern in context.

That means the quality of the review depends partly on the quality of the checklist and partly on what the AI agent knows about .NET and Angular security. For standard patterns, both are solid. For something novel or project-specific, the skill might miss it. That’s why there’s a diff review phase that reads changed files directly — it’s the catch-all for things the structured phases don’t cover.

Neither skill is a replacement for a manual security audit on a high-stakes system. What they are is a consistently executed baseline that catches the common issues before they become CI failures or production incidents. Running them before every PR means you’re not shipping the obvious stuff.

The checklist is also honest about what it doesn’t know. The event sourcing security section only activates if the project uses an EventSourcing library. The Aspire runtime checks only run if the AppHost is running. The skill is specific enough to be useful without pretending to be universal.

CI alignment

One design principle across both skills: the verification phases mirror what GitHub Actions runs. The .NET phases match dotnet restore, dotnet build --configuration Release, dotnet test. The Angular phases match npm ci, ng build --configuration production. If the local verification passes, the CI run should pass too.

This isn’t just about catching failures earlier. It’s about the same failure meaning the same thing locally and in CI. When you get a red build in CI after a green local verification, something is different between the environments, and that difference is worth understanding.

Summary

/dev-verify and /dev-security are the quality gate layer of the agentic workflow. Both skills auto-detect your stacks, run all phases before stopping, and present results with a fix menu. Both run a re-check after fixes to confirm they actually worked.

For verification: build, Roslyn analyzers, format, tests, Aspire health, and diff review for .NET; production build, TypeScript type check, ESLint, Prettier, tests, service worker, Aspire health, and diff review for Angular. For security: OWASP Top 10:2025 coverage for both stacks, with automated scanning plus reasoning about what changed.

The skills are defined as plain markdown files in .claude/skills/. What they do is fixed by the checklist. What they deliver depends on the AI agent applying that checklist to your actual code. That combination, defined structure plus reasoning, is what makes them more useful than a shell script and more consistent than remembering to check manually.

← Back to posts