armature-framework 0.5.1

A modern, type-safe HTTP framework for Rust inspired by Angular and NestJS. Features dependency injection, decorators, middleware, authentication (JWT/OAuth2/SAML), validation, OpenAPI/Swagger, caching, job queues, and observability.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
# Fuzzing Guide

Guide to fuzz testing Armature for security vulnerabilities and robustness.

## Table of Contents

- [Overview]#overview
- [Quick Start]#quick-start
- [Available Fuzz Targets]#available-fuzz-targets
- [Running Fuzz Tests]#running-fuzz-tests
- [Corpus Management]#corpus-management
- [CI Integration]#ci-integration
- [Writing New Fuzz Targets]#writing-new-fuzz-targets
- [Best Practices]#best-practices

---

## Overview

Armature includes comprehensive fuzz testing using [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz) with libFuzzer. Fuzzing helps discover:

- **Panics**: Unexpected crashes from malformed input
- **Hangs**: Infinite loops or excessive computation
- **Memory issues**: Buffer overflows, use-after-free
- **Logic errors**: Incorrect behavior with edge cases

---

## Quick Start

### Install cargo-fuzz

```bash
cargo install cargo-fuzz
```

### Run a Fuzz Target

Each crate owns its own fuzz targets in `<crate>/fuzz`, so `cargo fuzz` is run
from the crate directory rather than from a single workspace-wide fuzz crate:

```bash
cd armature-core
cargo +nightly fuzz run routing
```

### Run for a Limited Time

```bash
cargo +nightly fuzz run routing -- -max_total_time=60
```

### List What a Crate Has

```bash
cd armature-h1
cargo +nightly fuzz list
```

---

## Available Fuzz Targets

| Crate | Target | Covers |
|-------|--------|--------|
| `armature-core` | `http_request` | Request construction and accessors |
| `armature-core` | `http_response` | Response building |
| `armature-core` | `routing` | Route registration *and* matching, patterns chosen by the fuzzer |
| `armature-core` | `json` | JSON round-tripping |
| `armature-core` | `url_parsing` | Request-line and URI splitting |
| `armature-core` | `headers` | Header-name validation and parsing |
| `armature-core` | `query_params` | Query-string parsing and percent-decoding |
| `armature-core` | `path_params` | Path-parameter extraction against fixed routes |
| `armature-h1` | `parse_head` | Message-head parsing |
| `armature-h1` | `chunked` | Chunked decoding, including split-invariance |
| `armature-h1` | `framing_differential` | Framing decisions compared against hyper |
| `armature-i18n` | `accept_language` | `Accept-Language` negotiation |
| `armature-i18n` | `locale_tag` | Locale-tag parsing, asserting round-trip idempotence |
| `armature-webhooks` | `signature_verify` | HMAC verification: no forgery accepted, no genuine signature rejected |
| `armature-config` | `config_parse` | JSON/TOML/`.env` parsing, each driven with the others' bytes |
| `armature-jwt` | `token_verify` | Token verification: no unissued token accepted |
| `armature-validation` | `validators` | Validators cross-checked against independent implementations |

A target is worth adding where a crate parses or authenticates something it did
not produce. Most crates do neither and have no fuzz directory; an empty
harness would only suggest coverage that does not exist.

---

## Running Fuzz Tests

### Basic Usage

```bash
cd armature-core

# Run specific target
cargo +nightly fuzz run routing

# Run with more parallelism
cargo +nightly fuzz run routing -- -jobs=4 -workers=4

# Run with coverage report
cargo +nightly fuzz coverage routing
```

### Common Options

```bash
# Limit memory usage (MB)
cargo +nightly fuzz run routing -- -rss_limit_mb=2048

# Limit input size (bytes)
cargo +nightly fuzz run routing -- -max_len=4096

# Set random seed for reproducibility
cargo +nightly fuzz run routing -- -seed=12345

# Run for limited iterations
cargo +nightly fuzz run routing -- -runs=10000

# Run for limited time (seconds)
cargo +nightly fuzz run routing -- -max_total_time=300
```

