> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/TecharoHq/Anubis/llms.txt
> Use this file to discover all available pages before exploring further.

# Challenges

> Challenge types, proof-of-work mechanisms, and difficulty settings

Challenges are the core mechanism Anubis uses to distinguish humans from bots. When a request triggers a `CHALLENGE` action, Anubis issues a computational puzzle that is trivial for browsers but expensive for scrapers at scale.

## Challenge Metadata

Every challenge issued by Anubis contains structured metadata:

```go theme={null}
// From lib/challenge/challenge.go:6
type Challenge struct {
    IssuedAt       time.Time         `json:"issuedAt"`
    Metadata       map[string]string `json:"metadata"`
    ID             string            `json:"id"`
    Method         string            `json:"method"`
    RandomData     string            `json:"randomData"`
    PolicyRuleHash string            `json:"policyRuleHash,omitempty"`
    Difficulty     int               `json:"difficulty,omitempty"`
    Spent          bool              `json:"spent"`
}
```

<ParamField path="ID" type="string" required>
  UUID v7 identifier for the challenge. Used for lookups and validation.
</ParamField>

<ParamField path="Method" type="string" required>
  Challenge algorithm: `fast` or `slow` (deprecated).
</ParamField>

<ParamField path="RandomData" type="string" required>
  64 bytes of random data (hex-encoded) that the client must process.
</ParamField>

<ParamField path="Difficulty" type="int" default="0">
  Number of leading zero bits required in the proof-of-work hash (0-64).
</ParamField>

<ParamField path="PolicyRuleHash" type="string">
  Hash of the bot rule that issued this challenge. Used to invalidate tokens when policy changes.
</ParamField>

<ParamField path="Spent" type="bool" default="false">
  Whether this challenge has been solved. Prevents double-spend attacks.
</ParamField>

## Challenge Types

Anubis supports multiple challenge algorithms, registered in the challenge registry:

### Proof-of-Work (Recommended)

<CodeGroup>
  ```yaml Policy Configuration theme={null}
  bots:
    - name: suspicious-traffic
      expression:
        - "req.headers['user-agent'].contains('bot')"
      action: CHALLENGE
      challenge:
        algorithm: fast
        difficulty: 3
  ```

  ```go Registration (lib/challenge/proofofwork/proofofwork.go:19) theme={null}
  func init() {
      chall.Register("fast", &Impl{Algorithm: "fast")
      chall.Register("slow", &Impl{Algorithm: "slow")  // Deprecated
  }
  ```
</CodeGroup>

The proof-of-work challenge requires clients to find a nonce such that:

```
SHA256(randomData + nonce) = hash with N leading zero bits
```

Where N is the configured difficulty.

<Warning>
  The `slow` algorithm is deprecated. Use `fast` for all new deployments. The algorithms are functionally identical; the naming was legacy.
</Warning>

### Meta Refresh

<Info>
  The `metarefresh` challenge uses HTTP meta refresh tags to redirect browsers. This is a passive challenge that doesn't require JavaScript or computation.
</Info>

Registered as `metarefresh` in the challenge registry. Useful for detecting scrapers that don't process HTML meta tags.

### Preact (Interactive)

The `preact` challenge renders an interactive UI component using Preact. This challenge type verifies JavaScript execution and DOM manipulation capabilities.

## Proof-of-Work Mechanism

### Validation Algorithm

When a client submits a solution, Anubis validates it in constant time:

```go theme={null}
// From lib/challenge/proofofwork/proofofwork.go:35
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *chall.ValidateInput) error {
    // Extract parameters
    nonceStr := r.FormValue("nonce")
    elapsedTimeStr := r.FormValue("elapsedTime")
    response := r.FormValue("response")
    
    // Recompute hash
    calcString := fmt.Sprintf("%s%d", challenge, nonce)
    calculated := internal.SHA256sum(calcString)
    
    // Constant-time comparison prevents timing attacks
    if subtle.ConstantTimeCompare([]byte(response), []byte(calculated)) != 1 {
        return chall.ErrFailed
    }
    
    // Verify difficulty requirement
    if !strings.HasPrefix(response, strings.Repeat("0", difficulty)) {
        return chall.ErrFailed
    }
    
    return nil
}
```

