respdiff 0.2.0

Trait-based differential response analysis and probe learning for HTTP scanning.
Documentation
# respdiff

Trait-based differential response analysis and probe learning for HTTP scanning. It compares HTTP responses to find meaningful differences and automatically discovers injection gates from probe histories.

```rust
use std::time::Duration;
use respdiff::{ResponseSnapshot, compare_responses};

let baseline = ResponseSnapshot::new(
    200,
    vec![("Server", "nginx")],
    "hello world"
).with_elapsed(Duration::from_millis(50));

let current = ResponseSnapshot::new(
    500,
    vec![("Server", "nginx"), ("X-Error", "1")],
    "internal error"
).with_elapsed(Duration::from_millis(60));

let diff = compare_responses(&baseline, &current);

if diff.has_differences() {
    println!("Status changed: {}", diff.status_changed);
    println!("New headers: {:?}", diff.new_headers);
    println!("Body similarity: {:.2}", diff.body_similarity);
}
```

## Why this exists

Scanners need to know if a payload actually changed the server's behavior. A raw text diff of two HTTP responses is useless because dynamic tokens, timestamps, and randomized content change on every request. `respdiff` understands HTTP semantics. It diffs headers intelligently, calculates body similarity using Jaccard index, and evaluates timing changes against configurable thresholds.

## Differential policies

Configure the thresholds for what constitutes a meaningful difference.

```rust
use respdiff::{DiffPolicy, is_differential_match_with_policy};

let policy = DiffPolicy {
    timing_threshold_ms: 200,
    similarity_threshold: 0.85,
};

let is_match = is_differential_match_with_policy(&diff, &policy);
```

## Probe learning

The `DifferentialLearner` analyzes a history of observations to identify which input properties act as gates and which ones are injectable. It generates new variants based on successful shapes.

```rust
use std::time::Duration;
use respdiff::{DifferentialLearner, ProbeObservation};

let mut learner = DifferentialLearner::new().with_analyze_every(50);

learner.record(
    [("action", "run"), ("payload", "test")],
    ProbeObservation::matched(Duration::from_millis(5), ["code"])
);

let variants = learner.generate_variants(&["alert(1)"]);
for variant in variants {
    println!("Try properties {:?} because {}", variant.properties, variant.reason);
}
```

## Contributing

Pull requests are welcome. There is no such thing as a perfect crate. If you find a bug, a better API, or just a rough edge, open a PR. We review quickly.

## License

MIT. Copyright 2026 CORUM COLLECTIVE LLC.

[![crates.io](https://img.shields.io/crates/v/respdiff.svg)](https://crates.io/crates/respdiff)
[![docs.rs](https://docs.rs/respdiff/badge.svg)](https://docs.rs/respdiff)