Module: Posthaste::Redaction

Defined in:
lib/posthaste/redaction.rb

Overview

Keeping the API key out of everything that can be printed.

A key is a bearer credential: whoever holds it can send mail from every verified domain on the account. The places it escapes from are not the ones people guard — nobody logs the key on purpose. In Ruby it escapes through the DEFAULT #inspect, which prints every instance variable, and a lot of things call #inspect on your behalf:

* `p object`, and the console echoing a return value
* a `binding.irb` session pasted into a ticket
* Rails' exception page, which inspects the delivery method
* an error reporter (Sentry, Bugsnag, Honeybadger) capturing locals and
shipping them to a third party

So every object in this gem that can reach the key defines its own #inspect, and every string built for a human goes through redact first.

Constant Summary collapse

REDACTED =
'***redacted***'
PREFIX =

ph_live_… / ph_test_…. The prefix is not secret — it is printed in the dashboard beside every key — and it is the one piece that helps somebody staring at a 401 work out that they pasted the test key into production.

/\Aph_(?:live|test)_/
KEY_SHAPED =

Anything key-shaped, wherever it turns up. Used for text this gem did not build itself — a server message that echoed the credential back, a transport error that quoted the request line — where there is no key to compare against.

/ph_(?:live|test)_[A-Za-z0-9_-]{8,}/

Class Method Summary collapse

Class Method Details

.describe_key(api_key) ⇒ Object

A label for a key that cannot be turned back into the key.

Deliberately NOT the usual "last four characters" convention. Four characters of a token this size is not enough to authenticate with, but it is enough to confirm a guess, and the thing it is normally used for — telling two keys apart — is served just as well by the environment prefix without narrowing the search space at all.



43
44
45
46
47
48
# File 'lib/posthaste/redaction.rb', line 43

def describe_key(api_key)
  return '(none)' if api_key.nil? || api_key.to_s.empty?

  match = PREFIX.match(api_key.to_s)
  "#{match ? match[0] : ''}#{REDACTED}"
end

.redact(text, api_key = nil) ⇒ Object

Remove the key from a string meant for a human.

Two passes on purpose. The first removes this client's own key, which catches it however short or oddly formatted it is. The second removes anything key-shaped, which catches a DIFFERENT account's key echoed back by a server or quoted by a library underneath us.



56
57
58
59
60
# File 'lib/posthaste/redaction.rb', line 56

def redact(text, api_key = nil)
  out = text.to_s
  out = out.gsub(api_key.to_s, REDACTED) if api_key && !api_key.to_s.empty?
  out.gsub(KEY_SHAPED, REDACTED)
end