Skip to content

feat(register): filter device registration by trusted Mostro instance#23

Merged
AndreaDiazCorreia merged 5 commits into
mainfrom
feat/trusted-mostro-instances-whitelist
May 7, 2026
Merged

feat(register): filter device registration by trusted Mostro instance#23
AndreaDiazCorreia merged 5 commits into
mainfrom
feat/trusted-mostro-instances-whitelist

Conversation

@AndreaDiazCorreia
Copy link
Copy Markdown
Member

@AndreaDiazCorreia AndreaDiazCorreia commented May 7, 2026

Adds a compile-time whitelist of trusted Mostro instance pubkeys (loaded from config/trusted_mostro_pubkeys.json, mirroring the list in mobile/lib/core/config/communities.dart) and a new optional mostro_pubkey field on POST /api/register. When the whitelist is non-empty, registrations whose declared instance is missing or not on the list are rejected with 403 Forbidden; an empty whitelist keeps the endpoint permissive. This implements Option A from the discussion (filter at register, not at the Nostr listener) because the no-author-filter invariant on Gift Wrap events still applies. The previous unused MOSTRO_PUBKEY env var is removed. The filter is honour-system and will be hardened once registration carries a daemon-issued signature. Related: #6.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added trusted Mostro instance whitelist validation to strengthen security during registration.
    • Registration now requires mostro_pubkey when a whitelist is configured, returning 403 Forbidden for untrusted instances.
  • Changes

    • Replaced MOSTRO_PUBKEY environment variable with compile-time configuration from a trusted instances list.
    • Updated deployment scripts to reflect new configuration approach.
  • Documentation

    • Updated API and configuration documentation to explain whitelist behavior and validation rules.

Reorders imports and rewraps long lines per default rustfmt. No
functional changes. Touched files were unrelated to in-flight feature
work but had drifted from rustfmt; folding them into a single
formatting commit keeps subsequent feature diffs reviewable.
… pubkey

Mobile clients now declare which Mostro instance they use via a new
optional `mostro_pubkey` field on POST /api/register. The server checks
that value against a compile-time whitelist embedded from
config/trusted_mostro_pubkeys.json, mirroring the trusted instances
already listed in the mobile app
(mobile/lib/core/config/communities.dart).

Behaviour:

- Empty whitelist => permissive mode; the field is ignored. Keeps the
  existing JSON shape and lets the binary be built without an explicit
  list configured.
- Populated whitelist => the field MUST be present and must match an
  entry, otherwise registration is rejected with 403 Forbidden and the
  fixed body {"success":false,"message":"Mostro instance not trusted"}.
  Malformed values (length / hex) return 400 with a distinct body.

This is an honour-system filter only: there is no cryptographic proof
binding the device to the declared instance. It is intended to keep
well-behaved clients from arbitrary instances out of the push pipeline
and will be hardened once registration carries a daemon-issued
signature.

The previous MOSTRO_PUBKEY environment variable is removed; it was
only used as log context and never as an authors filter on the Nostr
listener (the no-author-filter invariant in CLAUDE.md still holds —
that path is unchanged). Deployment scripts, .env.example and docs
are updated accordingly.

Tests cover trusted, untrusted, missing-field, malformed and
permissive-mode paths. Existing byte-identical fixtures for /api/register
remain green: the new field is request-only and does not affect any
response shape.
@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 8 minutes and 17 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: 76395f90-a223-4d87-a54a-2829c67e83e9

📥 Commits

Reviewing files that changed from the base of the PR and between 4ae5373 and 39d7aad.

📒 Files selected for processing (10)
  • .env.example
  • CLAUDE.md
  • Dockerfile
  • docs/api.md
  • docs/configuration.md
  • src/api/routes.rs
  • src/api/test_support.rs
  • src/config.rs
  • src/main.rs
  • src/trusted_pubkeys.rs

Walkthrough

