Skip to content

Commit 0fbbcf6

Browse files
Add CI pipeline with fmt, clippy, and test checks (#23)
* Add CI pipeline with fmt, clippy, and test checks Runs on push to main and PRs targeting main. Three parallel jobs: - cargo fmt --check - cargo clippy -D warnings - cargo test Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix CI failures: remove flaky timing test and run cargo fmt Remove test_vulnerable_comparison_shows_timing_discrepancy — it relies on observable timing differences that are unreliable on CI shared runners. The constant-time verification test remains. Also apply cargo fmt. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Address Copilot review: add --locked flags, reword doc comment - Add --locked to cargo clippy and cargo test in CI to catch Cargo.lock drift - Reword timing test doc comment to be self-contained after removing the paired vulnerable comparison test Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 04f4578 commit 0fbbcf6

2 files changed

Lines changed: 44 additions & 42 deletions

File tree

.github/workflows/ci.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
env:
10+
CARGO_TERM_COLOR: always
11+
12+
jobs:
13+
fmt:
14+
name: Format
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
- uses: dtolnay/rust-toolchain@stable
19+
with:
20+
components: rustfmt
21+
- run: cargo fmt --all -- --check
22+
23+
clippy:
24+
name: Clippy
25+
runs-on: ubuntu-latest
26+
steps:
27+
- uses: actions/checkout@v4
28+
- uses: dtolnay/rust-toolchain@stable
29+
with:
30+
components: clippy
31+
- uses: Swatinem/rust-cache@v2
32+
- run: cargo clippy --locked --all-targets -- -D warnings
33+
34+
test:
35+
name: Test
36+
runs-on: ubuntu-latest
37+
steps:
38+
- uses: actions/checkout@v4
39+
- uses: dtolnay/rust-toolchain@stable
40+
- uses: Swatinem/rust-cache@v2
41+
- run: cargo test --locked

src/auth.rs

Lines changed: 3 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,6 @@ mod tests {
4848
use std::hint::black_box;
4949
use std::time::Instant;
5050

51-
/// Vulnerable comparison that short-circuits on first mismatch (the old behavior).
52-
fn token_eq_vulnerable(a: &str, b: &str) -> bool {
53-
a == b
54-
}
55-
5651
/// Measure the median duration (in nanoseconds) of `iterations` calls to `compare_fn(a, b)`.
5752
fn median_nanos(
5853
a: &str,
@@ -82,43 +77,9 @@ mod tests {
8277
assert!(token_eq("", ""));
8378
}
8479

85-
/// Demonstrates the timing attack vulnerability with standard `==` comparison.
86-
///
87-
/// With `==`, comparing a token that differs at byte 0 is faster than comparing
88-
/// a token that differs only at the last byte, because `==` short-circuits.
89-
/// We measure this as a timing ratio: late_mismatch / early_mismatch.
90-
/// A ratio significantly > 1.0 indicates an observable timing discrepancy.
91-
#[test]
92-
fn test_vulnerable_comparison_shows_timing_discrepancy() {
93-
// Use a long token to amplify the timing difference
94-
let secret = "a]9$kL2#mP7!xR4&wQ8*nJ5^tY1+hF3@vB6%cD0".repeat(8); // 320 chars
95-
let early_mismatch = format!("X{}", &secret[1..]); // differs at byte 0
96-
let late_mismatch = format!("{}X", &secret[..secret.len() - 1]); // differs at last byte
97-
98-
let iterations = 50_000;
99-
100-
// Warm up
101-
median_nanos(&secret, &early_mismatch, token_eq_vulnerable, 1_000);
102-
median_nanos(&secret, &late_mismatch, token_eq_vulnerable, 1_000);
103-
104-
let t_early = median_nanos(&secret, &early_mismatch, token_eq_vulnerable, iterations);
105-
let t_late = median_nanos(&secret, &late_mismatch, token_eq_vulnerable, iterations);
106-
107-
let ratio = t_late as f64 / t_early.max(1) as f64;
108-
eprintln!("Vulnerable ==: early={t_early}ns late={t_late}ns ratio={ratio:.2}");
109-
110-
// Standard == should show the late mismatch taking noticeably longer.
111-
// On most systems the ratio is 2-10x+. We use a conservative threshold.
112-
assert!(
113-
ratio > 1.2,
114-
"Expected vulnerable == to show timing discrepancy (ratio {ratio:.2} <= 1.2). \
115-
This may fail on unusual hardware; the important thing is the next test passes."
116-
);
117-
}
118-
119-
/// Verifies that `token_eq` (constant-time) does NOT exhibit the same timing discrepancy.
120-
///
121-
/// The ratio of late_mismatch / early_mismatch should be close to 1.0.
80+
/// Verifies that `token_eq` (constant-time) takes the same time regardless of
81+
/// where the mismatch occurs. The ratio of late_mismatch / early_mismatch
82+
/// should be close to 1.0, indicating no timing leak.
12283
#[test]
12384
fn test_constant_time_comparison_no_timing_discrepancy() {
12485
let secret = "a]9$kL2#mP7!xR4&wQ8*nJ5^tY1+hF3@vB6%cD0".repeat(8);

0 commit comments

Comments
 (0)