<Steps>
  <Step title="Extract Parameters">
    The client submits `nonce`, `elapsedTime`, and `response` (the computed hash).
  </Step>

  <Step title="Recompute Hash">
    Server independently computes `SHA256(randomData + nonce)`.
  </Step>

  <Step title="Constant-Time Comparison">
    Uses `crypto/subtle.ConstantTimeCompare` to prevent timing side-channels.
  </Step>

  <Step title="Verify Difficulty">
    Ensures the hash has the required number of leading zero hex characters.
  </Step>
</Steps>

### Client-Side Solving

The challenge page includes JavaScript that brute-forces the nonce:

```javascript theme={null}
const randomData = "/* from server */";
const difficulty = 3;

let nonce = 0;
const start = performance.now();

while (true) {
  const input = randomData + nonce;
  const hash = await sha256(input);
  
  if (hash.startsWith('0'.repeat(difficulty))) {
    const elapsed = (performance.now() - start) / 1000;
    submitSolution(nonce, hash, elapsed);
    break;
  }
  
  nonce++;
}
```

## Difficulty Settings

Difficulty determines how many leading zero hex characters (4 bits each) are required:

<ResponseField name="Difficulty Range" type="int" default="0">
  Valid range: 0-64

  * **0**: No proof-of-work required (instant pass)
  * **1-3**: Light verification (milliseconds)
  * **4-6**: Moderate difficulty (seconds)
  * **7-10**: Heavy computation (tens of seconds)
  * **11+**: Extreme difficulty (minutes to hours)
</ResponseField>

### Difficulty Performance Table

| Difficulty | Expected Attempts | Average Time (Modern CPU) | Use Case                |
| ---------- | ----------------- | ------------------------- | ----------------------- |
| `0`        | 1                 | Instant                   | Testing only            |
| `1`        | 16                | less than 10ms            | Very light verification |
| `2`        | 256               | \~50ms                    | Light bot deterrent     |
| `3`        | 4,096             | \~500ms                   | **Recommended default** |
| `4`        | 65,536            | \~5s                      | Moderate protection     |
| `5`        | 1,048,576         | \~1min                    | Heavy scraper deterrent |
| `6`        | 16,777,216        | \~15min                   | Extreme protection      |

<Note>
  These times are approximate and vary based on client hardware and JavaScript engine performance.
</Note>

### Configuring Difficulty

<CodeGroup>
  ```yaml Per-Rule Difficulty theme={null}
  bots:
    - name: aggressive-crawler
      user_agent_regex: "(curl|wget|python-requests)"
      action: CHALLENGE
      challenge:
        algorithm: fast
        difficulty: 4  # Harder for known scrapers

    - name: suspicious-behavior
      expression:
        - "req.path.matches('/api/.*')"
      action: CHALLENGE
      challenge:
        algorithm: fast
        difficulty: 2  # Lighter for API endpoints
  ```

  ```yaml Threshold-Based Difficulty theme={null}
  thresholds:
    - name: accumulated-suspicion
      expression: "weight >= 10"
      action: CHALLENGE
      challenge:
        algorithm: fast
        difficulty: 5  # Escalate for high-weight requests
  ```

  ```bash Global Default (CLI) theme={null}
  # Set default difficulty for all challenges
  anubis --difficulty 3
  ```
</CodeGroup>

## Challenge Lifecycle

```mermaid theme={null}
sequenceDiagram
    participant C as Client
    participant A as Anubis
    participant S as Store
    participant U as Upstream
    
    C->>A: GET /protected
    A->>A: Evaluate Policy
    A->>A: Generate UUID v7
    A->>A: Create Random Data
    A->>S: Store Challenge (30m TTL)
    A->>C: Render Challenge Page
    C->>C: Solve Proof-of-Work
    C->>A: POST solution
    A->>S: Fetch Challenge
    A->>A: Validate Solution
    A->>A: Check !Spent
    A->>S: Mark Spent=true
    A->>A: Generate JWT
    A->>C: Set Cookie + Redirect
    C->>A: GET /protected (with JWT)
    A->>A: Validate JWT
    A->>U: Proxy Request
    U->>A: Response
    A->>C: Response
```

