behavior

package
v0.0.23 Latest Latest
Warning

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

Go to latest
Published: May 5, 2026 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

Package behavior provides human-like behavioral simulation for the Foxhound scraping engine. Every exported type is pure computation — no I/O, no goroutines — so callers can use the helpers from any context.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GammaClamped

func GammaClamped(alpha, rate, lo, hi float64) float64

GammaClamped returns a Gamma sample clamped to [lo, hi] via rejection. Uses a safety cap of 1000 iterations. Falls back to the midpoint if no sample is accepted (indicates misconfigured parameters).

func GammaSample

func GammaSample(alpha, rate float64) float64

GammaSample returns a sample from a Gamma(alpha, rate) distribution using the Marsaglia-Tsang method. Pure Go, no external dependencies.

For alpha >= 1:

d = alpha - 1/3, c = 1/sqrt(9*d)
loop: x = NormFloat64(), v = (1+c*x)^3
accept if v > 0 and log(U) < 0.5*x^2 + d - d*v + d*ln(v)
return d*v / rate

For alpha < 1: use Gamma(alpha+1) * U^(1/alpha) transformation.

func GaussianClamped

func GaussianClamped(sigma, bound float64) float64

GaussianClamped returns a Gaussian sample with given sigma, within [-bound, +bound]. Uses rejection sampling to avoid probability spikes at the boundaries. sigma should be bound/2.5 so ~99% of raw samples are accepted.

func LogNormalSample

func LogNormalSample(mu, sigma float64) float64

LogNormalSample returns a sample from LogNormal(mu, sigma). Result = exp(mu + sigma * NormFloat64())

func WeibullClamped

func WeibullClamped(k, lambda, lo, hi float64) float64

WeibullClamped returns a Weibull sample clamped to [lo, hi]. Uses rejection sampling with a safety cap of 1000 iterations. Falls back to the midpoint of [lo, hi] if no sample is accepted.

func WeibullSample

func WeibullSample(k, lambda float64) float64

WeibullSample returns a sample from a Weibull distribution with shape k and scale lambda. Formula: X = lambda * (-ln(U))^(1/k) where U ~ Uniform(0,1) Weibull is used for rhythm delays and scroll pauses because it produces right-skewed distributions matching observed human reaction times.

Types

type BehaviorProfile

type BehaviorProfile struct {
	Name       ProfileName
	Timing     TimingConfig
	Mouse      MouseConfig
	Scroll     ScrollConfig
	Keyboard   KeyboardConfig
	Navigation NavigationConfig
	// Rhythm controls the burst/pause cadence of a virtual user session.
	// It is initialised from DefaultRhythmConfig and tuned per preset.
	Rhythm *Rhythm
	// Fatigue controls the session warmup and fatigue model.
	Fatigue FatigueConfig
}

BehaviorProfile bundles all per-subsystem configurations into a named preset. Pass individual Config structs to the subsystem constructors as needed.

func AggressiveProfile

func AggressiveProfile() *BehaviorProfile

AggressiveProfile returns a faster, higher-risk preset.

Suitable for sites with minimal or no behavioural analysis. Block rate will increase on heavily-protected targets.

func CarefulProfile

func CarefulProfile() *BehaviorProfile

CarefulProfile returns a very human-like, slow preset.

Designed for Cloudflare Enterprise / Akamai Bot Manager targets where any timing anomaly triggers a challenge. Throughput is intentionally low.

func GetProfile

func GetProfile(name ProfileName) *BehaviorProfile

GetProfile returns the named profile. Falls back to ModerateProfile for unknown names so callers never receive a nil pointer.

func ModerateProfile

func ModerateProfile() *BehaviorProfile

ModerateProfile returns the default balanced preset.

func (*BehaviorProfile) Jitter

func (p *BehaviorProfile) Jitter() *BehaviorProfile

