Help us shape the future of Go! Take the 2026 Go Developer Survey

posture

package
v0.6.1 Latest Latest
Warning

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

Go to latest
Published: Oct 24, 2026 License: MIT Imports: 13 Imported by: 0

README

Device risk evidence

Posture scores describe observed device configuration risk. They are not a SOC 2 attestation, an ISO 27001 certification, or a percentage of compliance. The control mappings below are osctrl's interpretation of relevant objectives from the AICPA Trust Services Criteria and ISO/IEC 27001:2022. Applicability, exceptions and audit conclusions belong to the organization's risk assessment and control owners.

Additional technical checks

These checks supplement encryption, account, inventory, network exposure and other existing rules. Each rule contributes once, even when it has multiple evidence sources or appears in multiple profiles. The primary mapping is returned in the existing control result; the related objective is documented here without scoring the same evidence twice.

Check osquery tables / scope Primary mapping; related objective Baseline and limitations
Secure Boot secureboot; all profiles ISO A.8.9; SOC 2 CC7.1 Explicitly enabled. Unsupported firmware or unreadable state warns. This does not assess every boot-policy option.
macOS firewall alf; macOS SOC 2 CC6.6; ISO A.8.20 global_state 1 or 2; 0 fails. Exceptions are not assessed.
Gatekeeper gatekeeper; macOS SOC 2 CC6.8; ISO A.8.7 Application assessments enabled. This is execution protection, not proof of complete malware protection.
System Integrity Protection sip_config; macOS ISO A.8.9; SOC 2 CC7.1 Overall sip flag enabled. Individual allow_* flags have different semantics and are excluded.
Screen lock screenlock; macOS ISO A.8.1; SOC 2 CC6.1 Password required on wake with at most 60 seconds grace. The 60-second value is an osctrl baseline, not a framework mandate. The table covers only osquery's current logged-in user context; it does not establish idle timeout or all-user coverage.
Windows firewall windows_security_center; Windows laptops SOC 2 CC6.6; ISO A.8.20 Firewall health Good; Poor or Snoozed fails.
Windows antivirus windows_security_center; Windows laptops SOC 2 CC6.8; ISO A.8.7 Antivirus health Good; Poor or Snoozed fails. Third-party product health depends on registration with Security Center.
Windows automatic updates windows_security_center; Windows laptops SOC 2 CC7.1; ISO A.8.8 Automatic update health Good. Does not prove patches are current.
User Account Control windows_security_center; Windows laptops ISO A.8.2; SOC 2 CC6.1 Elevation-control health Good. Does not inventory administrator rights.
Reversible password storage security_profile_info; Windows ISO A.5.17; SOC 2 CC6.1 clear_text_password must be 0. Assesses local policy only.
Mandatory access controls selinux_settings, apparmor_profiles; Linux ISO A.8.3; SOC 2 CC6.1 Enforcing SELinux or nonempty, entirely enforcing AppArmor profiles. Empty alternative providers are allowed. Missing/non-enforcing evidence warns; actual policy coverage still needs review.
Address space randomization system_controls; Linux ISO A.8.9; SOC 2 CC7.1 kernel.randomize_va_space must be 2; 0 or 1 fails this baseline.
Audit service availability systemd_units (auditd.service); Linux. services (EventLog); Windows SOC 2 CC7.2; ISO A.8.15 Running/active service. Stopped/failed/inactive fails; absent or transitional state warns. Non-systemd Linux requires alternate collection. Audit rules, event completeness, forwarding and retention are not assessed.

The queries use columns in the bundled deploy/osquery/data/5.24.0.json schema. Tests prepare the new SQL against that schema and check platform applicability. Actual table availability still depends on osquery version, OS, permissions and runtime configuration. Relevant upstream details:

Scoring and evidence quality

Lower scores mean less observed risk. Existing weights remain critical=30, high=20, medium=10 and low=5, with administrator overrides supported. A pass earns zero points, a failure earns the rule's weight, and a warning earns one quarter of its weight (integer division). The aggregate is earned points divided by possible points for evaluated rules, rounded to 0–100. A failed high-severity check raises the risk level to at least high; a failed critical check raises it to critical regardless of other passing checks.

New checks recognize explicit secure and insecure states. Empty results, missing fields, unsupported states, Error and Not Monitored warn rather than pass. A confirmed failure is retained even when other evidence is incomplete. Scoring uses the full stored snapshot when it contains more rows than the 100-row summary. Invalid or truncated evidence cannot produce a pass; if the 256 KiB snapshot cap prevents full evaluation, the available evidence can still produce a failure or a warning.

