{"record":{"id":"1fb6dc3f10a5e4cd","repo":"ankane/pghero","slug":"invalid-metric-name","errorCode":null,"errorMessage":"Invalid metric name","messagePattern":"Invalid metric name","errorType":"validation","errorClass":"PgHero::Error","httpStatus":null,"severity":"error","filePath":"lib/pghero/methods/system.rb","lineNumber":89,"sourceCode":"\n          data\n        else\n          raise NotEnabled, \"System stats not enabled\"\n        end\n      end\n\n      private\n\n      def gcp_stats(metric_name, duration: nil, period: nil, offset: nil, series: false)\n        # TODO DRY with RDS stats\n        duration = (duration || 1.hour).to_i\n        period = (period || 1.minute).to_i\n        offset = (offset || 0).to_i\n        end_time = Time.at(((Time.now - offset).to_f / period).ceil * period)\n        start_time = end_time - duration\n\n        # validate input since we need to interpolate below\n        raise Error, \"Invalid metric name\" unless /\\A[a-z\\/_]+\\z/i.match?(metric_name)\n        raise Error, \"Invalid database id\" unless /\\A[a-z0-9\\-:]+\\z/i.match?(gcp_database_id)\n\n        # we handle three situations:\n        # 1. google-cloud-monitoring-v3\n        # 2. google-cloud-monitoring\n        # 3. google-apis-monitoring_v3\n        begin\n          require \"google/cloud/monitoring/v3\"\n        rescue LoadError\n          require \"google/apis/monitoring_v3\"\n        end\n\n        # for situations 1 and 2\n        # Google::Cloud::Monitoring.metric_service doesn't work for situation 1\n        if defined?(Google::Cloud::Monitoring::V3)\n          client = Google::Cloud::Monitoring::V3::MetricService::Client.new\n\n          interval = Google::Cloud::Monitoring::V3::TimeInterval.new","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/ankane/pghero/blob/7edb57986ffd36f9d64f0830c4ccc90a5eac46d6/lib/pghero/methods/system.rb#L71-L107","documentation":"PgHero raises this from the private gcp_stats helper (lib/pghero/methods/system.rb:89) when metric_name fails /\\A[a-z\\/_]+\\z/i. The check exists because metric_name is interpolated directly into the Cloud Monitoring time-series filter string (metric.type = \"cloudsql.googleapis.com/database/#{metric_name}\"); anything outside letters, underscores, and slashes could break out of the quoted filter. PgHero's own callers only pass fixed names like cpu/utilization, disk/quota, postgresql/num_backends, so this fires when custom code invokes gcp_stats with an arbitrary metric string.","triggerScenarios":"Calling the private helper directly (obj.send(:gcp_stats, \"disk/bytes_used\")) or extending PgHero with new metrics whose names contain dots, digits, spaces, or quotes (e.g. \"v2/cpu\", \"disk.io\", \"cpu utilization\") — all fail the letters/underscore/slash-only whitelist.","commonSituations":"Plugins or monkey-patches adding custom Cloud SQL metrics to PgHero's system_stats metrics map; copy-pasted metric names from the GCP console (console paths use dots, e.g. cloudsql.googleapis.com/database/cpu/utilization) passed as the full dotted path instead of the trailing slash form.","solutions":["Pass the short slash-form metric name relative to cloudsql.googleapis.com/database/ (e.g. \"cpu/utilization\", \"disk/read_ops_count\"), matching the whitelist of letters, underscores, and slashes.","If you must call gcp_stats with a custom metric, normalize the name first: strip the cloudsql.googleapis.com/database/ prefix and replace invalid characters with underscores.","Rescue PgHero::Error around custom metric calls and log the rejected name, since the generic Error class carries no structured details."],"exampleFix":"# before - raises PgHero::Error: Invalid metric name\nobj.send(:gcp_stats, \"cloudsql.googleapis.com/database/cpu/utilization\")\n\n# after - use the short slash-form name (letters, underscore, slash only)\nobj.send(:gcp_stats, \"cpu/utilization\")","handlingStrategy":"validation","validationCode":"VALID_GCP_METRIC = /\\A[a-z\\/_]+\\z/i\nname = \"cloudsql.googleapis.com/database/cpu/utilization\".delete_prefix(\"cloudsql.googleapis.com/database/\")\nPgHero.send(:gcp_stats, name) if VALID_GCP_METRIC.match?(name)","typeGuard":"def valid_gcp_metric_name?(name)\n  name.is_a?(String) && name.match?(/\\A[a-z\\/_]+\\z/i)\nend","tryCatchPattern":"begin\n  PgHero.send(:gcp_stats, metric_name)\nrescue PgHero::Error => e\n  raise unless e.message == \"Invalid metric name\"\n  Rails.logger.error(\"Rejected GCP metric name: #{metric_name.inspect}\")\n  {}\nend","preventionTips":["Restrict custom metrics to the whitelist charset (letters, underscore, slash) - no dots or digits.","Derive metric names from a constant map instead of user or console input.","Prefer extending PgHero's metrics hash in system_stats with short slash-form names."],"tags":["pghero","gcp","cloud-sql","monitoring","input-validation"],"backgroundTag":"input-validation-failed","analyzedSha":"7edb57986ffd36f9d64f0830c4ccc90a5eac46d6","analyzedAt":"2026-08-21T17:33:54.942Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}