Jitter returns a copy of this profile with per-session random perturbation applied to all numeric parameters. This prevents clustering attacks that would otherwise identify all sessions using the same named profile.

Each parameter is multiplied by (1 + U(-jitterFrac, +jitterFrac)) where jitterFrac defaults to 0.15 (±15%).

func (*BehaviorProfile) JitterBy

func (p *BehaviorProfile) JitterBy(frac float64) *BehaviorProfile

JitterBy returns a copy with the specified jitter fraction applied.

type DurationRange

type DurationRange struct {
	Min, Max time.Duration
}

DurationRange specifies an inclusive duration interval.

type FatigueConfig

type FatigueConfig struct {
	// WarmupAmplitude is the fractional slowdown at session start (default 0.4 = 40% slower).
	WarmupAmplitude float64
	// WarmupTau is the warmup time constant -- 63% of warmup effect gone after this duration.
	WarmupTau time.Duration
	// FatigueAmplitude is the maximum fractional slowdown from fatigue (default 0.25 = 25% slower).
	FatigueAmplitude float64
	// FatigueTau is the fatigue time constant -- 63% of max fatigue reached after this duration.
	FatigueTau time.Duration
}

FatigueConfig controls the session warmup and fatigue model.

The speed factor follows an inverted-U curve:

speed_factor(t) = warmup(t) * fatigue(t)
warmup(t)  = 1.0 + WarmupAmplitude * exp(-t / WarmupTau)
fatigue(t) = 1.0 + FatigueAmplitude * (1 - exp(-t / FatigueTau))

At session start (t=0): factor = 1 + WarmupAmplitude (slow start). At cruise speed (~5 min): factor ~ 1.07. At late session (~30 min): fatigue dominates, factor rises again.

func DefaultFatigueConfig

func DefaultFatigueConfig() FatigueConfig

DefaultFatigueConfig returns the moderate fatigue preset.

type KeyAction

type KeyAction struct {
	// Char is the character to type.  For backspace actions Char is 0.
	Char rune
	// Delay is the pause before this keystroke.
	Delay time.Duration
	// IsBackspace is true when this action represents a correction keystroke.
	IsBackspace bool
}

KeyAction represents a single keystroke event in a typing sequence.

type Keyboard

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

Keyboard generates human-like typing sequences.

func NewKeyboard

func NewKeyboard(cfg KeyboardConfig) *Keyboard

NewKeyboard creates a Keyboard with the supplied configuration.

func (*Keyboard) TypeString

func (k *Keyboard) TypeString(text string) []KeyAction

TypeString converts text into a sequence of KeyActions that, when played back in order, reproduce the intended string.

For each rune in text:

  1. With probability TypoProb a random adjacent-key typo is inserted, followed immediately by a backspace and the correct character.
  2. When BigramModel is enabled, inter-key delay is computed from character frequency, hand/finger transitions, and position fatigue. Otherwise a uniform delay in [MinDelay, MaxDelay] is used.

type KeyboardConfig

type KeyboardConfig struct {
	// MinDelay is the minimum inter-key delay (default 50 ms).
	MinDelay time.Duration
	// MaxDelay is the maximum inter-key delay (default 200 ms).
	MaxDelay time.Duration
	// TypoProb is the per-character probability of introducing a typo followed
	// by a backspace correction (default 0.02).
	TypoProb float64
	// BigramModel enables the bigram-aware typing model that adjusts inter-key
	// delay based on character frequency, hand/finger transitions, and position
	// fatigue. When false, the original uniform delay is used.
	BigramModel bool
}

KeyboardConfig configures keyboard typing simulation.

func DefaultKeyboardConfig

func DefaultKeyboardConfig() KeyboardConfig

DefaultKeyboardConfig returns architecture-recommended defaults.

type Mouse

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

Mouse generates human-like mouse-movement trajectories.

func NewMouse

func NewMouse(cfg MouseConfig) *Mouse

NewMouse creates a Mouse with the supplied configuration.