Uncollected categories remain unevaluated to preserve existing platform and custom-schedule behavior. There is no freshness penalty, expected-control coverage calculation or vulnerability-feed correlation. Consequently, a low score does not establish complete or current coverage. Review last_seen and the applied schedule alongside the score. Package/hotfix inventory, arbitrary process counts and uptime alone do not prove patch compliance or compromise.

Rollout

  1. Upgrade the API and TLS services with posture enabled. Startup adds missing checks without overwriting administrator changes; no model change is needed.
  2. Review the appropriate posture profile and apply it to each environment's schedule through the existing environment configuration workflow or CLI. Existing saved schedules are not changed automatically. Review the merge if an existing scheduled query was customized.
  3. Wait for the new daily snapshot queries, or choose a shorter supported interval when applying the profile. Both device-detail and fleet risk summaries then use the new evidence. During a mixed-version rollout, older services cannot evaluate the new rules consistently.

Disabling a check excludes its evidence from scoring and from generated profiles. Reseeding preserves the disabled state. Removal of an already deployed query still requires updating the environment schedule. Per-check UI exclusions remain a local what-if calculation, not a persisted exception.

Documentation

Index

Constants

View Source
const DefaultQueryPrefix = "osctrl:posture:"

DefaultQueryPrefix identifies scheduled queries whose result logs should be ingested as posture data by default.

Recommended practice: set the osquery schedule interval for posture queries to once per day (86400 seconds). Posture data — installed packages, users, disk encryption state — changes infrequently, and daily collection is sufficient for compliance audits without overloading agents or the logging pipeline.

View Source
const UptimeCategory = "uptime"

Variables

View Source
var QueryPrefix = DefaultQueryPrefix

QueryPrefix is the active posture query prefix. It is configured once at TLS startup. Empty disables posture ingestion.

View Source
var SeverityWeight = map[Severity]int{
	SeverityCritical: 30,
	SeverityHigh:     20,
	SeverityMedium:   10,
	SeverityLow:      5,
}

SeverityWeight converts severity to risk points.

Functions

func IsPostureQuery

func IsPostureQuery(name string) bool

IsPostureQuery returns true if the query name starts with the posture prefix.

func ParseUptime

func ParseUptime(record NodePosture) (*types.NodeUptime, error)

ParseUptime converts the latest uptime posture result into node metadata.

func PostureCategory

func PostureCategory(name string) string

PostureCategory extracts the category name from a posture query name. e.g. "osctrl:posture:packages" → "packages"

func RiskLevelFromScore

func RiskLevelFromScore(score int) string

RiskLevelFromScore converts a normalized score to a risk level.

func SetPrefix

func SetPrefix(prefix string)

SetPrefix sets the active posture query prefix.

func SummaryFromRecords

func SummaryFromRecords(records []NodePosture) *types.NodePostureSummary

Types

type CheckPatch added in v0.5.7

type CheckPatch struct {
	ProfileID          *string `json:"profile_id,omitempty"`
	ProfileName        *string `json:"profile_name,omitempty"`
	ProfileDescription *string `json:"profile_description,omitempty"`
	ProfilePlatform    *string `json:"profile_platform,omitempty"`
	Category           *string `json:"category,omitempty"`
	Name               *string `json:"name,omitempty"`
	Description        *string `json:"description,omitempty"`
	QueryName          *string `json:"query_name,omitempty"`
	Query              *string `json:"query,omitempty"`
	Interval           *int    `json:"interval,omitempty"`
	Platform           *string `json:"platform,omitempty"`
	Snapshot           *bool   `json:"snapshot,omitempty"`
	Enabled            *bool   `json:"enabled,omitempty"`
	ScoringRule        *string `json:"scoring_rule,omitempty"`
	ControlID          *string `json:"control_id,omitempty"`
	Framework          *string `json:"framework,omitempty"`
	Severity           *string `json:"severity,omitempty"`
	Weight             *int    `json:"weight,omitempty"`
}

CheckPatch is the small PATCH/POST body used by the API. Pointer fields let PATCH distinguish "unset" from zero values.

type ControlResult

