Skip to main content

Security Testing

CRITICAL: All authentication, authorization, and user input handling MUST have security tests.

Security Test Categories

CategoryWhat to TestExample Payloads
SQL InjectionAll database queries' OR '1'='1, '; DROP TABLE users; --
Command InjectionAny shell/process calls; cat /etc/passwd, $(whoami), `id`
XSSUser-generated content<script>alert('xss')</script>
Path TraversalFile operations../../../etc/passwd
Auth BypassProtected endpointsMissing/invalid/expired tokens
IDORResource accessAccessing other users' data

SQL Injection Tests

#[tokio::test]
async fn test_sql_injection_vectors() {
let app = create_test_app().await;
let client = TestClient::new(app);

let payloads = vec![
"' OR '1'='1",
"'; DROP TABLE users; --",
"' UNION SELECT * FROM users --",
"1; SELECT * FROM passwords",
"admin'--",
"' OR 1=1 /*",
];

for payload in payloads {
let response = client
.post("/v1/search")
.json(&json!({ "query": payload }))
.send().await;

// Should never return 500 (indicates unhandled SQL error)
assert_ne!(
response.status(),
StatusCode::INTERNAL_SERVER_ERROR,
"SQL injection not handled: {}", payload
);
}
}

Command Injection Tests

#[tokio::test]
async fn test_command_injection_vectors() {
let payloads = vec![
"; cat /etc/passwd",
"| ls -la",
"$(whoami)",
"`id`",
"& ping -c 10 localhost",
"\n/bin/sh",
"|| cat /etc/shadow",
];

for payload in payloads {
let response = client
.post("/v1/process")
.json(&json!({ "filename": payload }))
.send().await;

assert_ne!(
response.status(),
StatusCode::INTERNAL_SERVER_ERROR,
"Command injection not handled: {}", payload
);
}
}

XSS Prevention Tests

Rust

#[tokio::test]
async fn test_xss_payloads_sanitized() {
let payloads = vec![
"<script>alert('xss')</script>",
"<img src=x onerror=alert('xss')>",
"<svg onload=alert('xss')>",
"javascript:alert('xss')",
"<body onload=alert('xss')>",
"'\"><script>alert('xss')</script>",
];

for payload in payloads {
let response = client
.post("/v1/profile")
.json(&json!({ "bio": payload }))
.send().await;

// Should sanitize or escape
assert_ne!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
}

TypeScript

describe('XSS Prevention', () => {
const xssPayloads = [
'<script>alert("xss")</script>',
'<img src=x onerror=alert("xss")>',
'<svg onload=alert("xss")>',
'javascript:alert("xss")',
];

xssPayloads.forEach((payload) => {
it(`escapes XSS payload: ${payload.slice(0, 30)}...`, () => {
render(<UserContent content={payload} />);

// Should not execute any scripts
expect(document.querySelector('script')).toBeNull();
expect(document.querySelector('img[onerror]')).toBeNull();
});
});
});

Path Traversal Tests

#[tokio::test]
async fn test_path_traversal_prevented() {
let payloads = vec![
"../../../etc/passwd",
"..\\..\\..\\windows\\system32\\config\\sam",
"....//....//....//etc/passwd",
"%2e%2e%2f%2e%2e%2f%2e%2e%2fetc/passwd",
"/etc/passwd%00.jpg",
];

for payload in payloads {
let response = client
.get(&format!("/v1/files/{}", payload))
.send().await;

// Should reject path traversal attempts
assert!(
response.status() == StatusCode::BAD_REQUEST ||
response.status() == StatusCode::NOT_FOUND,
"Path traversal not blocked: {}", payload
);
}
}

Authentication Tests

#[tokio::test]
async fn test_auth_required_endpoints() {
let protected_endpoints = vec![
("GET", "/v1/me"),
("GET", "/v1/sessions"),
("POST", "/v1/logout"),
("DELETE", "/v1/account"),
];

for (method, path) in protected_endpoints {
let response = match method {
"GET" => client.get(path).send().await,
"POST" => client.post(path).send().await,
"DELETE" => client.delete(path).send().await,
_ => unreachable!(),
};

assert_eq!(
response.status(),
StatusCode::UNAUTHORIZED,
"{} {} should require auth", method, path
);
}
}

#[tokio::test]
async fn test_invalid_token_rejected() {
let invalid_tokens = vec![
"invalid_token",
"Bearer ",
"Bearer malformed",
"",
];

for token in invalid_tokens {
let response = client
.get("/v1/me")
.header("Authorization", token)
.send().await;

assert_eq!(
response.status(),
StatusCode::UNAUTHORIZED,
"Invalid token should be rejected: {}", token
);
}
}

Authorization Tests (IDOR)

#[tokio::test]
async fn test_user_cannot_access_other_user_data() {
let user1_token = create_token_for_user("user1");
let user2_id = "user2";

let response = client
.get(&format!("/v1/users/{}/profile", user2_id))
.header("Authorization", format!("Bearer {}", user1_token))
.send().await;

assert_eq!(response.status(), StatusCode::FORBIDDEN);
}

#[tokio::test]
async fn test_admin_only_endpoints() {
let user_token = create_token_for_user("regular_user");
let admin_endpoints = vec![
"/v1/admin/users",
"/v1/admin/settings",
"/v1/admin/audit",
];

for endpoint in admin_endpoints {
let response = client
.get(endpoint)
.header("Authorization", format!("Bearer {}", user_token))
.send().await;

assert_eq!(
response.status(),
StatusCode::FORBIDDEN,
"{} should require admin", endpoint
);
}
}

Running Security Tests

# Run all security tests
just test-security

# Run API security tests only
just test-api-security

Best Practices

  1. Test all user inputs - forms, query params, headers, JSON bodies
  2. Test boundary conditions - max lengths, special characters, unicode
  3. Test authentication flows - login, logout, token refresh, 2FA
  4. Test authorization - role checks, resource ownership
  5. Test rate limiting - prevent brute force attacks
  6. Test session management - concurrent sessions, session fixation