func (*Mouse) ClickDuration

func (m *Mouse) ClickDuration() time.Duration

ClickDuration returns the duration between mouse-down and mouse-up. Uses LogNormal distribution (median ~90ms) matching observed human click press-and-release timing. Clamped to [40ms, 250ms].

func (*Mouse) ClickOffset

func (m *Mouse) ClickOffset() Point

ClickOffset returns a small Gaussian-distributed offset from an element's centre. Range: [-5, +5] px in each axis, center-heavy for natural click targeting.

func (*Mouse) IdleDrift

func (m *Mouse) IdleDrift() Point

IdleDrift returns a small Gaussian-distributed drift suitable for idle mouse simulation. Range: [-2, +2] px in each axis, center-heavy for realistic micro-movements.

func (*Mouse) MoveTo

func (m *Mouse) MoveTo(start, end Point) []Point

MoveTo generates a bezier-curve path from start to end.

Implementation details:

  1. 3-5 random control points are placed between start and end, offset perpendicular to the straight line to create natural curvature.
  2. The bezier curve is sampled at 20-50 evenly-spaced t values.
  3. Each sampled point receives independent micro-jitter (±Jitter px).
  4. Speed profile: slow at endpoints, fast in middle (ease-in-out via smoothstep remapping of t).
  5. With probability OvershootProb the final point overshoots by up to OvershootPx px, then a correction segment brings it back to end.

type MouseConfig

type MouseConfig struct {
	// Jitter is the maximum per-point micro-jitter in pixels (default 2.0).
	Jitter float64
	// OvershootProb is the probability that the cursor overshoots the target
	// before correcting back (default 0.2).
	OvershootProb float64
	// OvershootPx is the maximum overshoot distance in pixels (default 3.0).
	OvershootPx float64
}

MouseConfig configures mouse movement generation.

func DefaultMouseConfig

func DefaultMouseConfig() MouseConfig

DefaultMouseConfig returns the architecture-recommended defaults.

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

Navigation generates human-like browsing patterns.

func NewNavigation

func NewNavigation(cfg NavigationConfig) *Navigation

NewNavigation creates a Navigation with the supplied configuration.

func (n *Navigation) Referer(targetDomain string) string

Referer returns a plausible HTTP Referer header value for a walker arriving at targetDomain from a search engine or direct navigation.

The returned string is always an absolute HTTP/HTTPS URL.

func (n *Navigation) SessionDuration() time.Duration

SessionDuration returns how long the current session should last, drawn uniformly from [SessionDuration.Min, SessionDuration.Max].

func (n *Navigation) SessionGap() time.Duration

SessionGap returns the idle time before the next session begins, drawn uniformly from [SessionGap.Min, SessionGap.Max].

func (n *Navigation) SessionPages() int

SessionPages returns how many pages the current session should visit, drawn uniformly from [PagesPerSession.Min, PagesPerSession.Max].

func (n *Navigation) ShouldGoBack() bool

ShouldGoBack returns true when the walker should navigate backward.

func (n *Navigation) ShouldSearch() bool

ShouldSearch returns true when the walker should use the site search.

func (n *Navigation) ShouldVisitUseless() bool

ShouldVisitUseless returns true when the walker should visit a low-value page to simulate realistic browsing patterns.

type NavigationConfig struct {
	// PagesPerSession is how many pages a single session visits.
	PagesPerSession Range
	// SessionDuration is how long a session lasts.
	SessionDuration DurationRange
	// SessionGap is the idle gap between consecutive sessions.
	SessionGap DurationRange
	// BackButtonProb is the probability that the walker hits back.
	BackButtonProb float64
	// UselessPageProb is the probability of visiting a low-value page
	// (about, contact, FAQ) to add realism.
	UselessPageProb float64
	// SearchProb is the probability of using the site's search feature.
	SearchProb float64
}

NavigationConfig configures browsing-pattern generation.

func DefaultNavigationConfig

