π Secure .NET 8 Web API using GitHub Copilot, CodeQL, Dependabot, and GitHub Actions. Learn to identify and fix vulnerabilities, enforce CI security, and automate safe development practices.
| Component | Description |
|---|---|
| App | .NET 8 Web API (UserApp) |
| Focus | Secure development with CodeQL, Copilot, and GitHub Advanced Security |
| Tools | GitHub Copilot, CodeQL, Dependabot, GitHub Actions, markdownlint |
| Security | Secret Scanning, Push Protection, Custom CodeQL Queries, Org Policies |
| DevOps | CI/CD workflows, vulnerability detection, dependency management |
| Docs | XML Comments, Markdown Docs, Security Policies, GraphQL Querying |
- Scaffold a .NET 8 Web API project (
UserApp) - Implement clean architecture: model, service, controller
- Inject insecure logic (e.g. hardcoded secrets, raw SQL)
- Refactor with Copilot using secure coding practices
- Load secrets via
appsettings.jsonand configuration - Add required NuGet packages (e.g., SqlClient)
- Document APIs with XML comments
- Create
INSTRUCTIONS.mdandCONTRIBUTING.md - Add CI pipeline with GitHub Actions for build/test
- Enable secret scanning, push protection, and dependency graph
- Configure CodeQL static analysis for C#
- Build a custom CodeQL query to detect hardcoded secrets
- Add Dependabot for NuGet security updates
- Enforce organization-wide CodeQL policies (GHES)
- Query vulnerability alerts via GitHub Security GraphQL API
Copilot Prompt:
Provid CLI commands to create a .NET 8 Web API project calledUserAppin the current directory.
Then create the folders:Models,Services, andControllersinside the project folder.
# Create a new .NET 8 Web API project named UserApp
dotnet new webapi -n UserApp
# Create the Models, Services, and Controllers folders inside the UserApp project
mkdir -p UserApp/Models UserApp/Services UserApp/Controllers*Copilot Prompt:
Create the filesUserclass,UserServiceclass, andUserControllerclass
Provide code forUsermodeel,UserServiceandUserControllerwith/api/user?id=endpoint.
public class User
{
public int Id { get; set; }
public string Email { get; set; }
public string Name { get; set; }
}using UserApp.Models;
namespace UserApp.Services
{
public class UserService
{
private static readonly List<User> Users = new()
{
new User { Id = 1, Name = "Alice", Email = "[email protected]" },
new User { Id = 2, Name = "Bob", Email = "[email protected]" }
};
public User? GetUserById(int id)
{
return Users.FirstOrDefault(u => u.Id == id);
}
}
}using Microsoft.AspNetCore.Mvc;
using UserApp.Models;
using UserApp.Services;
namespace UserApp.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
private readonly UserService _userService;
public UserController()
{
_userService = new UserService();
}
[HttpGet]
public IActionResult GetUser([FromQuery] int id)
{
var user = _userService.GetUserById(id);
if (user == null)
{
return NotFound();
}
return Ok(user);
}
}
}Copilot Prompt:
Create anappsettings.jsonfile with a customApiKeyand a connection string namedDefaultfor a local SQL Server.
{
"ApiKey": "sk_secure_configured_123",
"ConnectionStrings": {
"Default": "Server=.;Database=TestDb;Trusted_Connection=True;"
}
}Copilot Prompt:
Add the required lines inProgram.csto enable MVC controller support in a .NET 8 Web API using:builder.Services.AddControllers()andapp.MapControllers().
builder.Services.AddControllers();
// ...existing code...
app.MapControllers();Copilot Prompt:
Run the app
dotnet run --project UserApp/UserApp.csprojCopilot Prompt:
Modify the existingUserControllerto simulate insecure logic:
- Add a hardcoded API key as a static string.
- Add a second GET method that retrieves a user by email using raw SQL string concatenation.
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
private static string API_KEY = "sk_test_123";
// ... existing code ...
// Insecure endpoint: GET /api/user/[email protected]
[HttpGet("email")]
public IActionResult GetUserByEmail([FromQuery] string email)
{
// Insecure: raw SQL query with user input
var query = $"SELECT * FROM Users WHERE Email = '{email}'";
// Simulate execution (do not actually run SQL)
return Ok(new { ApiKey = API_KEY, Query = query });
}
}Copilot Prompt:
- Identify if the SQL logic is vulnerable to injection.
- Refactor the method to use
SqlCommandwith parameterized queries.- Move the hardcoded
API_KEYintoappsettings.json.- Inject the configuration value using
IConfigurationand replace the static key.
{
...
// Secure endpoint: GET /api/user/[email protected]
[HttpGet("email")]
public IActionResult GetUserByEmail([FromQuery] string email)
{
// Secure: use parameterized query
var apiKey = _config["ApiKey"];
var connStr = _config.GetConnectionString("Default");
var query = "SELECT * FROM Users WHERE Email = @email";
// Simulate parameterized SQL command
return Ok(new { ApiKey = apiKey, Query = query, Parameter = email, ConnectionString = connStr });
}
}βΉοΈ Copilot Prompt:
Which NuGet package is required to useSqlConnectionandSqlCommandin a .NET 8 Web API?
Add the correct package using the CLI.
dotnet add package Microsoft.Data.SqlClient- Enables use of ADO.NET classes like
SqlConnectionandSqlCommand - Required to connect to and query SQL Server databases
- Supports secure database access with parameterized queries
- Compatible with .NET 8 and Microsoft SQL Server features
Copilot Prompt:
Add XML documentation toUserControllermethods.
- Include a summary that explains what the method does.
- Describe the query parameter clearly.
- Mention the possible return values and relevant HTTP status codes.
/// <summary>
/// Retrieves a user by their unique ID.
/// </summary>
/// <param name="id">Query parameter: the unique identifier of the user.</param>
/// <returns>
/// 200 OK with user data if found, or 404 Not Found if no user exists for the given ID.
/// </returns>Copilot Prompt:
- "Create INSTRUCTIONS.md with project setup and usage"
- "Generate CONTRIBUTING.md for new developers"
INTRUCTIONS.mdCONTRIBUTING.md
Copilot Prompt:
Create a GitHub Actions workflow.github/workflows/ci.yamlfor a .NET 8 Web API project.
- The project is located in the
UserAppfolder.- Use
dotnet restore,dotnet build, anddotnet test.- Trigger the workflow on
pushandpull_requestto themainbranch.- Use
ubuntu-latestas the runner.
name: .NET CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
- name: Restore
run: dotnet restore UserApp/UserApp.csproj
- name: Build
run: dotnet build UserApp/UserApp.csproj --no-restore
- name: Test
run: dotnet test UserApp/UserApp.csproj --no-build --verbosity normal
Copilot Prompt:
Provide the commit sequence to push the changes to main
git add .
git commit -m ".Net CI"
git push origin mainCopilot Prompt:
Generate step-by-step instructions for enabling GitHub security features:
- Enable Secret Scanning.
- Enable Push Protection under Secret Scanning.
- Enable the Dependency Graph.
- Include optional steps for enabling these features organization-wide (GitHub Enterprise).
Format the steps as clearly labeled bullet points.
-
Go to your repository β Settings β Security β Advanced Security
-
Enable the following:
- β Secret scanning
- β Push protection
- β Dependency graph
- β Dependabot alerts
- β Dependabot security updates
- β Dependabot version updates
- β Code scanning (enable CodeQL analysis for C#)
- β Copilot Autofix (optional, if CodeQL is set up)
-
(Optional): Enable Private vulnerability reporting
Copilot Prompt:
Create a GitHub Actions workflow for CodeQL.github/workflows/codeqlanalysis in a .NET 8 project.
- The project is located in the
UserAppfolder.- Use
dotnet buildto compileUserApp.csproj.- Trigger the workflow on push and pull request to the
mainbranch.- Enable permissions for
contents: readandsecurity-events: write.- Configure CodeQL to analyze C# and include a custom config file (
.github/codeql/config.yml)- The config file should enable
security-extendedqueries and support custom queries.
name: CodeQL Scan
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
security-events: write
jobs:
analyze:
name: CodeQL Analyze C#
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-dotnet@v3
with:
dotnet-version: '8.0.x'
- uses: github/codeql-action/init@v3
with:
languages: csharp
config-file: .github/codeql/config.yml
- run: dotnet build UserApp/UserApp.csproj --configuration Release
- uses: github/codeql-action/analyze@v3
- Analyzes source code for security vulnerabilities (e.g., injection, secrets)
- Enables static analysis during CI to catch issues before merge
- Supports built-in and custom security queries
- Integrates with GitHub Security for inline PR feedback
Copilot Prompt:
Modify the.github/codeql/config.ymlfor a .NET 8 project using CodeQL.
- Enable the
security-extendedquery suite.- Use the custom queries in the
.github/codeql/queriesfolder.- Set the source path to
UserApp/.- Ignore test folders and generated code.
- Use standard YAML formatting with proper indentation.
name: "CodeQL Config"
disable-default-queries: false
queries:
- uses: security-extended
- uses: ./.github/codeql/queries
paths:
- UserApp/
paths-ignore:
- '**/test/**'
- '**/obj/**'
- '**/bin/**'- Customizes CodeQL behavior for your repo
- Enables advanced query packs like
security-extended - Includes your own custom queries (e.g., hardcoded secrets)
- Scopes analysis to relevant folders (e.g.,
UserApp/) - Ignores noisy or irrelevant paths like
test/,obj/, andbin/
π Structure:
.github/
βββ codeql/
βββ config.yml
βββ qlpack.yml
βββ queries/
βββ FindHardcodedSecrets.ql
Copilot Prompt:
Create a custom CodeQL query namedFindHardcodedSecrets.qlfor C# to detect hardcoded secrets.
- Target fields that are initialized with StringLiteral.
- Match field names containing
apiKey,token,secret,password, orauth(case-insensitive).- Match values that resemble secrets, such as those starting with
sk_,token_,apikey_, or 32+ base64-like characters.- Use
FieldandLiteralfrom thecsharpCodeQL library.- Return the matched string literal and a message indicating a hardcoded secret.
- Include standard CodeQL metadata:
@name,@description,@id,@tags,@problem.severity, and@security-severity.
/**
* @name Hardcoded secrets in C# code
* @description Finds string literals that may contain hardcoded secrets.
* @kind problem
* @problem.severity warning
* @security-severity 8.0
* @id cs/hardcoded-secrets
* @tags security, external/cwe/cwe-798
*/
import csharp
from StringLiteral s
where
s.getValue().regexpMatch("(?i)(sk_[a-z0-9]{10,}|api[_-]?key|token|secret|[A-Za-z0-9+/=]{32,})")
select s, "π Possible hardcoded secret: '" + s.getValue() + "'"- Detects hardcoded secrets (API keys, tokens, passwords) in C# source code
- Focuses on fields initialized with suspicious string literals
- Helps identify security risks before code is merged
- Complements GitHub Advanced Security with custom rules
- Can be extended or reused across multiple projects
Copilot Prompt:
Create aqlpack.ymlfile for a custom CodeQL query pack targeting C#.
- Set the name to
userapp/secrets.- Use version
0.0.1.- Mark it as a library.
- Add a dependency on
codeql/csharp-all.
name: userapp/secrets
version: 0.0.1
library: true
dependencies:
codeql/csharp-all: "*"- Defines metadata for the custom query pack (name, version, type)
- Declares dependencies needed to analyze C# code (
codeql/csharp-all) - Allows CodeQL CLI and GitHub Actions to discover and run your custom query
- Enables reuse and packaging of queries in other repositories
- Required for integrating the query into
.github/codeql/config.yml
Copilot Prompt:
Create adependabot.ymlfile to enable daily NuGet dependency updates for a .NET 8 project in theUserAppfolder.
- Limit open PRs to 5.
- Add labels:
dependenciesandautomerge.- Prefix commit messages with
π¦ deps:.
version: 2
updates:
- package-ecosystem: "nuget"
directory: "/UserApp"
schedule:
interval: "daily"
labels:
- "dependencies"
- "automerge"
open-pull-requests-limit: 5
commit-message:
prefix: "π¦ deps:"- Keeps NuGet dependencies in the
UserAppproject up to date - Automatically detects outdated or vulnerable packages
- Opens pull requests with recommended updates
- Reduces manual effort in dependency management
- Helps maintain security posture with regular updates
- Works seamlessly with GitHub Actions and
automergelabels
Copilot Prompt:
- Create a CodeQL policy file at
.github/codeql/org-policy.yml- Enforce detection of hardcoded secrets in
UserApp/**/*.cs- Use custom query:
org/[email protected]/csharp/security/FindHardcodedSecrets.ql- Set severity to
errorand mode toblock- Add a message with secure development remediation steps
# .github/codeql/org-policy.yml
name: "Org-Wide CodeQL Policy"
disable-default-queries: false
queries:
- uses: org/[email protected]/csharp/security/FindHardcodedSecrets.ql
- uses: security-and-quality
languages:
- csharp
paths:
- '**/*.cs'
rules:
- id: cs/hardcoded-secrets
severity: error
paths:
- '**/*.cs'
mode: block
message: |
β Hardcoded secrets detected. Please:
1. Remove embedded credentials
2. Use environment variables or secrets config
3. Follow secure development best practices- β Enforce security standards across all repositories in your GitHub organization
- π Detect hardcoded secrets and other vulnerabilities using custom queries
- π« Block pull requests if violations are found, preventing insecure code merges
- π‘οΈ Centralize policy enforcement using a shared
.github/codeql/org-policy.ymlfile - π Improve code quality and reduce security risks at scale with automated analysis
Copilot Prompt:
Write a GitHub GraphQL query to retrieve open vulnerability alerts for a .NET 8 repository.
- Include NuGet package name, ecosystem, severity, advisory description, and GHSA ID.
- Include file path (
vulnerableManifestPath) and first patched version.- Add timestamps:
createdAt,updatedAt, anddismissedAt.- Include external advisory references (CVE links).
query VulnerabilityAlerts {
repository(owner: "YOUR_ORG", name: "UserApp") {
vulnerabilityAlerts(first: 100, states: OPEN) {
nodes {
vulnerableManifestPath
createdAt
updatedAt
dismissedAt
securityVulnerability {
package {
name
ecosystem
}
severity
advisory {
description
ghsaId
references {
url
}
identifiers {
type
value
}
}
firstPatchedVersion {
identifier
}
}
}
}
}
}- Queries open security vulnerabilities in the repository's dependencies
- Identifies affected NuGet packages declared in
.csprojfiles - Provides metadata like severity, advisory details, and remediation version
- Enables custom reporting, dashboards, or automation scripts
- Useful for organization-wide security enforcement (GitHub Enterprise only)
- Complements CodeQL and Dependabot by offering centralized vulnerability visibility
-
Open GraphQL Explorer
Navigate to https://docs.github.com/en/graphql/overview/explorer -
Sign In with GitHub
Authenticate using a GitHub account that has access to theUserApprepository. -
Copy and Paste the GraphQL Query
Ensure you replace placeholder values likeYOUR_ORGandUserAppwith actual values. -
Click βΆ Run
Review the security vulnerability results returned by the query.
-
Create a Query File
Save your GraphQL query in a file (e.g.,.github/security/vulnerability-query.graphql). -
Generate a GitHub Personal Access Token (PAT)
Ensure it has these scopes:read:orgsecurity_eventsread:packages
-
Export Your Token (optional but recommended)
export GH_TOKEN=ghp_your_personal_access_token -
Execute the curl command
curl -H "Authorization: bearer $GH_TOKEN" \
-H "Content-Type: application/json" \
-X POST \
--data-binary @<(jq -Rs '{query: .}' .github/security/vulnerability-query.graphql) \
https://api.github.com/graphql- View the JSON Response Inspect vulnerability alerts, severity, affected packages, and patch versions.
| Step | Feature | Tool / Prompt | β Outcome |
|---|---|---|---|
| 1 | Scaffold Project | dotnet new webapi |
Ready-to-code API |
| 2 | Clean Architecture | Copilot | Model, Service, Controller |
| 3 | Config + Secrets | Copilot Chat | appsettings + IConfiguration |
| 4 | Vulnerable Logic | Manual | Insecure logic w/ hardcoded key |
| 5 | Copilot Refactor | Copilot Chat | Safer SQL + secrets |
| 6 | NuGet Packages | dotnet add package | Installed SqlClient |
| 7 | XML Docs | /// |
Method doc added |
| 8 | Markdown Docs | Copilot Chat | README + CONTRIB |
| 9 | CI Workflow | GitHub Actions | Build + Test |
| 10 | GitHub Security Settings | GitHub UI | Secret scanning, push protection |
| 11 | CodeQL Scan | GitHub Actions | Static security scan |
| 12 | Custom CodeQL Query | Copilot + CodeQL | Detect secrets in C# |
| 13 | Dependabot | GitHub Config | Auto package updates |
| 14 | Org Policy (GHES) | CodeQL | Enforced org-wide rule |
| 15 | Security API (GHES) | GraphQL | Query vulnerabilities |
This repository demonstrates secure coding practices for .NET 8 Web APIs using GitHub Copilot, CodeQL, Dependabot, and GitHub Advanced Security β with real-world workflows for secret protection, refactoring, and automated CI/CD.