authentication

package
v0.5.3 Latest Latest
Warning

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

Go to latest
Published: Oct 10, 2026 License: MIT Imports: 30 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// LoginReasonUnverified: the flow ran without error but there was no expect
	// block, and nothing in the response proved the login worked (or the login
	// form came back). A warning: the session is kept, the command exits 0.
	LoginReasonUnverified = "unverified"
	// LoginReasonExpectStatus: an expect.status block rejected the status.
	LoginReasonExpectStatus = "expect_status_mismatch"
	// LoginReasonExpectBody: an expect.body_contains block did not match.
	LoginReasonExpectBody = "expect_body_missing"
	// LoginReasonUnexpectedStatus: no expect block, and the status was outside
	// 2xx/3xx.
	LoginReasonUnexpectedStatus = "unexpected_status"
	// LoginReasonExtractFailed: an extract rule found nothing.
	LoginReasonExtractFailed = "extract_failed"
	// LoginReasonNetworkError: the request did not complete (connect, TLS,
	// timeout, cancellation, body read).
	LoginReasonNetworkError = "network_error"
	// LoginReasonInvalidFlow: the flow could not be built (no login, bad URL,
	// unparsable login_request).
	LoginReasonInvalidFlow = "invalid_flow"

	// Set by callers that report a session without checking it (OK, not
	// Verified): one with static headers and no login, and one skipped by
	// `auth load --no-validate`.
	LoginReasonStaticHeaders = "static_headers"
	LoginReasonNotValidated  = "not_validated"
)

Reasons a login check reports. Stable strings: `auth load -j` prints them and a caller branches on them.

View Source
const (
	DefaultCheckMethod       = http.MethodGet
	DefaultCheckSampleEvery  = 50
	DefaultReauthMaxAttempts = 3
	DefaultReauthBackoff     = 10 * time.Second
)

Defaults for the check and reauth blocks.

View Source
const (
	SignalStatusPair         = "status_pair"
	SignalLoginRedirect      = "login_redirect"
	SignalPasswordInput      = "password_input"
	SignalBodySimilarity     = "body_similarity"
	SignalConfiguredLoggedIn = "configured_logged_in"
	SignalConfiguredLogOut   = "configured_logged_out"
	SignalRequestFailed      = "request_failed"
	SignalInvalidCheck       = "invalid_check"
)

Signal names.

View Source
const (
	// OAuth2AuthStyleHeader sends client_id/client_secret as HTTP Basic auth.
	OAuth2AuthStyleHeader = "header"
	// OAuth2AuthStyleBody sends them as form fields in the request body.
	OAuth2AuthStyleBody = "body"
)

OAuth2 client authentication styles (login.auth_style).

View Source
const DefaultLoginBudget = 2 * time.Minute

DefaultLoginBudget bounds the time ALL login flows together may take. It is a total, not a per-request timeout (each request keeps its own 30s): a scan configured with six sessions, each a three-step flow, could otherwise spend nine minutes in setup before sending a single scan request, with no output and no way to tell a slow login from a hung one. Two minutes is many times what a working login needs and short enough that a wedged one is a visible delay rather than an apparent hang.

Variables

This section is empty.

Functions

func BodySimilarity added in v0.5.3

func BodySimilarity(a, b []byte) float64

BodySimilarity is the Dice coefficient of the two bodies' token multisets, with volatile tokens (numbers, nonces, IDs) masked: 1 means the same words in the same quantities, 0 nothing shared. Two empty bodies are identical.

func IsRawHTTPRequest

func IsRawHTTPRequest(data []byte) bool

IsRawHTTPRequest returns true if the data looks like a raw HTTP request.

func NormalizeLoginFlow

func NormalizeLoginFlow(lf *LoginFlow)

NormalizeLoginFlow expands shorthand type/token_path into explicit extract rules and validates the URL. Called before execution; safe to call multiple times.

func ParseSessionBytes added in v0.5.3

func ParseSessionBytes(content []byte, name string, opt ParseOptions) ([]*Session, []LintIssue, error)

ParseSessionBytes parses a session file in any accepted form and returns its sessions plus the parse-level issues (unknown fields). name is the file path or a label such as "<stdin>"; a .json suffix selects JSON, otherwise the content is sniffed. ${ENV} references are expanded first.

A file that defines no session is an error here (a loader has nothing to load); ParseSessionDocument returns it as an empty document for lint.

func ResolveCheckURL added in v0.5.3

func ResolveCheckURL(checkURL, base string) (string, error)

ResolveCheckURL resolves the check URL against base: an absolute URL is returned as is, a path is joined to base's origin. base may be any URL on the target (the login URL, a scan target).

func ResolveSessionPath added in v0.5.3

func ResolveSessionPath(path, sessionDir string) string

ResolveSessionPath resolves an --auth-file value the way LoadFromAuthFiles does: a path is used as is, a bare name is looked up in sessionDir (default ~/.vigolium/sessions/) with .yaml, .yml and .json tried in order.

func ValidateExpect

func ValidateExpect(expect *ExpectResponse, statusCode int, body []byte) error

ValidateExpect checks if an HTTP response satisfies the expect constraints.

Types

type Assessment added in v0.5.3

