Skip to content

pkg/triage

import "github.com/professor-moody/crucible/pkg/triage"

The package has two layers:

  • the current observation and identity APIs, used for new execution paths;
  • compatibility APIs such as CrashType, HashStack, and the locked oracle, whose values remain stable for historical evidence.

The package surfaces evidence for human triage. It does not confirm vulnerabilities or automatically determine severity, affected versions, or disclosure readiness.

Observation types

ExecKind

type ExecKind string
Constant Value Meaning
KindClean clean target ran, exited zero, no supported fatal evidence
KindDiagnostic diagnostic target survived after diagnostic, leak, or non-fatal evidence
KindCrash crash process death plus evidence naming the fault
KindSuspect suspect process death without supported evidence naming the fault
KindAmbiguous ambiguous target ran and exited non-zero, but the cause is not established
KindIncomplete incomplete timeout, loader failure, failure to start, or missing execution
KindInterceptedFault intercepted-fault a declared harness intercepted a real target fault and survived
KindUnknown unknown no admissible process outcome is available

ArtifactAdmission

type ArtifactAdmission struct {
    Kind   ArtifactKind
    Status AdmissionStatus
    Reason string
}

ArtifactKind describes structure: live execution, single-process log, campaign log, differential excerpt, multi-process artifact, aggregate rows, transcript, or unclassified. AdmissionStatus is an explicit adjudication: admitted, excluded, or unadjudicated.

The zero value is unadjudicated and therefore fails closed.

func AdmitLiveExecution() ArtifactAdmission
func BankedLog(kind ArtifactKind, status AdmissionStatus, reason string) ArtifactAdmission
func (a ArtifactAdmission) Admitted() bool

BankedLog does not inspect bytes or infer admission from kind.

TextEvidence

type TextEvidence struct {
    SanitizerVerdict     bool
    SanitizerClass       string
    SanitizerDiagnostics int
    LeakVerdict          bool
    AllowlistedAssertion bool
    AssertionForm        string
    LoaderDiagnostic     bool
    ResourceExhaustion   bool
    ResourceKind         string
}

This type says what output contains. It never establishes whether a process started, died, timed out, or survived.

func ExtractTextEvidence(output string) TextEvidence

The extractor distinguishes fatal sanitizer verdicts, recover-mode diagnostics, leaks, anchored assertion forms, loader diagnostics, and resource exhaustion. Generic prose matches are deliberately insufficient.

ProcessFacts

type ProcessFacts struct {
    Known            bool
    CommandStarted   bool
    TargetRan        bool
    LoaderFailure    bool
    TimedOut         bool
    HasExitCode      bool
    ExitCode         int
    Signal           syscall.Signal
    InterceptedFault bool
}

InterceptedFault is a harness capability, not a text inference. Production replay obtains it from a manifest bound to the harness binary hash; rebuilding the binary invalidates a stale declaration.

Observation

type Observation struct {
    Admission ArtifactAdmission
    Text      TextEvidence
    Process   ProcessFacts
    Kind      ExecKind
    Reason    string
}

func Observe(a ArtifactAdmission, t TextEvidence, p ProcessFacts) Observation
func (o Observation) NeedsHumanTriage() bool

Observe is the authoritative combination rule. NeedsHumanTriage() is true for crash, suspect, and intercepted fault. Its name is contractual: it does not mean reportable or confirmed.

Replay APIs

func ObserveReplay(
    admission ArtifactAdmission,
    harness string,
    input []byte,
    timeout time.Duration,
    extraEnv []string,
) (Observation, string, error)

func ObserveReplayDeclared(
    admission ArtifactAdmission,
    caps HarnessCapabilities,
    harness string,
    input []byte,
    timeout time.Duration,
    extraEnv []string,
) (Observation, string, error)

func ObserveReplayWithManifest(
    admission ArtifactAdmission,
    harness string,
    input []byte,
    timeout time.Duration,
    extraEnv []string,
) (Observation, string, string, error)

These functions execute the target and collect process facts. The manifest-aware path validates any intercepted-fault capability against the binary.

Execute and ExecutionOutcome remain compatibility wrappers for paths that have not moved to the full observation surface. New callers should preserve admission and the complete Observation.

Crash identity

CrashIdentity

type CrashIdentity struct {
    Exact          string
    ExactNamespace string
    Stable         string
    Site           Frame
    StableFrames   []Frame
    TotalFrames    int
    TargetFrames   int
    Degraded       bool
    Basis          string
}

