Skip to content

ci: add fmt/clippy/test pipeline and weekly cargo audit#24

Merged
AndreaDiazCorreia merged 4 commits into
mainfrom
feat/ci-cd-pipeline
May 7, 2026
Merged

ci: add fmt/clippy/test pipeline and weekly cargo audit#24
AndreaDiazCorreia merged 4 commits into
mainfrom
feat/ci-cd-pipeline

Conversation

@AndreaDiazCorreia
Copy link
Copy Markdown
Member

@AndreaDiazCorreia AndreaDiazCorreia commented May 7, 2026

Closes #12.

Replaces the single-job rust.yml with two workflows that follow standard Rust CI practice:

  • .github/workflows/ci.yml — runs fmt, clippy -- -D warnings, and test --all-features as parallel jobs on every push to main and every PR. ~/.cargo and target/ are cached via Swatinem/rust-cache@v2.
  • .github/workflows/audit.yml — runs cargo audit --deny warnings weekly (Mondays 06:00 UTC), on demand via workflow_dispatch, and on PRs/pushes that touch Cargo.toml / Cargo.lock.

To enable clippy -- -D warnings from day one, this branch also clears the 37 pre-existing warnings: real fixes in src/crypto/mod.rs (needless borrows, redundant pattern matching, module-level allow(deprecated) for the chacha20poly1305 0.10 → generic-array 1.x deprecation), removal of unused imports, and #[allow(dead_code)] with one-line justifications on items kept intentionally for future phases (placeholder Config fields, reserved UnifiedPushService API, TokenStore::count, the utils::batching module, etc.).

Local verification: cargo fmt --check, cargo clippy --all-targets --all-features -- -D warnings, and cargo test --all-features all pass (42/42 tests).

Summary by CodeRabbit

  • New Features

    • API responses now include unique request ID headers for improved request traceability.
  • Improvements

    • Strengthened registration validation with stricter pubkey and token requirements.
    • Implemented trusted Mostro pubkey whitelist enforcement during registration.
    • Standardized unregister endpoint response format for consistency.
  • Chores

    • Expanded GitHub Actions workflows with automated dependency audit checks.

…precated APIs

Removes 11 needless borrows on Copy arrays passed to hex::encode and
Sha256::digest, replaces 'if let Err(_)' with .is_err(), and adds a
module-level #![allow(deprecated)] (with explanatory comment) to cover
the chacha20poly1305 0.10 / generic-array 0.14 deprecation pending the
upgrade to generic-array 1.x in the encrypted-token registration phase.
…d_code)

Removes five truly-unused imports (reqwest::Client x2, redundant super::*
in two test modules, Arc) and silences the remaining dead_code /
unused_variables lints on items kept intentionally for future phases or
API symmetry: Config placeholder fields (rate_limit, crypto, NostrConfig
plumbing fields, batching knobs, RateLimitConfig, CryptoConfig),
RATE_LIMIT_CLEANUP_INTERVAL_DEFAULT_SECS / PUBKEY_LIMITER_SOFT_CAP_DEFAULT
constants, DispatchOutcome::Delivered.backend, ServiceAccount.project_id,
the FcmPush::new(config, ...) parameter (kept for symmetry with
UnifiedPushService::new), UnifiedPushService.config + reserved
register/unregister/save_endpoints methods, TokenStore::count, and the
utils::batching module (gated like the existing utils-level pattern for
crypto). Each annotation carries a one-line comment explaining why the
item is kept.
…udit

Closes #12. The legacy rust.yml only ran a single build+test job. ci.yml
splits work into three parallel jobs (fmt, clippy with -D warnings,
test --all-features --no-fail-fast), all using Swatinem/rust-cache@v2
for ~/.cargo and target/ caching, and runs on push to main and on every
pull_request to main. audit.yml runs cargo audit --deny warnings on a
weekly cron (Mondays 06:00 UTC), via workflow_dispatch, and on PRs/pushes
that touch Cargo.toml or Cargo.lock.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

Warning

Rate limit exceeded

