Help us shape the future of Go! Take the 2026 Go Developer Survey

secret

package
v0.0.33 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 12, 2026 License: Apache-2.0 Imports: 4 Imported by: 0

Documentation

Overview

Package secret finds credentials in text.

It exists because a shell writes two things down that a person did not ask it to keep: the lines they typed, in the history file, and — once there is somewhere to put it — what those lines printed. Both are ordinary files with ordinary permissions, and a token that reaches either one has been written to disk by a program the person was only talking to.

The rules are patterns for credentials whose *shape* is public: an AWS key id begins with four known letters and is twenty characters long because AWS says so, a PEM private key starts with a line that is part of the format. None of that is a secret, and none of it is anyone's expression of it — the table here is written from those facts and from nothing else.

Two postures, one table

The callers want different things done with a match, and the difference is the whole reason this is a package rather than a function:

  • A *command line* that matches is rejected whole. The credential essentially is the command — `export TOKEN=…` is nothing else — so keeping a redacted skeleton of it saves nobody anything, and dropping the line is proportionate. Scanner.Match answers that question.

  • *Output* that matches is redacted, never rejected. Dropping a two-hundred-kilobyte build log because one line of it echoed a token destroys exactly what the person wanted to keep. Scanner.Redact replaces the credential and leaves the rest of the text standing.

The table has to be the same table for the two to mean anything together. A history that refuses a line whose output would have been kept, or the reverse, is a policy nobody can describe in a sentence.

What it is not

It is not an entropy scanner and does not try to be. A rule that fires on "this looks random" fires on a commit hash, a UUID and a base64 image, and a scrubber that rejects ordinary lines is worse than no scrubber at all: people turn it off, or worse, stop reading what it says. Every rule here either names its credential's own prefix or requires the text to say what the value is — `password=`, `Authorization:`, a password inside a URL.

The cost of that choice is real and worth stating: a bare secret, pasted with nothing around it to say what it is, does not match. There is nothing in the text to match on.

Index

Constants

View Source
const Placeholder = "[redacted]"

Placeholder is what Scanner.Redact leaves where a credential was.

It says something happened rather than eliding it silently — a log with a hole in it where a token used to be is a log someone will read as corrupt and go looking for the original of.

Variables

This section is empty.

Functions

This section is empty.

Types

type Scanner

type Scanner struct {
	// contains filtered or unexported fields
}

A Scanner matches text against a table of credential rules.

The zero value matches nothing, which is deliberate: a scanner is either the default table or one a caller assembled on purpose, and an empty one that silently approves everything is not a state to arrive at by accident.

func Default

func Default() *Scanner

Default is the standard table.

One compiled copy, shared: the patterns are constant, matching does not mutate them, and a shell that recompiled a dozen regular expressions per line typed would be paying for this feature at the prompt.

Compiled on the first ask rather than at package init, which is #1403. `driver` links `repl` so that a shell can prompt, `repl` links this so that a prompt can redact, and Go initializes a linked package whether or not the route that wants it is taken — so `GODEBUG=inittrace=1` charged **every** `sh -c` in the tree 0.34 ms and 2228 allocations to compile a dozen regular expressions for a prompt it was never going to draw. That was the largest single entry in the whole init trace, ours or the standard library's.

Still one shared copy and still compiled once: sync.OnceValue keeps both properties, so a prompt pays the 0.34 ms once on the first line it redacts and every route that never redacts pays nothing. The rules are constant, so there is nothing to invalidate and no reason for a second entry point.

func (*Scanner) Match

func (s *Scanner) Match(text string) (string, bool)

Match reports the first rule that finds a credential in the text, naming it.

The name is for the person, not for the program: a line that vanishes from the history with no reason given is indistinguishable from a shell that has lost it, and "aws-access-key-id" is also the only way anyone can tell us a rule is wrong.

First rather than every rule, because the answer is a yes or a no and the remaining rules cannot change it. The table is ordered so that the specific rules are asked before the general ones, which makes the name the most informative one available.

func (*Scanner) Redact

func (s *Scanner) Redact(text string) (string, int)

Redact replaces every credential in the text with Placeholder, returning the result and how many it replaced.

Text with nothing in it comes back untouched and unallocated, which is the case that happens every time: almost no output holds a credential, and this runs over all of it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL