Skip to main content

Security Testing

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

Where security tests live

Real Rust security tests live in the individual crates' tests/ directories — for example crates/heimdall-auth/tests/security_test.rs and crates/heimdall-rest/tests/. The platform/api binary is pure glue and has no tests of its own.

The create_test_app() / TestClient / client symbols used in the snippets below are illustrative pseudocode for an HTTP-style harness; they do not exist in the codebase. Treat them as a template for the payloads and assertions to cover, and adapt them to the public API of the crate you are testing.

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

The bulk of Rust security coverage runs as part of the crate test suites (e.g. cargo test -p heimdall-auth, cargo test -p heimdall-rest) and the workspace-wide just test-rust. Look in each crate's tests/ directory — notably crates/heimdall-auth/tests/security_test.rs and crates/heimdall-rest/tests/ — for the concrete security tests.

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