riskengine

package
v1.9.0 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 15 Imported by: 0

README

riskengine

Collects risk signals from other plugins, combines them into one score, and decides whether to allow the request, force a step-up, or block it.

Every other security plugin here answers yes or no on its own. That gets you a system where any single grumpy signal locks a legitimate user out. This plugin gives you the middle option. Contributors hand in a score and a weight, the engine averages them, and three thresholds turn the result into an action: allow, challenge with MFA, or block.

Contributors are discovered automatically. Any registered plugin implementing riskengine.RiskContributor gets picked up at init, so you rarely need to pass them by hand.

Use it when

  • You'd rather step a suspicious sign-in up to MFA than refuse it
  • You're running several security plugins and want one decision instead of a race between them
  • You want to tune how much each signal counts by changing a weight, no code change

Skip it when

  • You have exactly one signal. A single contributor's score becomes the whole composite, so you have built an indirection for nothing
  • Your policy is genuinely binary and you have no MFA to step up to
  • You need the block reason to be specific. The composite score tells you the request was risky, not which signal made it so

Wiring

authsome.WithPlugin(riskengine.New()), // contributors are auto-discovered

Or set the thresholds and weights explicitly:

authsome.WithPlugin(riskengine.NewWithConfig(riskengine.Config{
    LowThreshold:    30,
    MediumThreshold: 60,
    HighThreshold:   85,
    Weights: map[string]float64{
        "sharedsignals": 2.0,
    },
}))

Config

Field Type Default What it does
LowThreshold int 30 Below this, allow without comment
MediumThreshold int 60 At or above this, require step-up MFA
HighThreshold int 85 At or above this, refuse the request
Weights map[string]float64 1.0 each Multiplier per contributor name
BlockMessage string empty Error text returned on a block

Settings

Key Default
riskengine.low_threshold 30
riskengine.medium_threshold 60
riskengine.high_threshold 85
riskengine.block_message empty

Lifecycle hooks

Hook What happens
BeforeSignIn Scores the attempt and allows, challenges or blocks
BeforePrincipalAuth Same decision for non-user principals
BeforeSessionCreate Last gate before a session is minted

Writing a contributor

type RiskContributor interface {
    Name() string
    EvaluateRisk(ctx context.Context, req *RiskRequest) (*RiskSignal, error)
}

Return a RiskSignal with a Score from 0 to 100, a Weight, and a Reason a human can read in an audit log. sharedsignals is the worked example in this repo.

sharedsignals ships as a contributor. anomaly, geofence, ipreputation and vpndetect currently gate directly rather than contributing, so they act independently of this engine.

Documentation

Overview

contract.go: Wires the riskengine plugin into the forge-dashboard contract surface via plugin.ContractContributor.

Package riskengine provides a composite risk assessment engine that aggregates risk signals from multiple geo-security plugins into a unified score. Based on the score it decides: allow, challenge (step-up MFA), or block.

Index

Constants

This section is empty.

Variables

View Source
var (
	// SettingLowThreshold is the score below which risk is considered low.
	SettingLowThreshold = settings.Define("riskengine.low_threshold", 30,
		settings.WithDisplayName("Low Threshold"),
		settings.WithDescription("Score below which risk is considered low"),
		settings.WithCategory("Risk Engine"),
		settings.WithScopes(settings.ScopeGlobal, settings.ScopeApp),
		settings.WithEnforceable(),
		settings.WithInputType(formconfig.FieldNumber),
		settings.WithUIValidation(formconfig.Validation{Min: intPtr(0), Max: intPtr(100)}),
		settings.WithHelpText("Score below which risk is considered low"),
		settings.WithOrder(10),
	)

	// SettingMediumThreshold is the score below which risk is considered medium.
	SettingMediumThreshold = settings.Define("riskengine.medium_threshold", 60,
		settings.WithDisplayName("Medium Threshold"),
		settings.WithDescription("Score below which risk is considered medium"),
		settings.WithCategory("Risk Engine"),
		settings.WithScopes(settings.ScopeGlobal, settings.ScopeApp),
		settings.WithEnforceable(),
		settings.WithInputType(formconfig.FieldNumber),
		settings.WithUIValidation(formconfig.Validation{Min: intPtr(0), Max: intPtr(100)}),
		settings.WithHelpText("Score below which risk is considered medium"),
		settings.WithOrder(20),
	)

	// SettingHighThreshold is the score at or above which risk is considered high.
	SettingHighThreshold = settings.Define("riskengine.high_threshold", 85,
		settings.WithDisplayName("High Threshold"),
		settings.WithDescription("Score at or above which risk is considered high and action is taken"),
		settings.WithCategory("Risk Engine"),
		settings.WithScopes(settings.ScopeGlobal, settings.ScopeApp),
		settings.WithEnforceable(),
		settings.WithInputType(formconfig.FieldNumber),
		settings.WithUIValidation(formconfig.Validation{Min: intPtr(0), Max: intPtr(100)}),
		settings.WithHelpText("Score at or above which risk is considered high and action is taken"),
		settings.WithOrder(30),
	)

	// SettingBlockMessage is the error message shown when access is blocked.
	SettingBlockMessage = settings.Define("riskengine.block_message", "",
		settings.WithDisplayName("Block Message"),
		settings.WithDescription("Error message shown when access is blocked due to high risk score"),
		settings.WithCategory("Risk Engine"),
		settings.WithScopes(settings.ScopeGlobal, settings.ScopeApp),
		settings.WithHelpText("Error message shown when access is blocked due to high risk score"),
		settings.WithPlaceholder("Access denied due to security concerns"),
		settings.WithOrder(40),
	)
)

