Skip to content

fix(node): gate POST /api/v1/sync/trigger and rate-limit the peer-sync routes (#82)#161

Merged
kevincodex1 merged 6 commits into
mainfrom
fix/issue-82-peer-sync-auth-ratelimit
Jul 8, 2026
Merged

fix(node): gate POST /api/v1/sync/trigger and rate-limit the peer-sync routes (#82)#161
kevincodex1 merged 6 commits into
mainfrom
fix/issue-82-peer-sync-auth-ratelimit

Conversation

@beardthelion

@beardthelion beardthelion commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

POST /api/v1/sync/trigger was reachable unauthenticated in the default config and drove an O(peers) outbound fan-out plus one sync_queue enqueue per repo, an anonymous amplification/DoS (#82). The same enqueue_sync sink is also reachable through /sync/notify, which accepts unsigned known-peer requests and had no rate limit, so gating only trigger would have left an equivalent hole.

What changed:

  • /sync/trigger now requires a valid RFC 9421 signature in both config modes (its own always-signed route group), independent of require_signed_peer_writes.
  • Two dedicated per-client-IP rate limiters: a tight one (60/hr) on trigger, a generous one (600/hr) on the announce+notify group. Per-IP rather than per-DID, because a did:key farm self-registers and never trips a per-DID bucket; the brake is keyed before auth so DID rotation can't bypass it. notify's signature behavior is unchanged for rolling-upgrade compat, it only gains the brake. Knobs: GITLAWB_SYNC_TRIGGER_RATE_LIMIT, GITLAWB_PEER_WRITE_RATE_LIMIT (0 disables).
  • gl sync trigger now checks HTTP status before parsing (a 401/429 can no longer render as a fake "sync triggered" success), bounds the error-body read, and strips control and Unicode bidi characters from the node's error before printing.

Known limitation: the per-IP brake caps a single-source flood. A distributed multi-IP notify flood stays open until /peers/announce is gated (the free peer self-registration that enables it), which is tracked separately.

Tests are an adversarial RED to GREEN matrix: unsigned to 401 in both modes, IP flood to 429, DID-farm and forged-header resistance, cross-bucket isolation, disabled-when-0, the notify and announce brakes, the real-signature positive through the full router, and the client 401/429/sanitize/oversized-body paths.

Closes #82.

Summary by CodeRabbit

  • New Features
    • Added separate, configurable per-client rate limits for POST /api/v1/sync/trigger and peer-write routes (/api/v1/peers/announce, /api/v1/sync/notify), controlled via new CLI/env settings (including optional disable via 0).
    • Split sync/trigger into its own authenticated route with dedicated throttling.
  • Bug Fixes
    • Improved gl sync trigger to fail locally when no identity is configured and to surface server errors correctly.
    • Rate-limit responses now use a single generic 429 message for relevant sync/peer routes.
  • Documentation
    • Updated API surface and CLI usability notes to reflect sync/trigger signature requirements and staged-rollout behavior.

t added 5 commits July 6, 2026 20:45
…es (#82)

Plumb two dedicated per-IP RateLimiters into config/state so the peer-sync
enqueue routes can be braked independently: sync_trigger_rate_limiter (tight,
GITLAWB_SYNC_TRIGGER_RATE_LIMIT, default 60/hr) and peer_write_rate_limiter
(generous, GITLAWB_PEER_WRITE_RATE_LIMIT, default 600/hr). Both are bounded and
swept by the periodic cleanup loop, keyed via the node-wide push_limiter_trust.
No route is wired to them yet; that lands next.
…er-sync routes (#82)

/api/v1/sync/trigger was reachable unauthenticated in the default config and
drove an O(peers) outbound fan-out + per-repo sync_queue enqueue — an anonymous
amplification/DoS. The same enqueue_sink is reachable via /sync/notify, which
also accepts unsigned known-peer requests and had no rate limit.

- Move /sync/trigger into its own always-signed route group (add_auth_layers),
  independent of require_signed_peer_writes, and give it a tight per-IP brake.
- Add a separate, generous per-IP brake to the peer-write group
  (/peers/announce, /sync/notify); notify's signature behavior is unchanged
  (rolling-upgrade compat), it only gains the brake. Separate buckets so an
  unsigned notify flood can't drain the signed trigger caller's quota.
- Per-IP (not per-DID): a did:key farm self-registers, so a signature/per-DID
  gate does not cap cost (INV-10); the brake keys on the client IP before auth.

Adds an adversarial test matrix (unsigned->401 both modes, IP flood->429,
DID-agnostic brake, forged X-Forwarded-For can't bypass, notify flood->429,
distinct-IP not throttled, cross-route non-exhaustion, 0-disables-but-sig-holds,
announce still accepts unsigned).
…ody (#82)

Now that the node gates /api/v1/sync/trigger behind a signature and rate limit,
'gl sync trigger' must not render a 401/429 as a fabricated '✓ sync triggered /
0 peers' success. Check resp.status() before parsing; on a non-2xx, extract the
node's message and bail. Strip C0/C1 control bytes from the node-supplied string
before printing (INV-6) — a hostile node could embed ANSI/OSC escapes in the
body. Adds mockito tests: 401/429 surface as errors (RED without the status
check), control bytes are stripped, and the 200 path is unchanged.
…e 429 message (#82)

Code-review follow-ups on the #82 change:
- gl sync trigger: read the node error body with a capped reader
  (read_body_capped) instead of resp.text(), so a hostile/broken node can't
  force an unbounded allocation to surface a denial (INV-6, read half).
- sanitize_node_msg: also strip Unicode bidi/format controls (U+202A-202E,
  U+2066-2069, LRM/RLM/ALM) — is_control() only covers the Cc category, but a
  right-to-left override can reorder the displayed error line.
- too_many_requests(): the shared per-IP 429 middleware now serves the peer-sync
  routes too, so drop the push-specific wording ('rate limit exceeded'); the
  push-specific 429 in the receive-pack handler is unchanged.
Adds tests for the bidi/length sanitize and the oversized-body path.
Executes the branches that were previously only reasoned:
- read_body_capped byte-cap test (RED against resp.text() — 2MB body -> ≤cap):
  vets the bounded-read fix, not just the display cap.
- sync-route 429 body asserts 'rate limit exceeded' and NOT 'push' (RED before
  the message was genericized); also finishes genericizing the warn log, which
  the response-body change had missed.
- real RFC 9421 signature through the full router reaches the handler (200) in
  both config modes — the positive path through the actual gate, not a
  direct-mount with an injected DID.
- /peers/announce flood -> 429 (the peer_write brake covers announce, not just
  notify).
- trigger_counts extraction unit test (happy + malformed -> 0,0), closing the
  count-formatting gap in the 200 path.
No production behavior change beyond the warn-log wording.
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ee49625f-de2b-4572-babe-84f19ba1a33d

📥 Commits

Reviewing files that changed from the base of the PR and between e4626ab and c84c703.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • .env.example
  • README.md
  • crates/gl/src/sync.rs
  • docs/OSS-READINESS-AUDIT.md
✅ Files skipped from review due to trivial changes (1)
  • .env.example
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/gl/src/sync.rs

📝 Walkthrough

Walkthrough

Adds dedicated per-IP rate limiters and mandatory signature enforcement for /api/v1/sync/trigger, separates peer-write routes into their own limiter, updates shared 429 messaging, and hardens gl sync trigger response handling and docs.

Changes

Node server sync/trigger security and rate limiting

Layer / File(s) Summary
Rate-limit config and env docs
crates/gitlawb-node/src/config.rs, .env.example
Adds sync_trigger_rate_limit and peer_write_rate_limit config fields with clap/env support and documents them in .env.example.
AppState and startup limiter wiring
crates/gitlawb-node/src/state.rs, crates/gitlawb-node/src/main.rs, crates/gitlawb-node/src/auth/mod.rs, crates/gitlawb-node/src/test_support.rs
Adds sync_trigger_rate_limiter and peer_write_rate_limiter fields to AppState, initializes them at startup with cleanup task hooks, and updates test/auth state builders.
Route splitting and generic messaging
crates/gitlawb-node/src/server.rs, crates/gitlawb-node/src/rate_limit.rs
Splits /api/v1/sync/trigger into a dedicated router requiring signatures with its own limiter; keeps /api/v1/peers/announce and /api/v1/sync/notify conditional signature with separate limiter; generalizes 429 response text and warning logs.
Integration tests and node docs
crates/gitlawb-node/src/api/peers.rs, README.md, docs/OSS-READINESS-AUDIT.md
Adds tests for signature gating, IP throttling, bucket separation, announce compatibility, and generic 429 behavior; updates route documentation to match the new trigger requirements.

Estimated code review effort: 4 (Complex) | ~60 minutes

gl CLI sync trigger response handling

Layer / File(s) Summary
Trigger response validation and sanitization
crates/gl/src/sync.rs
Validates HTTP status before parsing JSON, reads error bodies with a byte cap, extracts and sanitizes error messages, and adds trigger_counts() for safe success parsing.
Unit tests and CLI docs
crates/gl/src/sync.rs, README.md, docs/OSS-READINESS-AUDIT.md
Adds tests for error surfacing, sanitization, bounded reads, and trigger_counts defaults; updates docs to describe the signed trigger flow.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SyncTriggerRoutes
  participant AuthLayer
  participant SyncTriggerRateLimiter
  participant TriggerSyncHandler

  Client->>SyncTriggerRoutes: POST /api/v1/sync/trigger
  SyncTriggerRoutes->>AuthLayer: enforce signature
  AuthLayer-->>SyncTriggerRoutes: 401 if unsigned
  SyncTriggerRoutes->>SyncTriggerRateLimiter: check per-IP quota
  SyncTriggerRateLimiter-->>SyncTriggerRoutes: 429 if exceeded
  SyncTriggerRoutes->>TriggerSyncHandler: forward request
  TriggerSyncHandler-->>Client: 200 with sync counts
Loading
sequenceDiagram
  participant User
  participant GlCli
  participant NodeServer

  User->>GlCli: gl sync trigger
  GlCli->>NodeServer: POST /api/v1/sync/trigger
  NodeServer-->>GlCli: HTTP response
  alt non-success status
    GlCli->>GlCli: read_body_capped()
    GlCli->>GlCli: extract message/error
    GlCli->>GlCli: sanitize_node_msg()
    GlCli-->>User: error with status and sanitized message
  else success
    GlCli->>GlCli: trigger_counts()
    GlCli-->>User: peers_reached, repos_enqueued
  end
Loading

Possibly related PRs

  • Gitlawb/node#152: Shares the same per-IP rate-limiting infrastructure and rate_limit.rs messaging behavior changed here.

Suggested labels: kind:security, subsystem:api, sev:high

Suggested reviewers: jatmn, kevincodex1

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the change but misses most required template sections like Summary, Kind of change, What changed, and How to verify. Fill in the template sections: Summary, Motivation & context, Kind of change, What changed, How a reviewer can verify, and the checklist items.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main fix: gating sync/trigger and adding peer-sync rate limits.
Linked Issues check ✅ Passed The changes require signatures on /sync/trigger, add per-IP rate limits, preserve notify behavior, and include tests, matching issue #82.
Out of Scope Changes check ✅ Passed The client and documentation updates support the same trigger and rate-limit fix, with no clear unrelated churn.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-82-peer-sync-auth-ratelimit

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

@beardthelion beardthelion added crate:gl gl — the contributor CLI crate:node gitlawb-node — the serving node and REST API kind:bug Defect fix — wrong or unsafe behavior subsystem:identity DID/UCAN, http-sig auth, push authorization subsystem:peers Peer announce, discovery, and registry labels Jul 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
crates/gitlawb-node/src/main.rs (1)

216-238: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Wiring is correct; consider deduplicating limiter setup.

The new sync_trigger_rate_limiter/peer_write_rate_limiter construction, AppState wiring, and cleanup-loop integration are all consistent with the existing push_rate_limiter pattern and correctly implement "0 disables" semantics. As an optional follow-up, the four near-identical RateLimiter::new_bounded(...) + "if limit == 0, warn" blocks (main limiter, push, sync-trigger, peer-write) could be collapsed into a small helper to reduce copy-paste and the risk of the warning/limiter values drifting out of sync in future edits.

♻️ Example helper to reduce duplication
+fn bounded_limiter_with_warning(
+    limit: usize,
+    window: std::time::Duration,
+    max_keys: usize,
+    env_var: &str,
+    label: &str,
+) -> rate_limit::RateLimiter {
+    if limit == 0 {
+        tracing::warn!("{env_var}=0 — {label} rate limiting disabled");
+    }
+    rate_limit::RateLimiter::new_bounded(limit, window, max_keys)
+}

Also applies to: 257-258, 320-321, 330-331

🤖 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 `@crates/gitlawb-node/src/main.rs` around lines 216 - 238, The limiter setup in
main is correct, but the repeated RateLimiter::new_bounded plus “if limit == 0,
warn” pattern is duplicated across the main, push, sync_trigger_rate_limiter,
and peer_write_rate_limiter blocks. Extract that repeated logic into a small
helper around the existing RateLimiter construction so the warning text and
configured limit stay in sync and future edits only need to change one place.
.env.example (1)

98-107: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reorder env vars to satisfy dotenv-linter.

GITLAWB_PEER_WRITE_RATE_LIMIT should be declared before GITLAWB_SYNC_TRIGGER_RATE_LIMIT per the linter's key-ordering rule.

🧹 Proposed reorder
-# /api/v1/sync/trigger requires a signature and fans out to every peer per call,
-# so it gets a tight bucket. 0 disables. Default 60.
-GITLAWB_SYNC_TRIGGER_RATE_LIMIT=60
-# /api/v1/peers/announce and /api/v1/sync/notify accept unsigned requests from
-# known peers and run at higher frequency, so a generous bucket. Separate from
-# the trigger bucket so an unsigned notify flood can't drain trigger's quota.
-# 0 disables. Default 600.
-GITLAWB_PEER_WRITE_RATE_LIMIT=600
+# /api/v1/peers/announce and /api/v1/sync/notify accept unsigned requests from
+# known peers and run at higher frequency, so a generous bucket. Separate from
+# the trigger bucket so an unsigned notify flood can't drain trigger's quota.
+# 0 disables. Default 600.
+GITLAWB_PEER_WRITE_RATE_LIMIT=600
+# /api/v1/sync/trigger requires a signature and fans out to every peer per call,
+# so it gets a tight bucket. 0 disables. Default 60.
+GITLAWB_SYNC_TRIGGER_RATE_LIMIT=60
🤖 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 @.env.example around lines 98 - 107, The environment variable declarations in
the .env.example rate-limiting section are out of the linter’s required key
order. Reorder the related entries so GITLAWB_PEER_WRITE_RATE_LIMIT appears
before GITLAWB_SYNC_TRIGGER_RATE_LIMIT, keeping the surrounding comments aligned
with the moved variables.

Source: Linters/SAST tools

crates/gitlawb-node/src/server.rs (1)

280-314: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Route split and layering look correct.

Signature gate is unconditional for /sync/trigger in both config modes, and the per-IP brake is outermost so it runs before signature verification — matches the documented threat model (DID farms can't buy cost immunity via a valid signature, and the IP brake can't be bypassed by rotating DIDs).

One minor naming nit: state.push_limiter_trust is now reused for the sync-trigger and peer-write limiters too (in addition to push). Functionally fine since it's a single trusted-proxy topology, but the name reads push-specific. Consider renaming to something like ip_limiter_trust in a follow-up for clarity (touches state.rs/main.rs/auth/mod.rs as well, not shown in this diff).

🤖 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 `@crates/gitlawb-node/src/server.rs` around lines 280 - 314, The rate-limit
trust flag used in the new sync-trigger and peer-write route layers is
semantically broader than “push” now that it is shared across multiple IP
limiters. Update the shared state field referenced in
server::sync_trigger_routes and peer_write_routes from the push-specific name to
a generic one like ip_limiter_trust, and propagate that rename through the state
initialization and any related config/auth plumbing so the naming matches its
actual scope.
🤖 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.

Nitpick comments:
In @.env.example:
- Around line 98-107: The environment variable declarations in the .env.example
rate-limiting section are out of the linter’s required key order. Reorder the
related entries so GITLAWB_PEER_WRITE_RATE_LIMIT appears before
GITLAWB_SYNC_TRIGGER_RATE_LIMIT, keeping the surrounding comments aligned with
the moved variables.

In `@crates/gitlawb-node/src/main.rs`:
- Around line 216-238: The limiter setup in main is correct, but the repeated
RateLimiter::new_bounded plus “if limit == 0, warn” pattern is duplicated across
the main, push, sync_trigger_rate_limiter, and peer_write_rate_limiter blocks.
Extract that repeated logic into a small helper around the existing RateLimiter
construction so the warning text and configured limit stay in sync and future
edits only need to change one place.

In `@crates/gitlawb-node/src/server.rs`:
- Around line 280-314: The rate-limit trust flag used in the new sync-trigger
and peer-write route layers is semantically broader than “push” now that it is
shared across multiple IP limiters. Update the shared state field referenced in
server::sync_trigger_routes and peer_write_routes from the push-specific name to
a generic one like ip_limiter_trust, and propagate that rename through the state
initialization and any related config/auth plumbing so the naming matches its
actual scope.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1ada22aa-e672-4cef-b14a-38ffb55a243a

📥 Commits

Reviewing files that changed from the base of the PR and between 6cff528 and e4626ab.

📒 Files selected for processing (10)
  • .env.example
  • crates/gitlawb-node/src/api/peers.rs
  • crates/gitlawb-node/src/auth/mod.rs
  • crates/gitlawb-node/src/config.rs
  • crates/gitlawb-node/src/main.rs
  • crates/gitlawb-node/src/rate_limit.rs
  • crates/gitlawb-node/src/server.rs
  • crates/gitlawb-node/src/state.rs
  • crates/gitlawb-node/src/test_support.rs
  • crates/gl/src/sync.rs

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I found an issue that needs to be addressed before this is ready.

Findings

  • [P2] Update the peer-write rollout docs for the always-signed trigger route
    README.md:305
    The README still lists POST /api/v1/sync/trigger under the staged peer-write rollout and says unsigned legacy peers are accepted when GITLAWB_REQUIRE_SIGNED_PEER_WRITES=false; docs/OSS-READINESS-AUDIT.md also still says gl sync trigger falls back to an unsigned request for live compatibility. This PR intentionally moves /sync/trigger into its own always-signed route group, so operators following the docs will expect the flag to preserve unsigned trigger compatibility even though the route now returns 401 without a signature. Please split /sync/trigger out of the staged peer-write list and document that only /peers/announce and /sync/notify keep the rolling-upgrade unsigned behavior.

  • [P2] Stop silently sending unsigned gl sync trigger requests
    crates/gl/src/sync.rs:33
    gl sync trigger still loads the keypair with .ok() and then builds NodeClient::new(&args.node, keypair), so a missing or unreadable identity is silently converted into an unsigned request. Since this PR makes /api/v1/sync/trigger always require a signature, that path can only fail remotely with a 401 and keeps the legacy unsigned fallback alive in the client instead of telling the operator to create or fix their identity before calling the route. Please make Trigger require a keypair locally, as the other signed CLI writes do, and cover the missing-identity path so the command fails before issuing an unsigned request.

Address review feedback on the always-signed /sync/trigger route.

- gl sync trigger now requires a local identity via load_keypair_from_dir and
  fails locally with "identity not found" before issuing a request, instead of
  .ok() silently sending an unsigned request that can only 401 remotely. Add a
  test proving it fails before dialing the node.
- Split /sync/trigger out of the staged unsigned peer-write rollout in the
  README and the OSS-readiness audit; only /peers/announce and /sync/notify keep
  the rolling-upgrade unsigned behavior.
- Reorder the two peer-sync rate-limit vars in .env.example for dotenv-linter.
- Bump crossbeam-epoch to 0.9.20 (RUSTSEC-2026-0204), matching main, so cargo
  audit passes.
@beardthelion

Copy link
Copy Markdown
Collaborator Author

@jatmn both addressed in c84c703:

  • Docs: split /sync/trigger out of the staged unsigned peer-write list in the README and dropped the unsigned-fallback line in docs/OSS-READINESS-AUDIT.md. Only /peers/announce and /sync/notify keep the rolling-upgrade unsigned behavior now.
  • Client: gl sync trigger loads the identity with ? like the other signed writes and fails locally with "identity not found" before issuing a request. Added trigger_requires_identity_fails_before_request, which points at an unreachable node so it can only pass if the command bails before dialing.

Also bumped crossbeam-epoch to 0.9.20 (RUSTSEC-2026-0204) so cargo audit is green again.

@beardthelion

Copy link
Copy Markdown
Collaborator Author

@coderabbitai the .env.example ordering is fixed (GITLAWB_PEER_WRITE_RATE_LIMIT now precedes GITLAWB_SYNC_TRIGGER_RATE_LIMIT). Leaving the limiter-setup dedup helper and the server.rs route-grouping note for a separate cleanup rather than widening this security PR's diff near approval.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@beardthelion Thanks for confirming — the .env.example ordering fix sounds good. That resolves my earlier ordering note.

Understood on deferring the limiter-setup dedup helper and the server.rs route-grouping note to a separate cleanup PR rather than expanding this security fix's diff — that's a reasonable call given the PR's focus on closing #82.

If you'd like, I can open a follow-up GitHub issue to track those two cleanup items so they don't get lost. Just let me know and I'll file it (tagging you as the requester and linking back to this PR/thread).

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the update. I rechecked the previously discussed paths and do not see any remaining actionable issues from my side.

@kevincodex1 kevincodex1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

crate:gl gl — the contributor CLI crate:node gitlawb-node — the serving node and REST API kind:bug Defect fix — wrong or unsafe behavior subsystem:identity DID/UCAN, http-sig auth, push authorization subsystem:peers Peer announce, discovery, and registry

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unauthenticated POST /api/v1/sync/trigger drives outbound fan-out and queue flooding

3 participants