func DefaultNavigationConfig() NavigationConfig

DefaultNavigationConfig returns the architecture-documented defaults.

type Point

type Point struct {
	X, Y float64
}

Point represents a 2-D screen coordinate.

type ProfileName

type ProfileName string

ProfileName identifies a preset behaviour configuration.

const (
	// ProfileCareful is slow and maximally human-like — best for heavily
	// protected sites where throughput is less important than stealth.
	ProfileCareful ProfileName = "careful"
	// ProfileModerate is the default balanced profile.
	ProfileModerate ProfileName = "moderate"
	// ProfileAggressive trades stealth for speed — suitable for lightly
	// protected sites.
	ProfileAggressive ProfileName = "aggressive"
)

type Range

type Range struct {
	Min, Max int
}

Range specifies an inclusive integer interval.

type Rhythm

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

Rhythm manages the burst/pause state machine for a single virtual user. It is not safe for concurrent use — each Walker owns its own Rhythm.

func NewRhythm

func NewRhythm(cfg RhythmConfig) *Rhythm

NewRhythm creates a Rhythm initialised at the beginning of the first burst.

func (*Rhythm) Next

func (r *Rhythm) Next() time.Duration

Next returns the delay that the caller should wait before performing the next action and advances the internal state machine.

State transitions:

RhythmBurst (actionsLeft > 0):
    Decrement actionsLeft, return a short burst delay. State stays Burst.

RhythmBurst (actionsLeft == 0):
    Burst exhausted. Transition to RhythmPause or RhythmLongPause and
    return the pause duration so the caller sleeps before the next burst.

RhythmPause / RhythmLongPause:
    Pause finished. Start a new burst and return 0 so the caller acts
    immediately (the caller already slept the pause duration on the
    previous call).

func (*Rhythm) State

func (r *Rhythm) State() RhythmState

State returns the current rhythm state.

type RhythmConfig

type RhythmConfig struct {
	// BurstMin is the minimum number of actions in a single burst.
	BurstMin int
	// BurstMax is the maximum number of actions in a single burst.
	BurstMax int
	// PauseMin is the lower bound for a normal inter-burst pause.
	PauseMin time.Duration
	// PauseMax is the upper bound for a normal inter-burst pause.
	PauseMax time.Duration
	// LongPauseMin is the lower bound for a long pause.
	LongPauseMin time.Duration
	// LongPauseMax is the upper bound for a long pause.
	LongPauseMax time.Duration
	// LongPauseProb is the probability (in [0, 1]) that a long pause is
	// chosen instead of a normal pause at the end of each burst.
	LongPauseProb float64
}

RhythmConfig controls the burst/pause pattern for a scraping session.

Architecture spec:

Burst:      5-15 rapid actions
Pause:      10-60 s
Burst:      3-8 actions
Long pause: 1-5 min (probability 0.15 after each burst)

func DefaultRhythmConfig

func DefaultRhythmConfig() RhythmConfig

DefaultRhythmConfig returns the rhythm configuration that matches the architecture specification:

BurstMin/Max:       5 – 15 actions
PauseMin/Max:       10 s – 60 s
LongPauseMin/Max:   1 min – 5 min
LongPauseProb:      0.15

type RhythmState

type RhythmState int

RhythmState describes the current phase of a scraping session's rhythm.

const (
	// RhythmBurst is the active phase: rapid sequential actions (page loads,
	// clicks, form submissions). Delays between actions are short.
	RhythmBurst RhythmState = iota
	// RhythmPause is the rest phase after a burst: simulates reading time or
	// light distraction. Duration is drawn from [PauseMin, PauseMax].
	RhythmPause
	// RhythmLongPause is a deeper rest phase: simulates a break or context
	// switch away from the browser. Duration is drawn from [LongPauseMin, LongPauseMax].
	RhythmLongPause
)

type Scroll

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

Scroll generates human-like scroll behaviour.

