Documentation
¶
Overview ¶
Package portcullis detects and redacts API tokens, cloud credentials, and other secret material in arbitrary text.
Contains reports whether any rule matches the input; Redact replaces every detected secret span with Marker while preserving the surrounding text. Both are safe for concurrent use and idempotent.
Performance ¶
Detection runs in O(len(text)) per rule via Go's RE2-based regexp engine, gated by an Aho–Corasick keyword pre-filter so clean inputs typically don't compile or run any regex. Memory allocations are zero on a clean input and small (a few hundred bytes) on a secret-bearing one.
Caller responsibilities ¶
portcullis intentionally does not cap input size: callers process inputs of widely different shapes (a chat message, a tool's stdout, a multi-megabyte log buffer) and can pick the right upper bound for their context. If the input is attacker-controlled and unbounded — e.g. an HTTP request body relayed through an untrusted intermediary — wrap the call site with the appropriate size limit before invoking Redact / Contains.
Provenance ¶
The default ruleset is derived from the MIT-licensed github.com/docker/mcp-gateway/pkg/secretsscan package, which adapted it from github.com/aquasecurity/trivy/pkg/fanal/secret, extended with additional patterns for modern AI providers, payment processors, and infrastructure tokens.
Index ¶
Examples ¶
Constants ¶
const Marker = "[REDACTED]"
Marker replaces every detected secret span. Chosen so it doesn't match any rule's keyword pre-filter — see TestMarkerIsNotASecret for the safety property that makes Redact idempotent.
Variables ¶
This section is empty.
Functions ¶
func Contains ¶
Contains reports whether text matches any built-in secret rule. It is safe for concurrent use.
Example ¶
package main
import (
"fmt"
"github.com/docker/portcullis"
)
// ghpFixture is a syntactically valid GitHub PAT (correct CRC32 over the
// 30-char body), built at runtime so the literal token never appears on a
// single source line — push protection would otherwise reject the push.
var ghpFixture = "ghp_" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "0uCPlr"
func main() {
fmt.Println(portcullis.Contains("hello world"))
fmt.Println(portcullis.Contains("token=" + ghpFixture))
}
Output: false true
func ContainsBytes ¶
ContainsBytes is like Contains but accepts a []byte without copying it. b is read but never mutated.
func Redact ¶
Redact returns a copy of text with every detected secret span replaced by Marker. When a rule defines a (?P<secret>…) named subgroup, only that span is replaced (so callers still see "AWS_SECRET_ACCESS_KEY=[REDACTED]"); otherwise the whole match is replaced.
Idempotent: Marker does not match any rule, so calling Redact twice yields the same result. Safe for concurrent use.
Example ¶
package main
import (
"fmt"
"github.com/docker/portcullis"
)
// ghpFixture is a syntactically valid GitHub PAT (correct CRC32 over the
// 30-char body), built at runtime so the literal token never appears on a
// single source line — push protection would otherwise reject the push.
var ghpFixture = "ghp_" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "0uCPlr"
func main() {
log := "Run this with token=" + ghpFixture + " please."
fmt.Println(portcullis.Redact(log))
}
Output: Run this with token=[REDACTED] please.
Example (ConnectionString) ¶
package main
import (
"fmt"
"github.com/docker/portcullis"
)
func main() {
// Connection-string rules redact only the password span so the
// surrounding URL stays useful for log readers.
uri := "postgresql://app:hunter2supersecret@db.internal:5432/orders"
fmt.Println(portcullis.Redact(uri))
}
Output: postgresql://app:[REDACTED]@db.internal:5432/orders
Example (MultipleSecrets) ¶
package main
import (
"fmt"
"github.com/docker/portcullis"
)
// ghpFixture is a syntactically valid GitHub PAT (correct CRC32 over the
// 30-char body), built at runtime so the literal token never appears on a
// single source line — push protection would otherwise reject the push.
var ghpFixture = "ghp_" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "0uCPlr"
func main() {
other := "ghp_" + "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + "1rpRcy"
in := "first " + ghpFixture + " and second " + other + " end"
fmt.Println(portcullis.Redact(in))
}
Output: first [REDACTED] and second [REDACTED] end
Types ¶
type Match ¶
Match describes a single secret span detected in an input. Start and End are byte offsets into the original text and delimit the same span Redact would replace with Marker: when the matching rule defines a (?P<secret>…) named subgroup, the span covers only that subgroup; otherwise it covers the whole match. Value is the substring text[Start:End] for caller convenience.
func Find ¶
Find returns every secret span detected in text, in left-to-right order. Overlapping matches are deduplicated: when two rules flag overlapping spans (e.g. a Grafana legacy `eyJrIjoi…` token whose suffix also matches the generic JWT shape) Find keeps the longest span and drops the shorter one, so each underlying secret is reported once.
Find is safe for concurrent use. The returned slice is freshly allocated and owned by the caller.
Example ¶
package main
import (
"fmt"
"github.com/docker/portcullis"
)
func main() {
// Demonstrated with an AWS access key + a Postgres password so the
// expected-output comment doesn't have to contain a checksum-valid
// GitHub PAT (which push protection would block).
awsKey := "AKIA" + "RZPUZDIKQEXAMPLE"
in := "first " + awsKey + " then " +
"postgresql://app:hunter2supersecret@db.internal/orders"
for _, m := range portcullis.Find(in) {
fmt.Printf("%d-%d: %s\n", m.Start, m.End, m.Value)
}
}
Output: 6-26: AKIARZPUZDIKQEXAMPLE 49-67: hunter2supersecret
func FindBytes ¶
FindBytes is like Find but accepts a []byte. It does not copy the input: each returned Match.Value aliases b. Callers must not mutate b for as long as those Value strings are in use.
Use this when scanning a buffer (file contents, HTTP body, log chunk) you'd otherwise pass through string(b) — that conversion always copies, FindBytes does not.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
portcullis-scan
command
Command portcullis-scan walks a directory tree and prints every secret occurrence found in regular files, as detected by portcullis.Find.
|
Command portcullis-scan walks a directory tree and prints every secret occurrence found in regular files, as detected by portcullis.Find. |