<Warning>
  **Double-Spend Protection**: Each challenge can only be solved once. The `Spent` flag prevents replay attacks where an attacker tries to reuse a valid solution.
</Warning>

## Storage Requirements

Challenges are stored with a 30-minute TTL:

```go theme={null}
// From lib/anubis.go:133
if err := j.Set(ctx, "challenge:"+id.String(), chall, 30*time.Minute); err != nil {
    return nil, err
}
```

Storage backends must support:

* **JSON serialization**: Challenges are stored as `store.JSON[challenge.Challenge]`
* **TTL/Expiration**: Automatic cleanup after 30 minutes
* **Atomic updates**: For marking challenges as spent

<Tip>
  For high-traffic deployments, use Valkey/Redis or another distributed store to share challenge state across multiple Anubis instances.
</Tip>

## Challenge Metrics

Anubis exposes Prometheus metrics for monitoring:

```prometheus theme={null}
# Challenges issued by method
anubis_challenges_issued{method="embedded"} 1523
anubis_challenges_issued{method="api"} 42

# Challenges successfully validated
anubis_challenges_validated{method="fast"} 1401

# Failed validation attempts
anubis_failed_validations{method="fast"} 164

# Time taken to solve challenges (histogram)
anubis_challenge_time_taken_seconds{algorithm="fast"}
```

## Error Handling

Challenge validation can fail with specific errors:

```go theme={null}
// From lib/challenge/error.go:9
var (
    ErrFailed        = errors.New("challenge: user failed challenge")
    ErrMissingField  = errors.New("challenge: missing field")
    ErrInvalidFormat = errors.New("challenge: field has invalid format")
)
```

<AccordionGroup>
  <Accordion title="ErrMissingField">
    The client didn't submit required parameters (`nonce`, `elapsedTime`, or `response`).

    **HTTP Status**: 400 Bad Request
  </Accordion>

  <Accordion title="ErrInvalidFormat">
    Parameters have wrong type (e.g., non-numeric `nonce`).

    **HTTP Status**: 400 Bad Request
  </Accordion>

  <Accordion title="ErrFailed">
    The solution is incorrect or doesn't meet difficulty requirements.

    **HTTP Status**: 403 Forbidden
  </Accordion>
</AccordionGroup>

## Best Practices

<CardGroup cols={2}>
  <Card title="Start Low" icon="arrow-down">
    Begin with difficulty 2-3 and increase only if you observe scraper persistence.
  </Card>

  <Card title="Monitor Solve Times" icon="clock">
    Use `anubis_challenge_time_taken_seconds` to ensure challenges aren't frustrating legitimate users.
  </Card>

  <Card title="Different Difficulty by Context" icon="layer-group">
    Use lower difficulty for public pages, higher for sensitive endpoints.
  </Card>

  <Card title="Test Cookie Support" icon="cookie">
    Anubis automatically checks cookie support. Failed cookie tests appear in logs.
  </Card>
</CardGroup>

## Custom Challenge Implementation

You can implement custom challenges by satisfying the `challenge.Impl` interface:

```go theme={null}
// From lib/challenge/interface.go:59
type Impl interface {
    // Setup registers any additional routes with the Impl for assets or API routes.
    Setup(mux *http.ServeMux)

    // Issue a new challenge to the user, called by the Anubis.
    Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *IssueInput) (templ.Component, error)

    // Validate a challenge, making sure that it passes muster.
    Validate(r *http.Request, lg *slog.Logger, in *ValidateInput) error
}
```

Register your implementation:

```go theme={null}
func init() {
    challenge.Register("my-custom-challenge", &MyImpl{)
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Policies" icon="shield" href="/concepts/policies">
    Learn how to configure bot detection rules that trigger challenges
  </Card>

  <Card title="How It Works" icon="gear" href="/concepts/how-it-works">
    Understand the complete request flow and JWT validation
  </Card>
</CardGroup>
