Repository navigation
Conversation
Institutional payroll and regulated token distributions need audit-ready proof that participating wallets were screened before funds move. This adds an opt-in compliance layer that screens stream creation, top-up and withdrawal requests against OFAC / sanctions data, caches results with a configurable TTL (Redis when available, bounded in-process cache otherwise) and records a structured audit trail for blocked interactions. It also exposes a SEP-0009 KYC attestation endpoint for organizations. Enforcement is disabled by default (COMPLIANCE_ENFORCEMENT_ENABLED=false) so local testing and self-hosted deployments stay fully permissive, and a fail-open / fail-closed mode controls behaviour when a provider is down. Closes LabsCrypt#1470 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
Reconcile the compliance branch with the latest upstream (allowance-based streams, dispute escrow, rate modification, token-price UI). Conflict resolution: - contracts: de-duplicate StreamClosedEvent and remove_stream, unify the fee helpers on the (net, fee, treasury) collect_fee + transfer_fee split, renumber the merged error variants (upstream 28-33 kept stable, LabsCrypt#1468 variants appended as 34-36), set STREAM_FIELD_COUNT to 17 for the merged Stream struct, and replace the re-entrant fee transfer test (Soroban forbids contract re-entry) with an equivalent balance/persistence check. - frontend: stop passing `undefined` explicitly to toast.success and update the stream-details test for the new token-price hook and background fetches. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
Introduce a SentinelService that watches stream lifecycle events as the indexer processes them and raises severity-graded incidents for high-velocity drains, rapid multi-stream withdrawals, high-value and near-total balance drains, per-token withdrawal spikes, stream-creation spikes, and near-zero-runway floods. - lib/redis: sliding-window tracker over Redis sorted sets (ZADD / ZREMRANGEBYSCORE) with a bounded in-memory fallback so detection still works on single-instance deployments and in tests. - services/sentinel.service: heuristics with configurable thresholds, alert-cooldown deduplication, a bounded incident history, an aggregate 0-100 threat score, and a unified alert adapter (Slack, Discord, PagerDuty). CRITICAL incidents also carry an HMAC-signed set_emergency_pause(true) proposal for multisig signers. - workers/soroban-event-worker: feed withdrawals and stream creations into the sentinel; analysis is best-effort and never quarantines a valid on-chain event. - admin API: GET /v1/admin/sentinel/alerts exposes the threat score, flagged addresses and incident history; POST .../alerts/:id/acknowledge marks an incident reviewed. - tests: 29 new cases covering the trackers, every heuristic and severity band, alert fan-out, and a simulated coordinated drain. Closes LabsCrypt#1469 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
Aj-Kayvee
force-pushed
the
feature/stream-anomaly-sentinel-1469
branch
from
September 28, 2026 15:38
32eb365 to
b21b4d1
Compare
Merging main into this branch unioned both sides of the contract files, so `lib.rs` kept the superseded `try_invoke` allowance block (leaving an unclosed `match`), `errors.rs` redeclared variants 28–33, and `test.rs` duplicated a fee test, the fuzz initializer's dispute fields, and a comment. Soroban Contracts CI aborted at `cargo fmt` and never reached the tests. This branch's feature is backend-only, so keep main's already-reconciled, green contract tree instead of the stacked-branch churn. `cargo fmt`/`clippy` and all 230 contract tests pass again. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements #1469 — a real-time Stream Anomaly Detection & High-Velocity Drain Sentinel.
Today the indexer records every withdrawal and stream creation only after the fact, so a compromised payroll wallet can drain many streams before anyone notices. This PR turns those indexed events into a live risk signal: a new
SentinelServicemaintains sliding-window velocity trackers, evaluates a set of heuristics on every indexed event, grades the resulting incidents by severity, fans alerts out to on-call channels, and — forCRITICALincidents — attaches an HMAC-signedset_emergency_pause(true)proposal so multisig signers can act immediately instead of hand-authoring the payload.Closes #1469
What changed
backend/src/lib/redis.tsSlidingWindowTrackerbuilt on Redis sorted sets (ZADD/ZREMRANGEBYSCORE) with a bounded in-memory fallback. ExposesgetRedisClient().backend/src/services/sentinel.service.tsbackend/src/workers/soroban-event-worker.tstokens_withdrawnandstream_createdevents into the sentinel (best-effort — analysis failures never quarantine a valid event).backend/src/controllers/admin.controller.tslistSentinelAlertsHandler,acknowledgeSentinelAlertHandler.backend/src/routes/v1/admin.routes.tsGET /v1/admin/sentinel/alerts,POST /v1/admin/sentinel/alerts/:id/acknowledge.backend/src/lib/metrics.tsflowfi_sentinel_incidents_total,flowfi_sentinel_alerts_dispatched_total,flowfi_sentinel_threat_score.backend/src/config/swagger.tsSentinelIncidentschema.backend/.env.exampleSENTINEL_*configuration.backend/tests/sentinel-service.test.tsbackend/tests/sentinel.admin.test.tsSliding-window velocity trackers
RedisSlidingWindowTrackerstores one sample per event in a sorted set, scored by timestamp, and answers two questions in a single range read:Samples age out with
ZREMRANGEBYSCOREon the retention horizon at insert time. Reads are deliberately read-only: a narrowsnapshot()must not prune the longer history a baselinesnapshot()depends on. WhenREDIS_URLis unset (single instance, tests) the same interface is served by a bounded in-memory implementation, so detection degrades rather than disappearing.Heuristics & severity
VELOCITY_SPIKEmultiplier× the trailing 24h baseline (default 4× = a 300% increase).TOKEN_VELOCITY_SPIKEMULTI_STREAM_DRAINSENTINEL_MULTI_STREAM_MAXdistinct streams inside the ledger window.HIGH_VALUE_DRAINSENTINEL_BALANCE_DRAIN_PCTof the stream's deposit (unit-free, so it works without a price feed).STREAM_CREATION_SPIKEZERO_RUNWAY_FLOODSENTINEL_ZERO_RUNWAY_FLOOD_THRESHOLDnear-zero-runway streams created in a burst.Severity is
LOW/MEDIUM/HIGH/CRITICAL, derived from how far past the threshold the observation is. Repeats of the same rule + subject insideSENTINEL_ALERT_COOLDOWN_MSare suppressed so one attack cannot generate an alert storm.Alerting & circuit breaker
A unified adapter fans each incident out to Slack, Discord and PagerDuty (any subset configured), with a rich contextual payload — rule, severity, address, token, stream, ledger, evidence, and the threat score. Delivery failures are recorded (
flowfi_sentinel_alerts_dispatched_total{channel,outcome}) and logged, but never thrown, so a stale webhook cannot stop the next incident from being detected.CRITICALincidents additionally carrycircuitBreaker: an HMAC-SHA256-signedset_emergency_pause(true)proposal (payloadHash+signature+reason+incidentId). Signing proves provenance to multisig tooling and cannot be used to move funds.Admin API
The list endpoint returns the aggregate 0–100 threat score (with a per-severity breakdown), flagged addresses ranked by accumulated risk, and the incident history, newest first. Both routes sit behind the existing
requireAdmin+ admin rate limiter.Configuration
All thresholds are env-driven with sane defaults — see the new
SENTINEL_*block inbackend/.env.example.SENTINEL_ENABLED=falsedisables the pipeline entirely.Testing
backend/tests/sentinel-service.test.ts— tracker contracts (Redis path via a fake sorted-set client + in-memory), every heuristic, all four severity bands, alert fan-out with a mockedfetch, cooldown suppression, threat score / flagged addresses, and an end-to-end simulated coordinated drain (15 withdrawals across 15 streams in one minute) asserting bothVELOCITY_SPIKEandMULTI_STREAM_DRAINfire.backend/tests/sentinel.admin.test.ts— route-level tests for the dashboard feed, severity filter, limit, and acknowledge/404.npx tsc --noEmitis clean forbackend/.Acceptance criteria
LOW/MEDIUM/HIGH/CRITICAL).🤖 Generated with Codebuff