type Assessment struct {
	Verdict Verdict  `json:"verdict"`
	Reason  string   `json:"reason"`
	Signals []Signal `json:"signals,omitempty"`
	// Similarity is the auth/anon body similarity in [0,1], or -1 when it was
	// not computed.
	Similarity float64 `json:"similarity"`
}

Assessment is AssessAuthenticated's result.

func AssessAuthenticated added in v0.5.3

func AssessAuthenticated(authResp, anonResp *ProbeResponse, check *SessionCheck) Assessment

AssessAuthenticated decides whether authResp (the URL fetched with the session's credentials) shows a logged-in session, using anonResp (the same request without them) as the baseline. check is the session's check block (nil for none): a configured logged_out match is decisive, a configured logged_in rule overrides the heuristics, and the heuristics are the status pair, a redirect to a login URL, a password input, and body similarity.

func (Assessment) Authenticated added in v0.5.3

func (a Assessment) Authenticated() bool

Authenticated is Verdict == VerdictAuthenticated.

type CheckMatcher added in v0.5.3

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

CheckMatcher is a compiled check block. A nil *CheckMatcher matches nothing (no rules configured).

func (*CheckMatcher) HasLoggedIn added in v0.5.3

func (m *CheckMatcher) HasLoggedIn() bool

HasLoggedIn reports whether a logged_in rule is configured.

func (*CheckMatcher) HasLoggedOut added in v0.5.3

func (m *CheckMatcher) HasLoggedOut() bool

HasLoggedOut reports whether a logged_out rule is configured.

func (*CheckMatcher) LoggedIn added in v0.5.3

func (m *CheckMatcher) LoggedIn(p *ProbeResponse) (bool, string)

LoggedIn reports whether every configured logged_in condition holds on p, and names the first that failed. False when no rule is configured.

func (*CheckMatcher) LoggedOut added in v0.5.3

func (m *CheckMatcher) LoggedOut(p *ProbeResponse, checkBody bool) (bool, string)

LoggedOut reports whether any logged_out condition matches p, and which ("status", "redirect" or "body"). checkBody=false skips the body regex: the scan's sampler evaluates status and Location on every response and the body only on 1 in sample_every.

type Credentials added in v0.5.3

type Credentials struct {
	// Headers are the headers a request sends for the session: the session's
	// own, minus any the operator pinned with an explicit -H.
	Headers map[string]string
	// Gen numbers the generation, from 1. Each successful re-login adds one.
	Gen uint64
	// At is when this generation was minted.
	At time.Time
	// ExpiresAt is when this generation expires, when the login said so (an
	// OAuth2 token's expires_in); zero when unknown. The session refreshes
	// shortly before it (see expiryLead).
	ExpiresAt time.Time
	// contains filtered or unexported fields
}

Credentials are one generation of a session's credentials. A published value is never mutated: a re-login publishes a new one.

type DiscoverResult

type DiscoverResult struct {
	Session      *Session
	StatusCode   int
	TokenSources []string // human-readable descriptions of where tokens were found
	RawResponse  string   // raw response for storage in login_response
}

DiscoverResult holds the output of auto-discovery.

func DiscoverLogin

func DiscoverLogin(loginReq *LoginRequest) (*DiscoverResult, error)

DiscoverLogin sends the given login request and auto-discovers auth tokens from the response. It checks JSON body fields, Set-Cookie headers, and auth response headers. Returns a Session with a LoginFlow populated with auto-generated ExtractRules.

func DiscoverLoginContext added in v0.5.3

func DiscoverLoginContext(ctx context.Context, loginReq *LoginRequest, client *http.Client) (*DiscoverResult, error)

DiscoverLoginContext is DiscoverLogin bounded by ctx and sent over client's transport (pkg/http.LoginTransport: the scan's proxy and TLS stance). client is copied, never mutated; nil keeps the old direct, certificate-ignoring transport.

type Duration added in v0.5.3

type Duration time.Duration

Duration is a session-file duration: "90s", "2m", "1h30m". A bare integer is seconds. Zero means unset.

func (Duration) D added in v0.5.3

func (d Duration) D() time.Duration

D returns the value as a time.Duration.

func (Duration) MarshalJSON added in v0.5.3

func (d Duration) MarshalJSON() ([]byte, error)

MarshalJSON writes the duration in Go's notation ("2m0s").

func (Duration) MarshalYAML added in v0.5.3

func (d Duration) MarshalYAML() (any, error)

MarshalYAML writes the duration in Go's notation ("2m0s").

func (Duration) String added in v0.5.3

func (d Duration) String() string

func (*Duration) UnmarshalJSON added in v0.5.3

func (d *Duration) UnmarshalJSON(b []byte) error

UnmarshalJSON accepts "2m" or a number of seconds.

func (*Duration) UnmarshalYAML added in v0.5.3

func (d *Duration) UnmarshalYAML(n *yaml.Node) error

UnmarshalYAML accepts "2m" or a bare number of seconds.

type ExpectResponse

type ExpectResponse struct {
	Status       []int  `yaml:"status,omitempty" json:"status,omitempty"`               // Acceptable status codes (e.g. [200, 201])
	BodyContains string `yaml:"body_contains,omitempty" json:"body_contains,omitempty"` // Response body must contain this string
}

ExpectResponse defines validation rules for a login response.

type ExtractRule