type ControlResult struct {
	Category    string    `json:"category"`   // posture category the evidence came from
	ControlID   string    `json:"control_id"` // e.g. "A.8.24" or "CC6.6"
	Framework   Framework `json:"framework"`
	Title       string    `json:"title"`
	Description string    `json:"description"`
	Status      string    `json:"status"` // "pass", "warn", "fail"
	Severity    Severity  `json:"severity"`
	Score       int       `json:"score"` // 0 = pass, otherwise earned risk points
	// MaxScore is the risk points this control would contribute if it
	// failed outright (its weight). A passing control still reports this so
	// callers can renormalize the total after excluding controls — e.g. the
	// SPA's "what if I ignore this check" recompute — without needing the
	// evaluation rules themselves.
	MaxScore int    `json:"max_score"`
	Detail   string `json:"detail"` // human-readable explanation
}

ControlResult is the evaluation outcome for a single control.

type EvidenceSource added in v0.6.0

type EvidenceSource interface {
	ScoringRules() []ScoringRule
	ScoreEvidence(nodeUUIDs []string) (map[string][]NodePosture, error)
}

EvidenceSource contributes scoring rules and per-node evidence from another subsystem (vulnerability findings, from pkg/vulns). Its records use the NodePosture shape the calculator reads. A node it returns no record for leaves its rules unevaluated, exactly like an uncollected posture category.

type FleetCategorySummary

type FleetCategorySummary struct {
	Category  string `json:"category"`
	NodeCount int    `json:"node_count"`
	TotalRows int    `json:"total_rows"`
}

FleetCategorySummary is a per-category summary across all nodes in an environment.

type Framework

type Framework string

Framework is the compliance framework a control belongs to.

const (
	FrameworkSOC2     Framework = "SOC2"
	FrameworkISO27001 Framework = "ISO27001"
)

type NodePosture

type NodePosture struct {
	ID          uint      `gorm:"primarykey" json:"id"`
	CreatedAt   time.Time `json:"created_at"`
	UpdatedAt   time.Time `json:"updated_at"`
	NodeUUID    string    `gorm:"type:varchar(36);uniqueIndex:idx_posture_node_category" json:"node_uuid"`
	Environment string    `gorm:"index:idx_posture_env" json:"environment"`
	Category    string    `gorm:"uniqueIndex:idx_posture_node_category;type:varchar(64)" json:"category"`
	// QueryName is the full osquery scheduled-query name including prefix.
	QueryName string `gorm:"type:varchar(255)" json:"query_name"`
	// RowCount is the number of rows in the result (e.g. 3 packages, 5 users).
	RowCount int `json:"row_count"`
	// Summary is a compact JSON snapshot of the result data. For small
	// results this is the full columns array; for large results it's
	// truncated to the first 100 rows to keep the record manageable.
	Summary string `gorm:"type:text" json:"summary"`
	// Snapshot is the raw columns JSON from the latest result, capped at
	// 256KB. Used for detail views and compatibility with existing posture data.
	Snapshot string `gorm:"type:text" json:"-"`
	// FirstSeen is when this category was first observed for this node.
	FirstSeen time.Time `json:"first_seen"`
	LastSeen  time.Time `gorm:"index" json:"last_seen"`
}

NodePosture stores the latest snapshot of a single posture category for one node. Updated (upserted) every time a matching result log arrives.

func (NodePosture) TableName

func (NodePosture) TableName() string

TableName overrides the default table name.

type PostureCheck added in v0.5.7

type PostureCheck struct {
	ID                 uint      `gorm:"primarykey" json:"id"`
	CreatedAt          time.Time `json:"created_at"`
	UpdatedAt          time.Time `json:"updated_at"`
	ProfileID          string    `gorm:"type:varchar(64);uniqueIndex:idx_posture_check_profile_category" json:"profile_id"`
	ProfileName        string    `gorm:"type:varchar(255)" json:"profile_name"`
	ProfileDescription string    `gorm:"type:text" json:"profile_description"`
	ProfilePlatform    string    `gorm:"type:varchar(64)" json:"profile_platform"`
	Category           string    `gorm:"type:varchar(64);uniqueIndex:idx_posture_check_profile_category;index" json:"category"`
	Name               string    `gorm:"type:varchar(255)" json:"name"`
	Description        string    `gorm:"type:text" json:"description"`
	QueryName          string    `gorm:"type:varchar(255)" json:"query_name"`
	Query              string    `gorm:"type:text" json:"query"`
	Interval           int       `json:"interval"`
	Platform           string    `gorm:"type:varchar(64)" json:"platform"`
	Snapshot           bool      `json:"snapshot"`
	Enabled            bool      `gorm:"index" json:"enabled"`
	ScoringRule        string    `gorm:"type:varchar(64);index" json:"scoring_rule"`
	ControlID          string    `gorm:"type:varchar(64)" json:"control_id"`
	Framework          Framework `gorm:"type:varchar(32)" json:"framework"`
	Severity           Severity  `gorm:"type:varchar(32)" json:"severity"`
	Weight             int       `json:"weight"`
}