### Running All Targets

```bash
#!/bin/bash
# Run every fuzz target in the repo for 60 seconds each.
set -euo pipefail

for manifest in */fuzz/Cargo.toml; do
    crate="$(dirname "$(dirname "$manifest")")"
    (cd "$crate" && cargo +nightly fuzz list) | while read -r target; do
        echo "== $crate/$target"
        (cd "$crate" && cargo +nightly fuzz run "$target" -- -max_total_time=60)
    done
done
```

---

## Corpus Management

### Seed Corpus

Create initial test cases in `<crate>/fuzz/corpus/<target>/`:

```bash
mkdir -p armature-h1/fuzz/corpus/parse_head

# Add seed files
printf 'GET /api/users HTTP/1.1\r\n\r\n' > armature-h1/fuzz/corpus/parse_head/simple_get
printf 'POST /api/users HTTP/1.1\r\nContent-Length: 15\r\n\r\n{"name":"test"}' > armature-h1/fuzz/corpus/parse_head/post_json
```

### Minimizing Corpus

After fuzzing, minimize the corpus to remove redundant inputs:

```bash
cargo +nightly fuzz cmin parse_head
```

### Sharing Corpus

The corpus is deliberately not version controlled — the root `.gitignore`
excludes `**/fuzz/corpus/`, `**/fuzz/artifacts/` and `**/fuzz/coverage/`,
because a corpus grows without bound and a crash artifact worth keeping belongs
in a test rather than in a directory of opaque binary blobs. Every CI run
therefore starts from an empty corpus and rediscovers coverage from scratch,
which is what the 60-second smoke budget is sized for. A corpus you build
locally is yours to keep locally; promote anything interesting it finds into a
regression test in the owning crate.

---

## CI Integration

### GitHub Actions

Fuzzing already runs in CI: the `fuzz-smoke` job in
[`.github/workflows/ci.yml`](../.github/workflows/ci.yml) fans out one matrix
entry per `{ crate, target }` pair, lints that crate's `fuzz` workspace with
`cargo clippy --all-targets -- -D warnings`, runs the target for 60 seconds and
uploads `<crate>/fuzz/artifacts/` on failure so the crashing input survives the
runner.

Sixty seconds is a regression gate, not a campaign. On pull requests each entry
runs only when the diff touches the code compiled into it — the owning crate,
the shared `armature-core`/`armature-log` roots, the workspace manifest, or the
workflow itself — and runs unconditionally on pushes, on the nightly schedule,
and on PRs into `main` or `release/**`.