type ExtractRule struct {
	Source  ExtractSource `yaml:"source" json:"source"`
	Name    string        `yaml:"name,omitempty" json:"name,omitempty"`         // Cookie name or header name
	Path    string        `yaml:"path,omitempty" json:"path,omitempty"`         // JSONPath for json source
	ApplyAs string        `yaml:"apply_as,omitempty" json:"apply_as,omitempty"` // Header template, e.g. "Authorization: Bearer {value}"
	// Regex extraction fields (source: "regex")
	Pattern string `yaml:"pattern,omitempty" json:"pattern,omitempty"` // Regex pattern with capture group
	Group   int    `yaml:"group,omitempty" json:"group,omitempty"`     // Capture group index (default: 1)
}

ExtractRule defines how to extract an auth token from a login response.

type ExtractSource

type ExtractSource string

ExtractSource specifies where to extract authentication tokens from a login response.

const (
	ExtractCookie ExtractSource = "cookie" // Extract from Set-Cookie header
	ExtractJSON   ExtractSource = "json"   // Extract from JSON response body
	ExtractHeader ExtractSource = "header" // Extract from response header
	ExtractRegex  ExtractSource = "regex"  // Extract from response body via regex
)

type LintIssue

type LintIssue struct {
	Field    string `json:"field"`    // dot-notation path, e.g. "sessions[0].login.url"
	Severity string `json:"severity"` // "error" or "warning"
	Message  string `json:"message"`
	// Line and Column locate the field in the source file (1-based); 0 when
	// the issue has no position (a missing field with no present parent, or a
	// document parsed without one).
	Line   int `json:"line,omitempty"`
	Column int `json:"column,omitempty"`
}

LintIssue represents a single validation issue found during linting.

func LintSessionConfig

func LintSessionConfig(cfg *SessionConfig) []LintIssue

LintSessionConfig performs detailed validation of a SessionConfig and returns all issues found, rather than stopping at the first error.

type LiveEvent added in v0.5.3

type LiveEvent struct {
	Kind    LiveEventKind
	Session string
	// Gen is the generation the event is about: the suspected one for suspect
	// and a failed relogin, the NEW one for a successful relogin.
	Gen uint64
	OK  bool
	// Reason says what triggered a suspect: "status", "redirect", "body"
	// (a logged_out match), "interval" (the proactive probe), "expiry" (the
	// credentials are about to expire) or "invalidate".
	Reason string
	// Attempts is how many logins a relogin cycle ran.
	Attempts  int
	Err       error
	OnFailure ReauthOnFailure
}

LiveEvent is what a LiveSession tells its owner. Delivered synchronously from the confirm/refresh goroutine, never from the request path.

type LiveEventKind added in v0.5.3

type LiveEventKind string

LiveEventKind names what a LiveEvent reports.

const (
	// LiveEventSuspect: scan traffic (or the proactive probe) looked logged out.
	LiveEventSuspect LiveEventKind = "suspect"
	// LiveEventRelogin: a re-login cycle ended; OK says how.
	LiveEventRelogin LiveEventKind = "relogin"
	// LiveEventLost: the session is logged out for good (re-login failed or is
	// disabled). The scan continues unauthenticated or aborts per on_failure.
	LiveEventLost LiveEventKind = "lost"
)

type LiveOptions added in v0.5.3

type LiveOptions struct {
	// Manager re-runs the login (rehydrate) and supplies the probe transport.
	// Nil disables re-login: a confirmed logout goes straight to failed.
	Manager *Manager
	// ProbeClient sends the check.url probe. Nil uses the manager's login
	// transport with redirects not followed (Go's default transport when
	// there is no manager).
	ProbeClient *http.Client
	// ProbeBase resolves a relative check.url (a scan target). Empty falls back
	// to the login URL.
	ProbeBase string
	// Pinned are header names the operator set explicitly (-H): the session
	// never overrides them.
	Pinned []string
	// OnEvent receives suspect / relogin / lost events. Optional.
	OnEvent func(LiveEvent)
	// Context bounds the probes and re-logins; Close cancels it too.
	Context context.Context
	// WaitBudget bounds how long Wait parks a request during a re-login.
	// Zero uses the manager's login budget (DefaultLoginBudget).
	WaitBudget time.Duration

	// Test seams. Nil means the real clock.
	Now   func() time.Time
	Sleep func(ctx context.Context, d time.Duration) error
}

LiveOptions configures a LiveSession.

type LiveSession added in v0.5.3

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

LiveSession is the scan-time owner of one session's credentials. All methods are safe for concurrent use; a nil *LiveSession is inert.

func NewLiveSession added in v0.5.3

func NewLiveSession(sess *Session, opt LiveOptions) (*LiveSession, error)

NewLiveSession builds the live owner of sess, which must be hydrated and carry a check block (call Session.ApplyDefaults first) or credentials with a known expiry (an OAuth2 login). It starts the proactive probe timer when check.interval is set and the expiry timer when the credentials expire; Close stops both.

func (*LiveSession) Close added in v0.5.3

func (s *LiveSession) Close()

Close stops the proactive probe, cancels any probe or login in flight and waits for them to return. Idempotent.

func (*LiveSession) Credentials added in v0.5.3

func (s *LiveSession) Credentials() *Credentials

