Development Patterns
February 28, 2026 ยท View on GitHub
This document outlines common code patterns, conventions, and best practices used throughout the WrongSecrets project.
Related documentation
Challenge Structure Patterns
Challenge Interface vs FixedAnswerChallenge
The project uses two main patterns for implementing challenges:
1. Challenge Interface (Dynamic Answers)
For challenges where the answer depends on calculation or user input:
public interface Challenge {
Spoiler spoiler(); // Returns the secret
boolean answerCorrect(String answer); // Validates user answer
}
Use when:
- Answer requires computation or external data
- Answer changes based on environment or input
- Complex validation logic is needed
2. FixedAnswerChallenge (Static Answers)
For challenges with predetermined, unchanging answers:
public abstract class FixedAnswerChallenge implements Challenge {
// Caches the answer for performance
private Supplier<String> cachedAnswer = Suppliers.memoize(() -> getAnswer());
protected abstract String getAnswer(); // Implement to return fixed answer
}
Use when:
- Answer is hardcoded or from environment variables
- Answer doesn't change during application runtime
- Simple string comparison validation
Challenge Implementation Pattern
All challenges follow this structure:
@Component
public class Challenge[Number] extends FixedAnswerChallenge {
private final RuntimeEnvironment runtimeEnvironment;
public Challenge[Number](RuntimeEnvironment runtimeEnvironment) {
this.runtimeEnvironment = runtimeEnvironment;
}
@Override
public String getAnswer() {
// Implementation specific to challenge
}
@Override
public boolean canRunInCTFMode() {
return true; // or false based on challenge requirements
}
@Override
public RuntimeEnvironment.Environment supportedRuntimeEnvironments() {
return RuntimeEnvironment.Environment.DOCKER; // or appropriate environment
}
}
Challenge Categories by Package
challenges/docker/- Challenges specific to Docker environmentchallenges/cloud/- Cloud provider challenges (AWS, GCP, Azure)challenges/kubernetes/- Kubernetes and Vault integration challenges
Configuration Management Approach
Environment-Specific Configuration
The project uses a layered configuration approach:
1. Base Configuration
application.properties- Core Spring Boot settingsapplication-[profile].properties- Environment-specific overrides
2. External Configuration Sources
- Environment variables (12-factor app approach)
- Kubernetes ConfigMaps and Secrets
- Cloud provider parameter stores (AWS SSM, GCP Secret Manager, Azure Key Vault)
3. Runtime Environment Detection
@Component
public class RuntimeEnvironment {
public enum Environment {
DOCKER, AWS, GCP, AZURE, K8S
}
public Environment getCurrentEnvironment() {
// Auto-detection logic based on environment variables
}
}
Configuration Pattern Example
@Component
public class ExampleChallenge extends FixedAnswerChallenge {
@Value("${challenge.secret:default-value}")
private String secret;
private final RuntimeEnvironment runtimeEnvironment;
@Override
public String getAnswer() {
return switch (runtimeEnvironment.getCurrentEnvironment()) {
case AWS -> getFromParameterStore();
case GCP -> getFromSecretManager();
case AZURE -> getFromKeyVault();
default -> secret; // fallback to configured value
};
}
}
Testing Patterns
Unit Test Pattern
@ExtendWith(MockitoExtension.class)
class Challenge[Number]Test {
@Mock
private RuntimeEnvironment runtimeEnvironment;
private Challenge[Number] challenge;
@BeforeEach
void setUp() {
challenge = new Challenge[Number](runtimeEnvironment);
}
@Test
void shouldReturnCorrectAnswer() {
// Given
when(runtimeEnvironment.getCurrentEnvironment())
.thenReturn(RuntimeEnvironment.Environment.DOCKER);
// When
String answer = challenge.spoiler().solution();
// Then
assertThat(answer).isEqualTo("expected-answer");
assertTrue(challenge.answerCorrect("expected-answer"));
}
}
Integration Test Pattern
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ChallengeControllerIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void shouldReturnChallengeResponse() {
ResponseEntity<String> response = restTemplate.getForEntity(
"/challenge/1", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
}
Workflow Patterns (GitHub Actions)
Common Workflow Structure
All workflows follow this pattern:
name: [Workflow Name]
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
[job-name]:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Cache Maven dependencies
uses: actions/cache@v4
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
- name: [Specific Action]
run: [commands]
Key Workflow Categories
-
Build & Test (
main.yml)- Compilation verification
- Unit and integration tests
- Code quality checks
-
Security Scanning (
scanners.yml,codeql-analysis.yml)- SAST/DAST security analysis
- Dependency vulnerability scanning
- CodeQL analysis
-
Container Testing (
container_test.yml,container-alts-test.yml)- Docker image building and testing
- Multi-platform container validation
-
Deployment Testing (
heroku_tests.yml,minikube-k8s-test.yml)- Platform-specific deployment validation
- Kubernetes deployment testing
Version Synchronization Pattern
Automated version management across files:
- name: Extract version from pom.xml
id: extract-version
run: |
VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
echo "VERSION=${VERSION%-SNAPSHOT}" >> $GITHUB_OUTPUT
- name: Use version in Docker build
run: |
docker build --build-arg argVersion=${{ steps.extract-version.outputs.VERSION }} .
Code Quality Patterns
Checkstyle Configuration
- Line length: 100 characters
- Indentation: 2 spaces
- Import organization: Java standard, third-party, static
- Javadoc required for public methods
PMD Rules
- Complexity thresholds enforced
- Dead code detection
- Security rule enforcement
- Performance anti-patterns detection
Pre-commit Hooks
- Code Formatting - Google Java Format
- Linting - ESLint for JavaScript/TypeScript
- Security - Git secrets scanning
- Commit Messages - Conventional commits format
Error Handling Patterns
Challenge Error Handling
@Override
public String getAnswer() {
try {
return retrieveSecret();
} catch (Exception e) {
log.error("Failed to retrieve secret for challenge", e);
throw new ChallengeConfigurationException(
"Challenge misconfigured: " + e.getMessage(), e);
}
}
Global Exception Handling
@ControllerAdvice
public class AllControllerAdvice {
@ExceptionHandler(ChallengeConfigurationException.class)
public ResponseEntity<String> handleChallengeConfigError(
ChallengeConfigurationException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Challenge configuration error");
}
}
Naming Conventions
Challenge Naming
- Class:
Challenge[Number](e.g.,Challenge44) - Test:
Challenge[Number]Test(e.g.,Challenge44Test) - Endpoint:
/challenge/[number](e.g.,/challenge/44)
Package Naming
- Environment-based:
challenges.[environment](e.g.,challenges.kubernetes) - Feature-based: Clear, descriptive names (e.g.,
oauth,asciidoc)
Configuration Naming
- Properties:
challenge.[feature].[property](e.g.,challenge.vault.enabled) - Environment variables:
CHALLENGE_[FEATURE]_[PROPERTY](e.g.,CHALLENGE_VAULT_ENABLED)
Documentation Patterns
Challenge Documentation
Each challenge should include:
- Clear problem description
- Hints for solving
- Learning objectives
- Related security concepts
Code Documentation
- Public APIs require Javadoc
- Complex logic requires inline comments
- Configuration options documented in properties files
- README files for deployment-specific instructions