Documentation
¶
Overview ¶
Package mask redacts sensitive values such as API keys and access tokens from text.
A Masker scans its input with the patterns it was given and redacts every value it locates:
m := mask.New(mask.WithPatterns(mask.AllBuiltinPatterns()...))
fmt.Println(m.Mask("GITHUB_TOKEN=ghp_0123456789abcdefghijklmnopqrstuvwxyz"))
Output:
GITHUB_TOKEN=****************************************
A Masker scans only with the patterns given to it; nothing is enabled implicitly. AllBuiltinPatterns returns the built-in ones, and a custom pattern comes from NewPattern, Regexp, MustRegexp or any implementation of the Pattern interface.
Example ¶
package main
import (
"fmt"
"github.com/koki-develop/mask-go"
)
func main() {
m := mask.New(mask.WithPatterns(mask.AllBuiltinPatterns()...))
fmt.Println(m.Mask("GITHUB_TOKEN=ghp_0123456789abcdefghijklmnopqrstuvwxyz"))
}
Output: GITHUB_TOKEN=****************************************
Index ¶
- Constants
- Variables
- type Masker
- type Match
- type Option
- type Pattern
- func AWSAccessKeyID() Pattern
- func AWSPatterns() []Pattern
- func AWSSecretAccessKey() Pattern
- func AgePatterns() []Pattern
- func AgeSecretKey() Pattern
- func AirtablePatterns() []Pattern
- func AirtablePersonalAccessToken() Pattern
- func AllBuiltinPatterns() []Pattern
- func AnthropicAPIKey() Pattern
- func AnthropicPatterns() []Pattern
- func BuildkitePatterns() []Pattern
- func BuildkiteToken() Pattern
- func CircleCIAPIToken() Pattern
- func CircleCIPatterns() []Pattern
- func CloudflareAPIKey() Pattern
- func CloudflareAPIToken() Pattern
- func CloudflarePatterns() []Pattern
- func CratesIOPatterns() []Pattern
- func CratesIOToken() Pattern
- func DatabricksOAuthClientSecret() Pattern
- func DatabricksPatterns() []Pattern
- func DatabricksPersonalAccessToken() Pattern
- func DigitalOceanPatterns() []Pattern
- func DigitalOceanToken() Pattern
- func DockerPatterns() []Pattern
- func DockerPersonalAccessToken() Pattern
- func DopplerAuthToken() Pattern
- func DopplerPatterns() []Pattern
- func DynatracePatterns() []Pattern
- func DynatraceToken() Pattern
- func FlyIOAccessToken() Pattern
- func FlyIOPatterns() []Pattern
- func GitHubPatterns() []Pattern
- func GitHubToken() Pattern
- func GitLabPatterns() []Pattern
- func GitLabToken() Pattern
- func GoogleAPIKey() Pattern
- func GooglePatterns() []Pattern
- func GrafanaPatterns() []Pattern
- func GrafanaServiceAccountToken() Pattern
- func GroqAPIKey() Pattern
- func GroqPatterns() []Pattern
- func HCPTerraformAPIToken() Pattern
- func HashiCorpPatterns() []Pattern
- func HashiCorpVaultToken() Pattern
- func HerokuAPIToken() Pattern
- func HerokuPatterns() []Pattern
- func HuggingFacePatterns() []Pattern
- func HuggingFaceUserAccessToken() Pattern
- func JWT() Pattern
- func LinearAPIKey() Pattern
- func LinearPatterns() []Pattern
- func MustRegexp(name, expr string) Pattern
- func NPMAccessToken() Pattern
- func NPMPatterns() []Pattern
- func NewPattern(name string, find func(src string) (spans []Span, retain int)) Pattern
- func NewRelicPatterns() []Pattern
- func NewRelicUserKey() Pattern
- func NotionAPIToken() Pattern
- func NotionPatterns() []Pattern
- func OnePasswordPatterns() []Pattern
- func OnePasswordServiceAccountToken() Pattern
- func OpenAIAPIKey() Pattern
- func OpenAIPatterns() []Pattern
- func OpenRouterAPIKey() Pattern
- func OpenRouterPatterns() []Pattern
- func PaddleAPIKey() Pattern
- func PaddlePatterns() []Pattern
- func PlanetScalePatterns() []Pattern
- func PlanetScaleToken() Pattern
- func PostHogPatterns() []Pattern
- func PostHogPersonalAPIKey() Pattern
- func PostmanAPIKey() Pattern
- func PostmanPatterns() []Pattern
- func PrivateKey() Pattern
- func PulumiAccessToken() Pattern
- func PulumiPatterns() []Pattern
- func PyPIAPIToken() Pattern
- func PyPIPatterns() []Pattern
- func Regexp(name, expr string) (Pattern, error)
- func ReplicateAPIToken() Pattern
- func ReplicatePatterns() []Pattern
- func ResendAPIKey() Pattern
- func ResendPatterns() []Pattern
- func RubyGemsAPIKey() Pattern
- func RubyGemsPatterns() []Pattern
- func SendGridAPIKey() Pattern
- func SendGridPatterns() []Pattern
- func SentryAuthToken() Pattern
- func SentryPatterns() []Pattern
- func ShopifyAccessToken() Pattern
- func ShopifyAppSecretKey() Pattern
- func ShopifyPatterns() []Pattern
- func SlackPatterns() []Pattern
- func SlackToken() Pattern
- func SonarQubePatterns() []Pattern
- func SonarQubeToken() Pattern
- func SourcegraphAccessToken() Pattern
- func SourcegraphPatterns() []Pattern
- func StripePatterns() []Pattern
- func StripePublishableKey() Pattern
- func StripeSecretKey() Pattern
- func StripeWebhookSigningSecret() Pattern
- func SupabaseAccessToken() Pattern
- func SupabasePatterns() []Pattern
- func SupabasePublishableKey() Pattern
- func SupabaseSecretKey() Pattern
- func XAIAPIKey() Pattern
- func XAIPatterns() []Pattern
- type Reader
- type Redactor
- type Span
- type StreamOption
- type Writer
Examples ¶
Constants ¶
const LookBehind = 64
LookBehind is how far in front of a value a Pattern may read: the bytes from LookBehind before the start of a span it reports up to that start, and no further back.
A Masker scanning a whole string hands every pattern the whole of it and the limit costs nothing. It is what NewReader and NewWriter rest on: a stream is masked by scanning a window that moves along it, and text the window has already carried past is what those readers keep rather than release. The limit is what tells them how much to keep.
Stated as a demand on a Find: hand it the text from an offset k on rather than the whole, where Find has settled at least k + LookBehind of the whole, and from k + LookBehind on it must report what it reports when handed everything. What that rules out is a Find whose answer at one place depends on the whole of the text in front of it. A scan walking the text from the start and stepping over each value it found would be such a Find: where the window begins would decide where the values fall, and a value would move under the window as the window moved.
A built-in pattern reads no further in front of a value than what decides whether a value stands there at all. Where that is the one character a prefix may not stand behind there is no count to state; a scan reading further than that character states in its own file how far, and holds it to this limit. A pattern built by Regexp reads one rune, which is what \b, \B and ^ are decided by. The limit is far above either so that a Pattern written by hand has room to read a keyword or an assignment in front of what it locates. A Find that cannot be held to it, where a value is decided by more text in front of it than this, must settle nothing: what settles nothing is never handed a window.
Variables ¶
var ErrClosed = errors.New("mask: writer is closed")
ErrClosed is returned by a Writer written to after it has been closed.
Functions ¶
This section is empty.
Types ¶
type Masker ¶
type Masker struct {
// contains filtered or unexported fields
}
Masker redacts sensitive values in text.
A Masker is fixed once created and is safe for concurrent use by multiple goroutines.
func New ¶
New returns a Masker that scans with the patterns given to WithPatterns and redacts what it locates with the redactor given to WithRedactor, which defaults to Fill('*').
A Masker with no patterns redacts nothing.
func (*Masker) Mask ¶
Mask returns src with every located value redacted.
Values that overlap are redacted together as one, so that no part of a located value survives. The combined text is attributed to the pattern that located the value starting earliest; among those, the longest; among those, the one added first by WithPatterns.
Masking is not idempotent, and what Mask is for is the text a program is about to write rather than text it has already masked. A redaction is itself text, and it does not read as the value it replaced: Fill('*') leaves an asterisk where a letter stood, and a prefix that letter closed is then open, so an AWS access key ID written against a Slack prefix is redacted on the first pass and takes a Slack token with it on the second. Fixed("") takes the value out altogether, splicing the text either side of it into text that was never written. Either way masking again may redact more than masking once did, and neither is a defect in a scan.
type Match ¶
type Match struct {
// Pattern is the pattern that located the value.
Pattern Pattern
// Value is the text about to be redacted. When a pattern redacts only
// part of what it matched, Value holds that part rather than the whole
// match.
//
// It reaches the other way too. Values that overlap are redacted
// together as one, and the combined text goes to the redactor under the
// single pattern Masker.Mask attributes it to, so Value can hold more
// than that pattern located and most of it can be what another pattern
// found. Masker.Mask states how the attribution is decided.
Value string
}
Match is a sensitive value located by a Pattern.
type Option ¶
type Option func(*options)
Option configures a Masker.
func WithPatterns ¶
WithPatterns adds patterns for a Masker to scan with. Repeated options accumulate in the order given:
m := mask.New(
mask.WithPatterns(mask.GitHubToken(), mask.JWT()),
mask.WithPatterns(mask.MustRegexp("internal-token", `INT-[0-9a-f]{32}`)),
)
Example ¶
package main
import (
"fmt"
"github.com/koki-develop/mask-go"
)
func main() {
// Repeated options accumulate, so the built-in patterns and one of your
// own can be given separately.
m := mask.New(
mask.WithPatterns(mask.AllBuiltinPatterns()...),
mask.WithPatterns(mask.MustRegexp("internal-token", `INT-[0-9a-f]{32}`)),
)
fmt.Println(m.Mask("github=ghp_0123456789abcdefghijklmnopqrstuvwxyz internal=INT-0123456789abcdef0123456789abcdef"))
}
Output: github=**************************************** internal=************************************
func WithRedactor ¶
WithRedactor sets what located values are redacted to, replacing Fill('*').
type Pattern ¶
type Pattern interface {
// Name identifies the pattern. It should be stable, lowercase and
// hyphenated, such as "github-token".
Name() string
// Find returns the byte ranges to redact in src, and the offset from
// which src is not yet settled.
//
// The spans may be unordered and may overlap; a Masker sorts them and
// resolves the overlaps. Spans reaching outside src, and spans whose
// Start is not less than their End, are ignored.
//
// Both ends must fall on a rune boundary. A span cutting a multi-byte
// rune in half is neither ignored nor repaired: the bytes either side of
// it are written back as they were found, so what is left of that rune
// stands beside the redaction and the output is not valid UTF-8. The
// built-in patterns and Regexp cannot report such a span — every
// built-in decides its ends on an ASCII alphabet, and Go's regexp
// matches runes — so this is a demand on a Find written by hand.
//
// retain answers what src alone cannot: whether src is all there is. For
// every text beginning with src, the values in it that begin before
// retain are exactly the spans reported here that begin before retain,
// with the same Start and the same End. Nothing is promised about what
// begins at retain or after it: a value there may grow, may appear where
// nothing was reported, and may turn out to be no value at all.
//
// Both directions of that are load-bearing. A value the shorter text
// misses is one a stream writes out before it is found; one it reports
// that the longer text does not is a redaction a stream cannot take
// back.
//
// Zero promises nothing and is always true. It is what a Find written
// without a stream in mind returns, and what a Find returns when the
// whole of src is still open — a value running to the end of it that
// more text would carry further. Reporting len(src) says the opposite:
// src stands complete, and nothing appended to it changes any of this.
//
// Mask reads the whole of its input at once and ignores retain. NewReader
// and NewWriter are what it is for: they hold back the text from retain
// on until more of the stream settles it.
Find(src string) (spans []Span, retain int)
}
Pattern locates sensitive values in text.
Implementations must be safe for concurrent use by multiple goroutines.
func AWSAccessKeyID ¶
func AWSAccessKeyID() Pattern
AWSAccessKeyID locates AWS access key IDs: twenty characters opening with AKIA, which AWS gives the long-term key of an IAM user or of the account root user, or ASIA, which it gives the temporary credentials AWS STS issues. Those are the two prefixes AWS documents for an access key ID, and STS tells the two apart by them.
A key is located wherever it is written, with no word boundary either side, and exactly twenty characters of it are. So an unbroken run of twenty uppercase letters and digits opening with one of the prefixes is redacted whether or not AWS issued it, which ASIA, being a word, makes reachable: ASIAPACIFICSOUTHEAST is redacted, and ASIANELEPHANTCONSERVATION loses its first twenty characters. A space, a hyphen or a lowercase letter ends the run, so text as it is ordinarily written is not affected.
Its name is "aws-access-key-id".
Example ¶
package main
import (
"fmt"
"github.com/koki-develop/mask-go"
)
func main() {
m := mask.New(mask.WithPatterns(mask.AWSAccessKeyID()))
fmt.Println(m.Mask("AWS_ACCESS_KEY_ID=AKIA0123456789ABCDEF"))
}
Output: AWS_ACCESS_KEY_ID=********************
func AWSPatterns ¶
func AWSPatterns() []Pattern
AWSPatterns returns every built-in pattern that locates a credential AWS issues.
The returned slice is freshly allocated and may be modified by the caller.
func AWSSecretAccessKey ¶
func AWSSecretAccessKey() Pattern
AWSSecretAccessKey locates AWS secret access keys: the forty characters the secret half of an access key is written in, standing behind the name it is assigned to. The name is what says a key is there — SECRET_ACCESS_KEY, the aws_secret_access_key of a shared credentials file, the SecretAccessKey of the JSON an assume-role call prints, and the rest of the ways those three words are written — and only the forty characters are redacted, so the name and the assignment stay in the text to be read.
A key is located wherever those three words stand in front of one: the aws in front of them is not read, so a name is enough on its own, and a service naming its own credential the same way has that credential located too.
Its name is "aws-secret-access-key".
Example ¶
package main
import (
"fmt"
"github.com/koki-develop/mask-go"
)
func main() {
// The value carries nothing to be recognised by — forty characters of
// base64 are as much a git object as a key — so this pattern reads the name
// the value is assigned to, and redacts the value alone. The name is what
// stays behind to say which credential leaked.
m := mask.New(mask.WithPatterns(mask.AWSSecretAccessKey()))
fmt.Println(m.Mask("AWS_SECRET_ACCESS_KEY=0123456789abcdef0123456789abcdef01234567"))
}
Output: AWS_SECRET_ACCESS_KEY=****************************************
func AgePatterns ¶ added in v0.1.0
func AgePatterns() []Pattern
AgePatterns returns every built-in pattern that locates a credential age generates.
The returned slice is freshly allocated and may be modified by the caller.
func AgeSecretKey ¶ added in v0.1.0
func AgeSecretKey() Pattern
AgeSecretKey locates age secret keys: the identities age-keygen writes, each of which decrypts every file encrypted to the recipient beside it. Two kinds carry the name and both are located — the X25519 identity, written AGE-SECRET-KEY-1..., and the MLKEM768-X25519 hybrid one age-keygen -pq writes, AGE-SECRET-KEY-PQ-1....
Either kind is one of those prefixes and exactly fifty-eight characters of the Bech32 alphabet behind it, in the uppercase age writes and reads them in. A key is located wherever it stands, with no word boundary either side.
The recipient age prints above a key in an identity file, age1..., is the public half of the same key pair and is left in the text.
Its name is "age-secret-key".
func AirtablePatterns ¶ added in v0.1.0
func AirtablePatterns() []Pattern
AirtablePatterns returns every built-in pattern that locates a credential Airtable issues.
The returned slice is freshly allocated and may be modified by the caller.
func AirtablePersonalAccessToken ¶ added in v0.1.0
func AirtablePersonalAccessToken() Pattern
AirtablePersonalAccessToken locates Airtable personal access tokens: the prefix pat, the fourteen characters that finish the token's identifier, a dot, and the sixty-four lowercase hexadecimal characters of the secret behind it — eighty-two characters altogether. One string serves every scope a token is created with and every base it is granted, so nothing in a token says what it may reach.
A token is located wherever it is written, with no word boundary either side, and exactly eighty-two characters of it are. So text of that shape is redacted whether or not Airtable issued it. A space, an identifier of the wrong length, a hyphen where the dot belongs or an uppercase letter in the secret ends the reading, so text as it is ordinarily written is not affected. A longer run is a token with something written after it, and the token alone is redacted.
Its name is "airtable-personal-access-token".
func AllBuiltinPatterns ¶
func AllBuiltinPatterns() []Pattern
AllBuiltinPatterns returns every built-in pattern:
m := mask.New(mask.WithPatterns(mask.AllBuiltinPatterns()...))
The set grows as patterns are added to this package. The returned slice is freshly allocated and may be modified by the caller.
Example ¶
package main
import (
"fmt"
"github.com/koki-develop/mask-go"
)
func main() {
// The built-in patterns are given to a Masker together, and each of them
// scans for what it knows.
m := mask.New(mask.WithPatterns(mask.AllBuiltinPatterns()...))
fmt.Println(m.Mask("token=ghp_0123456789abcdefghijklmnopqrstuvwxyz jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhYmMifQ.0123456789abcdef"))
}
Output: token=**************************************** jwt=************************************************************************
func AnthropicAPIKey ¶
func AnthropicAPIKey() Pattern
AnthropicAPIKey locates Anthropic API keys: the keys the Claude Console issues (sk-ant-api03-) and the keys of the Admin API (sk-ant-admin01-). Both are written the same way — the prefix sk-ant-, the name of a kind, a hyphen, and a long run of random characters — and it is that shape, rather than the two names, that this pattern is anchored on. The OAuth tokens and the session keys Anthropic writes the same way are therefore located as well.
A key is located wherever it is written, with no word boundary either side, and is redacted from its sk-ant- to the end of the run it stands in. So a key written against a word character keeps its span, and a character of the key's own alphabet written straight after a key is redacted with it.
Its name is "anthropic-api-key".
func AnthropicPatterns ¶
func AnthropicPatterns() []Pattern
AnthropicPatterns returns every built-in pattern that locates a credential Anthropic issues.
The returned slice is freshly allocated and may be modified by the caller.
func BuildkitePatterns ¶ added in v0.2.0
func BuildkitePatterns() []Pattern
BuildkitePatterns returns every built-in pattern that locates a credential Buildkite issues.
The returned slice is freshly allocated and may be modified by the caller.
func BuildkiteToken ¶ added in v0.2.0
func BuildkiteToken() Pattern
BuildkiteToken locates the tokens Buildkite issues with a prefix of its own: API access tokens (bkua_), agent session tokens (bkaa_), agent job tokens (bkaj_), unclustered agent tokens (bkar_), agent tokens (bkct_), registry and Package Registries temporary tokens (bkpt_), portal tokens (bkpat_), portal secrets (bkps_), job acquisition tokens (bkjat_) and the tokens an exchanged assertion is minted into (bktx_).
Buildkite documents the prefixes and no length, so this pattern keys on the prefix: a token is redacted from it to the end of the run of base64url characters behind it, whatever that run comes to, once it is long enough to be a body at all.
A token is located wherever it is written, with no word boundary either side. So a token written against a word character keeps its span, and a character of the token's own alphabet written straight after a token is redacted with it.
Its name is "buildkite-token".
func CircleCIAPIToken ¶ added in v0.1.0
func CircleCIAPIToken() Pattern
CircleCIAPIToken locates CircleCI API tokens: the prefix CCIPAT_ a personal API token is written with or the CCIPRJ_ a project API token is written with, then twenty-two letters and digits, an underscore, and forty hexadecimal characters — seventy characters altogether.
A token is located wherever it is written, with no word boundary either side, and exactly seventy characters of it are. So text of that shape is redacted whether or not CircleCI issued it. A space, a hyphen, a run of other than twenty-two letters and digits in front of the second underscore, or a character outside hexadecimal behind it, all end the reading, so text as it is ordinarily written is not affected. A longer run of hexadecimal is a token with something written after it, and the token alone is redacted.
Its name is "circleci-api-token".
func CircleCIPatterns ¶ added in v0.1.0
func CircleCIPatterns() []Pattern
CircleCIPatterns returns every built-in pattern that locates a credential CircleCI issues.
The returned slice is freshly allocated and may be modified by the caller.
func CloudflareAPIKey ¶
func CloudflareAPIKey() Pattern
CloudflareAPIKey locates Cloudflare API keys: the prefix cfk_, the forty characters of the secret behind it and the eight hexadecimal digits of the checksum behind that — fifty-two characters. One key carries everything the user who holds it can do, over every account and zone they can reach, and nothing in it says otherwise.
A key is located wherever it is written, with no word boundary either side, and exactly fifty-two characters of it are. So text of that shape is redacted whether or not Cloudflare issued it. A space, a hyphen, an underscore behind the prefix, a letter past f in the last eight characters or a body of the wrong length ends the reading, so text as it is ordinarily written is not affected.
Its name is "cloudflare-api-key".
func CloudflareAPIToken ¶
func CloudflareAPIToken() Pattern
CloudflareAPIToken locates Cloudflare API tokens: the prefix cfut_ of a token a user owns or cfat_ of one an account owns, the forty characters of the secret behind it and the eight hexadecimal digits of the checksum behind that — fifty-three characters either way. One string serves every permission a token can be scoped to and every account and zone it can be scoped over, so nothing in a token says what it is allowed to do.
A token is located wherever it is written, with no word boundary either side, and exactly fifty-three characters of it are. So text of that shape is redacted whether or not Cloudflare issued it. A space, a hyphen, an underscore behind the prefix, a letter past f in the last eight characters or a body of the wrong length ends the reading, so text as it is ordinarily written is not affected.
Its name is "cloudflare-api-token".
func CloudflarePatterns ¶
func CloudflarePatterns() []Pattern
CloudflarePatterns returns every built-in pattern that locates a credential Cloudflare issues.
The returned slice is freshly allocated and may be modified by the caller.
func CratesIOPatterns ¶ added in v0.1.0
func CratesIOPatterns() []Pattern
CratesIOPatterns returns every built-in pattern that locates a credential crates.io issues.
The returned slice is freshly allocated and may be modified by the caller.
func CratesIOToken ¶ added in v0.1.0
func CratesIOToken() Pattern
CratesIOToken locates the tokens crates.io issues: the API tokens a user creates on the settings page and cargo authenticates with, which are the prefix cio and thirty-two alphanumeric characters, and the short-lived access tokens Trusted Publishing mints in a CI job, which are the prefix cio_tp_ and thirty-two more. Thirty-five characters and thirty-nine.
A token is located wherever it is written, with no word boundary either side, and exactly thirty-five characters of an API token or thirty-nine of a Trusted Publishing one are. So text of that shape is redacted whether or not crates.io issued it. A character that is neither a letter nor a digit ends the reading, so text as it is ordinarily written is not affected.
Its name is "crates-io-token".
func DatabricksOAuthClientSecret ¶ added in v0.1.0
func DatabricksOAuthClientSecret() Pattern
DatabricksOAuthClientSecret locates Databricks OAuth client secrets: the prefix dose and the thirty-two lowercase hexadecimal characters behind it, thirty-six characters altogether. One string serves the secret a service principal is issued for the machine-to-machine flow and the secret a registered OAuth application is given for the user-to-machine one, so nothing in a secret says which of the two issued it.
A secret is located wherever it is written, with no word boundary either side, and exactly thirty-six characters of it are. So text of that shape is redacted whether or not Databricks issued it. An uppercase letter, a character outside hexadecimal or a run of fewer than thirty-two characters ends the reading, so text as it is ordinarily written is not affected. A longer run is a secret with something written after it, and the secret alone is redacted.
Its name is "databricks-oauth-client-secret".
func DatabricksPatterns ¶ added in v0.1.0
func DatabricksPatterns() []Pattern
DatabricksPatterns returns every built-in pattern that locates a credential Databricks issues.
The returned slice is freshly allocated and may be modified by the caller.
func DatabricksPersonalAccessToken ¶ added in v0.1.0
func DatabricksPersonalAccessToken() Pattern
DatabricksPersonalAccessToken locates Databricks personal access tokens: the prefix dapi and the thirty-two lowercase hexadecimal characters behind it — thirty-six characters altogether — followed where one is written by a hyphen and one digit, which brings such a token to thirty-eight. One string serves the tokens a workspace user creates and the tokens a service principal is issued, so nothing in a token says which of the two it authenticates as.
A token is located wherever it is written, with no word boundary either side, and exactly thirty-six characters of it are, or thirty-eight where the hyphen and the digit stand. So text of that shape is redacted whether or not Databricks issued it. An uppercase letter, a character outside hexadecimal or a run of fewer than thirty-two characters ends the reading, so text as it is ordinarily written is not affected. A longer run is a token with something written after it, and the token alone is redacted.
Its name is "databricks-personal-access-token".
func DigitalOceanPatterns ¶ added in v0.1.0
func DigitalOceanPatterns() []Pattern
DigitalOceanPatterns returns every built-in pattern that locates a credential DigitalOcean issues.
The returned slice is freshly allocated and may be modified by the caller.
func DigitalOceanToken ¶ added in v0.1.0
func DigitalOceanToken() Pattern
DigitalOceanToken locates the tokens DigitalOcean issues in the format it prefixes: the personal access tokens generated in the control panel (dop_v1_), the access tokens an application receives from the OAuth flow (doo_v1_) and the refresh tokens handed out beside them (dor_v1_), each with sixty-four lowercase hexadecimal characters behind it — seventy-one characters altogether.
A token is located wherever it is written, with no word boundary either side, and exactly seventy-one characters of it are. So text of that shape is redacted whether or not DigitalOcean issued it. A space, an uppercase letter, a letter past f or a body of the wrong length ends the reading, so text as it is ordinarily written is not affected.
Its name is "digitalocean-token".
func DockerPatterns ¶ added in v0.1.0
func DockerPatterns() []Pattern
DockerPatterns returns every built-in pattern that locates a credential Docker issues.
The returned slice is freshly allocated and may be modified by the caller.
func DockerPersonalAccessToken ¶ added in v0.1.0
func DockerPersonalAccessToken() Pattern
DockerPersonalAccessToken locates Docker personal access tokens: the prefix dckr_pat_ and twenty-seven base64url characters behind it — thirty-six characters altogether.
A token is located wherever it is written, with no word boundary either side, and exactly thirty-six characters of it are. So text of that shape is redacted whether or not Docker issued it. A space, a dot, a character outside the alphabet or an uppercase prefix ends the reading, so text as it is ordinarily written is not affected. A longer run of the alphabet is a token with something written after it, and the token alone is redacted.
Its name is "docker-personal-access-token".
func DopplerAuthToken ¶ added in v0.1.0
func DopplerAuthToken() Pattern
DopplerAuthToken locates the auth tokens Doppler issues: the CLI token, the personal token, the service token, the service account token, the service account identity token, the SCIM token and the audit token, each written as dp, the two to five characters naming its kind and forty to forty-four letters and digits, with a full stop between each part. A service token may carry the name of the environment it was cut for between its prefix and its body, which no other kind does.
A token is located wherever it is written, with no word boundary either side, and no more than forty-four characters of a body are. So text of that shape is redacted whether or not Doppler issued it. A space, a full stop, a hyphen or a body shorter than forty characters ends the reading, so text as it is ordinarily written is not affected.
Its name is "doppler-auth-token".
func DopplerPatterns ¶ added in v0.1.0
func DopplerPatterns() []Pattern
DopplerPatterns returns every built-in pattern that locates a credential Doppler issues.
The returned slice is freshly allocated and may be modified by the caller.
func DynatracePatterns ¶ added in v0.2.0
func DynatracePatterns() []Pattern
DynatracePatterns returns every built-in pattern that locates a credential Dynatrace issues.
The returned slice is freshly allocated and may be modified by the caller.
func DynatraceToken ¶ added in v0.2.0
func DynatraceToken() Pattern
DynatraceToken locates the tokens Dynatrace issues in the format it publishes: the three characters dt0, one letter and two digits naming the token type, then a full stop, the twenty-four characters of the public identifier, a second full stop and the sixty-four characters of the secret — ninety-six characters altogether.
A token is located wherever it is written, with no word boundary either side, and exactly ninety-six characters of it are. So text of that shape is redacted whether or not Dynatrace issued it. A space, a lowercase letter in either portion, a full stop out of place or a portion of the wrong length ends the reading, so text as it is ordinarily written is not affected.
Its name is "dynatrace-token".
func FlyIOAccessToken ¶ added in v0.2.0
func FlyIOAccessToken() Pattern
FlyIOAccessToken locates the access tokens Fly.io issues: one of the labels fm2_, fm1r_ and fm1a_, and behind it a body of sixty-four characters or more written in standard base64 and closing with the padding base64 calls for, redacted to the end of the run it stands in. The padding counts toward the sixty-four. Every token Fly.io mints today carries fm2_, whatever it was scoped to — a personal access token, a deploy token cut for one app, an org token, an SSH token or a machine-exec token — and a token authenticates the API within the scope it was created with.
A token is located wherever it is written, with no word boundary either side, and with or without the FlyV1 scheme it is sent under. So text of that shape is redacted whether or not Fly.io issued it. A space, an underscore, a character outside the alphabet or a body of fewer than sixty-four characters ends the reading, so text as it is ordinarily written is not affected. Where the run carries on past the sixty-fourth character, it is redacted to its end.
Its name is "fly-io-access-token".
func FlyIOPatterns ¶ added in v0.2.0
func FlyIOPatterns() []Pattern
FlyIOPatterns returns every built-in pattern that locates a credential Fly.io issues.
The returned slice is freshly allocated and may be modified by the caller.
func GitHubPatterns ¶
func GitHubPatterns() []Pattern
GitHubPatterns returns every built-in pattern that locates a credential GitHub issues.
The returned slice is freshly allocated and may be modified by the caller.
func GitHubToken ¶
func GitHubToken() Pattern
GitHubToken locates GitHub credentials that carry a token prefix: personal access tokens (ghp_, github_pat_), OAuth app access tokens (gho_), GitHub App user and installation access tokens (ghu_, ghs_) and GitHub App refresh tokens (ghr_).
GitHub documents the prefixes but no token length, and changed installation tokens in 2026 from 40 characters to a longer format holding a JWT. This pattern therefore keys on the prefix rather than on an exact length. That longer format is read for installation tokens, which carry it, and for user access tokens, whose format GitHub has said is to change without saying what to; the kinds that announcement leaves out are read for the classic form alone.
Its name is "github-token".
Example ¶
package main
import (
"fmt"
"github.com/koki-develop/mask-go"
)
func main() {
m := mask.New(mask.WithPatterns(mask.GitHubToken()))
fmt.Println(m.Mask("GITHUB_TOKEN=ghp_0123456789abcdefghijklmnopqrstuvwxyz"))
}
Output: GITHUB_TOKEN=****************************************
func GitLabPatterns ¶
func GitLabPatterns() []Pattern
GitLabPatterns returns every built-in pattern that locates a credential GitLab issues.
The returned slice is freshly allocated and may be modified by the caller.
func GitLabToken ¶
func GitLabToken() Pattern
GitLabToken locates GitLab credentials that carry a token prefix: personal, project, group and impersonation access tokens (glpat-), OAuth application secrets (gloas-), deploy tokens (gldt-), runner authentication tokens (glrt- and glrtr-), CI/CD job tokens (glcbt-), pipeline trigger tokens (glptt-), feed tokens (glft-), incoming mail tokens (glimt-), agent tokens for Kubernetes (glagent-), SCIM OAuth tokens (glsoat-) and feature flags client tokens (glffct-).
Both shapes a body is written in are read. The classic one is a count of base64url characters that kind of token has carried since GitLab gave it a prefix; the count differs by kind, and a pipeline trigger token is written to either of two. The routable one is what GitLab.com is moving to for Cells: a longer payload carrying the routing information, closed by a dot, the length of that payload and a checksum.
Its name is "gitlab-token".
Example ¶
package main
import (
"fmt"
"github.com/koki-develop/mask-go"
)
func main() {
m := mask.New(mask.WithPatterns(mask.GitLabToken()))
fmt.Println(m.Mask("GITLAB_TOKEN=glpat-0123456789abcdefghij"))
}
Output: GITLAB_TOKEN=**************************
func GoogleAPIKey ¶
func GoogleAPIKey() Pattern
GoogleAPIKey locates Google API keys: the prefix AIza and thirty-five characters behind it. One string serves every Google API that takes a key rather than a credentialled principal — Maps, YouTube Data, Firebase, the Cloud APIs reaching no private user data, and the Gemini API among them — so a key says which project it bills and not which API it was made for.
A key is located wherever it is written, with no word boundary either side, and exactly thirty-nine characters of it are. So an unbroken run of thirty-nine base64url characters opening with AIza is redacted whether or not Google issued it. A space, a dot or a slash ends the run and no word is spelled AIza, so text as it is ordinarily written is not affected.
Its name is "google-api-key".
Example ¶
package main
import (
"fmt"
"github.com/koki-develop/mask-go"
)
func main() {
m := mask.New(mask.WithPatterns(mask.GoogleAPIKey()))
fmt.Println(m.Mask("GOOGLE_API_KEY=AIza0123456789abcdefghijklmnopqrstuvwxy"))
}
Output: GOOGLE_API_KEY=***************************************
func GooglePatterns ¶
func GooglePatterns() []Pattern
GooglePatterns returns every built-in pattern that locates a credential Google issues.
The returned slice is freshly allocated and may be modified by the caller.
func GrafanaPatterns ¶
func GrafanaPatterns() []Pattern
GrafanaPatterns returns every built-in pattern that locates a credential Grafana issues.
The returned slice is freshly allocated and may be modified by the caller.
func GrafanaServiceAccountToken ¶
func GrafanaServiceAccountToken() Pattern
GrafanaServiceAccountToken locates Grafana service account tokens: the prefix glsa_, the thirty-two characters of the secret, an underscore, and the eight hexadecimal digits of the checksum behind it — forty-six characters altogether. One string serves every service account Grafana issues a token for, whatever role it carries and whichever stack it belongs to, so nothing in a token says what it is allowed to do.
A token is located wherever it is written, with no word boundary either side, and exactly forty-six characters of it are. So text of that shape is redacted whether or not Grafana issued it. A space, a hyphen where the underscore belongs, or a secret of the wrong length ends the reading, so text as it is ordinarily written is not affected.
Its name is "grafana-service-account-token".
func GroqAPIKey ¶ added in v0.2.0
func GroqAPIKey() Pattern
GroqAPIKey locates Groq API keys: the prefix gsk_ and the fifty or more letters and digits behind it, redacted to the end of the run they stand in. Every key any published ruleset carries is fifty-two characters behind the prefix, fifty-six altogether. One string serves every model Groq serves and every endpoint it is served at, so nothing in a key says what it may be spent on.
A key is located wherever it is written, with no word boundary either side. So text of that shape is redacted whether or not Groq issued it. A space, a hyphen, an underscore or a run of fewer than fifty letters and digits ends the reading, so text as it is ordinarily written is not affected. Where the run carries on past the fiftieth character, it is redacted to its end.
Its name is "groq-api-key".
func GroqPatterns ¶ added in v0.2.0
func GroqPatterns() []Pattern
GroqPatterns returns every built-in pattern that locates a credential Groq issues.
The returned slice is freshly allocated and may be modified by the caller.
func HCPTerraformAPIToken ¶ added in v0.2.0
func HCPTerraformAPIToken() Pattern
HCPTerraformAPIToken locates the API tokens HCP Terraform issues: fourteen letters and digits, the nine characters .atlasv1. and sixty-seven letters and digits behind them — ninety characters altogether. One format carries every kind of token the service hands out — the user token a person authenticates with, the team token a pipeline runs plans and applies under, the organization token that manages teams and workspaces, and the token an agent pool authenticates with — so nothing in a token says which of those it is.
A token is located wherever it is written, with no word boundary either side, and exactly ninety characters of it are. So text of that shape is redacted whether or not HashiCorp issued it. A space, a hyphen, an underscore, a full stop out of place or a portion of the wrong length ends the reading, so text as it is ordinarily written is not affected.
Its name is "hcp-terraform-api-token".
func HashiCorpPatterns ¶
func HashiCorpPatterns() []Pattern
HashiCorpPatterns returns every built-in pattern that locates a credential HashiCorp issues.
The returned slice is freshly allocated and may be modified by the caller.
func HashiCorpVaultToken ¶
func HashiCorpVaultToken() Pattern
HashiCorpVaultToken locates HashiCorp Vault tokens: the prefix hvs., hvb. or hvr. and the characters behind it. The three name the three kinds of token Vault issues — a service token, a batch token and a recovery token — and nothing else in the string says them apart, nor what any of them is allowed to do, which is carried by the policies attached to the token inside Vault.
A token is located wherever it is written, with no word boundary either side, and is redacted from its prefix to the end of the run it stands in. So a token written against a word character keeps its span, and a character of the token's own alphabet written straight after a token is redacted with it.
What Vault states of the format is a prefix and "24 or more" characters behind it, and this pattern reads exactly that, so text of that shape is redacted whether or not Vault issued it. The shape is reachable by writing as well as by encoding, which is worth knowing before this pattern is switched on over source code: a name written in dot-separated segments is located wherever the segment behind an hvs, hvb or hvr runs to twenty-four characters, so both hvs.example-host-name-of-that-length and a method call on a receiver named hvs are redacted from the h to the end of that segment. What holds ordinary text back is the twenty-four unbroken characters and nothing else.
Its name is "hashicorp-vault-token".
func HerokuAPIToken ¶ added in v0.1.0
func HerokuAPIToken() Pattern
HerokuAPIToken locates Heroku API tokens: the prefix HRKU- and either the sixty base64url characters Heroku writes one with now or the UUID it wrote one with before — sixty-five characters or forty-one.
A token is located wherever it is written, with no word boundary either side, and exactly as many characters of it are as the reading it matched comes to. So text of that shape is redacted whether or not Heroku issued it. A space, a dot, an equals sign or a run of fewer than sixty characters ends the longer reading, and anything but a UUID behind the prefix ends the shorter one, so text as it is ordinarily written is not affected. A longer run is a token with something written after it, and the token alone is redacted.
Its name is "heroku-api-token".
func HerokuPatterns ¶ added in v0.1.0
func HerokuPatterns() []Pattern
HerokuPatterns returns every built-in pattern that locates a credential Heroku issues.
The returned slice is freshly allocated and may be modified by the caller.
func HuggingFacePatterns ¶ added in v0.1.0
func HuggingFacePatterns() []Pattern
HuggingFacePatterns returns every built-in pattern that locates a credential Hugging Face issues.
The returned slice is freshly allocated and may be modified by the caller.
func HuggingFaceUserAccessToken ¶ added in v0.1.0
func HuggingFaceUserAccessToken() Pattern
HuggingFaceUserAccessToken locates Hugging Face user access tokens: the prefix hf_ and the thirty-four letters and digits behind it — thirty-seven characters altogether. One string serves every role a token is issued under, so nothing in a token says whether it may read a private repository, write to one, or reach only the resources a fine-grained token was scoped to.
A token is located wherever it is written, with no word boundary either side, and exactly thirty-seven characters of it are. So text of that shape is redacted whether or not Hugging Face issued it. A space, a hyphen, an underscore or a run of fewer than thirty-four letters and digits ends the reading, so text as it is ordinarily written is not affected. A longer run is a token with something written after it, and the token alone is redacted.
Its name is "huggingface-user-access-token".
func JWT ¶
func JWT() Pattern
JWT locates JSON Web Tokens in compact serialization: a base64url encoded header, followed by the two segments of a signed token or the four of an encrypted one, separated by dots.
A header is read for the marks RFC 7515 and RFC 7516 require of one, namely a JSON object naming an algorithm in alg, and enc where the token is encrypted. Text that carries none of them is left alone.
The header may be written with a space between the brace and the first member name, which JSON allows though the compact JSON an encoder emits carries none. One written with a tab, a carriage return or a newline there is not located.
Its name is "jwt".
Example ¶
package main
import (
"fmt"
"github.com/koki-develop/mask-go"
)
func main() {
m := mask.New(mask.WithPatterns(mask.JWT()))
fmt.Println(m.Mask("Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhYmMifQ.0123456789abcdef"))
}
Output: Authorization: Bearer ************************************************************************
func LinearAPIKey ¶
func LinearAPIKey() Pattern
LinearAPIKey locates Linear personal API keys: the prefix lin_api_ and the characters behind it. One shape serves every key Linear issues — a key carries the permissions of whoever created it and may be narrowed to reading alone or to a single team, so nothing in the string says what it is allowed to do.
A key is located wherever it is written, with no word boundary either side, and is redacted from its lin_api_ to the end of the run it stands in. So a key written against a word character keeps its span, and a character of the key's own alphabet written straight after a key is redacted with it.
Its name is "linear-api-key".
func LinearPatterns ¶
func LinearPatterns() []Pattern
LinearPatterns returns every built-in pattern that locates a credential Linear issues.
The returned slice is freshly allocated and may be modified by the caller.
func MustRegexp ¶
MustRegexp returns what Regexp returns for name and expr, and panics where Regexp reports an error:
var internal = mask.MustRegexp("internal-token", `INT-[0-9a-f]{32}`)
It is what an expression written into the source is built with, where an invalid one is a bug in the program rather than something a caller could act on. Regexp is what an expression that arrives at run time — out of a configuration file, or off a flag — is built with.
Example ¶
package main
import (
"fmt"
"github.com/koki-develop/mask-go"
)
func main() {
m := mask.New(mask.WithPatterns(
mask.MustRegexp("internal-token", `INT-[0-9a-f]{32}`),
))
fmt.Println(m.Mask("token: INT-0123456789abcdef0123456789abcdef"))
}
Output: token: ************************************
Example (MaskGroup) ¶
package main
import (
"fmt"
"github.com/koki-develop/mask-go"
)
func main() {
m := mask.New(mask.WithPatterns(
mask.MustRegexp("user-id", `user_id=(?P<mask>\d+)`),
))
fmt.Println(m.Mask("user_id=12345 name=alice"))
}
Output: user_id=***** name=alice
func NPMAccessToken ¶
func NPMAccessToken() Pattern
NPMAccessToken locates npm access tokens: the prefix npm_ and thirty-six characters behind it. One shape serves every token the registry issues in this format — the granular access tokens npmjs.com creates today, and the classic tokens, read-only, automation and publish alike, created in it until npm disabled them — so a token says which registry it authenticates against and not what it is allowed to do there.
A token is located wherever it is written, with no word boundary either side, and is redacted from its npm_ to the end of the run it stands in. So a token written against a word character keeps its span, and a character of the token's own alphabet written straight after a token is redacted with it.
Its name is "npm-access-token".
func NPMPatterns ¶
func NPMPatterns() []Pattern
NPMPatterns returns every built-in pattern that locates a credential npm issues.
The returned slice is freshly allocated and may be modified by the caller.
func NewPattern ¶
NewPattern returns a Pattern that reports name as its name and locates values with find:
mask.NewPattern("high-entropy", func(src string) ([]mask.Span, int) {
// ...
return spans, len(src)
})
find must be safe for concurrent use by multiple goroutines, and owes what Pattern.Find says about both of its results.
Example ¶
package main
import (
"fmt"
"strings"
"github.com/koki-develop/mask-go"
)
func main() {
secret := "s3cr3t-value"
p := mask.NewPattern("shared-secret", func(src string) ([]mask.Span, int) {
var spans []mask.Span
for i := 0; ; {
j := strings.Index(src[i:], secret)
if j < 0 {
break
}
spans = append(spans, mask.Span{Start: i + j, End: i + j + len(secret)})
// One byte past where this one began, not past where it ended: a
// value written inside another is a value, and a scan resuming
// past the match would step over it. LookBehind asks for the same
// thing from the other side — where a scan resumes must not depend
// on how much of the text in front of it it was shown.
i += j + 1
}
// The value is one fixed width, so text further than a width from
// the end of src holds nothing more of it to come.
return spans, max(0, len(src)-len(secret)+1)
})
m := mask.New(mask.WithPatterns(p))
fmt.Println(m.Mask("password=s3cr3t-value"))
}
Output: password=************
func NewRelicPatterns ¶ added in v0.2.0
func NewRelicPatterns() []Pattern
NewRelicPatterns returns every built-in pattern that locates a credential New Relic issues.
The returned slice is freshly allocated and may be modified by the caller.
func NewRelicUserKey ¶ added in v0.2.0
func NewRelicUserKey() Pattern
NewRelicUserKey locates New Relic user keys: the prefix NRAK- and the twenty-seven or more uppercase letters and digits behind it, or the prefix NRAA- and the twenty-seven or more hexadecimal characters behind it, redacted to the end of the run they stand in. Every key anybody has published is twenty-seven characters behind the prefix, thirty-two altogether. A key queries NerdGraph and the REST API as the user it was issued to, across every account that user can see, so what one reaches is whatever its user was granted rather than one account's data.
A key is located wherever it is written, with no word boundary either side. So text of that shape is redacted whether or not New Relic issued it. A space, an underscore, a second hyphen or a run of fewer than twenty-seven characters of the kind's own alphabet ends the reading, so text as it is ordinarily written is not affected. Where the run carries on past the twenty-seventh character, it is redacted to its end.
Its name is "newrelic-user-key".
func NotionAPIToken ¶
func NotionAPIToken() Pattern
NotionAPIToken locates Notion API tokens: the prefix ntn_ and the forty-six characters behind it, or the prefix secret_ and the forty-three behind that — fifty characters either way. One string serves every kind of token the Public API takes, the static token of an internal connection, the OAuth access token of a public one and a personal access token alike, so nothing in a token says what it authenticates as.
A token is located wherever it is written, with no word boundary either side, and exactly fifty characters of it are. So text of that shape is redacted whether or not Notion issued it. A space, a hyphen, a further underscore or a body of the wrong length ends the reading, so text as it is ordinarily written is not affected.
Its name is "notion-api-token".
func NotionPatterns ¶
func NotionPatterns() []Pattern
NotionPatterns returns every built-in pattern that locates a credential Notion issues.
The returned slice is freshly allocated and may be modified by the caller.
func OnePasswordPatterns ¶ added in v0.1.0
func OnePasswordPatterns() []Pattern
OnePasswordPatterns returns every built-in pattern that locates a credential 1Password issues.
The returned slice is freshly allocated and may be modified by the caller.
func OnePasswordServiceAccountToken ¶ added in v0.1.0
func OnePasswordServiceAccountToken() Pattern
OnePasswordServiceAccountToken locates 1Password service account tokens: the prefix ops_, and behind it the account's credentials serialized into a JSON object and written out in base64url. One string serves every service account, whichever vaults it was granted and whatever it may do with them, so nothing in a token says what it reaches.
A token is located wherever it is written, with no word boundary either side, and is redacted from its ops_ to the end of the run it stands in. So a token written against a word character keeps its span, and a character of the token's own alphabet written straight after a token is redacted with it.
Its name is "1password-service-account-token".
func OpenAIAPIKey ¶
func OpenAIAPIKey() Pattern
OpenAIAPIKey locates OpenAI API keys: the project keys the platform issues today (sk-proj-), the keys of a service account (sk-svcacct-), the keys of the Admin API (sk-admin-) and the user keys issued before projects existed (sk-, and the sk-None- written beside them). Every one of them is written the same way — the prefix sk-, a run of random characters, the marker T3BlbkFJ, and more random characters — and it is that marker, rather than the prefix a kind is named for, that this pattern is anchored on.
A key is located wherever it is written, with no word boundary either side, and is redacted from its sk- to the end of the run it stands in. So a key written against a word character keeps its span, and a character of the key's own alphabet written straight after a key is redacted with it.
Its name is "openai-api-key".
func OpenAIPatterns ¶
func OpenAIPatterns() []Pattern
OpenAIPatterns returns every built-in pattern that locates a credential OpenAI issues.
The returned slice is freshly allocated and may be modified by the caller.
func OpenRouterAPIKey ¶
func OpenRouterAPIKey() Pattern
OpenRouterAPIKey locates OpenRouter API keys: the prefix sk-or-v1- and the sixty-four hexadecimal digits behind it — seventy-three characters altogether. One string serves every model OpenRouter routes to and every provider behind them, so nothing in a key says what it may be spent on.
A key is located wherever it is written, with no word boundary either side, and exactly seventy-three characters of it are. So text of that shape is redacted whether or not OpenRouter issued it. A space, a letter past f, or a run of fewer than sixty-four hexadecimal digits ends the reading, so text as it is ordinarily written is not affected. A longer run is a key with something written after it, and the key alone is redacted.
Its name is "openrouter-api-key".
func OpenRouterPatterns ¶
func OpenRouterPatterns() []Pattern
OpenRouterPatterns returns every built-in pattern that locates a credential OpenRouter issues.
The returned slice is freshly allocated and may be modified by the caller.
func PaddleAPIKey ¶ added in v0.2.0
func PaddleAPIKey() Pattern
PaddleAPIKey locates the API keys Paddle issues: the four characters pdl_, the environment written as live or sdbx, the eight characters _apikey_, then twenty-six lowercase letters and digits, an underscore, twenty-two letters and digits, a second underscore and three more — sixty-nine characters altogether.
A key is located wherever it is written, with no word boundary either side, and exactly sixty-nine characters of it are. So text of that shape is redacted whether or not Paddle issued it. A space, an underscore out of place, an uppercase letter in the first segment or a segment of the wrong length ends the reading, so text as it is ordinarily written is not affected.
Its name is "paddle-api-key".
func PaddlePatterns ¶ added in v0.2.0
func PaddlePatterns() []Pattern
PaddlePatterns returns every built-in pattern that locates a credential Paddle issues.
The returned slice is freshly allocated and may be modified by the caller.
func PlanetScalePatterns ¶ added in v0.1.0
func PlanetScalePatterns() []Pattern
PlanetScalePatterns returns every built-in pattern that locates a credential PlanetScale issues.
The returned slice is freshly allocated and may be modified by the caller.
func PlanetScaleToken ¶ added in v0.1.0
func PlanetScaleToken() Pattern
PlanetScaleToken locates the tokens PlanetScale issues in the format it prefixes: the service token an organization is given (pscale_tkn_), the access token an application receives from the OAuth flow (pscale_oauth_) and the refresh token handed out beside it (pscale_oauth_refresh_), each with forty-three base64url characters behind it — fifty-four, fifty-six and sixty-four characters altogether.
A token is located wherever it is written, with no word boundary either side, and exactly as many characters of it are as its own prefix and the count come to. So text of that shape is redacted whether or not PlanetScale issued it. A space, a dot, an equals sign or a run of fewer than forty-three characters ends the reading, so text as it is ordinarily written is not affected. A longer run is a token with something written after it, and the token alone is redacted.
Its name is "planetscale-token".
func PostHogPatterns ¶ added in v0.2.0
func PostHogPatterns() []Pattern
PostHogPatterns returns every built-in pattern that locates a credential PostHog issues.
The returned slice is freshly allocated and may be modified by the caller.
func PostHogPersonalAPIKey ¶ added in v0.2.0
func PostHogPersonalAPIKey() Pattern
PostHogPersonalAPIKey locates PostHog personal API keys: the prefix phx_ and the forty-one or more letters and digits behind it, redacted to the end of the run they stand in. One key reads and writes the projects and organizations its owner can reach, within the scopes and the teams it was cut for, so it is worth as much as the access of the person who made it.
A key is located wherever it is written, with no word boundary either side. So text of that shape is redacted whether or not PostHog issued it. A space, a hyphen, an underscore or a run of fewer than forty-one letters and digits ends the reading, so text as it is ordinarily written is not affected. Where the run carries on past the forty-first character, it is redacted to its end.
Its name is "posthog-personal-api-key".
func PostmanAPIKey ¶ added in v0.1.0
func PostmanAPIKey() Pattern
PostmanAPIKey locates Postman API keys: the prefix PMAK-, twenty-four characters, a hyphen and thirty-four more — sixty-four characters altogether. One key carries the whole of the account that created it, over every workspace, collection and environment that account can reach, and nothing in it says otherwise.
A key is located wherever it is written, with no word boundary either side, and exactly sixty-four characters of it are. So text of that shape is redacted whether or not Postman issued it. A space, an underscore, a dot, a segment of the wrong length or a hyphen standing anywhere but the twenty-fifth character behind the prefix ends the reading, so text as it is ordinarily written is not affected.
Its name is "postman-api-key".
func PostmanPatterns ¶ added in v0.1.0
func PostmanPatterns() []Pattern
PostmanPatterns returns every built-in pattern that locates a credential Postman issues.
The returned slice is freshly allocated and may be modified by the caller.
func PrivateKey ¶
func PrivateKey() Pattern
PrivateKey locates private keys written in the armor RFC 7468 lays out: a line opening with five dashes, BEGIN and a label, the base64 of the key behind it, and a closing line naming the same label. Every label whose last words are PRIVATE KEY is read, so the PKCS#8 key of RFC 7468, the encrypted PKCS#8 key beside it, the PKCS#1 and EC and DSA keys OpenSSL writes, the OPENSSH key ssh-keygen writes and the PGP PRIVATE KEY BLOCK of RFC 9580 are all located, as is a label no such document has been written for yet.
The whole block is redacted, the two boundary lines included, not the base64 alone. A block cut short — a key a log truncated before its closing line — is located as far as its last whole line of base64, so a truncation landing inside a line leaves that line, and one landing inside the first leaves the key.
A block written into JSON or into an environment assignment, where the line breaks stand as the two characters \ and n rather than as line breaks, is located as one written across lines is, and so is one indented under a name in YAML. Text escaped twice over is not: a block whose line breaks are written \\n is located nowhere.
Its name is "private-key".
func PulumiAccessToken ¶ added in v0.1.0
func PulumiAccessToken() Pattern
PulumiAccessToken locates Pulumi access tokens: the prefix pul- and the forty lowercase hexadecimal characters behind it — forty-four characters altogether. One string serves the token a user creates for themselves, the token an organization is issued and the token a team is issued, so nothing in a token says which of the three it authenticates as.
A token is located wherever it is written, with no word boundary either side, and exactly forty-four characters of it are. So text of that shape is redacted whether or not Pulumi issued it. A space, an uppercase letter, a letter past f or a body of the wrong length ends the reading, so text as it is ordinarily written is not affected.
Its name is "pulumi-access-token".
func PulumiPatterns ¶ added in v0.1.0
func PulumiPatterns() []Pattern
PulumiPatterns returns every built-in pattern that locates a credential Pulumi issues.
The returned slice is freshly allocated and may be modified by the caller.
func PyPIAPIToken ¶
func PyPIAPIToken() Pattern
PyPIAPIToken locates PyPI API tokens: the upload tokens pypi.org issues, the ones test.pypi.org issues beside them, and the short-lived ones minted for a Trusted Publisher. Every one of them is written the same way — the prefix pypi-, and behind it a macaroon serialized into base64url — and it is that serialization, rather than the index which issued it, that this pattern is anchored on.
A token is located wherever it is written, with no word boundary either side, and is redacted from its pypi- to the end of the run it stands in. So a token written against a word character keeps its span, and a character of the token's own alphabet written straight after a token is redacted with it.
Its name is "pypi-api-token".
func PyPIPatterns ¶
func PyPIPatterns() []Pattern
PyPIPatterns returns every built-in pattern that locates a credential PyPI issues.
The returned slice is freshly allocated and may be modified by the caller.
func Regexp ¶ added in v0.2.0
Regexp returns a Pattern backed by expr, and an error where expr is not one regexp.Compile accepts. The syntax is Go's, and the error is the one regexp.Compile reports, handed back as it stands.
The whole match is redacted, unless expr contains a capture group named "mask", in which case only that group is:
// "Authorization: Bearer abc123" -> "Authorization: Bearer ******"
mask.Regexp("bearer-token", `Bearer (?P<mask>[\w.~+/-]+=*)`)
Go admits the name more than once, which is what a marker written in variants asks for — one branch of an alternation apiece — and every group named "mask" that took part in the match is redacted. A match where none of them did is redacted nowhere.
Where the expression has a ceiling on its width, every match it admits is located, including the ones that begin inside another: forty characters of hexadecimal written against forty more are redacted whole, where Go's FindAll resumes past each match it takes and would leave the second forty. What that costs is a second run of the expression at each position inside a match where one could open — nothing where matches are rare, and what a caller pays for an expression matching densely and never far.
What such a pattern settles, in the sense Pattern.Find gives the word, is worked out from expr two ways. A match can be no wider than the expression can match, so everything more than that many bytes in front of the end of the text is settled; and a match opens with whatever literal the expression opens with, so the text in front of the first place that literal could stand holds no match and is settled whatever the width.
An expression that can match text of any width — one written with * or + or an open repetition — has only the second of those, and one naming no literal to open with has neither and settles nothing at all. A Reader or a Writer holds what no pattern has settled, and gives up holding at WithMaxRetained by redacting what it holds, so such an expression turns everything from a match's opening onwards into a redaction once the limit is reached — whether or not a match was ever written there. Write a counted repetition for a pattern a stream is to mask with: INT-[0-9a-f]{32} rather than INT-[0-9a-f]+.
Example ¶
package main
import (
"fmt"
"github.com/koki-develop/mask-go"
)
func main() {
// An expression that arrives at run time — off a flag, or out of a
// configuration file — is where the error is worth having.
p, err := mask.Regexp("internal-token", `INT-[0-9a-f]{32}`)
if err != nil {
fmt.Println(err)
return
}
m := mask.New(mask.WithPatterns(p))
fmt.Println(m.Mask("token: INT-0123456789abcdef0123456789abcdef"))
}
Output: token: ************************************
func ReplicateAPIToken ¶ added in v0.2.0
func ReplicateAPIToken() Pattern
ReplicateAPIToken locates Replicate API tokens: the prefix r8_ and the thirty-seven letters and digits behind it — forty characters altogether. One string authenticates every request to Replicate's HTTP API, so nothing in a token says what it may be spent on or which of an owner's tokens it is.
A token is located wherever it is written, with no word boundary either side, and exactly forty characters of it are. So text of that shape is redacted whether or not Replicate issued it. A space, a hyphen, an underscore or a run of fewer than thirty-seven letters and digits ends the reading, so text as it is ordinarily written is not affected. A longer run is a token with something written after it, and the token alone is redacted.
Its name is "replicate-api-token".
func ReplicatePatterns ¶ added in v0.2.0
func ReplicatePatterns() []Pattern
ReplicatePatterns returns every built-in pattern that locates a credential Replicate issues.
The returned slice is freshly allocated and may be modified by the caller.
func ResendAPIKey ¶ added in v0.2.0
func ResendAPIKey() Pattern
ResendAPIKey locates Resend API keys: the prefix re_, eight letters and digits, an underscore and the twenty-four letters and digits that follow — thirty-six characters altogether. One string authenticates every request to the Resend API and is the password its SMTP endpoint takes as well, so whoever holds one can send mail from the account's verified domains.
A key is located wherever it is written, with no word boundary either side, and exactly thirty-six characters of it are. So text of that shape is redacted whether or not Resend issued it. A space, a hyphen, a segment of the wrong length or an underscore anywhere but the two places one stands ends the reading, so text as it is ordinarily written is not affected. A longer run is a key with something written after it, and the key alone is redacted.
Its name is "resend-api-key".
func ResendPatterns ¶ added in v0.2.0
func ResendPatterns() []Pattern
ResendPatterns returns every built-in pattern that locates a credential Resend issues.
The returned slice is freshly allocated and may be modified by the caller.
func RubyGemsAPIKey ¶
func RubyGemsAPIKey() Pattern
RubyGemsAPIKey locates RubyGems.org API keys: the prefix rubygems_ and the forty-eight lowercase hexadecimal characters behind it — fifty-seven characters altogether. One string serves every key RubyGems.org issues, whatever scopes it carries and whichever gem it is scoped to, so nothing in a key says what it is allowed to do.
A key is located wherever it is written, with no word boundary either side, and exactly fifty-seven characters of it are. So text of that shape is redacted whether or not RubyGems.org issued it. A space, an uppercase letter, a letter past f or a body of the wrong length ends the reading, so text as it is ordinarily written is not affected.
Its name is "rubygems-api-key".
func RubyGemsPatterns ¶
func RubyGemsPatterns() []Pattern
RubyGemsPatterns returns every built-in pattern that locates a credential RubyGems issues.
The returned slice is freshly allocated and may be modified by the caller.
func SendGridAPIKey ¶
func SendGridAPIKey() Pattern
SendGridAPIKey locates Twilio SendGrid API keys: the prefix SG., the twenty-two characters that identify the key, a dot, and the forty-three characters of the secret behind it. One string serves every access level SendGrid issues — full access, custom access and billing access — so nothing in a key says what it is allowed to do.
A key is located wherever it is written, with no word boundary either side, and exactly sixty-nine characters of it are. So text of that shape is redacted whether or not SendGrid issued it. A space, a hyphen where the dot belongs, or a segment of the wrong length ends the reading, so text as it is ordinarily written is not affected.
Its name is "sendgrid-api-key".
func SendGridPatterns ¶
func SendGridPatterns() []Pattern
SendGridPatterns returns every built-in pattern that locates a credential SendGrid issues.
The returned slice is freshly allocated and may be modified by the caller.
func SentryAuthToken ¶
func SentryAuthToken() Pattern
SentryAuthToken locates the Sentry auth tokens that carry a token type prefix: user auth tokens (sntryu_), user application tokens (sntrya_), internal integration tokens (sntryi_) and organization auth tokens (sntrys_).
Two shapes a body is written in are read. Three of the four kinds carry thirty-two random bytes written as hexadecimal, which is sixty-four characters. The organization token carries a shape of its own: the base64 of a JSON payload naming the organization and the region it is served from, an underscore, and the base64 of thirty-two random bytes behind it.
A token is located wherever it is written, with no word boundary either side, and exactly as many characters of it are as the kind is written to. So text of that shape is redacted whether or not Sentry issued it. A space, a character outside the alphabet, or a body of the wrong length ends the reading, so text as it is ordinarily written is not affected.
Its name is "sentry-auth-token".
func SentryPatterns ¶
func SentryPatterns() []Pattern
SentryPatterns returns every built-in pattern that locates a credential Sentry issues.
The returned slice is freshly allocated and may be modified by the caller.
func ShopifyAccessToken ¶ added in v0.1.0
func ShopifyAccessToken() Pattern
ShopifyAccessToken locates the access tokens Shopify issues in the format it prefixes: the tokens a public app receives for a shop (shpat_), the tokens a custom app is given (shpca_) and the tokens a private app was issued and a delegate token carries today (shppa_), each with thirty-two hexadecimal characters behind it — thirty-eight characters altogether.
A token is located wherever it is written, with no word boundary either side, and exactly thirty-eight characters of it are. So text of that shape is redacted whether or not Shopify issued it. A space, a letter past f or a body of the wrong length ends the reading, so text as it is ordinarily written is not affected.
Its name is "shopify-access-token".
func ShopifyAppSecretKey ¶ added in v0.1.0
func ShopifyAppSecretKey() Pattern
ShopifyAppSecretKey locates the secret half of a Shopify app's client credentials: the prefix shpss_ and the thirty-two hexadecimal characters behind it — thirty-eight characters altogether.
A key is located wherever it is written, with no word boundary either side, and exactly thirty-eight characters of it are. So text of that shape is redacted whether or not Shopify issued it. A space, a letter past f or a body of the wrong length ends the reading, so text as it is ordinarily written is not affected.
Its name is "shopify-app-secret-key".
func ShopifyPatterns ¶ added in v0.1.0
func ShopifyPatterns() []Pattern
ShopifyPatterns returns every built-in pattern that locates a credential Shopify issues.
The returned slice is freshly allocated and may be modified by the caller.
func SlackPatterns ¶
func SlackPatterns() []Pattern
SlackPatterns returns every built-in pattern that locates a credential Slack issues.
The returned slice is freshly allocated and may be modified by the caller.
func SlackToken ¶
func SlackToken() Pattern
SlackToken locates Slack credentials that carry a token prefix: bot tokens (xoxb-), user tokens (xoxp-), app-level tokens (xapp-), workflow tokens (xwfp-), and the pair token rotation issues — refresh tokens (xoxe-) and the rotatable access tokens a xoxe. prefix puts in front of a bot or user token (xoxe.xoxb-, xoxe.xoxp-).
Slack documents the prefixes and nothing else: no length, no alphabet, no count of the parts a token is written in. So a body is read as the hyphen separated segments every Slack token anyone has published is written in, and a token is one where some segment other than the first is long enough to be the secret such a token ends with and carries a letter as every such secret does. Asking that the secret stand behind a part rather than against the prefix is what keeps a bare digest out. A prefix written against a letter or a digit opens nothing, which is what keeps an identifier ending in one of them from being read as a token.
Its name is "slack-token".
Example ¶
package main
import (
"fmt"
"github.com/koki-develop/mask-go"
)
func main() {
m := mask.New(mask.WithPatterns(mask.SlackToken()))
fmt.Println(m.Mask("SLACK_BOT_TOKEN=xoxb-0123456789ab-0123456789abc-0123456789abcdefghijklmn"))
}
Output: SLACK_BOT_TOKEN=********************************************************
func SonarQubePatterns ¶ added in v0.2.0
func SonarQubePatterns() []Pattern
SonarQubePatterns returns every built-in pattern that locates a credential SonarQube issues.
The returned slice is freshly allocated and may be modified by the caller.
func SonarQubeToken ¶ added in v0.2.0
func SonarQubeToken() Pattern
SonarQubeToken locates SonarQube tokens: the prefix squ_ a user token is written with, the sqa_ of a global analysis token, the sqp_ of a project analysis token or the sqb_ of a project badge token, then forty hexadecimal characters — forty-four characters altogether.
A token is located wherever it is written, with no word boundary either side, and exactly forty-four characters of it are. So text of that shape is redacted whether or not SonarQube issued it. A space, a dot, an uppercase prefix or a character outside lowercase hexadecimal ends the reading, so text as it is ordinarily written is not affected. A longer run of hexadecimal is a token with something written after it, and the token alone is redacted.
Its name is "sonarqube-token".
func SourcegraphAccessToken ¶ added in v0.1.0
func SourcegraphAccessToken() Pattern
SourcegraphAccessToken locates the access tokens the Sourcegraph GraphQL API takes: the prefix sgp_, an instance identifier and the separator behind it where one is written, and the forty hexadecimal characters that are the token value. A token carrying no identifier is forty-four characters, one carrying the identifier a licensed instance writes is sixty-one, and one carrying the identifier a development instance writes is fifty.
A token is located wherever it is written, with no word boundary either side, and exactly forty-four, fifty or sixty-one characters of it are. So text of that shape is redacted whether or not Sourcegraph issued it. A letter past f, a body of the wrong length or an identifier of neither shape ends the reading, so text as it is ordinarily written is not affected.
Its name is "sourcegraph-access-token".
func SourcegraphPatterns ¶ added in v0.1.0
func SourcegraphPatterns() []Pattern
SourcegraphPatterns returns every built-in pattern that locates a credential Sourcegraph issues.
The returned slice is freshly allocated and may be modified by the caller.
func StripePatterns ¶
func StripePatterns() []Pattern
StripePatterns returns every built-in pattern that locates a credential Stripe issues.
The returned slice is freshly allocated and may be modified by the caller.
Example ¶
package main
import (
"fmt"
"github.com/koki-develop/mask-go"
)
func main() {
// Every vendor has an accessor of its own, returning the built-in patterns
// that read what that vendor issues. A vendor with more than one is given
// whole by it.
m := mask.New(mask.WithPatterns(mask.StripePatterns()...))
fmt.Println(m.Mask("secret=sk_live_0123456789abcdef01234567 publishable=pk_live_0123456789abcdef01234567 webhook=whsec_0123456789abcdef0123456789abcdef"))
}
Output: secret=******************************** publishable=******************************** webhook=**************************************
func StripePublishableKey ¶
func StripePublishableKey() Pattern
StripePublishableKey locates the Stripe publishable API keys a page is initialized with (pk_live_, pk_test_).
A key is located wherever it is written, so long as no letter or digit stands in front of it, and is redacted from its prefix to the end of the run it stands in. So a key written after an underscore, a quote, an equals sign or a space keeps its span, and a letter or a digit written straight after a key is redacted with it.
Stripe says a publishable key is safe to expose: it is embedded in the page it initializes, so a reader who has one has taken nothing. It is a pattern of its own for that reason rather than in spite of it — a caller masking a frontend bundle, a browser console log or a bug report has no reason to redact a value that belongs there and every reason to keep it, since it says which account and which mode the report came from, while a caller masking a configuration dump they are about to share may want it gone. Reaching for StripeSecretKey (builtin_stripe_secret_key.go) alone is how the first caller says so, and reaching for both, or for StripePatterns, is how the second does.
Its name is "stripe-publishable-key".
func StripeSecretKey ¶
func StripeSecretKey() Pattern
StripeSecretKey locates the Stripe API keys that must not be exposed: the restricted keys Stripe now asks a server to use (rk_live_, rk_test_), the unrestricted secret keys (sk_live_, sk_test_) and the organization keys reaching every account under an organization (sk_org_).
A key is located wherever it is written, so long as no letter or digit stands in front of it, and is redacted from its prefix to the end of the run it stands in. So a key written after an underscore, a quote, an equals sign or a space keeps its span, and a letter or a digit written straight after a key is redacted with it.
Its name is "stripe-secret-key".
Example ¶
package main
import (
"fmt"
"github.com/koki-develop/mask-go"
)
func main() {
// Stripe marks the publishable key safe to expose and the restricted,
// secret and organization keys not, so a pattern of its own reads each side
// of that column. Reaching for this one alone redacts the keys that matter
// and leaves the publishable key, which belongs in the page it initializes.
m := mask.New(mask.WithPatterns(mask.StripeSecretKey()))
fmt.Println(m.Mask("secret=sk_live_0123456789abcdef01234567 publishable=pk_live_0123456789abcdef01234567"))
}
Output: secret=******************************** publishable=pk_live_0123456789abcdef01234567
func StripeWebhookSigningSecret ¶
func StripeWebhookSigningSecret() Pattern
StripeWebhookSigningSecret locates the signing secrets Stripe issues for a webhook endpoint (whsec_): the key a handler computes the HMAC in the Stripe-Signature header with, and the one the Stripe CLI prints when it begins forwarding events to a local endpoint.
A secret is located wherever it is written, with no word boundary either side, and is redacted from its whsec_ to the end of the run it stands in. So a secret written against a word character keeps its span, and a character of the secret's own alphabet written straight after one is redacted with it.
Its name is "stripe-webhook-signing-secret".
Example ¶
package main
import (
"fmt"
"github.com/koki-develop/mask-go"
)
func main() {
// The signing secret is no row of Stripe's table of key types: it is
// issued per endpoint rather than per account, and what it authenticates
// is Stripe to the reader's server rather than the other way about.
// Reaching for it alone redacts what verifies Stripe's own signature and
// leaves the API keys, which the patterns beside it read.
m := mask.New(mask.WithPatterns(mask.StripeWebhookSigningSecret()))
fmt.Println(m.Mask("secret=whsec_0123456789abcdef0123456789abcdef key=sk_live_0123456789abcdef01234567"))
}
Output: secret=************************************** key=sk_live_0123456789abcdef01234567
func SupabaseAccessToken ¶
func SupabaseAccessToken() Pattern
SupabaseAccessToken locates the access tokens the Supabase Management API takes: the personal access token a user creates for themselves, which is the prefix sbp_ and the forty lowercase hexadecimal digits behind it, forty-four characters altogether, and the token an OAuth application is issued in a user's name, which writes oauth_ between the two and so is fifty. Nothing in either says what it is allowed to do — a personal access token carries the whole of the account that created it, and an OAuth issued one the scopes that account approved — so one string serves the Management API either way.
A token is located wherever it is written, with no word boundary either side, and exactly forty-four or fifty characters of it are. So text of that shape is redacted whether or not Supabase issued it. A letter past f, an uppercase one, or a run of the wrong length ends the reading, so text as it is ordinarily written is not affected.
Its name is "supabase-access-token".
func SupabasePatterns ¶
func SupabasePatterns() []Pattern
SupabasePatterns returns every built-in pattern that locates a credential Supabase issues.
The returned slice is freshly allocated and may be modified by the caller.
func SupabasePublishableKey ¶ added in v0.1.0
func SupabasePublishableKey() Pattern
SupabasePublishableKey locates the publishable API keys a Supabase client is initialized with: the prefix sb_publishable_ and the thirty-one characters behind it, forty-six characters altogether.
A key is located wherever it is written, with no word boundary either side, and exactly forty-six characters of it are. So text of that shape is redacted whether or not Supabase issued it. A character outside the base64url alphabet, or anything but an underscore twenty-three characters behind the prefix, ends the reading, so text as it is ordinarily written is not affected.
Supabase says a publishable key is safe to expose: it is embedded in the client it initializes, it carries no privilege row level security has not already granted, and the documentation prints it into browser code. It is a pattern of its own for that reason rather than in spite of it — a caller masking a frontend bundle, a browser console log or a bug report has no reason to redact a value that belongs there and every reason to keep it, since it says which project the report came from, while a caller masking a configuration dump they are about to share may want it gone. Reaching for SupabaseSecretKey (builtin_supabase_secret_key.go) alone is how the first caller says so, and reaching for both, or for SupabasePatterns, is how the second does.
Its name is "supabase-publishable-key".
func SupabaseSecretKey ¶ added in v0.1.0
func SupabaseSecretKey() Pattern
SupabaseSecretKey locates the secret API keys a Supabase project is reached with from a server: the prefix sb_secret_ and the thirty-one characters behind it, forty-one characters altogether. A key bypasses row level security and stands for the whole of the project's data, which is why Supabase refuses one sent from a browser.
A key is located wherever it is written, with no word boundary either side, and exactly forty-one characters of it are. So text of that shape is redacted whether or not Supabase issued it. A character outside the base64url alphabet, or anything but an underscore twenty-three characters behind the prefix, ends the reading, so text as it is ordinarily written is not affected.
Its name is "supabase-secret-key".
func XAIAPIKey ¶ added in v0.2.0
func XAIAPIKey() Pattern
XAIAPIKey locates xAI API keys: the prefix xai- and the letters and digits behind it, of which there are at least eighty — eighty-four characters altogether where a key is the length xAI prints one at. The key an account manages its other keys with is written xai-token- and a body of the same shape, and is located too. One string authenticates every request a key of either kind is issued for, so nothing in one says what it may be spent on.
A key is located wherever it is written, with no word boundary either side, and is redacted from its prefix to the end of the run it stands in. So a letter or a digit written straight after a key is redacted with it, and text of that shape is redacted whether or not xAI issued it. A space, a hyphen, an underscore or a run of fewer than eighty letters and digits ends the reading, so text as it is ordinarily written is not affected.
Its name is "xai-api-key".
func XAIPatterns ¶ added in v0.2.0
func XAIPatterns() []Pattern
XAIPatterns returns every built-in pattern that locates a credential xAI issues.
The returned slice is freshly allocated and may be modified by the caller.
type Reader ¶
type Reader struct {
// contains filtered or unexported fields
}
Reader masks the text read from another reader.
A Reader holds text back — a value split across two reads is in neither of them — so what it returns lags what it has read. The end of the stream settles the last of it, and the text held back is returned before the error that ended the stream is.
A Reader is not safe for concurrent use.
func NewReader ¶
func NewReader(src io.Reader, m *Masker, opts ...StreamOption) *Reader
NewReader returns a Reader that reads from src and masks what it reads with m:
body, err := io.ReadAll(mask.NewReader(resp.Body, m))
Example ¶
package main
import (
"fmt"
"io"
"strings"
"github.com/koki-develop/mask-go"
)
func main() {
m := mask.New(mask.WithPatterns(mask.AllBuiltinPatterns()...))
src := strings.NewReader("GITHUB_TOKEN=ghp_0123456789abcdefghijklmnopqrstuvwxyz")
masked, err := io.ReadAll(mask.NewReader(src, m))
if err != nil {
panic(err)
}
fmt.Println(string(masked))
}
Output: GITHUB_TOKEN=****************************************
func (*Reader) Read ¶
Read fills p with masked text.
Reading returns nothing until the stream settles enough text to fill something, so a read here can take several reads of the reader underneath. The error that ended that reader is held back until the text held with it has been returned, and is then reported as it was.
type Redactor ¶
Redactor produces the text that replaces a located value.
Implementations must be safe for concurrent use by multiple goroutines.
func Fill ¶
Fill redacts every value to r repeated once per rune of the original, so the length of the original survives. A Masker uses Fill('*') unless given another redactor.
Example ¶
package main
import (
"fmt"
"github.com/koki-develop/mask-go"
)
func main() {
m := mask.New(
mask.WithPatterns(mask.AllBuiltinPatterns()...),
mask.WithRedactor(mask.Fill('#')),
)
fmt.Println(m.Mask("GITHUB_TOKEN=ghp_0123456789abcdefghijklmnopqrstuvwxyz"))
}
Output: GITHUB_TOKEN=########################################
func Fixed ¶
Fixed redacts every value to s. Neither the content nor the length of the original survives:
mask.Fixed("[REDACTED]")
Example ¶
package main
import (
"fmt"
"github.com/koki-develop/mask-go"
)
func main() {
m := mask.New(
mask.WithPatterns(mask.AllBuiltinPatterns()...),
mask.WithRedactor(mask.Fixed("[REDACTED]")),
)
fmt.Println(m.Mask("GITHUB_TOKEN=ghp_0123456789abcdefghijklmnopqrstuvwxyz"))
}
Output: GITHUB_TOKEN=[REDACTED]
func NewRedactor ¶
NewRedactor returns a Redactor that redacts values with redact:
mask.NewRedactor(func(m mask.Match) string {
if m.Pattern.Name() == "jwt" {
return "[JWT]"
}
return "[REDACTED]"
})
A redactor reading the name, as this one does, is reading the attribution of what it was handed rather than a promise about the whole of it: a JWT written against another credential is redacted together with it, and the one label then stands for both. Match.Value says where that comes from.
redact must be safe for concurrent use by multiple goroutines.
Example ¶
package main
import (
"fmt"
"strings"
"github.com/koki-develop/mask-go"
)
func main() {
m := mask.New(
mask.WithPatterns(mask.AllBuiltinPatterns()...),
mask.WithRedactor(mask.NewRedactor(func(m mask.Match) string {
return "[" + strings.ToUpper(m.Pattern.Name()) + "]"
})),
)
fmt.Println(m.Mask("GITHUB_TOKEN=ghp_0123456789abcdefghijklmnopqrstuvwxyz"))
}
Output: GITHUB_TOKEN=[GITHUB-TOKEN]
type Span ¶
Span is a half-open byte range [Start, End) within the scanned text. Offsets are zero-based, and Start must be less than End.
type StreamOption ¶
type StreamOption func(*streamOptions)
StreamOption configures a Reader or a Writer.
func WithMaxRetained ¶
func WithMaxRetained(n int) StreamOption
WithMaxRetained sets how much text a Reader or a Writer holds back before it gives up and redacts what it is holding, in bytes. Zero holds without limit.
A run of the characters a value is written in, arriving without end, is a value without end to every pattern that reads one, so somewhere the holding has to stop. It stops with a redaction rather than a release, because releasing held text is releasing the credential the pattern was still reading.
The redaction covers everything held at that moment and is attributed to the pattern that was holding it, so a redactor reading Match.Pattern sees which grammar ran long — and so does everything written after it, to the end of the stream. Giving up takes the opening of the value out of the window along with the rest, and a pattern shown the middle of a value without its opening reports nothing and settles everything; a stream going back to passing text through would write out the rest of the very value it gave up holding, and nothing but the pattern could have said the value had ended.
So the limit is a last resort rather than a knob to tune down. The default is generous enough that no credential written in one piece comes near it, and zero holds without limit for a caller who would rather spend the memory. Any n below zero is read as that same zero rather than as a limit no text can come under, which is what strings.SplitN reads a negative count as and what a caller computing the limit from a budget gets when the budget runs out.
What is redacted after that is redacted a write at a time, since a stream cannot hold the rest of itself back to redact it as one. Fill writes a rune for a rune either way — a rune the writes are split inside is held until the rest of it arrives — and a redactor writing a fixed string writes it once a write rather than once.
Example ¶
package main
import (
"io"
"os"
"strings"
"github.com/koki-develop/mask-go"
)
func main() {
// A run of the characters a key is written in, arriving without end, is a
// key without end to the pattern reading it. The limit is where holding
// stops, and what it stops with is a redaction of everything held.
m := mask.New(
mask.WithPatterns(mask.StripeSecretKey()),
mask.WithRedactor(mask.Fixed("[REDACTED]")),
)
w := mask.NewWriter(os.Stdout, m, mask.WithMaxRetained(32))
if _, err := io.WriteString(w, "sk_live_"+strings.Repeat("0123456789abcdef", 8)); err != nil {
panic(err)
}
if err := w.Close(); err != nil {
panic(err)
}
}
Output: [REDACTED]
type Writer ¶
type Writer struct {
// contains filtered or unexported fields
}
Writer masks the text written to it and writes the result on to another writer.
A Writer holds text back — a value split across two writes is in neither of them — so what reaches the writer underneath lags what was written here, and Close is what settles the end of the stream and lets the last of it go. A Writer that is never closed leaves whatever it was holding unwritten.
A Writer is not safe for concurrent use.
func NewWriter ¶
func NewWriter(dst io.Writer, m *Masker, opts ...StreamOption) *Writer
NewWriter returns a Writer that masks what is written to it with m and writes the result to dst:
w := mask.NewWriter(os.Stderr, m) defer w.Close() log.SetOutput(w)
Close writes out what the Writer is still holding. It does not close dst.
Example ¶
package main
import (
"io"
"os"
"github.com/koki-develop/mask-go"
)
func main() {
m := mask.New(mask.WithPatterns(mask.AllBuiltinPatterns()...))
w := mask.NewWriter(os.Stdout, m)
// A value split across two writes is in neither of them, so the first
// half is held back until the second arrives.
for _, piece := range []string{"GITHUB_TOKEN=ghp_0123456789abcdefghijklmnopqrstu", "vwxyz\n"} {
if _, err := io.WriteString(w, piece); err != nil {
panic(err)
}
}
// Close writes out whatever the Writer is still holding back. A Writer
// that is never closed leaves it unwritten.
if err := w.Close(); err != nil {
panic(err)
}
}
Output: GITHUB_TOKEN=****************************************
Source Files
¶
- builtin_1password_service_account_token.go
- builtin_age_secret_key.go
- builtin_airtable_personal_access_token.go
- builtin_anthropic_api_key.go
- builtin_aws_access_key_id.go
- builtin_aws_secret_access_key.go
- builtin_buildkite_token.go
- builtin_circleci_api_token.go
- builtin_cloudflare_api_key.go
- builtin_cloudflare_api_token.go
- builtin_crates_io_token.go
- builtin_databricks_oauth_client_secret.go
- builtin_databricks_personal_access_token.go
- builtin_digitalocean_token.go
- builtin_docker_personal_access_token.go
- builtin_doppler_auth_token.go
- builtin_dynatrace_token.go
- builtin_fly_io_access_token.go
- builtin_github_token.go
- builtin_gitlab_token.go
- builtin_google_api_key.go
- builtin_grafana_service_account_token.go
- builtin_groq_api_key.go
- builtin_hashicorp_vault_token.go
- builtin_hcp_terraform_api_token.go
- builtin_heroku_api_token.go
- builtin_huggingface_user_access_token.go
- builtin_jwt.go
- builtin_linear_api_key.go
- builtin_newrelic_user_key.go
- builtin_notion_api_token.go
- builtin_npm_access_token.go
- builtin_openai_api_key.go
- builtin_openrouter_api_key.go
- builtin_paddle_api_key.go
- builtin_planetscale_token.go
- builtin_posthog_personal_api_key.go
- builtin_postman_api_key.go
- builtin_private_key.go
- builtin_pulumi_access_token.go
- builtin_pypi_api_token.go
- builtin_replicate_api_token.go
- builtin_resend_api_key.go
- builtin_rubygems_api_key.go
- builtin_scan.go
- builtin_sendgrid_api_key.go
- builtin_sentry_auth_token.go
- builtin_shopify_access_token.go
- builtin_shopify_app_secret_key.go
- builtin_slack_token.go
- builtin_sonarqube_token.go
- builtin_sourcegraph_access_token.go
- builtin_stripe_publishable_key.go
- builtin_stripe_secret_key.go
- builtin_stripe_webhook_signing_secret.go
- builtin_supabase_access_token.go
- builtin_supabase_publishable_key.go
- builtin_supabase_secret_key.go
- builtin_xai_api_key.go
- builtins.go
- mask.go
- option.go
- pattern.go
- redactor.go
- regexp.go
- stream.go
- vendors.go