Skip to content

Commit

Permalink
Merge pull request #9 from github/fix_message
Browse files Browse the repository at this point in the history
fix: allow to pass messages as arrays or hashes
  • Loading branch information
GrantBirki authored Dec 18, 2023
2 parents 61b641e + a9ada2a commit 2b00dbb
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 105 deletions.
18 changes: 15 additions & 3 deletions lib/redacting_logger.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,26 @@ def initialize(
# Adds a message to the log.
#
# @param severity [Integer] The severity level of the message.
# @param message [String] The message to log.
# @param message [String|Array|Hash] The message to log.
# @param progname [String] The name of the program.
def add(severity, message = nil, progname = nil)
message, progname = yield if block_given?

if message
@redact_patterns.each do |pattern|
@redact_patterns.each do |pattern|
case message

when String
message = message.to_s.gsub(pattern, @redacted_msg)

when Array
message = message.map do |m|
m = m.to_s.gsub(pattern, @redacted_msg)
end

when Hash
message = message.map do |k, v|
[k, v.to_s.gsub(pattern, @redacted_msg)]
end.to_h
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

module RedactingLogger
module Version
VERSION = "1.0.0"
VERSION = "1.1.0"
end
end
175 changes: 74 additions & 101 deletions spec/lib/redacting_logger_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,114 +45,87 @@
let(:logdev) { StringIO.new }
let(:logger) { RedactingLogger.new(logdev, redact_patterns: [/secret/, /password/, /token_[A-Z]{5}/]) }

it "ensures the message is redacted" do
logger.info { ["This is a secret password", nil] }

logdev.rewind
log_output = logdev.read

expect(log_output).to match(/This is a \[REDACTED\] \[REDACTED\]/)
end

it "ensures the progname is redacted" do
logger.info { ["This is a message", "secret"] }

logdev.rewind
log_output = logdev.read

expect(log_output).to match(/\[REDACTED\]: This is a message/)
end

it "redacts the message when it is a substring of the redact pattern" do
logger.info("This is a supersecretmessage")

logdev.rewind
log_output = logdev.read
expect(log_output).to match(/This is a super\[REDACTED\]message/)
end

it "redacts a GitHub Personal Access Token that is 40 characters" do
token = "ghp_aBcdeFghIjklMnoPqRSTUvwXYZ1234567890"

logger.info("logging in with token #{token} ...")

logdev.rewind
log_output = logdev.read
expect(log_output).to match(/logging in with token \[REDACTED\] .../)
end

it "redacts a GitHub Personal Access Token got mashed with another string" do
token = "ghp_aBcdeFghIjklMnoPqRSTUvwXYZ1234567890ohnothisisnotgood"

logger.info("logging in with token #{token} ...")

logdev.rewind
log_output = logdev.read
expect(log_output).to match(/logging in with token \[REDACTED\] .../)
end

it "redacts a fine-grained GitHub Personal Access Token" do
# This token is not real, but it is the correct length and format
token = "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"

logger.warn("oh no, I failed to login with that token: #{token}, try again")

logdev.rewind
log_output = logdev.read
expect(log_output).to match(/oh no, I failed to login with that token: \[REDACTED\], try again/)
end

it "redacts a GitHub Actions temp token" do
token = "ghs_1234567890abcdefghijklmnopqrstuvwxyz123456"

logger.debug("GitHub Actions token: #{token}")

logdev.rewind
log_output = logdev.read
expect(log_output).to match(/GitHub Actions token: \[REDACTED\]/)
end

it "redacts a custom token" do
token = "token_ABCDE"

logger.fatal("Custom token: #{token}")

logdev.rewind
log_output = logdev.read
expect(log_output).to match(/Custom token: \[REDACTED\]/)
[
{
case: "secret message",
message: "This is a secret password",
expected_message: "This is a [REDACTED] [REDACTED]",
},
{
case: "secret progname",
progname: "secret progname",
expected_progname: "[REDACTED] progname",
},
{
case: "secret substring",
message: "This is a supersecretmessage",
expected_message: "This is a super[REDACTED]message",
},
{
case: "github token",
message: "token ghp_aBcdeFghIjklMnoPqRSTUvwXYZ1234567890",
expected_message: "token [REDACTED]",
},
{
case: "github token hidden in another string",
message: "token ghp_aBcdeFghIjklMnoPqRSTUvwXYZ1234567890ohnothisisnotgood",
expected_message: "token [REDACTED]",
},
{
case: "fine-grained github pat",
message: "token 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",
expected_message: "token [REDACTED]",
},
{
case: "github action pat",
message: "token ghs_1234567890abcdefghijklmnopqrstuvwxyz123456",
expected_message: "token [REDACTED]123456",
},
{
case: "custom token",
message: "token token_ABCDE",
expected_message: "token [REDACTED]",
},
{
case: "custom token only if long enough",
message: "token token_ABCD",
expected_message: "token token_ABCD",
},
{
case: "JWT token",
message: "token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
expected_message: "token [REDACTED]",
},
{
case: "RSA private key",
message: "token #{File.read("spec/fixtures/fake.private_key")}",
expected_message: "token [REDACTED]-\n",
},
{
case: "list of messages",
message: ["this", "is", "a", "secret"],
expected_message: ["this", "is", "a", "[REDACTED]"],
},
{
case: "hash of messages",
message: { this: "is", "a" => "secret" },
expected_message: { this: "is", "a" => "[REDACTED]" },
},
].each do |test|
it "redacts #{test[:case]}" do
expect_any_instance_of(Logger).to receive(:add).with(0, test[:expected_message], test[:expected_progname])
logger.add(0, test[:message], test[:progname])
end
end

it "does not remove a token that is too short" do
token = "token_ABCD"

logger.fatal("Custom token: #{token}")

logdev.rewind
log_output = logdev.read

expect(log_output).to match(/Custom token: token_ABCD/)
end

it "redacts a JWT token" do
# this is a dummy JWT token, but it is the correct length and format
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"

logger.info("JWT token: #{token}")
it "redacts with given block" do
logger.info { ["This is a secret password", nil] }

logdev.rewind
log_output = logdev.read

expect(log_output).to match(/JWT token: \[REDACTED\]/)
expect(log_output).to match(/This is a \[REDACTED\] \[REDACTED\]/)
end

it "redacts a RSA private key" do
fake_private_key = File.read("spec/fixtures/fake.private_key")

logger.info("RSA private key: #{fake_private_key}")

logdev.rewind
log_output = logdev.read
expect(log_output).to match(/RSA private key: \[REDACTED\]/)
end
end
end

0 comments on commit 2b00dbb

Please sign in to comment.