Documentation
¶
Overview ¶
Package policy is lockhttp's policy: the profile the human writes in the console account's config file, in canonical form. The console approves it (lockkit's "the AI tightens, the human loosens" review) and serves it to the gate; the gate never reads a policy file.
Index ¶
- Constants
- Variables
- func AmbiguousPath(path string) bool
- func CheckHostPattern(key, h string, single bool) (string, error)
- func CredentialName(name string) bool
- func DefaultConfig(name string, production bool, extra []string) string
- func DefaultProfile(name string, production bool, extra []string) string
- func FormatSize(n int64) string
- func HostMatch(pat, host string) bool
- func ImportHosts(imports []string) ([]string, error)
- func IsMessaging(host string) bool
- func IsPublish(host string) bool
- func IsReceivePack(path, rawQuery string) bool
- func ParseSize(s string) (int64, error)
- func Profiles(data []byte, name string) ([]string, error)
- func PublishMethods(host string, methods []string) bool
- func UnderAnthropic(host string) bool
- type Inject
- type InternalHost
- type Policy
- func (p Policy) AutoForbidden(r Rule) string
- func (p Policy) CDNFor(origin, target string) bool
- func (p Policy) Clone() Policy
- func (p Policy) Denied(host string) bool
- func (p Policy) Describe() []string
- func (p Policy) Diff(approved kconfig.Policy) []kconfig.Change
- func (p Policy) Fingerprint() string
- func (p Policy) InjectAt(host string, port int) []Inject
- func (p Policy) InjectFor(container, host string, port int) []Inject
- func (p Policy) InjectForRequest(container, host string, port int, method, path string) []Inject
- func (p Policy) InternalHost(host string) (InternalHost, bool)
- func (p Policy) Profile() console.Profile
- func (p Policy) Spliced(host string) bool
- func (p Policy) Trusts(host string) bool
- type Rule
Constants ¶
const ( // ModeEnforce applies every verdict. ModeEnforce = "enforce" // ModeShadow logs what would have asked or been denied and lets it // pass; the floor and secret matches stay enforced. ModeShadow = "shadow" // ModeOff (heuristics only) computes no score. ModeOff = "off" )
Modes of the policy and of the heuristics.
const ( VerdictDeny = "deny" VerdictAsk = "ask" // VerdictAuto asks without NeverAuto: auto mode may answer it. VerdictAuto = "auto" VerdictFree = "free" )
Verdicts of a rule.
const ( DefaultBufferLimit = 10 << 20 DefaultHold = 60 DefaultConnectGrant = 600 DefaultIdle = 24 * 3600 DefaultMaxSession = 24 * 3600 )
Defaults (decisions D5 and the connect grant).
const ConfigHeader = `` /* 207-byte string literal not displayed */
ConfigHeader opens the config file `lockhttp profile add` creates.
Variables ¶
var AnthropicDomains = []string{"anthropic.com", "claude.ai", "claude.com"}
AnthropicDomains are the domains whose other hosts always ask (NeverAuto) unless a rule says otherwise.
var ClaudeHosts = []string{"api.anthropic.com", "claude.ai", "console.anthropic.com", "statsig.anthropic.com"}
ClaudeHosts are free at the domain in phase 1 (CONNECT): the hosts Claude Code reaches. Any other host under the Anthropic domains asks.
var DefaultRedirectCDN = []string{
"ghcr.io>pkg-containers.githubusercontent.com",
"github.com>objects.githubusercontent.com",
"github.com>release-assets.githubusercontent.com",
"github.com>codeload.github.com",
"api.github.com>codeload.github.com",
"registry-1.docker.io>production.cloudflare.docker.com",
"proxy.golang.org>storage.googleapis.com",
"pypi.org>files.pythonhosted.org",
}
DefaultRedirectCDN are the CDNs an origin's 3xx may send a GET to (the exact target, once, within 60 s), whatever the policy adds.
var DocsTrusted = []string{
"go.dev", "pkg.go.dev", "proxy.golang.org", "sum.golang.org", "storage.googleapis.com",
"docs.python.org", "pypi.org", "files.pythonhosted.org",
"registry.npmjs.org", "nodejs.org",
"developer.mozilla.org", "docs.docker.com", "kubernetes.io", "man7.org", "docs.anthropic.com",
"github.com", "api.github.com", "codeload.github.com", "objects.githubusercontent.com",
"raw.githubusercontent.com", "release-assets.githubusercontent.com",
"crates.io", "static.crates.io", "index.crates.io",
"deb.debian.org", "security.debian.org", "archive.ubuntu.com", "security.ubuntu.com",
"dl-cdn.alpinelinux.org",
}
DocsTrusted is the initial trusted read set (decision D8).
var MessagingHosts = []string{
"hooks.slack.com", "slack.com", "*.slack.com",
"gmail.googleapis.com", "graph.microsoft.com", "outlook.office.com", "outlook.office365.com",
"discord.com", "discordapp.com", "api.telegram.org", "api.twilio.com",
"api.sendgrid.com", "api.mailgun.net", "api.postmarkapp.com", "smtp.gmail.com",
"api.pushover.net", "ntfy.sh", "webhook.site", "*.webhook.office.com",
}
MessagingHosts send messages on someone's behalf: every write asks, and no rule may make them auto.
var PublishHosts = []string{
"registry.npmjs.org", "upload.pypi.org", "test.pypi.org", "crates.io", "rubygems.org",
"registry-1.docker.io", "ghcr.io", "quay.io", "*.pkg.dev", "*.azurecr.io",
"api.nuget.org", "repo.maven.apache.org", "uploads.github.com",
}
PublishHosts are package and image registries: a write there publishes.
Functions ¶
func AmbiguousPath ¶ added in v0.8.0
AmbiguousPath reports whether an escaped path may name, at a target, another path than its string, so that a path-scoped credential must not be added to it. Only a canonical path is unambiguous (an allowlist, not a list of known tricks):
- its bytes are RFC 3986 unreserved characters (ALPHA, DIGIT, '-', '.', '_', '~') and '/', or percent-escapes;
- an escape of an ASCII byte names a reserved character other than '/' (":?#[]@!$&'()*+,;="): never '.', '/', '\', '%', a space, a control byte or an unreserved character ("%2e", "%2f", "%5c", "%25", "%20", "%00", "%41");
- escapes of bytes >= 0x80 decode to valid, shortest-form UTF-8 of letters, marks or decimal digits ("%c3%a9"): never an overlong form ("%c0%ae"), a stray byte, a space, a punctuation sign or a symbol (a fullwidth dot or slash a target may normalise to '.' or '/'), never a default-ignorable letter or mark ("%cd%8f" U+034F, "%ef%b8%8f" U+FE0F, "%e3%85%a4" U+3164), and a mark only right after a letter, a digit or such a mark, never after a dot, a reserved character or at the start of a segment ("..%cc%b8", ".%cc%81.", "%cc%81..") (a target that drops ignorables or marks would read "..");
- no segment, decoded and cut at its first reserved character (";", "?", ":", ...), is made only of dots ("." "..", "...", "..;x", "..%3f").
Anything else (literal sub-delims or spaces, a backslash, an invalid escape, a twice escaped byte) is ambiguous: a target that resolves dot segments, trims dots, spaces or NULs, decodes the path or reads overlong UTF-8 would route it outside the prefix it matched as a string.
func CheckHostPattern ¶
CheckHostPattern validates and normalises a host or "*.domain" pattern. A single-label host is accepted only when single is set (internal hosts); IP literals never are.
func CredentialName ¶ added in v0.8.0
CredentialName reports whether name is a valid name of a secret, in the policy and in the gate's secrets file: lower-case letters, digits, '_', '-' and '.', at most 32 characters, no run of more than 16 letters and digits (a name, not a pasted token).
func DefaultConfig ¶
DefaultConfig is a new config file: ConfigHeader and DefaultProfile.
func DefaultProfile ¶ added in v0.7.0
DefaultProfile is the profile `lockhttp profile add` writes: the docs set, plus extra (checked entries, from ImportHosts) as trusted reads. name is a valid profile name (config.CheckProfileName).
func HostMatch ¶
HostMatch matches a host against a pattern: an exact host, "*.domain" (any subdomain, not the domain itself) or "*" (any host).
func ImportHosts ¶ added in v0.7.0
ImportHosts reads hosts to trust for reads in the format of lea's LEA_EGRESS_ALLOW: "example.org" trusts that host, ".corp.example" the domain and its subdomains. It returns the trusted entries they make, checked and normalised. Its error names the entry's position, never the entry (it may be anything pasted in the wrong place).
func IsMessaging ¶
IsMessaging reports whether host sends messages.
func IsReceivePack ¶
IsReceivePack reports whether path pushes to a git repository.
func PublishMethods ¶
PublishMethods reports whether methods (empty: all) include one that publishes on registry host: PUT, PATCH, DELETE anywhere, and any write on an upload host.
func UnderAnthropic ¶
UnderAnthropic reports whether host is one of the Anthropic domains or a subdomain of one.
Types ¶
type Inject ¶ added in v0.8.0
type Inject struct {
ID string `json:"id"`
// Container limits the injection to one container ("" for every one).
Container string `json:"container,omitempty"`
// Host is an exact host name (an internal host may be single-label).
Host string `json:"host"`
Port int `json:"port"`
// Header is the header's canonical name ("Authorization").
Header string `json:"header"`
// Scheme prefixes the value ("Bearer"), "" for the bare secret.
Scheme string `json:"scheme,omitempty"`
// Credential names the value in the gate's secrets file ("secret" is
// a word config keys may not use).
Credential string `json:"credential"`
// Methods limits the injection to these methods (upper case, sorted;
// empty: every method but TRACE and TRACK, in any case, which the gate
// refuses).
Methods []string `json:"methods,omitempty"`
// Paths limits it to these paths, each exact or a prefix when it ends
// with "*" (sorted; empty: every path). A path a target may resolve to
// another one never matches (AmbiguousPath).
Paths []string `json:"paths,omitempty"`
Note string `json:"note,omitempty"`
}
Inject is one credential injection: the gate adds Header to the requests a container sends to Host:Port, after the verdict, with the value of the secret named Credential in the gate's secrets file (prefixed with Scheme and a space when Scheme is set). The policy names the secret, never its value: the value stays in the gate's container and never reaches the agent, the console or the hub.
func (Inject) Applies ¶ added in v0.8.0
Applies reports whether i covers a request of container to host:port.
func (Inject) MatchesRequest ¶ added in v0.8.0
MatchesRequest reports whether i covers method and path (the escaped path, as rules match it). TRACE and TRACK, in any case, never match: they echo the request, the injected header included. With paths, a path a target may resolve to another one (see AmbiguousPath) never matches: "/api/../admin" is not under "/api/*" at the target.
type InternalHost ¶
type InternalHost struct {
Host string `json:"host"`
Ports []int `json:"ports"`
// Read makes reads free; otherwise every request asks.
Read bool `json:"read"`
}
InternalHost is a host of the user's internal-hosts list: it may resolve to a private address, on the ports it declares.
type Policy ¶
type Policy struct {
Name string `json:"name"`
Production bool `json:"production"`
Mode string `json:"mode"`
Heuristics string `json:"heuristics"`
// BufferLimit is the largest body the gate holds for a human (bytes).
BufferLimit int64 `json:"buffer_limit"`
// Hold is how long the gate holds a request for the human (seconds).
Hold int64 `json:"hold"`
// ConnectGrant is how long an approved CONNECT opens its host for the
// container (seconds).
ConnectGrant int64 `json:"connect_grant"`
IdleTimeout int64 `json:"idle_timeout"`
MaxSession int64 `json:"max_session"`
// Trusted hosts are free for reads (GET, HEAD, OPTIONS without body).
Trusted []string `json:"trusted"`
// Splice hosts are never decrypted: decided at the domain.
Splice []string `json:"splice"`
// Deny hosts are always refused.
Deny []string `json:"deny"`
// RedirectCDN lists "origin>cdn": a 3xx from origin to cdn lets the
// same container GET the exact target once, within 60 s.
RedirectCDN []string `json:"redirect_cdn"`
Internal []InternalHost `json:"internal_hosts"`
Rules []Rule `json:"rules"`
// Inject lists the credential injections: headers the gate adds, from
// its own secrets file, to the requests of one host and port. Omitted
// when empty, so that a policy without injection keeps its
// fingerprint.
Inject []Inject `json:"inject,omitempty"`
}
Policy is one profile in canonical form: sorted lists, defaults applied, durations in seconds. Its JSON is what the console fingerprints and stores as the approved policy, and what the gate receives.
func Parse ¶
Parse decodes the config file data and returns profile in canonical form. Any unknown key, any key that looks like a secret, and any rule that would let auto mode answer a forbidden class is refused.
func (Policy) AutoForbidden ¶
AutoForbidden returns why rule may not have verdict auto in p, or "". The classes are those that always need a human: production, all hosts, a spliced host, messaging, registry publishing, git-receive-pack. The runtime classes (an exfiltration score, a secret, an upload) are checked per request.
func (Policy) Diff ¶
Diff implements config.Policy: what changed since approved, each change marked Loosens when it widens what passes without a human.
func (Policy) Fingerprint ¶
Fingerprint is the canonical hash of p, as the console stores it.
func (Policy) InjectAt ¶ added in v0.8.0
InjectAt returns every injection to host:port, whatever its container, methods and paths: each response from that host and port may carry one of their credentials (stored by an injected request, served to another path or another container).
func (Policy) InjectFor ¶ added in v0.8.0
InjectFor returns the injections that apply to a request of container to host:port (any method and path), a container-scoped one before an all-containers one of the same header (only the first of each header applies).
func (Policy) InjectForRequest ¶ added in v0.8.0
InjectForRequest is InjectFor for one method and path: an injection whose methods or paths do not name them does not apply (and does not hide an all-containers one).
func (Policy) InternalHost ¶
func (p Policy) InternalHost(host string) (InternalHost, bool)
InternalHost returns the internal host entry of host.
type Rule ¶
type Rule struct {
ID string `json:"id"`
Container string `json:"container,omitempty"`
Host string `json:"host"`
Methods []string `json:"methods"`
// Path is an exact path, or a prefix when it ends with "*"; "" is any.
Path string `json:"path,omitempty"`
Verdict string `json:"verdict"`
// MaxBody bounds the body of a matching request (bytes, 0 = the
// buffer limit).
MaxBody int64 `json:"max_body,omitempty"`
// Expires is the last day (UTC) the rule applies, "" for never.
Expires string `json:"expires,omitempty"`
Note string `json:"note,omitempty"`
}
Rule is one policy rule. It applies to every container or to one, never to an agent: an agent name is declarative.
func (Rule) MatchesMethod ¶
MatchesMethod reports whether method is covered by r.Methods (empty: every method).
func (Rule) MatchesPath ¶
MatchesPath reports whether path is covered by r.Path.