@AndreaDiazCorreia has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 49 minutes and 30 seconds before requesting another review.

To continue reviewing without waiting, purchase usage credits in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7b6e6b5d-9ae1-4ba4-8d9f-03080e56c7ed

📥 Commits

Reviewing files that changed from the base of the PR and between b82cf76 and bfb11c5.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • .cargo/audit.toml
  • .github/workflows/audit.yml

Walkthrough

A GitHub Actions CI/CD pipeline is established with automated checks for code formatting, linting, testing, and security audits. The API handlers add strict validation for trade pubkeys and tokens, implement a conditional trusted Mostro pubkey whitelist, and standardize response bodies. The codebase is prepared for stricter compiler checks through reserved fields and deprecation/dead-code suppressions.

Changes

CI/CD Setup and API Validation Infrastructure

Layer / File(s) Summary
CI/CD Workflows
.github/workflows/audit.yml, .github/workflows/ci.yml
New GitHub Actions workflows enforce cargo fmt --check, cargo clippy (warnings-as-errors), cargo test (all features), and cargo audit --deny warnings on push/PR to main with weekly audit scheduling and Rust cache management.
Configuration & Reserved Fields
src/config.rs
Config, NostrConfig, and PushConfig gain reserved/future-use fields (rate_limit, subscription_id, event_kinds, batch_delay_ms, crypto) marked with #[allow(dead_code)] to suppress unused warnings.
API Validation & Whitelist
src/api/routes.rs
/api/register validates trade_pubkey (64 hex chars), enforces non-empty token, parses platform case-insensitively, and conditionally applies a compile-time trusted Mostro pubkey whitelist (lowercase-canonicalized) with distinct 403/400 outcomes. /api/unregister standardizes success responses for both removed and not-found cases. Tests verify byte-identical JSON, no rate-limiting on health/status/info, and full whitelist behavior matrix.
Rate Limit & Notify
src/api/rate_limit.rs, src/api/notify.rs
Rate limit introduces RATE_LIMIT_CLEANUP_INTERVAL_DEFAULT_SECS constant with #[allow(dead_code)]. Notify tests validate x-request-id middleware overwrites client headers with fresh UUIDs for both 202 and 400 responses.
Compiler Warnings & Deprecations
src/crypto/mod.rs, src/push/dispatcher.rs, src/push/fcm.rs, src/push/unifiedpush.rs, src/store/mod.rs, src/utils/mod.rs
Crate-level #![allow(deprecated)] suppresses upstream deprecation warnings. Field/method-level #[allow(dead_code)] and #[allow(unused_variables)] annotations mark reserved future APIs (DispatchOutcome::Delivered.backend, ServiceAccount.project_id, FcmPush::new.config, UnifiedPushService endpoints, TokenStore::count, batching module) with comments.
Workflow Migration
.github/workflows/rust.yml
Old generic Rust workflow removed in favor of structured, modular CI jobs.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • MostroP2P/mostro-push-server#1: Implements the foundational POST /api/notify endpoint, request-id middleware, push dispatcher, and core config/store/utils infrastructure that directly underpin the API validation and configuration changes in this PR.
  • MostroP2P/mostro-push-server#23: Introduces the trusted Mostro pubkey whitelist feature and compile-time allowlist logic that matches the whitelist validation logic added to /api/register in this PR.

Poem