func NewScroll

func NewScroll(cfg ScrollConfig) *Scroll

NewScroll creates a Scroll with the supplied configuration.

func (*Scroll) ScrollGesture

func (s *Scroll) ScrollGesture(mode ScrollMode) (distance int, pause time.Duration, up bool)

ScrollGesture returns the attributes of a single scroll action:

  • distance: pixels scrolled (always positive)
  • pause: delay to wait after the gesture
  • up: direction flag

func (*Scroll) ScrollGestureAxis

func (s *Scroll) ScrollGestureAxis(mode ScrollMode, axis ScrollAxis) (distance int, pause time.Duration, reverse bool)

ScrollGestureAxis returns the attributes of a single scroll action for the given axis. For vertical, this delegates to ScrollGesture. For horizontal, distances are drawn from the horizontal config ranges.

func (*Scroll) ScrollSequence

func (s *Scroll) ScrollSequence(pageHeight int, mode ScrollMode) []ScrollAction

ScrollSequence generates a complete scroll sequence for a page with the given pixel height. It keeps producing gestures until the cumulative net downward scroll roughly covers pageHeight, then stops. Upward gestures count against the total to ensure the sequence is realistic in length. Every page gets at least one action.

func (*Scroll) ScrollSequenceAxis

func (s *Scroll) ScrollSequenceAxis(extent int, mode ScrollMode, axis ScrollAxis) []ScrollAction

ScrollSequenceAxis generates a complete scroll sequence for a page with the given pixel extent along the specified axis.

type ScrollAction

type ScrollAction struct {
	// Distance is the scroll magnitude in pixels (always positive; Up indicates
	// direction).
	Distance int
	// Pause is the delay after this gesture before the next action.
	Pause time.Duration
	// Up is true if the scroll gesture moves the viewport upward.
	Up bool
	// Axis is the scroll direction (vertical or horizontal).
	Axis ScrollAxis
}

ScrollAction describes a single scroll gesture and the pause that follows it.

type ScrollAxis

type ScrollAxis int

ScrollAxis determines the scroll direction.

const (
	// ScrollVertical scrolls the viewport up/down (default).
	ScrollVertical ScrollAxis = iota
	// ScrollHorizontal scrolls the viewport left/right.
	ScrollHorizontal
)

type ScrollConfig

type ScrollConfig struct {
	// ReadMinPx is the minimum scroll distance per gesture in reading mode.
	ReadMinPx int
	// ReadMaxPx is the maximum scroll distance per gesture in reading mode.
	ReadMaxPx int
	// ScanMinPx is the minimum scroll distance per gesture in scan mode.
	ScanMinPx int
	// ScanMaxPx is the maximum scroll distance per gesture in scan mode.
	ScanMaxPx int
	// ReadPause is the midpoint pause for reading mode. Actual pauses are
	// sampled from [ReadPause*0.5, ReadPause*2.5] to simulate natural variance.
	ReadPause time.Duration
	// ScanPause is the midpoint pause for scan mode. Actual pauses are
	// sampled from [ScanPause*0.6, ScanPause*2.0].
	ScanPause time.Duration
	// ScrollUpProb is the probability that any given gesture scrolls upward
	// (re-reading behaviour).
	ScrollUpProb float64
	// HorizMinPx is the minimum horizontal scroll distance per gesture in reading mode.
	HorizMinPx int
	// HorizMaxPx is the maximum horizontal scroll distance per gesture in reading mode.
	HorizMaxPx int
	// HorizScanMinPx is the minimum horizontal scroll distance per gesture in scan mode.
	HorizScanMinPx int
	// HorizScanMaxPx is the maximum horizontal scroll distance per gesture in scan mode.
	HorizScanMaxPx int
}

ScrollConfig configures scroll behaviour generation.

func DefaultScrollConfig

func DefaultScrollConfig() ScrollConfig

DefaultScrollConfig returns the architecture-recommended defaults.

