Skip to content

Multi-User Testing

Multi-user testing enables cross-role access control validation, IDOR detection, and privilege escalation testing. Credentials are defined in credentials.json at the project root, and every phase of the pentest uses them to test with all configured roles.


credentials.json Format

Never Commit

credentials.json is in .gitignore. Never commit real credentials to version control. Use credentials.json.example as a template.

Mode 1: Pre-Authenticated Sessions

Provide existing session cookies or Bearer tokens. Simplest setup -- paste values from your browser's DevTools.

{
  "users": [
    {
      "role": "admin",
      "username": "admin@example.com",
      "auth": "Cookie: session=abc123def456"
    },
    {
      "role": "user",
      "username": "user@example.com",
      "auth": "Cookie: session=xyz789ghi012"
    },
    {
      "role": "viewer",
      "username": "viewer@example.com",
      "auth": "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
    }
  ],
  "unauthenticated": true
}

Mode 2: Auto-Login

Provide login endpoint details and raw credentials. Sessions are acquired automatically during /context init.

{
  "login": {
    "endpoint": "/api/auth/login",
    "method": "POST",
    "content_type": "application/json",
    "body_template": "{\"email\": \"{{username}}\", \"password\": \"{{password}}\"}",
    "token_extract": "json:access_token",
    "auth_format": "Authorization: Bearer {{token}}"
  },
  "users": [
    {
      "role": "admin",
      "username": "admin@example.com",
      "password": "AdminPass123!"
    },
    {
      "role": "user",
      "username": "user@example.com",
      "password": "UserPass123!"
    }
  ],
  "unauthenticated": true
}

Token Extraction Options

The token_extract field supports multiple response formats:

Value Behavior
json:access_token Extract access_token from JSON response body
json:data.token Extract nested field from JSON response body
cookie:session_id Extract session_id from Set-Cookie header
header:X-Auth-Token Extract X-Auth-Token from response headers

Login Section Requirement

Critical

The credentials.json must include a login section (e.g., "login": {"url": "http://target/login", "method": "form"}) so the crawler can authenticate each role. Without the login field, the crawler runs unauthenticated and misses all protected pages. Always verify login succeeded by checking login_success in results before proceeding.


Multi-Role Testing Workflow

Phase 0: Context Init (/context init)

  • Reads credentials.json
  • Logs in all users via the configured login endpoint
  • Stores validated tokens in discovery/api-tokens.json
  • Populates context.json with role information

Phase 0.5: Walkthrough (/walkthrough)

The Playwright-based crawler navigates every user role defined in credentials.json:

  1. Parallel crawling -- with 2+ roles, launches parallel Docker processes per role using crawler.py --role <name>
  2. Merge results -- combines all role-specific crawl data with crawler.py --merge
  3. Output -- app-map.json, crawled-urls.txt, api-endpoints.txt containing all discovered URLs across all roles

Max 3 Users Per Batch

When crawling 4+ users, split into batches of maximum 3 users. Crawling too many users in a single process causes timeouts and internal errors.

Users Batching
1-3 Single batch
4-6 Batch 1: users 1-3, Batch 2: users 4-6
7-9 Batch 1: users 1-3, Batch 2: users 4-6, Batch 3: users 7-9

Use --role <name> per user or create temporary credentials.json files with max 3 users per batch.

Walkthrough Completeness Gate

All Users Must Be Crawled

The walkthrough is not complete until the app has been crawled with all users in credentials.json. Every user/role must have a successful crawl result in app-map.json. If some users were skipped or failed, go back and crawl them before proceeding to Phase 2.

Verify: roles_tested count in results must match the total user count in credentials.json.


Access Control Testing (/test-access)

Section F: Access Matrix Generation

/test-access Section F produces a comprehensive access matrix mapping every discovered endpoint against every role. The matrix is stored in evidence/access-matrix.md.

Endpoint Admin Manager Employee Unauth
GET /api/users 200 200 403 401
POST /api/users 200 403 403 401
DELETE /api/users/1 200 403 403 401
GET /api/salary 200 200 403 401

Per-Endpoint Mandatory Checks

Every endpoint is tested with all of the following patterns:

  1. Without auth -- expect 401/403
  2. With wrong role (lowest privilege user)
  3. With neighbor ID (+/-1 from valid ID) for IDOR detection
  4. With extra fields in request body (role_id, is_admin, salary_gross)
  5. With negative numeric values
  6. With hidden flags (?debug=1, ?admin=1, ?test=1, ?verbose=1, ?internal=1, ?dev_mode=1, ?env=dev, ?dbg=1, ?trace=1, ?console=1, ?profiler=1, ?show_errors=1, ?sandbox=1, ?staging=1, ?beta=1)
  7. With Content-Type: application/xml (XXE on JSON endpoints)
  8. With Content-Type: multipart/form-data (validation bypass)

Anomaly Detection

The multi-user testing framework automatically flags the following anomalies:

Vertical Privilege Escalation

A lower-privilege role (e.g., employee) successfully accesses an endpoint that should be restricted to a higher-privilege role (e.g., admin). Detected by comparing HTTP status codes across roles in the access matrix.

Horizontal Privilege Escalation (IDOR)

A user successfully accesses or modifies resources belonging to another user of the same privilege level. Detected by testing neighbor IDs (+/-1) and cross-user resource references.

Missing Authentication

An endpoint returns a 200 response without any authentication headers. The unauth sweep tests all discovered endpoints (not just a hardcoded list) across crawled-urls.txt, api-endpoints.txt, and resource-map.json, including POST/PUT/DELETE methods.

Dual Authentication Testing

If the application uses both Bearer token and session-cookie authentication, both are obtained for all roles and tested independently. All discovered endpoints are tested with session cookies as well as Bearer tokens.


Token Management

Loading and Validation

Every test skill loads tokens at startup via the shared boilerplate:

load_tokens()                              # Read discovery/api-tokens.json
validate_token "$ADMIN_TOKEN" "admin"      # Verify token works on a real endpoint

Token Refresh

If validation fails (expired token, wrong type), the boilerplate automatically attempts re-authentication:

refresh_token "$email" "$password" "$target"  # Try /api/v1/auth/login, then /api/v1/auth/token

Refreshed tokens are saved back to api-tokens.json for subsequent skills.

Auth Endpoint Discovery Gate

All Auth Systems Must Be Discovered

Applications may have multiple authentication systems (JWT + Sanctum + session). All auth endpoints must be probed: /api/v1/auth/login, /api/v1/auth/token, /api/auth/login, /oauth/token, etc. Admin email may differ from config -- enumerate, do not assume.


Best Practices

  1. Include all roles -- admin, manager, employee, viewer, and any application-specific roles
  2. Set unauthenticated: true -- always test unauthenticated access as a baseline
  3. Use auto-login when possible -- tokens can be refreshed mid-engagement if they expire
  4. Verify login success -- check login_success in crawl results before proceeding
  5. Test endpoint variants -- both web form (/admin/export/*) and REST API (/api/v1/*/export) variants of the same operation, as access control may differ between them