The PR transitions trusted Mostro instance authentication from a runtime environment variable (MOSTRO_PUBKEY) to a compile-time embedded configuration file (config/trusted_mostro_pubkeys.json). The /api/register endpoint now validates incoming mostro_pubkey requests against the whitelist, returning 403 for untrusted instances and 400 for malformed keys.

Changes

Trusted Mostro Instance Whitelist

Layer / File(s) Summary
Configuration & Data
config/trusted_mostro_pubkeys.json, .gitignore
New JSON array of trusted 64-hex pubkeys; unignore rule added to prevent file from being excluded.
Type Extensions
src/api/routes.rs
RegisterTokenRequest gains optional mostro_pubkey field; AppState gains trusted_mostro_pubkeys: Arc<HashSet<String>>.
Compile-time Loading
src/trusted_pubkeys.rs
New module uses include_str! to embed the JSON config at build time; load() validates each key is 64-hex characters, panics on invalid entries, and returns a HashSet<String>.
Registration Validation
src/api/routes.rs
register_token handler now validates mostro_pubkey against the whitelist: returns 403 if missing/untrusted when whitelist is non-empty, 400 if malformed (not 64 hex), otherwise succeeds.
Wiring & State Initialization
src/main.rs, src/config.rs
main.rs calls trusted_pubkeys::load(), logs whitelist status, and passes trusted_mostro_pubkeys to AppState. Config::from_env no longer reads the removed MOSTRO_PUBKEY environment variable.
Test Helpers & Fixtures
src/api/test_support.rs
New make_app_state_with_whitelist() function and make_test_components_with_trusted_whitelist() helper added; public test constants TRUSTED_MOSTRO_PUBKEY and UNTRUSTED_MOSTRO_PUBKEY introduced.
Test Coverage
src/api/routes.rs, src/api/test_support.rs
New test cases verify whitelist-enabled registration success/failure scenarios, malformed key rejection, and permissive mode when whitelist is empty; rate-limit burst tests strengthened.
Documentation
docs/api.md, docs/configuration.md, CLAUDE.md
API docs expanded to document mostro_pubkey field and 403 trusted-instance error; configuration guide replaces MOSTRO_PUBKEY entry with trusted-whitelist contract including format, behavior, and enforcement details.
Deployment & Environment
.env.example, deploy-fly.sh, docs/deployment.md
MOSTRO_PUBKEY removed from .env.example; Fly.io deployment script no longer passes MOSTRO_PUBKEY secret (replaced by whitelist JSON); deployment docs updated accordingly.
Minor Refactoring
src/api/notify.rs, src/api/rate_limit.rs, src/api/mod.rs, src/crypto/mod.rs, src/nostr/listener.rs, src/push/fcm.rs, src/push/unifiedpush.rs, src/store/mod.rs
Code formatting and import reorganization across multiple files (multi-line macro invocations, import reordering, refactored method chains) without semantic changes to control flow or behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • MostroP2P/mostro-push-server#1: Both PRs modify src/api/routes.rs (AppState and routing surface) and deployment/environment configuration, impacting the same trust/pubkey handling code entities.

Poem

🐰 The config hops from .env to JSON bright,
Compiled at build-time, locked in tight,
No more runtime pubkey dance—
Just trusted instances at a glance!
Whitelist magic makes registration right.

🚥 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 'feat(register): filter device registration by trusted Mostro instance' clearly and specifically summarizes the main feature change—filtering registration by a trusted instance whitelist.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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/trusted-mostro-instances-whitelist

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4ae53735d6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/trusted_pubkeys.rs Outdated
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.

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (2)
src/nostr/listener.rs (1)

40-47: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reconnect delay log is inaccurate on error path

Line 41 says reconnecting in 10 seconds, but this path waits 10s (Line 44) plus the unconditional 5s at Line 47, so effective delay is 15s. This can mislead ops during incidents.