type ScrollMode

type ScrollMode int

ScrollMode determines which scroll-speed profile is used.

const (
	// ScrollReading simulates a user reading content: short gestures with
	// long pauses.
	ScrollReading ScrollMode = iota
	// ScrollScan simulates a user skimming a page: long gestures, short
	// pauses.
	ScrollScan
)

type SessionFatigue

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

SessionFatigue tracks elapsed session time and computes the current speed multiplier. Not safe for concurrent use -- each Walker owns its own instance.

func NewSessionFatigue

func NewSessionFatigue(cfg FatigueConfig) *SessionFatigue

NewSessionFatigue creates a SessionFatigue with the given configuration.

func (*SessionFatigue) AdjustDelay

func (f *SessionFatigue) AdjustDelay(base time.Duration) time.Duration

AdjustDelay multiplies the base delay by the current fatigue factor.

func (*SessionFatigue) Factor

func (f *SessionFatigue) Factor() float64

Factor returns the current speed multiplier. Values > 1.0 mean slower.

Timeline with default config:

t=0s:    1.40 (40% slower -- session warmup)
t=60s:   1.24 (warming up)
t=120s:  1.15 (near cruise speed)
t=300s:  1.07 (cruising, slight fatigue)
t=1800s: 1.16 (fatigue building)
t=3600s: 1.22 (noticeable slowdown)

func (*SessionFatigue) FactorAt

func (f *SessionFatigue) FactorAt(elapsed time.Duration) float64

FactorAt returns the speed multiplier at a given elapsed time in seconds. Exposed for testing.

func (*SessionFatigue) Start

func (f *SessionFatigue) Start()

Start records the session start time. Call once when the walker begins.

func (*SessionFatigue) StartAt

func (f *SessionFatigue) StartAt(t time.Time)

StartAt records a specific start time (useful for testing).

type Timing

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

Timing generates human-like delays using log-normal distribution.

Why log-normal? Real human inter-action times are right-skewed: most actions happen quickly but rare long pauses (reading, distraction) pull the mean well above the median. Uniform random delay is trivially detectable by ML-based anti-bot systems because its distribution is not bursty.

func NewTiming

func NewTiming(cfg TimingConfig) *Timing

NewTiming creates a Timing instance with the supplied configuration.

func (*Timing) Delay

func (t *Timing) Delay() time.Duration

Delay returns a human-like general-purpose delay drawn from the configured log-normal distribution, clamped to [Min, Max].

With default parameters (Mu=1.0, Sigma=0.8):

  • median ≈ 2.7 s
  • mean ≈ 4.1 s
  • 95th ≈ 13 s

func (*Timing) PageReadDelay

func (t *Timing) PageReadDelay() time.Duration

PageReadDelay returns a delay appropriate for page-reading behaviour. Range: [3 s, 15 s].

func (*Timing) PaginationDelay

func (t *Timing) PaginationDelay() time.Duration

PaginationDelay returns a delay for clicking pagination controls. Range: [0.5 s, 2 s].

func (*Timing) SearchDelay

func (t *Timing) SearchDelay() time.Duration

SearchDelay returns a delay for analysing search-result pages. Range: [5 s, 20 s].

func (*Timing) TypingDelay

func (t *Timing) TypingDelay() time.Duration

TypingDelay returns a per-character typing delay. Range: [50 ms, 200 ms].

type TimingConfig

type TimingConfig struct {
	// Mu is the log-normal location parameter (default 1.0).
	// Controls the median: median = exp(Mu).
	Mu float64
	// Sigma is the log-normal scale parameter (default 0.8).
	// Controls spread; higher = more variance / heavier tail.
	Sigma float64
	// Min is the hard lower bound for any generated delay.
	Min time.Duration
	// Max is the hard upper bound for any generated delay.
	Max time.Duration
}

TimingConfig configures the log-normal delay generator.

Jump to

Keyboard shortcuts

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