Adding a target means adding a line to that matrix; see
[Writing New Fuzz Targets](#writing-new-fuzz-targets).

### OSS-Fuzz Integration

Armature is compatible with [OSS-Fuzz](https://github.com/google/oss-fuzz). See the OSS-Fuzz documentation for continuous fuzzing on Google's infrastructure.

---

## Writing New Fuzz Targets

### 1. Add Target to `<crate>/fuzz/Cargo.toml`

The bin name is the target name `cargo fuzz run` takes, and it must match the
file stem — no `fuzz_` prefix; every existing target is named bare.

```toml
[[bin]]
name = "new_target"
path = "fuzz_targets/new_target.rs"
test = false
doc = false
bench = false
```

### 2. Create the Fuzz Target

```rust
#![no_main]

use arbitrary::Arbitrary;
use libfuzzer_sys::fuzz_target;

/// Input structure for fuzzing
#[derive(Debug, Arbitrary)]
struct FuzzInput {
    field1: String,
    field2: Vec<u8>,
    field3: Option<u32>,
}

fuzz_target!(|data: FuzzInput| {
    // Limit input sizes to prevent OOM
    if data.field1.len() > 10000 || data.field2.len() > 100000 {
        return;
    }

    // Call the code under test
    // Should NOT panic for any valid Arbitrary input
    let result = your_function(&data.field1, &data.field2);

    // Optionally verify invariants
    if let Ok(output) = result {
        assert!(output.len() <= data.field1.len() * 2);
    }
});
```

### 3. Register It in CI

A target nothing runs is a target nothing catches. Add one line to the
`fuzz-smoke` matrix in `.github/workflows/ci.yml`:

```yaml
          - { crate: armature-core, target: new_target }
```

### 4. Add It to the Target Table

Add a row to [Available Fuzz Targets](#available-fuzz-targets) above, saying
what the target covers — the table is how anyone finds out the surface is
already fuzzed before writing a second harness for it.

### 5. Using Arbitrary

The `Arbitrary` derive macro generates random test inputs:

```rust
use arbitrary::Arbitrary;

#[derive(Debug, Arbitrary)]
struct ComplexInput {
    // Primitives
    number: u32,
    text: String,
    bytes: Vec<u8>,

    // Optionals
    maybe: Option<String>,

    // Enums
    choice: Choice,

    // Nested
    nested: Box<NestedInput>,
}

#[derive(Debug, Arbitrary)]
enum Choice {
    A,
    B(String),
    C { value: i32 },
}
```

---

## Best Practices

### 1. Limit Input Size

```rust
fuzz_target!(|data: FuzzInput| {
    // Prevent OOM/timeouts
    if data.bytes.len() > 1_000_000 {
        return;
    }
    // ...
});
```

### 2. Handle Errors Gracefully

```rust
fuzz_target!(|data: FuzzInput| {
    // Code should handle all inputs without panicking
    // Errors are expected and OK
    let _ = parse_input(&data.raw);

    // DON'T use unwrap() - this will cause false positives
    // BAD: let result = parse_input(&data.raw).unwrap();
});
```

### 3. Test Invariants

```rust
fuzz_target!(|data: FuzzInput| {
    // Verify round-trip
    if let Ok(parsed) = parse(&data.raw) {
        let serialized = serialize(&parsed);
        let reparsed = parse(&serialized);
        assert_eq!(parsed, reparsed.unwrap());
    }
});
```

### 4. Focus on Attack Surfaces

Prioritize fuzzing:
- Input parsers (HTTP, JSON, URLs)
- Routing/path matching
- Authentication/authorization
- Serialization/deserialization
- Memory-intensive operations

### 5. Regular Fuzzing

- Run fuzz tests weekly in CI
- Fuzz after major changes to parsing code
- Keep corpus updated with interesting inputs

---

## Reporting Vulnerabilities

If fuzzing discovers a security vulnerability:

1. **Do not** create a public GitHub issue
2. Email quinn.josephr@proton.me with:
   - Description of the issue
   - Reproduction steps (crash input)
   - Potential impact assessment
3. We will respond within 48 hours

---

## Summary

### Quick Commands

```bash
# Install
cargo install cargo-fuzz

# Run one target (from the crate that owns it)
cd armature-core && cargo +nightly fuzz run routing

# Coverage
cargo +nightly fuzz coverage routing

# Minimize corpus (from armature-h1, which owns parse_head)
cargo +nightly fuzz cmin parse_head
```

To run every target in the repo for 60 seconds each, use the script under
[Running All Targets](#running-all-targets) — it walks `*/fuzz/Cargo.toml` and
asks each crate for its own target list.

### Directory Structure

Fuzzing lives beside the code it exercises, one `fuzz/` per crate:

```
armature-core/
├── src/
└── fuzz/
    ├── Cargo.toml       # Its own [workspace]; nightly-only, so the root
    │                    # workspace must not reach it
    ├── fuzz_targets/
    │   ├── routing.rs
    │   ├── headers.rs
    │   └── ...
    ├── corpus/          # Seed and discovered inputs (gitignored)
    │   └── routing/
    └── artifacts/       # Crash reproductions (gitignored)
```

Each fuzz crate declares its own `[workspace]` because `libfuzzer-sys` links a
sanitizer runtime and builds only on nightly — without that, a plain
`cargo build` at the repo root would try to compile it and fail.

---

**Happy fuzzing!** 🐛🔍