Suggested fix
-                    error!(
-                        "Error in Nostr listener: {}, reconnecting in 10 seconds...",
-                        e
-                    );
+                    error!(
+                        "Error in Nostr listener: {}, reconnecting in 15 seconds...",
+                        e
+                    );
🤖 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/nostr/listener.rs` around lines 40 - 47, The error log message in the
Nostr listener's error handling (the error! macro call) incorrectly states
"reconnecting in 10 seconds..." while the code actually awaits
sleep(Duration::from_secs(10)).await and then later does an unconditional
sleep(Duration::from_secs(5)).await (total 15s); update the error! log to state
"reconnecting in 15 seconds..." (or alternatively change the sleeps to match a
10s total) so the message matches the actual delay, referencing the error! call
and the two sleep(Duration::from_secs(...)).await calls in the listener loop.
docs/api.md (1)

200-208: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add 403 to the HTTP status summary table.

The 403 Forbidden response introduced by this PR for /api/register is documented in the endpoint section but missing from the summary table, making the table incomplete.

📝 Proposed fix
 | 200    | `/api/health`, `/api/info`, `/api/status`, `/api/register`, `/api/unregister` |
 | 202    | `/api/notify` on parse-valid input                                            |
 | 400    | Malformed body, invalid `trade_pubkey`, invalid `platform`, empty `token`     |
+| 403    | `/api/register` — `mostro_pubkey` missing or not on the trusted-instance whitelist |
 | 429    | `/api/notify` only — per-IP or per-pubkey rate limit                          |
 | 500    | `/api/notify` only — fail-closed when the per-IP key cannot be extracted      |
🤖 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 `@docs/api.md` around lines 200 - 208, Update the HTTP status summary table in
docs/api.md to include the new 403 status used by the /api/register endpoint:
add a row for "403" and list "/api/register" (or "/api/register" and any other
endpoints returning 403) under "Used by" so the summary table matches the
endpoint documentation; modify the existing table block (the pipe-delimited
Status/Used by table) accordingly.
🤖 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.

Inline comments:
In `@src/api/routes.rs`:
- Around line 161-191: The whitelist check is case-sensitive while hex::decode
accepts mixed-case; normalize the incoming req.mostro_pubkey to a canonical form
(e.g., lowercase) at the HTTP boundary before format validation and the
contains() check so hex::decode and state.trusted_mostro_pubkeys comparisons use
the same casing; update the logic around req.mostro_pubkey (used in the match)
to lowercase the string prior to length/hex validation and membership testing,
and also ensure trusted_mostro_pubkeys is populated in lowercase (e.g., in
src/main.rs) so the HashSet<String> invariant holds.

In `@src/trusted_pubkeys.rs`:
- Around line 15-25: Normalize trusted pubkeys to lowercase when loading them:
in the load() function that parses TRUSTED_PUBKEYS_JSON, after validating each
pk (hex::decode and len==64) convert it to lowercase before inserting into the
HashSet so all stored entries are canonical lowercase; additionally ensure the
incoming mostro_pubkey value checked with trusted_mostro_pubkeys.contains(...)
in routes.rs is lowercased (or otherwise canonicalized) before the contains call
so comparisons are case-insensitive and avoid false 403s.
- Around line 15-52: There is a case-sensitivity bug in the whitelist check in
src/api/routes.rs at the line where it calls HashSet::contains(). Although the
hex format validation allows both uppercase and lowercase, the contains() check
is case-sensitive and uses the original input string, causing mismatches if the
input uses uppercase letters. To fix this, convert the variable (mostro_pk) to
lowercase using to_ascii_lowercase() before passing it to contains(), ensuring
consistent normalization with the stored lowercase keys.

---

Outside diff comments:
In `@docs/api.md`:
- Around line 200-208: Update the HTTP status summary table in docs/api.md to
include the new 403 status used by the /api/register endpoint: add a row for
"403" and list "/api/register" (or "/api/register" and any other endpoints
returning 403) under "Used by" so the summary table matches the endpoint
documentation; modify the existing table block (the pipe-delimited Status/Used
by table) accordingly.

In `@src/nostr/listener.rs`:
- Around line 40-47: The error log message in the Nostr listener's error
handling (the error! macro call) incorrectly states "reconnecting in 10
seconds..." while the code actually awaits sleep(Duration::from_secs(10)).await
and then later does an unconditional sleep(Duration::from_secs(5)).await (total
15s); update the error! log to state "reconnecting in 15 seconds..." (or
alternatively change the sleeps to match a 10s total) so the message matches the
actual delay, referencing the error! call and the two
sleep(Duration::from_secs(...)).await calls in the listener loop.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f71673cd-2b62-4f30-99f4-ff556aa94bd8

📥 Commits

Reviewing files that changed from the base of the PR and between 04532b5 and 4ae5373.

📒 Files selected for processing (21)
  • .env.example
  • .gitignore
  • CLAUDE.md
  • config/trusted_mostro_pubkeys.json
  • deploy-fly.sh
  • docs/api.md
  • docs/configuration.md
  • docs/deployment.md
  • src/api/mod.rs
  • src/api/notify.rs
  • src/api/rate_limit.rs
  • src/api/routes.rs
  • src/api/test_support.rs
  • src/config.rs
  • src/crypto/mod.rs
  • src/main.rs
  • src/nostr/listener.rs
  • src/push/fcm.rs
  • src/push/unifiedpush.rs
  • src/store/mod.rs
  • src/trusted_pubkeys.rs
💤 Files with no reviewable changes (2)
  • docs/deployment.md
  • deploy-fly.sh

Comment thread src/api/routes.rs Outdated
Comment thread src/trusted_pubkeys.rs Outdated
Comment thread src/trusted_pubkeys.rs Outdated
…ist build

Address PR #23 review feedback:

- coderabbit (3 instances of the same finding): the 400-format gate uses
  hex::decode which is case-insensitive, but the 403 whitelist check used
  byte-exact HashSet::contains. A client sending an uppercase but
  otherwise valid pubkey would pass the 400 gate and falsely hit 403.
  Lowercase entries when populating the set in trusted_pubkeys::load and
  lowercase the incoming mostro_pubkey at the HTTP boundary in
  register_token before contains.

- chatgpt-codex: the test embedded_json_parses_and_validates asserted
  that the embedded set was non-empty, which broke `cargo test` for an
  operator who chose the documented permissive configuration (`[]` in
  config/trusted_mostro_pubkeys.json). Drop the cardinality assertion,
  drop the redundant default_main_instance_is_present test (it pinned a
  specific pubkey that should not gate the build), and keep only a
  format/canonicalization check that succeeds on any valid input
  including the empty array.

Adds a regression test (register_with_uppercase_trusted_mostro_pubkey_succeeds)
that submits the uppercase form of a trusted entry and asserts 200.
@AndreaDiazCorreia
Copy link
Copy Markdown
Member Author

@codex review"

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9b4e85929c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/trusted_pubkeys.rs
PR #23 review (chatgpt-codex, P1): the builder stage copies only
Cargo.toml, Cargo.lock and src/ before `cargo build --release`. With the
trusted-instances whitelist now embedded via
`include_str!("../config/trusted_mostro_pubkeys.json")`, that path is
absent inside the image and the build fails. Add a COPY for `config/`
ahead of the build step so Fly/Docker deployments compile against the
same JSON the developer commits.
@AndreaDiazCorreia
Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@AndreaDiazCorreia
Copy link
Copy Markdown
Member Author

@codex review

@AndreaDiazCorreia AndreaDiazCorreia merged commit 93dc41e into main May 7, 2026
2 checks passed
@AndreaDiazCorreia AndreaDiazCorreia deleted the feat/trusted-mostro-instances-whitelist branch May 7, 2026 04:10
@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

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.

1 participant