PostureCheck is an admin-editable posture scheduled-query template. ScoringRule selects a built-in evaluator; no executable logic is stored.

func (PostureCheck) TableName added in v0.5.7

func (PostureCheck) TableName() string

type PostureManager

type PostureManager struct {
	DB *gorm.DB
	// Evidence, when set, adds scoring evidence posture queries do not
	// collect. osctrl-api wires vulnerability findings here when both
	// --posture-enabled and --vuln-enabled are on.
	Evidence EvidenceSource
}

PostureManager manages the node posture table.

func NewPostureManager

func NewPostureManager(db *gorm.DB) *PostureManager

NewPostureManager creates the manager and auto-migrates the table.

func (*PostureManager) AllProfiles added in v0.5.7

func (pm *PostureManager) AllProfiles() ([]PostureProfile, error)

func (*PostureManager) CreateCheck added in v0.5.7

func (pm *PostureManager) CreateCheck(patch CheckPatch) (*PostureCheck, error)

func (*PostureManager) DeleteCheck added in v0.5.7

func (pm *PostureManager) DeleteCheck(id uint) error

func (*PostureManager) GetByNode

func (pm *PostureManager) GetByNode(nodeUUID string) ([]NodePosture, error)

GetByNode returns all posture categories for a node, ordered by category.

func (*PostureManager) GetByNodeCategory

func (pm *PostureManager) GetByNodeCategory(nodeUUID, category string) (*NodePosture, error)

GetByNodeCategory returns a single posture category for a node.

func (*PostureManager) GetCheck added in v0.5.7

func (pm *PostureManager) GetCheck(id uint) (*PostureCheck, error)

func (*PostureManager) GetFleetSummary

func (pm *PostureManager) GetFleetSummary(environment string) ([]FleetCategorySummary, error)

GetFleetSummary returns per-category posture counts across all nodes in an environment.

func (*PostureManager) GetProfile added in v0.5.7

func (pm *PostureManager) GetProfile(id string) (*PostureProfile, error)

func (*PostureManager) GetSummaryByNode

func (pm *PostureManager) GetSummaryByNode(nodeUUID string) (*types.NodePostureSummary, error)

func (*PostureManager) GetSummaryByNodes

func (pm *PostureManager) GetSummaryByNodes(nodeUUIDs []string) (map[string]*types.NodePostureSummary, error)

func (*PostureManager) GetUptimeByNode

func (pm *PostureManager) GetUptimeByNode(nodeUUID string) (*types.NodeUptime, error)

func (*PostureManager) GetUptimeByNodes

func (pm *PostureManager) GetUptimeByNodes(nodeUUIDs []string) (map[string]*types.NodeUptime, error)

func (*PostureManager) IngestResult

func (pm *PostureManager) IngestResult(nodeUUID, environment, queryName string, columns json.RawMessage) error

IngestResult processes a single result log entry and upserts the posture record if the query name matches the posture prefix.

func (*PostureManager) ListChecks added in v0.5.7

func (pm *PostureManager) ListChecks() ([]PostureCheck, error)

func (*PostureManager) Score added in v0.5.7

func (pm *PostureManager) Score(records []NodePosture) (PostureScore, error)

func (*PostureManager) ScoreNode added in v0.6.0

func (pm *PostureManager) ScoreNode(nodeUUID string) (PostureScore, error)

ScoreNode scores one node from its posture records and any evidence.

func (*PostureManager) SeedDefaultChecks added in v0.5.7

func (pm *PostureManager) SeedDefaultChecks() error

func (*PostureManager) UpdateCheck added in v0.5.7

func (pm *PostureManager) UpdateCheck(id uint, patch CheckPatch) (*PostureCheck, error)

type PostureProfile

type PostureProfile struct {
	ID          string                  `json:"id"`
	Name        string                  `json:"name"`
	Description string                  `json:"description"`
	Platform    string                  `json:"platform"`
	Queries     map[string]ProfileQuery `json:"queries"`
}