Credentials is the current generation. Lock-free; never nil on a live session (nil on a nil receiver).

func (*LiveSession) Invalidate added in v0.5.3

func (s *LiveSession) Invalidate(gen uint64) bool

Invalidate reports generation gen as logged out, skipping the confirm probe. Only the first caller for the current generation starts a re-login; it returns whether this call did.

func (*LiveSession) LoggedOutResponses added in v0.5.3

func (s *LiveSession) LoggedOutResponses() int64

LoggedOutResponses counts responses the scan received while the session was logged out: every logged_out match (from any generation), plus every response once the session has failed. Work behind them ran unauthenticated; v1 reports the count rather than re-queueing it.

func (*LiveSession) Name added in v0.5.3

func (s *LiveSession) Name() string

Name is the session's name.

func (*LiveSession) Observe added in v0.5.3

func (s *LiveSession) Observe(gen uint64, status int, header http.Header, body []byte)

Observe feeds one scan response, sent with generation gen's credentials, to the logged-out detector. It never blocks: status and Location are checked on every response, the logged_out body regex on one in check.sample_every (first 64 KiB), and a match that needs confirming hands off to a goroutine.

func (*LiveSession) Observed added in v0.5.3

func (s *LiveSession) Observed() int64

Observed counts the responses Observe was given.

func (*LiveSession) OnFailure added in v0.5.3

func (s *LiveSession) OnFailure() ReauthOnFailure

OnFailure is the session's reauth.on_failure policy.

func (*LiveSession) State added in v0.5.3

func (s *LiveSession) State() LiveState

State is where the session is in its cycle.

func (*LiveSession) Wait added in v0.5.3

func (s *LiveSession) Wait(ctx context.Context) error

Wait parks the caller while the session confirms a suspected logout and re-logs in, when reauth pause_traffic is on (the default), so requests are not sent with credentials about to be replaced. An expiry refresh does not park (the credentials are still good while it runs). It returns at once when nothing is held, when the cycle ends, after the login budget, or with ctx's error when ctx ends first. Call it BEFORE taking an egress host slot: a parked request must not hold a slot the re-login needs.

type LiveState added in v0.5.3

type LiveState int32

LiveState is where a LiveSession is in its cycle.

const (
	// LiveValid: the credentials are believed good.
	LiveValid LiveState = iota
	// LiveSuspect: scan traffic looked logged out; the confirm probe is running.
	LiveSuspect
	// LiveRefreshing: confirmed logged out; the login flow is re-running.
	LiveRefreshing
	// LiveFailed: re-login gave up (or is disabled). Terminal for the scan.
	LiveFailed
)

func (LiveState) String added in v0.5.3

func (s LiveState) String() string

type LoggedInRule added in v0.5.3

type LoggedInRule struct {
	Status       []int  `yaml:"status,omitempty" json:"status,omitempty"`
	BodyContains string `yaml:"body_contains,omitempty" json:"body_contains,omitempty"`
	BodyRegex    string `yaml:"body_regex,omitempty" json:"body_regex,omitempty"`
	// HeaderRegex is matched against each response header rendered as
	// "Name: value" (e.g. 'Content-Type: application/json').
	HeaderRegex string `yaml:"header_regex,omitempty" json:"header_regex,omitempty"`
}

LoggedInRule holds on a response that proves the session is logged in. ALL configured conditions must hold.

type LoggedOutRule added in v0.5.3

type LoggedOutRule struct {
	Status []int `yaml:"status,omitempty" json:"status,omitempty"`
	// RedirectRegex is matched against the Location header of a 3xx.
	RedirectRegex string `yaml:"redirect_regex,omitempty" json:"redirect_regex,omitempty"`
	BodyRegex     string `yaml:"body_regex,omitempty" json:"body_regex,omitempty"`
}

LoggedOutRule marks a response as a sign the session is gone. ANY matching condition counts.

type LoginFlow

