This document outlines common code patterns, conventions, and best practices used throughout the WrongSecrets project.
The project uses two main patterns for implementing challenges:
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
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
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
}
}challenges/docker/- Challenges specific to Docker environmentchallenges/cloud/- Cloud provider challenges (AWS, GCP, Azure)challenges/kubernetes/- Kubernetes and Vault integration challenges
The project uses a layered configuration approach:
application.properties- Core Spring Boot settingsapplication-[profile].properties- Environment-specific overrides
- Environment variables (12-factor app approach)
- Kubernetes ConfigMaps and Secrets
- Cloud provider parameter stores (AWS SSM, GCP Secret Manager, Azure Key Vault)
@Component
public class RuntimeEnvironment {
public enum Environment {
DOCKER, AWS, GCP, AZURE, K8S
}
public Environment getCurrentEnvironment() {
// Auto-detection logic based on environment variables
}
}@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
};
}
}@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"));
}
}@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);
}
}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]-
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
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 }} .- Line length: 100 characters
- Indentation: 2 spaces
- Import organization: Java standard, third-party, static
- Javadoc required for public methods
- Complexity thresholds enforced
- Dead code detection
- Security rule enforcement
- Performance anti-patterns detection
- Code Formatting - Google Java Format
- Linting - ESLint for JavaScript/TypeScript
- Security - Git secrets scanning
- Commit Messages - Conventional commits format
@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);
}
}@ControllerAdvice
public class AllControllerAdvice {
@ExceptionHandler(ChallengeConfigurationException.class)
public ResponseEntity<String> handleChallengeConfigError(
ChallengeConfigurationException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Challenge configuration error");
}
}- Class:
Challenge[Number](e.g.,Challenge44) - Test:
Challenge[Number]Test(e.g.,Challenge44Test) - Endpoint:
/challenge/[number](e.g.,/challenge/44)
- Environment-based:
challenges.[environment](e.g.,challenges.kubernetes) - Feature-based: Clear, descriptive names (e.g.,
oauth,asciidoc)
- Properties:
challenge.[feature].[property](e.g.,challenge.vault.enabled) - Environment variables:
CHALLENGE_[FEATURE]_[PROPERTY](e.g.,CHALLENGE_VAULT_ENABLED)
Each challenge should include:
- Clear problem description
- Hints for solving
- Learning objectives
- Related security concepts
- Public APIs require Javadoc
- Complex logic requires inline comments
- Configuration options documented in properties files
- README files for deployment-specific instructions