PostureProfile is a named set of posture check queries that an operator can merge into an environment's schedule config. Each profile targets a specific node type and platform with checks tailored to that environment's compliance requirements.

func AllProfiles

func AllProfiles() []PostureProfile

AllProfiles returns every predefined posture profile, sorted by name.

func GetProfile

func GetProfile(id string) *PostureProfile

GetProfile returns a single profile by ID, or nil if not found.

func LinuxLaptopProfile

func LinuxLaptopProfile() PostureProfile

func LinuxServerProfile

func LinuxServerProfile() PostureProfile

func MacOSLaptopProfile

func MacOSLaptopProfile() PostureProfile

func WindowsLaptopProfile

func WindowsLaptopProfile() PostureProfile

func WindowsServerProfile

func WindowsServerProfile() PostureProfile

func (PostureProfile) ToScheduleEntries

func (p PostureProfile) ToScheduleEntries() (map[string]map[string]interface{}, error)

ToScheduleEntries converts a profile's queries into a JSON map suitable for merging into an environment's schedule config section.

func (PostureProfile) ToScheduleJSON

func (p PostureProfile) ToScheduleJSON() (string, error)

ToScheduleJSON returns the schedule entries as pretty-printed JSON.

type PostureScore

type PostureScore struct {
	NodeUUID   string          `json:"node_uuid"`
	Timestamp  time.Time       `json:"timestamp"`
	TotalScore int             `json:"total_score"` // 0-100 normalized, lower is better
	RiskLevel  string          `json:"risk_level"`  // "low", "medium", "high", "critical"
	Controls   []ControlResult `json:"controls"`
	PassCount  int             `json:"pass_count"`
	WarnCount  int             `json:"warn_count"`
	FailCount  int             `json:"fail_count"`
}

PostureScore is the aggregate risk assessment for a node.

type ProfileQuery

type ProfileQuery struct {
	QueryName string `json:"query_name,omitempty"`
	Query     string `json:"query"`
	Interval  int    `json:"interval"`
	Platform  string `json:"platform,omitempty"`
	Snapshot  bool   `json:"snapshot"`
}

ProfileQuery is a single scheduled query within a posture profile.

type ScoreCalculator

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

ScoreCalculator evaluates posture data against compliance controls.

func NewScoreCalculator

func NewScoreCalculator() *ScoreCalculator

NewScoreCalculator returns a calculator with all built-in rules.

func NewScoreCalculatorWithChecks added in v0.5.7

func NewScoreCalculatorWithChecks(checks []PostureCheck) *ScoreCalculator

NewScoreCalculatorWithChecks returns a calculator configured from enabled DB checks. Checks without a built-in ScoringRule still collect posture data but do not contribute to risk score.

func (*ScoreCalculator) Score

func (sc *ScoreCalculator) Score(records []NodePosture) PostureScore

Score evaluates all posture records and returns the aggregate score.

type ScoringRule

type ScoringRule struct {
	RuleKey string `json:"rule_key"`
	// Categories lists every posture category that can provide evidence
	// for this control. The rule is evaluated when at least one of them
	// has been collected; mutually exclusive sources (deb vs rpm) belong
	// in the same rule so an empty inapplicable source is not a finding.
	Categories  []string  `json:"categories"`
	ControlID   string    `json:"control_id"`
	Framework   Framework `json:"framework"`
	Title       string    `json:"title"`
	Description string    `json:"description"`
	Severity    Severity  `json:"severity"`
	Weight      int       `json:"weight"`
	// Evaluate receives the collected categories (only those present for
	// the node) with their parsed rows and returns (status, detail).
	// status is "pass", "warn", or "fail".
	Evaluate func(data map[string][]map[string]interface{}) (status, detail string)
	// Grade, when set, replaces Evaluate for controls whose weight depends
	// on the evidence: a known-exploited vulnerability fails at critical
	// weight, a high-severity one at high weight. It returns the status,
	// the detail and the severity the result is scored and escalated at.
	Grade func(data map[string][]map[string]interface{}) (status, detail string, severity Severity) `json:"-"`
}

ScoringRule defines how to evaluate one control from posture data.

type Severity

type Severity string

Severity is the risk weight of a failing control.

const (
	SeverityCritical Severity = "critical" // immediate risk, must fix
	SeverityHigh     Severity = "high"     // significant risk
	SeverityMedium   Severity = "medium"   // moderate risk
	SeverityLow      Severity = "low"      // minor risk
)

Jump to

Keyboard shortcuts

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