type LoginFlow struct {
	URL         string        `yaml:"url" json:"url"`
	Method      string        `yaml:"method" json:"method"`
	ContentType string        `yaml:"content_type,omitempty" json:"content_type,omitempty"`
	Body        string        `yaml:"body,omitempty" json:"body,omitempty"`
	Extract     []ExtractRule `yaml:"extract,omitempty" json:"extract,omitempty"`
	// Shorthand: type + token_path expand into extract rules automatically.
	Type      LoginType `yaml:"type,omitempty" json:"type,omitempty"`
	TokenPath string    `yaml:"token_path,omitempty" json:"token_path,omitempty"`
	// Response validation.
	Expect *ExpectResponse `yaml:"expect,omitempty" json:"expect,omitempty"`
	// Multi-step login flows. When set, URL/Method/Body/Extract on the parent are ignored.
	Steps []LoginStep `yaml:"steps,omitempty" json:"steps,omitempty"`
	// Headers are extra request headers sent with every login request: the
	// single request, each step (a step's own headers win), or the OAuth2
	// token request. Values support the {{env:…}} / {{totp:…}} templates.
	Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`

	// OAuth2 token endpoint (type oauth2_client_credentials or
	// oauth2_refresh_token; see oauth.go). Every value supports the
	// {{env:…}} templates; ${ENV} is expanded when the file loads.
	TokenURL     string `yaml:"token_url,omitempty" json:"token_url,omitempty"`
	ClientID     string `yaml:"client_id,omitempty" json:"client_id,omitempty"`
	ClientSecret string `yaml:"client_secret,omitempty" json:"client_secret,omitempty"`
	// Scope is space- (or comma-) separated. Sent with client_credentials.
	Scope string `yaml:"scope,omitempty" json:"scope,omitempty"`
	// Audience is sent as the `audience` parameter with client_credentials.
	Audience     string `yaml:"audience,omitempty" json:"audience,omitempty"`
	RefreshToken string `yaml:"refresh_token,omitempty" json:"refresh_token,omitempty"`
	// AuthStyle is how the client authenticates: "header" (HTTP Basic) or
	// "body" (form fields). Empty tries header, then body.
	AuthStyle string `yaml:"auth_style,omitempty" json:"auth_style,omitempty"`
	// contains filtered or unexported fields
}

LoginFlow defines how to authenticate to get session credentials.

func (*LoginFlow) LoginURL

func (lf *LoginFlow) LoginURL() string

LoginURL returns the login URL for the session. For multi-step flows, returns the URL of the first step.

func (*LoginFlow) Validate

func (lf *LoginFlow) Validate(sessionName string) error

Validate checks that a LoginFlow is well-formed.

type LoginRequest

type LoginRequest struct {
	Method  string
	URL     string
	Headers map[string]string
	Body    string
	Raw     string // original raw request text
}

LoginRequest holds a parsed raw HTTP login request.

func ParseRawLoginRequest

func ParseRawLoginRequest(raw string) (*LoginRequest, error)

ParseRawLoginRequest parses a raw HTTP request string into a LoginRequest. Supports the format:

POST /rest/user/login HTTP/1.1
Host: localhost:3000
Content-Type: application/json

{"email":"admin@juice-sh.op","password":"admin123"}

type LoginResult added in v0.5.3

type LoginResult struct {
	Session string `json:"session"`
	// OK is false when the login failed: exit 1 for `auth load`. An OK result
	// with a Reason (unverified) is a warning.
	OK bool `json:"ok"`
	// Verified: an expect block passed, or the response showed the login
	// worked (a credential extracted, or a redirect away from the login URL)
	// with no login form left in it.
	Verified bool `json:"verified"`
	// Status is the status of the last response the flow received.
	Status int `json:"status,omitempty"`
	// URL is the request URL of the last request sent (the failing step's, for
	// a multi-step flow that stopped early).
	URL string `json:"url,omitempty"`
	// FinalURL is where that request ended up after redirects.
	FinalURL  string   `json:"final_url,omitempty"`
	Redirects []string `json:"redirects,omitempty"`
	// Step is the index of the last step attempted in a multi-step flow; absent
	// for a single-request flow.
	Step *int `json:"step,omitempty"`
	// ExtractedHeaders names (never values) the headers the login produced.
	ExtractedHeaders []string `json:"extracted_headers,omitempty"`
	// ExpiresAt is when the credentials expire, when the login said so (an
	// OAuth2 token's expires_in).
	ExpiresAt *time.Time `json:"expires_at,omitempty"`
	Reason    string     `json:"reason,omitempty"`
	// Message explains a failure or a warning in prose.
	Message string `json:"message,omitempty"`
	// Err is the failure, for Go callers.
	Err error `json:"-"`
}

LoginResult is what CheckLogin observed for one session.

func CheckLogin added in v0.5.3

func CheckLogin(ctx context.Context, sess *Session, client *http.Client) LoginResult

CheckLogin runs sess's login flow — single request or multi-step `steps`, with every `expect` block enforced — and reports what happened instead of a bare error. It hydrates sess on success, exactly as a scan's login does, because it IS a scan's login (executeLogin), not a separate probe.

client supplies the transport (proxy, target TLS; see pkg/http.LoginTransport) and is copied, never mutated. A nil client uses Go's default transport.

A session with only login_request is converted first, as NewManager does.

With no expect block, success is unproven, so the response is judged: a password input still in the final body means the login form came back (unverified, with that said); a credential extracted or a redirect away from the login URL verifies it; anything else is unverified. Unverified is OK with a warning, never a failure: only an explicit expect, a status outside 2xx/3xx, a failed extract or a transport error fails.

type LoginStep

type LoginStep struct {
	URL         string `yaml:"url" json:"url"`
	Method      string `yaml:"method" json:"method"`
	ContentType string `yaml:"content_type,omitempty" json:"content_type,omitempty"`
	// Headers are extra request headers for this step, applied after the
	// flow's own login.headers (a step header wins). Values support {var}
	// and the {{env:…}} / {{totp:…}} templates, like url and body.
	Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`
	Body    string            `yaml:"body,omitempty" json:"body,omitempty"`
	Extract []ExtractRule     `yaml:"extract,omitempty" json:"extract,omitempty"`
	Expect  *ExpectResponse   `yaml:"expect,omitempty" json:"expect,omitempty"`
}

LoginStep defines a single step in a multi-step login flow.

type LoginType

type LoginType string

LoginType is a shorthand preset that auto-expands into extract rules.