🐰 Whiskers twitched with glee
CI checks now run so free,
fmt, clippy, tests aligned—
Dead code marked, no warnings bind!
Mostro's keys pass whitelist's gate,
Security sealed, the project's great!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: adding CI/CD workflows for fmt, clippy, test, and weekly cargo audit, matching the commit and PR objectives.
Linked Issues check ✅ Passed All coding requirements from issue #12 are met: CI workflow added with fmt/clippy/test jobs [#12], cargo caching implemented [#12], weekly cargo audit workflow added [#12], and lint failure enforcement via -D warnings [#12].
Out of Scope Changes check ✅ Passed Changes beyond CI/CD setup include cleanup work (removing unused imports, adding #[allow(dead_code)] annotations) necessary to satisfy clippy -D warnings, which is directly required by issue #12's acceptance criteria.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ci-cd-pipeline

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/push/unifiedpush.rs (1)

76-119: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update async error bounds for multithreading compatibility.

The methods save_endpoints, register_endpoint, and unregister_endpoint return Result<(), Box<dyn std::error::Error>>. Per project guidelines, async fallible operations must use Box<dyn std::error::Error + Send + Sync> to ensure compatibility with multithreaded async contexts. Add + Send + Sync bounds to the three error types.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/push/unifiedpush.rs` around lines 76 - 119, Update the async function
error bounds to be multithread-safe: change the return types of save_endpoints,
register_endpoint, and unregister_endpoint from Result<(), Box<dyn
std::error::Error>> to Result<(), Box<dyn std::error::Error + Send + Sync>> so
the boxed error is Send + Sync for multithreaded async contexts; ensure the
function signatures for async fn save_endpoints(&self) -> ..., async fn
register_endpoint(&self, device_id: String, endpoint_url: String) -> ..., and
async fn unregister_endpoint(&self, device_id: &str) -> ... are updated
accordingly.
src/config.rs (1)

128-132: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

server_private_key fallback is a known secp256k1 key — use an empty default instead.

"0000...0001" is the smallest valid secp256k1 private key and a well-known test vector. While Config.crypto is #[allow(dead_code)] today, when phase 4 removes that annotation and the field is consumed by crypto operations, any deployment that starts without SERVER_PRIVATE_KEY set will silently use a publicly known key. An attacker who knows the server's public key (derived from this private key) could forge or decrypt traffic depending on the protocol used.

Using unwrap_or_default() (empty string) instead means any phase-4 activation path that tries to parse the key will get an immediate, obvious failure rather than a silently operational but compromised key.

🔒 Proposed fix
-            crypto: CryptoConfig {
-                server_private_key: env::var("SERVER_PRIVATE_KEY").unwrap_or_else(|_| {
-                    "0000000000000000000000000000000000000000000000000000000000000001".to_string()
-                }),
-            },
+            // Phase 4 MUST validate this is non-empty before first crypto use.
+            crypto: CryptoConfig {
+                server_private_key: env::var("SERVER_PRIVATE_KEY").unwrap_or_default(),
+            },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/config.rs` around lines 128 - 132, The current fallback for
CryptoConfig.server_private_key uses a well-known secp256k1 test vector string;
replace this insecure hardcoded default by returning an empty string when the
env var is missing (e.g., use
env::var("SERVER_PRIVATE_KEY").unwrap_or_default()) so attempts to parse/use the
key in CryptoConfig (and Config.crypto) will fail loudly instead of silently
using a public test key; update the server_private_key initialization
accordingly and ensure any downstream parsing will surface the missing-key
error.
🧹 Nitpick comments (1)
src/api/rate_limit.rs (1)

55-62: ⚡ Quick win

Move .max(1) inside rate_limited_response to protect all callers.

The coding guideline says Retry-After MUST be .max(1), but the function itself does not enforce it — it relies on callers to clamp before passing retry_after_secs. The per-IP path applies it correctly at line 160, but the per-pubkey path in notify.rs (not reviewed here) must also do so. A caller that omits .max(1) would emit Retry-After: 0, silently violating the MUST constraint. Enforcing it inside the function removes the per-caller burden entirely.

🛡️ Proposed fix
 pub fn rate_limited_response(retry_after_secs: u64) -> HttpResponse {
+    let retry_after_secs = retry_after_secs.max(1);
     HttpResponse::TooManyRequests()
         .insert_header(("Retry-After", retry_after_secs.to_string()))

As per coding guidelines, "Retry-After MUST be whole seconds with .max(1)."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/rate_limit.rs` around lines 55 - 62, The rate_limited_response
function currently trusts callers to clamp retry_after_secs but must enforce the
guideline that Retry-After is whole seconds with a minimum of 1; inside
rate_limited_response (the function that builds the HttpResponse and inserts the
"Retry-After" header) clamp retry_after_secs with retry_after_secs.max(1) before
converting to string and inserting the header so every caller (including
notify.rs paths) always emits a Retry-After >= 1.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/config.rs`:
- Around line 128-132: The current fallback for CryptoConfig.server_private_key
uses a well-known secp256k1 test vector string; replace this insecure hardcoded
default by returning an empty string when the env var is missing (e.g., use
env::var("SERVER_PRIVATE_KEY").unwrap_or_default()) so attempts to parse/use the
key in CryptoConfig (and Config.crypto) will fail loudly instead of silently
using a public test key; update the server_private_key initialization
accordingly and ensure any downstream parsing will surface the missing-key
error.

In `@src/push/unifiedpush.rs`:
- Around line 76-119: Update the async function error bounds to be
multithread-safe: change the return types of save_endpoints, register_endpoint,
and unregister_endpoint from Result<(), Box<dyn std::error::Error>> to
Result<(), Box<dyn std::error::Error + Send + Sync>> so the boxed error is Send
+ Sync for multithreaded async contexts; ensure the function signatures for
async fn save_endpoints(&self) -> ..., async fn register_endpoint(&self,
device_id: String, endpoint_url: String) -> ..., and async fn
unregister_endpoint(&self, device_id: &str) -> ... are updated accordingly.

---

Nitpick comments:
In `@src/api/rate_limit.rs`:
- Around line 55-62: The rate_limited_response function currently trusts callers
to clamp retry_after_secs but must enforce the guideline that Retry-After is
whole seconds with a minimum of 1; inside rate_limited_response (the function
that builds the HttpResponse and inserts the "Retry-After" header) clamp
retry_after_secs with retry_after_secs.max(1) before converting to string and
inserting the header so every caller (including notify.rs paths) always emits a
Retry-After >= 1.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9e95b8e7-07b4-464b-b0fc-8890a94dd56a

📥 Commits

Reviewing files that changed from the base of the PR and between 93dc41e and b82cf76.

📒 Files selected for processing (13)
  • .github/workflows/audit.yml
  • .github/workflows/ci.yml
  • .github/workflows/rust.yml
  • src/api/notify.rs
  • src/api/rate_limit.rs
  • src/api/routes.rs
  • src/config.rs
  • src/crypto/mod.rs
  • src/push/dispatcher.rs
  • src/push/fcm.rs
  • src/push/unifiedpush.rs
  • src/store/mod.rs
  • src/utils/mod.rs
💤 Files with no reviewable changes (3)
  • .github/workflows/rust.yml
  • src/api/notify.rs
  • src/api/routes.rs

…sories

The first run of the new audit job flagged 6 vulnerabilities and 5
unmaintained/unsound warnings. This commit resolves what can be fixed
without changing Cargo.toml and documents the rest:

- cargo update -p bytes -p time (semver-compatible) clears RUSTSEC-2026-0007
  (BytesMut::reserve integer overflow) and RUSTSEC-2026-0009 (time stack
  exhaustion DoS).
- The remaining 4 vulnerabilities (idna 0.5.0 via nostr-sdk 0.27 and three
  rustls-webpki 0.101.7 advisories via rustls 0.21) require coordinated
  major bumps of nostr-sdk / reqwest / rustls and are tracked as follow-up;
  each is now ignored in .cargo/audit.toml with an inline rationale and the
  exact upstream pin that keeps us on the vulnerable version.
- audit.yml drops --deny warnings so the unmaintained advisories
  (dotenv, instant, rustls-pemfile, rand x2) surface in the log without
  failing the job. Vulnerabilities still fail because cargo audit defaults
  to deny on actual vulns, and the .cargo/audit.toml path is now also a
  trigger so changes to the ignore list re-run the audit.
@AndreaDiazCorreia AndreaDiazCorreia merged commit 893e7e1 into main May 7, 2026
5 checks passed
@AndreaDiazCorreia AndreaDiazCorreia deleted the feat/ci-cd-pipeline branch May 7, 2026 04:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P1] [ops] Configurar CI/CD con fmt, clippy, test, audit

1 participant