Skip to content

fix(security): redact secrets, harden dispatch, add timeouts, tighten deploy - #455

Merged
spalen0 merged 9 commits into
mainfrom
sec-review
Nov 11, 2026
Merged

spalen0 merged 9 commits into
mainfrom
sec-review

Conversation

@spalen0

@spalen0 spalen0 commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Fixes from a security and stability review of the monitoring box. I reproduced the first two issues before fixing them.

Secret leakage (high)

  • ProviderConnectionError put full RPC URLs (/v2/<alchemy-key>) in its message. run_with_alert then sent str(exc) to Telegram and stored it in alert_events under the protocol key, which the API and public site query. The automation digest did the same with stderr tails.
  • New utils/redact.py masks configured secrets: PROVIDER_URL_* becomes scheme://host/***, and the values of *_KEY, *_TOKEN, *_SECRET and *_PASSWORD vars become ***. It also has pattern fallbacks for bot tokens, ?apikey= query params and keys in RPC URL paths.
  • Redaction is applied in the logger formatter (scripts and the automation orchestrator), in send_telegram_message before the message is stored or sent, in crash alerts, in the digest error tail, in web3 provider errors and logs (host only), and in the dispatched message.

Emergency dispatch (high)

  • The hook used to run only after a successful Telegram send. A 429 or Telegram outage therefore skipped the emergency withdrawal. The hook now runs before the Telegram send.
  • Telegram 429s are retried once after retry_after (capped at 30s).
  • New Alert.dispatch flag. Unconfirmed signals still alert but don't move funds:
    • Stables depeg: one DeFiLlama sample alerts as "Unconfirmed". Dispatch waits until the next run confirms it (2 consecutive breaches). A missing sample neither confirms nor clears.
    • 3Jane USD3/sUSD3 PPS: dispatch only for drops of at least 0.01%. Float and rounding noise still alerts.
    • Cap stcUSD assets-per-share: dispatch only for drops of at least 1 bp.
  • Built on main's durable dispatch queue (Persist emergency deliveries and bypass HIGH cooldown for CRITICAL #457): dispatch=False alerts are never enqueued, and the message is redacted before it is written to the outbox DB or sent. The signed body has no sent_at, because retries resend the exact persisted bytes and the liquidity envelope stays unchanged. Vaults-mommy dedupes on event_id.

Stability (medium)

  • Per-task timeout: in jobs.yaml: default 3600s, 300s for the stables sync-heartbeat task, 600s for the per-minute emergency-delivery-retries task. A timed-out task is killed and shows up in the digest. Before this, a hung task held its flock forever, and if that was ten_minute, code sync stopped too.
  • git fetch (120s), git reset (60s) and uv sync (600s) now have timeouts.
  • Gauntlet requests.get calls now have timeouts.

Deploy / CI

  • monitoring-api.service: no longer loads an EnvironmentFile (the API needs no credentials), and /etc/monitoring is InaccessiblePaths.

  • Caddyfile: rejects any bearer value that isn't 64 hex characters before comparing tokens. An empty ALERTS_API_TOKENS previously reduced the token check to ^Bearer ()$; it now fails closed. I checked this against a local Caddy 2.10.2:

    Token list No/empty/wrong token Correct token
    empty 401 401
    set 401 passed to upstream

    The docs now include an ExecStartPre check so Caddy refuses to start without tokens.

  • install.sh: no longer copies the operator's personal gh auth token into ~/.git-credentials, a file the service can read. It requires an explicit GITHUB_TOKEN and the docs recommend a fine-grained, read-only token.

  • CI: actions pinned to commit SHAs, permissions: contents: read, and persist-credentials: false.

  • Runbook: new "Security baseline" section and a note on rotating keys after redaction.

LLM explainer (low)

  • The system prompt now treats supplied context as untrusted data.
  • The Telegram header reads AI Summary (unverified).
  • No alert routing depends on the model's risk tag (checked).

Smaller fixes

  • Telegram truncation now counts UTF-16 units.
  • The test-chat override now gets the plain-text retry after a Markdown parse error.
  • Fixed the stale jobs.yaml comment about manual uv sync.

Behaviour changes to check

  • Stables depeg dispatch is delayed by one run (~10 min). The first alert still fires immediately.
  • 3Jane/Cap PPS drops below the thresholds no longer dispatch. Adjust PPS_DISPATCH_MIN_DECREASE_BPS and ASSETS_PER_SHARE_DISPATCH_MIN_DECREASE_BPS if you want different thresholds.
  • Long tasks: anything legitimately over 60 minutes needs an explicit timeout:.
  • API unit: the new unit only takes effect when install.sh re-renders it.

