This demo guides instructors through building a secure, layered Spring Boot application (UserApp) using GitHub Copilot and GitHub Security tools. It includes model, repository, service, and controller components with security scans and CI/CD integration.
Prompt:
βUse Spring Initializr to generate a Maven-based Spring Boot project namedUserAppwith:
- Java 21
- Dependencies: Spring Web, Spring Data JPA, H2 Database
- Output as a ZIP
- Unzip it into
/workspaces/ghcp-course/UserApp, then delete the ZIP.β
cd /workspaces/ghcp-course/UserApp
curl https://start.spring.io/starter.zip \
-d dependencies=web,data-jpa,h2 \
-d name=UserApp \
-d artifactId=UserApp \
-d type=maven-project \
-d language=java \
-d javaVersion=21 \
-o UserApp.zip
unzip UserApp.zip
rm UserApp.zipβ Expected Output:
UserApp/ βββ src/ β βββ main/ β β βββ java/com/example/UserApp/ β β βββ resources/ β βββ test/ βββ pom.xml βββ mvnw, mvnw.cmd
Prompt:
Generate a standard Spring Boot architecture for a REST API namedUserAppthat includes:
- Model:
Userentity with fields:id (Long),email (String), andname (String)idshould be the primary key (@Id)- Repository:
UserRepositorythat extendsJpaRepository<User, Long>- Service:
UserServicewith methodgetUserById(Long id)- Controller:
UserControllermapped to/api/user/{id}, returning user info by ID- Use
touchto create the Java class files (in a singlebashdisplay block)- Provide the full Java class code for each component in separate
javablocks
β Expected Output:
com.example.UserApp βββ controller/UserController.java βββ service/UserService.java βββ model/User.java βββ repository/UserRepository.java
Prompt:
βCreateapplication.propertieswith:
app.api.key- H2 DB connection settings
- Hibernate config for auto schema generationβ
β Expected Output (
src/main/resources/application.properties):
app.api.key=sk_configured_123
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create
spring.h2.console.enabled=truePrompt:
βCreate an insecure version ofUserController:
- Use
Statementand string concatenation for SQL query- Hardcode secret in the classβ
β Expected Output (simplified vulnerable controller):
private static final String API_KEY = "sk_test_123";
@GetMapping("/user")
public ResponseEntity<String> getUser(@RequestParam String email) throws SQLException {
Connection conn = DriverManager.getConnection("jdbc:h2:mem:testdb", "sa", "");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users WHERE email = '" + email + "'");
return rs.next() ? ResponseEntity.ok("User Found") : ResponseEntity.notFound().build();
}Prompt:
βIs this vulnerable to SQL injection?β
βRefactor to usePreparedStatement.β
βInject API key using@Valuefrom config.β
β Expected Output:
@Value("${app.api.key}")
private String apiKey;
@GetMapping("/user")
public ResponseEntity<String> getUser(@RequestParam String email) throws SQLException {
Connection conn = DriverManager.getConnection("jdbc:h2:mem:testdb", "sa", "");
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE email = ?");
stmt.setString(1, email);
ResultSet rs = stmt.executeQuery();
return rs.next() ? ResponseEntity.ok("User Found") : ResponseEntity.notFound().build();
}Prompt:
Open thepom.xmlfile
Add the required dependencies to support:
- Spring Web (
spring-boot-starter-web)- Spring Data JPA (
spring-boot-starter-data-jpa)- H2 in-memory database (
h2)These are required to fix compilation errors related to:
jakarta.persistence(for@Entity,@Id)JpaRepositoryFormat the output as a single XML block (to copy into
<dependencies>).
<!-- ...existing code... -->
<dependencies>
<!-- Spring Boot Starter Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- H2 Database (for local testing) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Jakarta Persistence API -->
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.1.0</version>
</dependency>
<!-- ...other dependencies... -->
</dependencies>
<!-- ...existing code... -->Prompt:
Generate Javadoc forUserControllermethods:
- Describe the parameters, return types, and exceptions.
β Expected Output:
/**
* Retrieves a user by email.
* @param email The email address to search for.
* @return 200 OK with user info if found, otherwise 404.
* @throws SQLException if the DB connection fails.
*/Prompt 1 (INSTRUCTIONS.md):
Create newINSTRUCTIONS.mdin the UserApp directory
Provide instructions on setup, how to run, API endpoints, and technologies
Prompt 2 (CONTRIBUTING.md):
Create newCONTRIBUTING.mdin the UserApp directory
Provide a guide for this Spring Boot app using Maven.
Prompt:
Create the file user-ci.yaml in github root direction .github/workflows Generate GitHub Actions workflow for:
- Java 21
mvn clean installon push and pull requestβ- Workflow triggers on changes
β Expected Output (
.github/workflows/user-ci.yml):
name: Java CI
on:
push:
branches: [main]
jobs:
build:
name: Build and Test User App
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'temurin'
- name: Build with Maven
run: mvn clean package
working-directory: UserApp- Go to your repository on GitHub.
- Click on Settings (top right of the repo page).
- In the left sidebar, click Code security.
- Enable the following options:
- Secret scanning π΅οΈββοΈ
- Push protection π¦
- Dependency graph π
- Click Save πΎ if
These steps will enable secret scanning, push protection, and the dependency graph for
- Automated security analysis for Java code
- Runs on push/PR
- Uses built-in and extended security queries
- Checkout code
- Setup JDK 21
- Enable debug logging
- Initialize CodeQL
- Build Java code
- Run security analysis
Prompt: Create CodeQL Workflow
Create the file.github/workflows/codeql.yamlwith the following configuration to analyze Java code securely:
- Use CodeQL v3 to scan Java code
- Trigger analysis on
pushandpull_request- Set up JDK 21 using
temurinwith Maven caching enabled- Enable debug logging and root cause flags for troubleshooting
- Use CodeQL query suites:
security-extendedandsecurity-and-quality- Perform
mvn -B clean compileinsideUserApp- Configure permissions correctly for CodeQL uploads
- Ensure analysis runs on
ubuntu-latest- Include all required steps: checkout, JDK setup, debug mode, CodeQL init, Maven build, CodeQL analyze
β Expected Output (
.github/workflows/codeql.yaml):
name: CodeQL
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
security-events: write
contents: read
jobs:
analyze:
name: CodeQL Analyze Java
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'temurin'
cache: maven
- name: Enable Debug Mode
run: |
echo "ACTIONS_STEP_DEBUG=true" >> $GITHUB_ENV
echo "CODEQL_EXTRACTOR_JAVA_ROOT_CAUSE_ANALYSIS=true" >> $GITHUB_ENV
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: java
config-file: .github/codeql/config.yml
- name: Build with Maven
run: |
cd UserApp
mvn -B clean compile --no-transfer-progress
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:java"
output: results
- name: Debug Info
run: |
echo "Contents of .github/codeql:"
ls -R .github/codeql/
echo "Maven build directory:"
ls -R UserApp/target/
- name: Upload SARIF
uses: actions/upload-artifact@v4
with:
name: codeql-results
path: results/java.sarif
retention-days: 5# Create directory for CodeQL
mkdir -p ~/codeql-home
cd ~/codeql-home
# Download latest release
wget https://github.com/github/codeql-cli-binaries/releases/latest/download/codeql-linux64.zip
# Unzip the package
unzip codeql-linux64.zip
# Add to .bashrc
echo 'export PATH=$PATH:~/codeql-home/codeql' >> ~/.bashrc
source ~/.bashrc
# Check CodeQL version
codeql --version
# Test CLI works
codeql resolve languages
# Download standard query bundle
codeql pack download codeql/java-queries
# Initialize workspace
codeql pack init# Create new database
codeql database create db \
--language=java \
--command "mvn clean compile" \
--source-root UserApp \
--overwrite# Navigate to CodeQL config directory
cd /workspaces/ghcp-course/.github/codeql
# Install dependencies
codeql pack install
```bash
# Navigate to CodeQL config directory
cd /workspaces/ghcp-course/.github/codeql
# Install dependencies
codeql pack install# Install Java analysis pack
codeql pack download codeql/java-all
# Install security queries
codeql pack download codeql/java-queries# Navigate back to project root
cd /workspaces/ghcp-course
# Run the query
codeql query run .github/codeql/queries/FindHardcodedSecrets.ql --database=dbBuild and use a custom CodeQL query in a Java project to detect hardcoded secrets using GitHub Copilot and Actions.
Prompt:
Create the following directory structure for a custom CodeQL Java query pack:
.github/codeql/queries
Inside codeql, create:
qlpack.ymlconfig.yml- A
queries/folder containingFindHardcodedSecrets.ql
β Expected Output:
β Expected Output:
# Create directories
mkdir -p .github/codeql/queries
# Create configuration files
touch .github/codeql/config.yml
touch .github/codeql/qlpack.yml
touch .github/codeql/queries/FindHardcodedSecrets.qlβ Expected Output:
.github/
βββ codeql
β βββ config.yml
β βββ qlpack.yml
β βββ queries
β βββ FindHardcodedSecrets.ql
config.yml-> Configures CodeQL analysis settings, paths, and query selection
name: "CodeQL Config"
disable-default-queries: false
queries:
- uses: security-and-quality
- uses: security-extended
- uses: .
from: userapp/secrets
paths:
- 'UserApp/src/main/java'
paths-ignore:
- '**/test/**'
- '**/generated/**'
- '**/target/**'
query-filters:
- exclude:
tags contain: test
qlpack.yml-> Defines query pack metadata and dependencies for custom queries
name: userapp/secrets
version: 0.0.1
dependencies:
codeql/java-all: "*"Prompt: Create Custom CodeQL Query β Detect Hardcoded Secrets
Create a custom CodeQL query inFindHardcodedSecrets.qlto:
- Detect hardcoded secrets in Java source code
- Match field names containing sensitive keywords (
api_key,token,secret,password)- Find values matching common secret patterns (
sk_*,apikey_*,token_*, base64)- Report detected secrets with their actual values
- Include standard CodeQL metadata
- Ensure the query matches all field initializations with sensitive names and secret-like values
- Fix any missing parentheses or syntax issues
- Report detected secrets with their actual values
β Expected Output:
/**
* @name Find hardcoded secrets
* @description Detects hardcoded secrets in code
* @kind problem
* @problem.severity warning
* @security-severity 8.0
* @id java/hardcoded-secrets
* @tags security
*/
import java
from StringLiteral literal
where
exists(Field field |
// Match field name patterns (case-insensitive)
field.getName().regexpMatch("(?i).*(api_?key|token|secret|password).*") and
// Match field initialization - this links the literal to the field
literal = field.getInitializer() and
// Match common secret patterns in the literal's value
literal.getValue().regexpMatch("(?i).*(sk_.*|apikey_.*|token_.*|[a-zA-Z0-9+/=]{32,})")
)
select
literal,
"Hardcoded secret detected: '" + literal.getValue() + "'"**Prompt:
Provide Test cases for the ql script in User.java model API_KEY, String_TOKEN, SECRET, ENCODED, DESCRIPTION
package com.example.UserApp.model;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
private String name;
// β
Test case: API key pattern
private static final String API_KEY = "sk_test_abc123";
// β
Test case: Token pattern
private static final String TOKEN = "token_1234567890abcdef";
// β
Test case: Secret pattern
private static final String SECRET = "apikey_secretvalue";
// β
Test case: Password pattern
private static final String PASSWORD = "myS3cretP@ssw0rd";
// β
Test case: Base64-like string
private static final String ENCODED = "dGhpcyBpcyBhIHZlcnlMb25nU3RyaW5nQmFzZTY0";
// β Should NOT match (not a sensitive field name)
private static final String DESCRIPTION = "This is a regular description.";
// getters/setters...
}Push this test file and monitor GitHub Actions CodeQL workflow logs for detected secrets
β Expected Output:
Potential hardcoded secret found: sk_test_abc123π§ NOTE: Viewing CodeQL Analysis Results
On GitHub Pro (Public Repos):
- β Results can only be viewed in GitHub Actions CI logs
- β No access to the Security tab for CodeQL scan results
On GitHub Enterprise (GHAS):
Accessing the Security Tab
- Navigate to your repository
- Click the "Security" tab
- Select "Code scanning" from the left sidebar
- View detailed CodeQL analysis results
Available Features
- Full analysis visualization
- Custom query results
- Interactive code navigation
- Vulnerability tracking
- Pull Request (PR) integration
- Historical trend analysis
Key Locations
SecurityβCode scanning alertsSecurityβCode scanning analyses- Pull Request checks with inline annotations
- Security Overview dashboard
Prompt: Create a
.github/dependabot.ymlfile with:
version: 2- Enable updates for
mavenpackages- Set directory to
UserApp(wherepom.xmlresides)- Schedule updates to run daily
- Add dependencies and automerge labels
- Limit number of open PRs
- Configure PR title format
β Expected Output (
.github/dependabot.yml):
version: 2
updates:
- package-ecosystem: "maven"
directory: "UserApp"
schedule:
interval: "daily"
labels:
- "dependencies"
- "automerge"
open-pull-requests-limit: 10
pull-request-branch-name:
separator: "-"
commit-message:
prefix: "π¦ deps:"Prompt:
Inside your organizationβs.githubrepository (e.g.github.com/your-org/.github), create a file named.github/codeql/org-policy.yml.
Define a policy to enforce a custom CodeQL query (FindHardcodedSecrets.ql) across all Java repositories.
The policy should:
- Include the query pack from a central location
- Target Java projects
- Block commits that violate the rule
β Expected Output (
.github/codeql/org-policy.yml):
name: "Organization Security Policy"
disable-default-queries: false
# Query configuration
queries:
- uses: org/codeql-queries@v1.0.0/java/security/FindHardcodedSecrets.ql
- uses: security-and-quality
# Language settings
languages:
- java
# Path filters
paths:
- '**/*.java'
paths-ignore:
- '**/test/**'
- '**/generated/**'
# Enforcement rules
rules:
- id: java/hardcoded-secrets
severity: error
paths:
- '**/*.java'
mode: block
message: |
Hardcoded secrets detected. Please:
1. Remove hardcoded credentials
2. Use GitHub Secrets instead
3. Update configuration to use environment variablesπ‘οΈ NOTE: Purpose of
Organization Security PolicyThis CodeQL configuration (
codeql-config.yml) enforces a centralized security scanning policy across the organization:
π Custom Query Usage
Integrates a custom queryFindHardcodedSecrets.qlfrom an internal org pack (org/codeql-queries@v1.0.0), along with the defaultsecurity-and-qualitysuite.ποΈ Scope Definition
Restricts scanning to.javasource files only, excluding test and generated code paths.π« Policy Enforcement
Applies a blocking rule for thejava/hardcoded-secretsquery:
- Marks any violations as
error- Prevents merges unless hardcoded secrets are removed
π£ Developer Guidance
Displays a message with instructions to:
- Remove hardcoded credentials
- Use GitHub Secrets for sensitive values
- Configure app to use environment variables
π This policy ensures all Java code in the org complies with security best practices by enforcing strict scanning and remediation before code is merged.
Prompt:
Use GitHubβs Security GraphQL API to retrieve open vulnerability alerts from theUserApprepository.
Include:
vulnerableManifestFilename- Package name
- Severity
- Advisory description
β Expected Output (GraphQL Query):
query VulnerabilityAlerts {
repository(owner: "YOUR_USERNAME", name: "ghcp-course") {
vulnerabilityAlerts(first: 100, states: OPEN) {
nodes {
vulnerableManifestPath
securityVulnerability {
package {
name
}
severity
advisory {
description
}
}
}
}
}
}You can retrieve vulnerability alerts (e.g. from CodeQL, Dependabot) using GitHubβs GraphQL API in two main ways:
- Open: GitHub GraphQL Explorer
- Sign in with an account that has access to the repository
- Paste the query in left panel
- Click the "Play" button (
βΆοΈ ) - View results in right panel
π§ NOTE: Required Setup for Security Features
Repository Settings
- Replace
YOUR_USERNAMEwith your actual GitHub username- Replace
ghcp-coursewith your repository nameToken Permissions
read:security_eventsscopereposcope (required for private repositories)security_eventsscopeAccess Requirements
Repository Type Required Access Public Read access Private Write access Security-related Admin access Feature Enablement
- Navigate to your repositoryβs Settings
- Go to Security β Code scanning
- Enable Dependabot alerts
- Enable Security updates
Use this if you want to automate the query or run it from CI tools:
# Set your token
export GITHUB_TOKEN="your_token"
# Run query
curl -H "Authorization: bearer $GITHUB_TOKEN" \
-H "Content-Type: application/json" \
-X POST https://api.github.com/graphql \
-d '{"query":"query { repository(owner: \"YOUR_USERNAME\", name: \"ghcp-course\") { vulnerabilityAlerts(first: 100, states: OPEN) { nodes { vulnerableManifestPath securityVulnerability { package { name } severity advisory { description } } } } } }"}'β Expected Output (GraphQL Query):
{
"data": {
"repository": {
"vulnerabilityAlerts": {
"nodes": [
{
"securityVulnerability": {
"package": {
"name": "org.springframework.boot"
},
"severity": "HIGH",
"advisory": {
"description": "Vulnerability in Spring Boot..."
}
},
"vulnerableManifestPath": "UserApp/pom.xml"
}
]
}
}
}
}| Step | Task | Description |
|---|---|---|
| 1 | Scaffold Project | Generate a Spring Boot app with Java 21, Spring Web, JPA, and H2 using Spring Initializr. |
| 2 | Define Architecture | Implement User, UserRepository, UserService, and UserController components. |
| 3 | Add Configuration | Set up application.properties with API key, H2 DB config, and Hibernate settings. |
| 4 | Add Vulnerable Code | Simulate poor practices with hardcoded secrets and unsafe SQL via Statement. |
| 5 | Refactor with Copilot | Use GitHub Copilot to fix vulnerabilities using @Value and PreparedStatement. |
| 6 | Add Dependencies | Define required dependencies (Spring Web, JPA, H2) in pom.xml. |
| 7 | Generate Javadoc | Use Copilot to auto-generate method-level documentation for the controller. |
| 8 | Add Documentation Files | Create INSTRUCTIONS.md and CONTRIBUTING.md with setup and usage guidelines. |
| 9 | Configure CI Workflow | Add user-ci.yml to run Maven build/test on push and pull request events. |
| 10 | Enable GitHub Security Features | Turn on Secret Scanning, Push Protection, and Dependency Graph in repository settings. |
| 11 | Add CodeQL Scan | Configure codeql.yml workflow with security-extended rules for Java. |
| 12 | Add Custom CodeQL Query | Write FindHardcodedSecrets.ql to detect embedded secrets via regex. |
| 13 | Enable Dependabot Updates | Use dependabot.yml to automate Maven dependency updates with custom settings. |
| 14 | Enforce Org-Wide CodeQL Policy | (GHES) Add org-policy.yml to block hardcoded secrets across all Java repos. |
| 15 | Query Security Alerts via GraphQL | (GHES) Use GitHub GraphQL API to fetch vulnerability alerts and advisories. |
This repository demonstrates secure coding practices for Spring Boot applications using GitHub Copilot, CodeQL, Dependabot, and GitHub Advanced Security β with real-world workflows for layered architecture, vulnerability scanning, and automated CI/CD.