func Identify(trace string) CrashIdentity

The two current identities answer different questions:

Field Contract
Exact build-local dedup key using every parsed frame's observed function/raw location/module
ExactNamespace formula identifier; consumers reject unknown namespaces
Stable cross-build key using normalized target functions and repo-relative paths without lines
Site best target attribution after skipping runtime, sanitizer, harness, and compiler-library frames
Degraded true when no target frame supports attribution

Stable is the literal UNATTRIBUTED when no target frame survives. Never use Stable to merge within-build crash buckets.

Legacy HashStack

func HashStack(stackTrace string) string

This is the frozen historical five-frame hash. It remains comparable to locked oracle manifests and banked reports. It is not the current campaign-dedup key and can merge traces that first differ below frame five.

Compatibility crash types

type CrashType string

func ClassifyCrash(output string) CrashType
func ClassifyCrashWithFilename(output, filename string) CrashType

CrashType is a taxonomy extracted from output. It predates the observation model and cannot represent process start failure, timeout facts, recover-mode diagnostics, admission, or intercepted faults. Do not branch on it when process facts are available.

Crash

Crash carries the observed type, source attribution, input, target metadata, and identity fields. The important distinction is:

  • ExactID: current build-local bucket and report filename identity;
  • StableID: cross-build observation;
  • StackHash: legacy compatibility value.

Triager.AddCrash deduplicates on Exact identity. It does not establish that two inputs share a source-level root cause.

Deduplication

func DeduplicateDir(
    crashDir, harness string,
    timeout time.Duration,
    extraEnv []string,
    delete bool,
    outputDir string,
    recursive bool,
) (*DedupResult, error)

Full mode replays candidate inputs and groups observations by Exact identity. Timeouts and incomplete executions are counted but never bucketed as crashes. Intercepted faults are supported when the harness manifest establishes that capability.

func FastDeduplicateDir(crashDir string, delete bool) (*DedupResult, error)

Fast mode groups by content fingerprint without executing the harness. It is storage cleanup, not a root-cause or execution equivalence check.

Both CLI paths default to dry-run. Destructive callers must review the proposed actions.

Minimization

func MinimizeCrashDir(
    crashDir, harness, outputDir string,
    timeout time.Duration,
    extraEnv []string,
) ([]MinimizeResult, *MinimizeSummary, error)

A successful minimization preserves the relevant Exact identity. A smaller input that merely crashes somewhere else is not a reproduction of the original observation.

Reports

type Report struct {
    Crash            *Crash
    CVSSScore        float64
    Severity         string
    CWE              CWE
    Description      string
    AffectedVersions string
    CVSSVector       string
    ReproducerPath   string
    ReportDate       string
}

func GenerateReport(crash *Crash) *Report
func (r *Report) String() string
func (r *Report) WriteToFile(path string) error

GenerateReport creates an internal investigation report. Defaults are deliberately fail-closed:

  • severity is UNRATED;
  • affected versions are unknown;
  • no CVSS score or vector is printed;
  • location is explicit about unattributed or unavailable evidence.

Only an operator-supplied CVSSVector can add a score. The deprecated internal per-type score helper is not used to populate reports.

CWE hints

type CWE struct {
    ID   string
    Name string
}

func CWEForType(ct CrashType) CWE

CWEForType maps a text-derived crash class to a taxonomy hint. Source review sometimes requires a different classification. A heap-buffer-overflow banner, for example, does not by itself distinguish the submitted primitive or prove the correct CWE.

SARIF

func WriteSARIF(w io.Writer, reports []*Report) error

SARIF level is taxonomy-based, not CVSS-based: memory-safety classes are error, crash-only classes are warning, and resource exhaustion is note. SARIF transport does not turn an observation into a confirmed vulnerability.

Minimal usage

obs, output, err := triage.ObserveReplay(
    triage.AdmitLiveExecution(),
    "./crucible-libfuzzer-model",
    input,
    30*time.Second,
    nil,
)
if err != nil || obs.Kind == triage.KindIncomplete {
    // No verdict: preserve the reason and repair the execution.
}
if obs.NeedsHumanTriage() {
    id := triage.Identify(output)
    if id.Degraded {
        // Preserve UNATTRIBUTED rather than inventing a target site.
    }
    // Bank raw output, replay, read target source, and adjudicate manually.
}