Ops follow-ups (not code)

  • Rotate RPC/API keys that may have appeared in past crash alerts or logs. Consider deleting affected alert_events rows.
  • Branch protection on main: required review and CI, no force push, signed commits if possible.
  • Replace the box's git credential with a fine-grained read-only token or a deploy key.
  • Add the Caddy ExecStartPre token check to the caddy drop-in.
  • liquidity-monitoring receiver: decide on replay protection. Its envelope has no ID or timestamp, so the 127.0.0.1-only binding is the current guard.
  • Not done here: give the subscriber unit its own minimal env file. That needs coordination because it changes which env file the unit reads.
  • Decide whether Wavey Gist reports for queued, not-yet-executed Safe transactions should stay at public URLs.

Test plan

  • uv run pytest tests/: 2318 passed, 4 skipped (after merging main). New tests cover redaction, 429 retry, hook-on-failure, dispatch gating, task/git/uv timeouts, stables confirmation, and the 3Jane/Cap thresholds.
  • ruff check, ruff format --check, ty check
  • Caddyfile adapts, and the auth behaviour above was confirmed with a local Caddy run.
  • bash -n deploy/install.sh

🤖 Generated with Claude Code

spalen0 and others added 2 commits October 27, 2026 09:52
… deploy

- Add utils/redact.py and apply it to logs, Telegram sends, crash alerts, the
  automation digest, and web3 provider errors so RPC/API keys never reach
  Telegram, the alerts DB/API, or journald.
- Run the emergency dispatch hook even when Telegram delivery fails; retry
  Telegram 429s once after retry_after; truncate by UTF-16 length; share the
  parse-error plain-text retry with the test-chat path.
- Add Alert.dispatch so unconfirmed signals notify without moving funds: stables
  depegs need two consecutive breaches, 3Jane/Cap PPS drops need a minimum size.
  Sign a sent_at timestamp into the dispatch body for replay protection.
- Per-task timeouts in jobs.yaml (default 3600s, stables 300s) and timeouts on
  git fetch/reset and uv sync so a hang can't freeze a profile or code sync;
  timeouts on Gauntlet requests.
- Deploy: API unit loads no secrets and cannot read /etc/monitoring; Caddy fails
  closed on an empty token list; install.sh no longer stores personal gh tokens;
  CI pins actions to SHAs with read-only permissions.
- Explainer: treat supplied context as untrusted and label AI summaries unverified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@spalen0
spalen0 marked this pull request as ready for review November 5, 2026 11:28
spalen0 and others added 7 commits November 5, 2026 13:04
- git sync: on timeout send SIGTERM before SIGKILL so git cleans up its
  lock files, and remove lock files older than 30 minutes left by a
  killed process.
- dispatch: run the alert hook before the Telegram send so 429 waits
  and Markdown retries cannot delay it.
- 3Jane PPS / Cap stcUSD: measure the dispatch gate as drawdown from
  the peak since the last dispatch-eligible alert, so a loss split into
  many small drops still dispatches.
- stables: a prior breach older than 25 minutes no longer confirms a
  new one.
- redaction: match configured secrets in escape_markdown form, cover
  suffixed names (TELEGRAM_BOT_TOKEN_DEFAULT, *_SECRET_X) and bare bot
  tokens, mask scheme-less provider URLs, use a Markdown-safe REDACTED
  marker, cache the secret list, and drop a duplicate redaction call.
- docs: 3Jane and Cap READMEs, new stables README, monitoring.yaml.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	utils/alert.py
#	utils/telegram.py
…erate malformed provider URLs

- git sync: run each step in its own process group, signal the whole
  group on timeout (SIGTERM, then SIGKILL), and bound output draining so
  a descendant holding the pipes cannot keep the sync profile locked.
- cap: a drop of convertToAssets(1e18) to zero is dispatch-eligible.
- redaction: a provider URL that urlsplit rejects no longer makes every
  redact_secrets call raise; the value is still masked whole.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…meouts

- stables: a failed send no longer skips later protocols' dispatch; a breach
  only confirms on a newer DeFiLlama sample with minimum spacing
- telegram: replace lone surrogates; count the test-chat label in the limit
- redact: keep token-address links and word slugs, mask drpc dkey
- automation: stop timed-out tasks with SIGTERM then SIGKILL per process
  group; run_with_alert kills detached CLI agents on SIGTERM
- share the peak-drawdown dispatch gate between Cap and 3Jane

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Port the PR's dispatch hardening onto main's durable emergency queue (#457):
- Keep the Alert.dispatch gate: unconfirmed signals are never enqueued.
- Redact the alert message before it is persisted to the outbox or sent.
- Drop the signed `sent_at` field. Retries must resend the exact persisted
  bytes and keep the liquidity envelope unchanged; vaults-mommy dedupes on
  the signed event_id instead. Runbook updated accordingly.
- Give the per-minute emergency-delivery-retries task a 600s timeout so a
  hang can't block retries behind its flock for the 1h default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@spalen0
spalen0 merged commit 5cb0cb9 into main Nov 11, 2026
3 checks passed
@spalen0
spalen0 deleted the sec-review branch November 11, 2026 10:13
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