mirror of
https://github.com/warmbly/warmbly.git
synced 2026-10-15 00:02:42 +00:00
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
320 lines
13 KiB
Go
320 lines
13 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"io"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/warmbly/warmbly/internal/utils/paging"
|
|
)
|
|
|
|
func remoteWordSet(words string) map[string]bool {
|
|
out := map[string]bool{}
|
|
for _, word := range strings.Fields(words) {
|
|
out[word] = true
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Unknown fields and free text fail closed, including newly added backend fields.
|
|
var remoteOutputFields = remoteWordSet(`
|
|
data pagination summary permissions user organization workers nodes mailboxes
|
|
commands results metrics broker partitions topics consumer_group topic events capture
|
|
generic_error_coverage generic_error_reason
|
|
sources counts health totals failing_endpoints jobs plans discounts members roles
|
|
id node_id worker_id email_id email_account_id mailbox_id user_id organization_id
|
|
campaign_id contact_id sequence_id task_id run_id scope_id provider account_status
|
|
status task_status task_type backfill_status level category event availability coverage
|
|
condition reason severity unit role service transport version protocol kind type
|
|
is_admin admin_permissions session_mfa_verified mfa_verified enabled active healthy
|
|
delivers replayed cleared reloaded reset requested revoked truncated has_message_id
|
|
has_next has_previous has_more next_cursor prev_cursor cursor limit total count attempts
|
|
max_attempts pending failed throttled backfilling stalled complete deferred
|
|
backfill_synced in_flight_stale pending_due delivered_last_24h failed_last_24h
|
|
abandoned_last_24h drops_last_7d lease_minutes consecutive_failures deliveries_last_7d
|
|
failed_last_7d measured_scopes expected_scopes affected_mailboxes affected_organizations
|
|
unknown_organization_rows evidence_age_seconds threshold_seconds partition committed
|
|
earliest latest committed_lag dropped retained_count retention_seconds max_events
|
|
under_5m under_30m past_reclaim_window reclaim_after_minutes age_seconds
|
|
observed_at checked_at evidence_at latest_evidence_at next_eligible_at window_start
|
|
window_end started_at received_at oldest_retained_at newest_retained_at created_at
|
|
updated_at dispatched_at oldest_dispatched_at next_retry_at replayed_at occurred_at
|
|
backfill_since backfill_started_at backfill_completed_at throttled_until last_synced_at
|
|
last_success_at last_failure_at last_started_at last_finished_at last_duration_ms
|
|
last_status interval_seconds http_status refresh_after expires_at last_seen_at
|
|
memory_bytes cpu_percent load capacity reserved used free mailbox_count online offline
|
|
mailboxes_count worker_count email_count campaign_count organization_count timestamp
|
|
access_token_expires_at refresh_token_expires_at email verified banned_at deleted_at
|
|
account_count health_state load_score usage live worker_live capacity_target
|
|
base_capacity health_multiplier age_multiplier effective_capacity utilization
|
|
sends_attempted_1h sends_succeeded_1h bounces_hard_1h bounces_soft_1h complaints_1h
|
|
auth_errors_1h total_emails_sent emails_sent_today emails_sent_total emails_sent_this_week
|
|
average_delivery_time_ms success_rate queue_depth active_campaigns connected_emails
|
|
warmup_emails warmup_enabled risk_band risk_evaluated_at warmup_health blocked_until
|
|
memory_mb goroutines uptime_seconds cpu_scope memory_scope memory_used_mb memory_limit_mb
|
|
resident_mb pinned_version desired_version enrolled_at assigned_at released_at
|
|
subscription_id run_count error_count run_requested_at next_run_at completed_at
|
|
requests_last_7d revoked_at last_used_at backends settings risk state warmup_status
|
|
auth_state send_lifecycle in_campaign daily_usage score warmup_pool_type pool_type
|
|
email_verified registration_enabled support ops analyst super view_users view_workers
|
|
view_analytics view_campaigns view_organizations manage_workers manage_settings
|
|
view_audit_logs view_warmup_pool name description bit value super_admin
|
|
`)
|
|
|
|
var remoteSafeEnums = remoteWordSet(`
|
|
fresh stale unavailable partial complete unknown healthy pending approved denied
|
|
active disabled banned suspended connected disconnected failed error running idle ok
|
|
sent sending scheduled queued delivered cancelled canceled paused stopped finished
|
|
replayed throttled backfilling stalled deferred reserved completed incomplete
|
|
info warn control_plane provider sync_policy compatibility worker_runtime
|
|
sync_control_plane_held mailbox_provider_unreachable mailbox_provider_auth_failed
|
|
mailbox_provider_throttled mailbox_provider_sync_completed sync_folder_skipped
|
|
sync_skipped_folder_search_failed sync_message_deferred sync_message_deferred_by_policy
|
|
sync_legacy_arrival worker_error log_credential_unavailable capture_not_observed
|
|
cache_unavailable no_recent_capture no_recent_evidence allowlisted_evidence_only
|
|
unhooked_runtime_sources
|
|
dependency_missing resource_absent permission_denied timeout collection_failed unsupported
|
|
committed_lag members worker_events seconds count bytes percent messages lag mailboxes
|
|
no_evidence recent_failure persistent_failure recovery_unverified safety_hold backlog
|
|
in_flight waiting_schedule waiting_retry budget daily_limit stale_telemetry
|
|
worker consumer backend smtp log gmail google outlook microsoft imap oauth password smtp_imap
|
|
watch quarantined quarantine blocked clean risky online offline loading passing failing
|
|
process container host SEND_EMAIL ADD_EMAIL REMOVE_EMAIL SYNC_EMAIL UPDATE_EMAIL
|
|
sync warmup campaign follow_up campaign_send warmup_send inbox_sync community private
|
|
super support ops analyst VIEW_USERS VIEW_WORKERS VIEW_ANALYTICS VIEW_CAMPAIGNS
|
|
VIEW_ORGANIZATIONS MANAGE_WORKERS MANAGE_SETTINGS VIEW_AUDIT_LOGS GRANT_ADMIN_ACCESS
|
|
VIEW_WARMUP_POOL STOP_CAMPAIGNS BAN_USERS MANAGE_RATE_LIMITS MANAGE_ORGANIZATIONS
|
|
MANAGE_TESTERS MANAGE_WARMUP_BANS REVIEW_APPEALS public dedicated shared global
|
|
`)
|
|
|
|
var remoteSemver = regexp.MustCompile(`^v?[0-9]{1,5}\.[0-9]{1,5}\.[0-9]{1,5}(-[a-z0-9.-]{1,30})?$`)
|
|
|
|
var remoteMonitoringIDs = remoteWordSet(`
|
|
send_safety sends campaign_reservations dispatch sync mailbox_errors worker_samples
|
|
jobs dead_letters webhooks notifications result_effects arrivals workers domain_auth
|
|
warmup_loading backend_event_broker worker_command_broker send_wait_attribution tracking_broker
|
|
arrival_aging arrival_due arrival_leased arrival_pending arrival_stage_0 arrival_stage_1
|
|
arrival_stage_2 arrival_stage_3 arrival_wait backfill_complete backfill_pending
|
|
backfill_running backfill_stale_running campaign_reserved campaign_reserved_aging
|
|
confirmed_other_1h confirmed_warmup_1h confirmed_warmup_proxy_1h credential_errors
|
|
dispatch_due dispatch_old_failure dispatch_persistent dlq_due dlq_exhausted dlq_replayed
|
|
dlq_unscheduled dlq_wait effects_leased effects_pending effects_repeated effects_unleased
|
|
failed_results_1h failed_results_24h future_schedule holds_authentication holds_conflict
|
|
holds_permanent holds_unknown inactive_credential_errors job_error_old job_error_recent
|
|
job_future job_ok job_overdue job_running notification_aging notification_due
|
|
notification_exhausted notification_retry notification_sending notification_settled_24h
|
|
notification_stale notification_wait original_slot_overdue_1h other_mailbox_errors
|
|
reservations_5_to_30m reservations_over_30m reservations_under_5m retry_wait send_cooldowns
|
|
send_paused sync_deferred sync_eligible sync_errors sync_future_clock sync_held_stale
|
|
sync_hold_burst sync_hold_daily sync_hold_hourly sync_hold_org_daily sync_hold_other
|
|
sync_hold_priority_daily sync_missing sync_safety_reports sync_stale sync_state_rows
|
|
sync_unassigned task_failures_recent unknown_results unscheduled_pending webhook_abandoned_24h
|
|
webhook_auto_disabled webhook_delivered_24h webhook_drop_windows webhook_due webhook_endpoint_old
|
|
webhook_endpoint_persistent webhook_endpoint_recent webhook_retry webhook_stale_claim webhook_wait
|
|
worker_sample_auth_errors_1h worker_sample_rate_errors_1h worker_sample_sends_1h worker_samples_missing
|
|
domain_blocked domain_failing domain_future_clock domain_passing_fresh domain_stale domain_unknown
|
|
loading_old loading_persistent loading_recent workers_missing_heartbeat worker_missing_heartbeat
|
|
send_budget_wait send_daily_limit_wait send_schedule_reason tracking_queue
|
|
broker_queue committed_lag members
|
|
`)
|
|
|
|
var remoteMonitoringUnits = remoteWordSet(`
|
|
arrivals deliveries effects endpoints jobs mailboxes messages records reports reservations
|
|
rows tasks windows workers members
|
|
`)
|
|
|
|
var remoteMonitoringReasons = remoteWordSet(`
|
|
scope_limit sample_limit query_failed schema_absent no_registered_scopes
|
|
dependency_missing resource_absent permission_denied timeout collection_failed
|
|
unsupported no_recent_evidence
|
|
`)
|
|
|
|
func safeRemoteMonitoringString(key, value string) string {
|
|
if value == "" {
|
|
return ""
|
|
}
|
|
switch key {
|
|
case "id":
|
|
if remoteMonitoringIDs[value] {
|
|
return value
|
|
}
|
|
case "unit":
|
|
if remoteMonitoringUnits[value] {
|
|
return value
|
|
}
|
|
case "severity":
|
|
if value == "info" || value == "warning" || value == "critical" {
|
|
return value
|
|
}
|
|
case "reason":
|
|
if remoteMonitoringReasons[value] {
|
|
return value
|
|
}
|
|
case "version":
|
|
if value == "1" {
|
|
return value
|
|
}
|
|
}
|
|
return "[REDACTED]"
|
|
}
|
|
|
|
func decodeRemoteJSON(payload []byte) (any, error) {
|
|
dec := json.NewDecoder(bytes.NewReader(payload))
|
|
dec.UseNumber()
|
|
var read func(int) (any, error)
|
|
read = func(depth int) (any, error) {
|
|
if depth > 64 {
|
|
return nil, remoteFailure(2, "invalid_json", "JSON nesting exceeds 64 levels.")
|
|
}
|
|
t, err := dec.Token()
|
|
if err != nil {
|
|
return nil, remoteFailure(2, "invalid_json", "Malformed JSON. No raw input was printed.")
|
|
}
|
|
if delimiter, ok := t.(json.Delim); ok {
|
|
switch delimiter {
|
|
case '{':
|
|
out := map[string]any{}
|
|
for dec.More() {
|
|
key, err := dec.Token()
|
|
if err != nil {
|
|
return nil, remoteFailure(2, "invalid_json", "Malformed JSON object.")
|
|
}
|
|
name, ok := key.(string)
|
|
if !ok {
|
|
return nil, remoteFailure(2, "invalid_json", "Malformed JSON object key.")
|
|
}
|
|
if _, duplicate := out[name]; duplicate {
|
|
return nil, remoteFailure(2, "invalid_json", "Duplicate JSON object keys are not accepted.")
|
|
}
|
|
out[name], err = read(depth + 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if _, err := dec.Token(); err != nil {
|
|
return nil, remoteFailure(2, "invalid_json", "Unterminated JSON object.")
|
|
}
|
|
return out, nil
|
|
case '[':
|
|
out := []any{}
|
|
for dec.More() {
|
|
value, err := read(depth + 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, value)
|
|
}
|
|
if _, err := dec.Token(); err != nil {
|
|
return nil, remoteFailure(2, "invalid_json", "Unterminated JSON array.")
|
|
}
|
|
return out, nil
|
|
default:
|
|
return nil, remoteFailure(2, "invalid_json", "Unexpected JSON delimiter.")
|
|
}
|
|
}
|
|
return t, nil
|
|
}
|
|
value, err := read(0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := dec.Token(); err != io.EOF {
|
|
return nil, remoteFailure(2, "invalid_json", "Expected exactly one JSON value.")
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func safeRemoteString(key, value string) any {
|
|
if value == "" {
|
|
return ""
|
|
}
|
|
|
|
if (key == "cursor" || key == "next_cursor" || key == "prev_cursor") && len(value) <= 512 {
|
|
if _, err := paging.DecodeOffsetCursor(value); err == nil {
|
|
return value
|
|
}
|
|
if _, err := paging.DecodeUUID(value); err == nil {
|
|
return value
|
|
}
|
|
if _, _, err := paging.DecodeTimeCursor(value); err == nil {
|
|
return value
|
|
}
|
|
}
|
|
if _, err := time.Parse(time.RFC3339Nano, value); err == nil {
|
|
return value
|
|
}
|
|
if id, err := uuid.Parse(value); err == nil && id.String() == strings.ToLower(value) {
|
|
return value
|
|
}
|
|
if remoteSafeEnums[value] {
|
|
return value
|
|
}
|
|
if key == "version" && remoteSemver.MatchString(value) {
|
|
return value
|
|
}
|
|
if key == "topic" || key == "topics" || key == "consumer_group" {
|
|
if value == "jobs.worker-events" || value == "consumer-group" {
|
|
return value
|
|
}
|
|
for _, prefix := range []string{"w.", "worker-"} {
|
|
if suffix, found := strings.CutPrefix(value, prefix); found {
|
|
if id, err := uuid.Parse(suffix); err == nil && id.String() == suffix {
|
|
return value
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return "[REDACTED]"
|
|
}
|
|
|
|
func redactRemoteJSON(key string, value any) any {
|
|
switch v := value.(type) {
|
|
case map[string]any:
|
|
out := map[string]any{}
|
|
redacted := 0
|
|
for name, item := range v {
|
|
if !remoteOutputFields[name] {
|
|
redacted++
|
|
continue
|
|
}
|
|
if (key == "sources" || key == "metrics") && (name == "id" || name == "unit" || name == "severity" || name == "reason") ||
|
|
key == "" && name == "version" && v["sources"] != nil {
|
|
if value, ok := item.(string); ok {
|
|
out[name] = safeRemoteMonitoringString(name, value)
|
|
} else {
|
|
out[name] = "[REDACTED]"
|
|
}
|
|
continue
|
|
}
|
|
out[name] = redactRemoteJSON(name, item)
|
|
}
|
|
if redacted > 0 {
|
|
out["_redacted_fields"] = redacted
|
|
}
|
|
return out
|
|
case []any:
|
|
out := make([]any, len(v))
|
|
for i, item := range v {
|
|
out[i] = redactRemoteJSON(key, item)
|
|
}
|
|
return out
|
|
case string:
|
|
return safeRemoteString(key, v)
|
|
default:
|
|
return value
|
|
}
|
|
}
|
|
|
|
func printRemoteResponse(payload json.RawMessage) error {
|
|
value, err := decodeRemoteJSON(payload)
|
|
if err != nil {
|
|
return remoteFailure(5, "invalid_response", "Response JSON is malformed, ambiguous or too deeply nested. No raw response was printed.")
|
|
}
|
|
return writeRemoteJSON(os.Stdout, redactRemoteJSON("", value))
|
|
}
|