Functions

This section is empty.

Types

type Config

type Config struct {
	// LowThreshold is the score below which requests are allowed (default: 30).
	LowThreshold int

	// MediumThreshold is the score above which step-up MFA is required (default: 60).
	MediumThreshold int

	// HighThreshold is the score above which requests are blocked (default: 85).
	HighThreshold int

	// Weights maps contributor names to weight multipliers (default: 1.0 for all).
	Weights map[string]float64

	// BlockMessage is the error message when blocked.
	BlockMessage string
}

Config configures the risk engine plugin.

type Plugin

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

Plugin is the adaptive risk engine that aggregates signals from contributors.

func New

func New(contributors ...RiskContributor) *Plugin

New creates a new risk engine plugin. Config is optional; if omitted sensible defaults are applied.

func NewWithConfig

func NewWithConfig(cfg Config, contributors ...RiskContributor) *Plugin

NewWithConfig creates a new risk engine plugin with explicit configuration.

func (*Plugin) AddContributor

func (p *Plugin) AddContributor(c RiskContributor)

AddContributor adds a risk contributor to the engine. A contributor whose name is already registered is not added a second time, so a host that wires one by hand and also registers it as a plugin gets it scored once.

func (*Plugin) DeclareSettings

func (p *Plugin) DeclareSettings(m *settings.Manager) error

DeclareSettings implements plugin.SettingsProvider. It registers all risk-engine-related configurable settings.

func (*Plugin) Name

func (p *Plugin) Name() string

Name returns the plugin name.

func (*Plugin) OnBeforePrincipalAuth

func (p *Plugin) OnBeforePrincipalAuth(ctx context.Context, a *principal.AuthAttempt) error

OnBeforePrincipalAuth scores a machine caller and blocks above the high threshold, exactly as OnBeforeSignIn does for a person.

func (*Plugin) OnBeforeSessionCreate

func (p *Plugin) OnBeforeSessionCreate(_ context.Context, _ *session.Session) error

OnBeforeSessionCreate attaches the risk score to the session metadata.

func (*Plugin) OnBeforeSignIn

func (p *Plugin) OnBeforeSignIn(ctx context.Context, req *account.SignInRequest) error

OnBeforeSignIn evaluates risk and blocks if score exceeds high threshold.

func (*Plugin) OnInit

func (p *Plugin) OnInit(_ context.Context, engine plugin.Engine) error

OnInit discovers engine dependencies.

func (*Plugin) RegisterContract

func (p *Plugin) RegisterContract(
	d *dispatcher.Dispatcher,
	reg contract.Registry,
	wreg contract.WardenRegistry,
	engine plugin.Engine,
) error

type RiskAssessment

type RiskAssessment struct {
	OverallScore int          `json:"overall_score"`
	Signals      []RiskSignal `json:"signals"`
	Decision     string       `json:"decision"` // "allow", "challenge", "block"
}

RiskAssessment is the aggregated risk evaluation.

type RiskContributor

type RiskContributor interface {
	plugin.Plugin
	EvaluateRisk(ctx context.Context, req *RiskRequest) (*RiskSignal, error)
}

RiskContributor is a sub-plugin interface for plugins that contribute risk signals to the risk engine.

type RiskRequest

type RiskRequest struct {
	IPAddress string
	UserAgent string
	AppID     string
	EnvID     string

	// UserID is set once the principal is known. It is empty on the
	// pre-authentication path, where Email or Username identify the attempt
	// instead.
	UserID string

	// Email and Username carry the sign-in identifier so a contributor that
	// scores a user rather than an address has something to resolve.
	Email    string
	Username string

	// Principal is the caller rendered as "kind:id" by principal.Ref.String().
	// Set on the machine path, where the caller is already known. Left empty
	// on sign-in: account.SignInRequest carries no user id at
	// OnBeforeSignIn time, only Email and Username, and putting an email in
	// an id position would let a contributor mistake it for one.
	Principal string
}

RiskRequest contains the data needed for risk evaluation.

type RiskSignal

type RiskSignal struct {
	Source string  `json:"source"`
	Score  int     `json:"score"` // 0-100
	Weight float64 `json:"weight"`
	Reason string  `json:"reason"`
}

RiskSignal is a single risk factor from a contributor.

Directories

Path Synopsis
Package contract wires the riskengine plugin's deep-link settings page into the forge-dashboard contract registry.
Package contract wires the riskengine plugin's deep-link settings page into the forge-dashboard contract registry.

Jump to

Keyboard shortcuts

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