const (
	LoginTypeBearer LoginType = "bearer" // JSON body → Authorization: Bearer {value}
	LoginTypeCookie LoginType = "cookie" // Capture all Set-Cookie headers
	// LoginTypeOAuth2ClientCredentials fetches an access token from token_url
	// with the client_credentials grant (oauth.go).
	LoginTypeOAuth2ClientCredentials LoginType = "oauth2_client_credentials"
	// LoginTypeOAuth2RefreshToken trades refresh_token for an access token at
	// token_url (oauth.go).
	LoginTypeOAuth2RefreshToken LoginType = "oauth2_refresh_token"
)

func (LoginType) IsOAuth2 added in v0.5.3

func (t LoginType) IsOAuth2() bool

IsOAuth2 reports whether t is one of the OAuth2 token-endpoint types, which take token_url/client_id/... instead of url/method/body/extract.

type Manager

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

Manager loads, validates, and hydrates sessions for multi-session scanning.

func NewManager

func NewManager(sessions []*Session, opts ...ManagerOption) (*Manager, error)

NewManager creates a Manager from the resolved session list.

func (*Manager) AllSessions

func (m *Manager) AllSessions() []*Session

AllSessions returns all sessions.

func (*Manager) CompareSessions

func (m *Manager) CompareSessions() []*Session

CompareSessions returns all non-primary sessions used for comparison.

func (*Manager) HydrateSessions

func (m *Manager) HydrateSessions() error

HydrateSessions executes login flows for sessions that need them on a background context. It is the convenience wrapper for callers with no context of their own; anything inside a scan should call HydrateSessionsContext so a cancelled scan stops logging in.

func (*Manager) HydrateSessionsContext added in v0.5.2

func (m *Manager) HydrateSessionsContext(ctx context.Context) error

HydrateSessionsContext executes login flows for sessions that need them, bounded by ctx AND by the manager's total login budget (whichever expires first). The budget wraps the WHOLE loop rather than each session, so N sessions cannot cost N × the budget; a flow that runs out mid-way reports which session it died on.

A cancelled or expired ctx is reported before the first request, so a cancelled scan does not start a login it is about to abandon.

func (*Manager) LoginBudget added in v0.5.3

func (m *Manager) LoginBudget() time.Duration

LoginBudget is the total time one hydration (or one re-login) may take.

func (*Manager) Primary

func (m *Manager) Primary() *Session

Primary returns the primary session.

func (*Manager) PrimaryHeaders

func (m *Manager) PrimaryHeaders() []string

PrimaryHeaders returns the primary session's headers as a slice for types.Options.Headers.

type ManagerOption

type ManagerOption func(*Manager)

ManagerOption configures optional Manager behavior.

func WithLoginBudget added in v0.5.2

func WithLoginBudget(d time.Duration) ManagerOption

WithLoginBudget overrides the total time all login flows together may take. A non-positive value keeps DefaultLoginBudget.

func WithLoginTransport added in v0.5.2

func WithLoginTransport(rt http.RoundTripper) ManagerOption

WithLoginTransport routes login requests over rt. A login request targets the host the scan is about to attack, so it belongs on the same proxy and the same permissive TLS stance as the rest of the scan's traffic; pkg/http.LoginTransport builds exactly that. A nil rt is ignored (Go's default transport stands).

func WithSessionDir

func WithSessionDir(dir string) ManagerOption

WithSessionDir overrides the default directory used to resolve session file names.

type ParseError added in v0.5.3

type ParseError struct {
	File   string
	Line   int
	Column int
	Err    error
}

ParseError is a session file that could not be decoded at all. Line is 0 when the decoder did not say where.

func (*ParseError) Error added in v0.5.3

func (e *ParseError) Error() string

func (*ParseError) Unwrap added in v0.5.3

func (e *ParseError) Unwrap() error

type ParseOptions added in v0.5.3

type ParseOptions struct {
	// Strict reports unknown fields as errors (auth lint). Otherwise they are
	// warnings, so an existing file with an extra key keeps loading.
	Strict bool
	// Agent forces the lenient agent session-config.json decoder (auth load
	// --agent-format, or a path that names an agent session file).
	Agent bool
	// KeepEnvRefs leaves ${ENV} references unexpanded, for a caller that
	// writes the sessions back to a file another loader expands (expanding
	// twice would mangle a value containing '$').
	KeepEnvRefs bool
}

ParseOptions tunes ParseSessionDocument.

type ProbeResponse added in v0.5.3

type ProbeResponse struct {
	Status int
	Header http.Header
	Body   []byte
	// URL is the request URL, used to resolve a relative Location.
	URL string
	Err error
}

ProbeResponse is one response the logged-in rules judge. Err set means the request did not complete.

func (*ProbeResponse) Location added in v0.5.3

func (p *ProbeResponse) Location() string

Location is the response's Location header, resolved against URL.

type ReauthOnFailure added in v0.5.3

type ReauthOnFailure string

ReauthOnFailure is what a scan does when re-login keeps failing.

const (
	// ReauthContinue keeps scanning unauthenticated; the phase is partial with
	// reason auth_lost. The default (decision D19).
	ReauthContinue ReauthOnFailure = "continue"
	// ReauthAbort cancels the scan with a typed error.
	ReauthAbort ReauthOnFailure = "abort"
)

type Role

type Role string

Role identifies how a session is used during scanning.

const (
	// RolePrimary drives discovery and spidering — the "owner" of the resources.
	RolePrimary Role = "primary"
	// RoleCompare is replayed during audit phase for cross-session comparison.
	RoleCompare Role = "compare"
)

type Session

type Session struct {
	Name    string            `yaml:"name" json:"name"`
	Role    Role              `yaml:"role" json:"role"`
	Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`
	Login   *LoginFlow        `yaml:"login,omitempty" json:"login,omitempty"`
	// LoginRequest is a raw HTTP request string for the login flow.
	LoginRequest string `yaml:"login_request,omitempty" json:"login_request,omitempty"`
	// Check says how to tell whether the session is still logged in
	// (loggedin.go). Optional; scanning_strategy.session supplies defaults.
	Check *SessionCheck `yaml:"check,omitempty" json:"check,omitempty"`
	// Reauth says what a scan does once Check finds the session logged out.
	Reauth *SessionReauth `yaml:"reauth,omitempty" json:"reauth,omitempty"`
	// contains filtered or unexported fields
}

Session represents a named authentication identity used during scanning.

func LoadFromAuthFiles

func LoadFromAuthFiles(paths []string, sessionDir string) ([]*Session, error)

LoadFromAuthFiles loads sessions from one or more --auth-file values. Each value is either a file path (YAML/JSON: a sessions: bundle, a single session, or an agent session-config.json) or a bare name resolved against sessionDir (default ~/.vigolium/sessions/), trying .yaml, .yml, .json in order. Files support ${ENV} expansion before parsing.

An unknown field is logged as a warning, not refused: a file that loaded before the parser learned to notice extra keys keeps loading. `auth lint` reports the same field as an error, with its line.

func LoadFromAuthInline

func LoadFromAuthInline(values []string) ([]*Session, error)

LoadFromAuthInline parses --auth values in "name:Header:value" format.

func ParseInlineSession

func ParseInlineSession(s string) (*Session, error)

ParseInlineSession parses a CLI --auth flag value in "name:Header:value" format. Example: "admin:Cookie:session=abc" → Session{Name:"admin", Headers:{"Cookie":"session=abc"}}

func (*Session) ApplyDefaults added in v0.5.3

func (s *Session) ApplyDefaults(d SessionDefaults)

ApplyDefaults fills s's check block from d: a value the session sets always wins. A session with no check block gets one when d has anything to say. The session's own block is copied, never mutated in place, so a Session shared between callers is not changed behind their back.

func (*Session) ExpiresAt added in v0.5.3

func (s *Session) ExpiresAt() time.Time

ExpiresAt is when the session's current credentials expire, as reported by the login that produced them (an OAuth2 token response's expires_in). Zero when the login did not say. A scan's LiveSession refreshes the credentials shortly before this time.

func (*Session) HeaderSlice

func (s *Session) HeaderSlice() []string

HeaderSlice converts the session headers map to a slice of "Name: Value" strings, compatible with types.Options.Headers.

func (*Session) IsHydrated

func (s *Session) IsHydrated() bool

IsHydrated returns true if the session has been populated with credentials.

func (*Session) ReauthEnabled added in v0.5.3

func (s *Session) ReauthEnabled() bool

ReauthEnabled reports whether a scan should re-login s when its check says it is logged out, or ahead of its credentials' known expiry: it needs a check to notice (or an expiry to refresh ahead of) and a login flow to re-run, and `reauth.enabled: false` turns it off.

func (*Session) Validate

func (s *Session) Validate() error

Validate checks that the session definition is well-formed.

type SessionCheck added in v0.5.3

type SessionCheck struct {
	// URL is the active probe. A path ("/api/me") resolves against the target
	// origin. Optional when logged_out has a status or redirect indicator.
	URL    string `yaml:"url,omitempty" json:"url,omitempty"`
	Method string `yaml:"method,omitempty" json:"method,omitempty"`
	// LoggedIn must hold on the probe's response.
	LoggedIn *LoggedInRule `yaml:"logged_in,omitempty" json:"logged_in,omitempty"`
	// LoggedOut flags scan traffic (and the probe) as logged out.
	LoggedOut *LoggedOutRule `yaml:"logged_out,omitempty" json:"logged_out,omitempty"`
	// SampleEvery evaluates the logged_out body regex on 1 in N scan
	// responses (status and Location are checked on every response). 0 means
	// DefaultCheckSampleEvery.
	SampleEvery int `yaml:"sample_every,omitempty" json:"sample_every,omitempty"`
	// Interval runs the active probe proactively at most this often; 0 probes
	// only when traffic looks logged out.
	Interval Duration `yaml:"interval,omitempty" json:"interval,omitempty"`
}

SessionCheck is a session's `check` block: how to tell whether it is still logged in.

func (*SessionCheck) Compile added in v0.5.3

func (c *SessionCheck) Compile() (*CheckMatcher, error)

Compile compiles the check's regexes. A nil check compiles to nil.

func (*SessionCheck) EffectiveMethod added in v0.5.3

func (c *SessionCheck) EffectiveMethod() string

EffectiveMethod is Method or GET.

func (*SessionCheck) EffectiveSampleEvery added in v0.5.3

func (c *SessionCheck) EffectiveSampleEvery() int

EffectiveSampleEvery is SampleEvery or DefaultCheckSampleEvery.

type SessionConfig

type SessionConfig struct {
	Sessions []Session `yaml:"sessions" json:"sessions"`
}

SessionConfig holds multiple sessions, used for --auth-config files (YAML or JSON).

type SessionDefaults added in v0.5.3

type SessionDefaults struct {
	// ValidateURL fills check.url.
	ValidateURL string
	// ReauthOnStatus fills check.logged_out.status.
	ReauthOnStatus []int
	// ReauthInterval fills check.interval.
	ReauthInterval time.Duration
}

SessionDefaults are the config-wide values (scanning_strategy.session) that fill in what a session's own check block leaves unset.

type SessionDocument added in v0.5.3

type SessionDocument struct {
	Form     SessionForm
	Sessions []*Session
	// Issues are what parsing itself found: unknown fields, with line/column.
	// Semantic problems come from Lint.
	Issues []LintIssue
	// contains filtered or unexported fields
}

SessionDocument is a parsed session file.

func ParseSessionDocument added in v0.5.3

func ParseSessionDocument(content []byte, name string, opt ParseOptions) (*SessionDocument, error)

ParseSessionDocument is ParseSessionBytes without the "at least one session" rule, and with the form and source positions kept for Lint.

func (*SessionDocument) Lint added in v0.5.3

func (d *SessionDocument) Lint() []LintIssue

Lint runs the semantic checks over the document's sessions (field paths follow its form: sessions[0].login.url for a bundle, login.url for a single session) and attaches source positions where the field, or its nearest present parent, appears in the file. Parse-level Issues are not included.

type SessionForm added in v0.5.3

type SessionForm string

SessionForm names which accepted document shape a session file used.

const (
	// FormBundle is `sessions: [...]`.
	FormBundle SessionForm = "bundle"
	// FormSingle is one session at the top level (name, role, login, ...).
	FormSingle SessionForm = "single"
	// FormAgent is the agent's session-config.json: a bundle written by a model,
	// decoded leniently (status as strings, group as a string).
	FormAgent SessionForm = "agent"
)

type SessionReauth added in v0.5.3

type SessionReauth struct {
	// Enabled defaults to true when the session has a check block.
	Enabled *bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
	// MaxAttempts is the number of consecutive failed re-logins before giving
	// up. 0 means DefaultReauthMaxAttempts.
	MaxAttempts int `yaml:"max_attempts,omitempty" json:"max_attempts,omitempty"`
	// Backoff is the wait after the first failure; it doubles per failure.
	// 0 means DefaultReauthBackoff.
	Backoff   Duration        `yaml:"backoff,omitempty" json:"backoff,omitempty"`
	OnFailure ReauthOnFailure `yaml:"on_failure,omitempty" json:"on_failure,omitempty"`
	// PauseTraffic holds this session's requests while it re-logs in. Default
	// true.
	PauseTraffic *bool `yaml:"pause_traffic,omitempty" json:"pause_traffic,omitempty"`
}

SessionReauth is a session's `reauth` block: what to do once the check confirms it is logged out.

func (*SessionReauth) EffectiveBackoff added in v0.5.3

func (r *SessionReauth) EffectiveBackoff() time.Duration

EffectiveBackoff is Backoff or DefaultReauthBackoff.

func (*SessionReauth) EffectiveMaxAttempts added in v0.5.3

func (r *SessionReauth) EffectiveMaxAttempts() int

EffectiveMaxAttempts is MaxAttempts or DefaultReauthMaxAttempts.

func (*SessionReauth) EffectiveOnFailure added in v0.5.3

func (r *SessionReauth) EffectiveOnFailure() ReauthOnFailure

EffectiveOnFailure is OnFailure or ReauthContinue.

func (*SessionReauth) EffectivePauseTraffic added in v0.5.3

func (r *SessionReauth) EffectivePauseTraffic() bool

EffectivePauseTraffic is PauseTraffic or true.

type Signal added in v0.5.3

type Signal struct {
	Name   string `json:"name"`
	Side   string `json:"side,omitempty"`
	Detail string `json:"detail,omitempty"`
}

Signal is one piece of evidence AssessAuthenticated used. Side is "auth" or "anon" when the signal is about one response, empty for a comparison.

type Verdict added in v0.5.3

type Verdict string

Verdict is what AssessAuthenticated concluded about a session on one URL.

const (
	// VerdictAuthenticated: the session reached content the anonymous baseline
	// did not (or the configured logged_in rule held and the baseline failed
	// it).
	VerdictAuthenticated Verdict = "authenticated"
	// VerdictLoginWall: the authenticated request was sent to a login page
	// (a redirect to a login URL, or a password form in the body).
	VerdictLoginWall Verdict = "login_wall"
	// VerdictUnauthorized: the server refused the credentials (401/403, a
	// configured logged_out status, or a failed logged_in rule).
	VerdictUnauthorized Verdict = "unauthorized"
	// VerdictIndistinguishable: the authenticated and anonymous responses are
	// the same, so the URL proves nothing (a public page, or credentials the
	// server ignores). Pick a protected URL or configure check.logged_in.
	VerdictIndistinguishable Verdict = "indistinguishable"
	// VerdictError: a request failed or the check block is invalid.
	VerdictError Verdict = "error"
)

Jump to

Keyboard shortcuts

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