Skip to content

Custom Heuristics

Build, compile, install. No PRs to core CloudSlash required.

A heuristic plugin is a compiled binary that implements two gRPC methods. The engine discovers it at startup, streams graph nodes to it, and applies its findings alongside the built-in heuristics: indistinguishable from either side.

The Interface

service HeuristicPlugin {
    rpc Metadata(MetadataRequest) returns (MetadataResponse);
    rpc Analyze(stream NodeBatch)  returns (stream HeuristicFinding);
}

Metadata() is called once at startup. Declare your name, version, which resource types you want to see, and which node property keys you need. The engine sends only the keys you declare: everything else is stripped before your binary receives it.

Analyze() is a bidirectional stream. The engine sends NodeBatch chunks; you stream HeuristicFinding back for each wasteful resource you detect.

Scaffold

cs scaffold --kind heuristic --name my-rds-scorer --output ./my-rds-scorer

This generates a ready-to-compile Go project:

my-rds-scorer/
  main.go        ← plugin.Serve() boilerplate
  heuristic.go   ← implement Metadata() and Analyze() here
  go.mod         ← imports cloudslash/v2/pkg/engine/heuristic/shared
  Makefile       ← make build, make install
  README.md

make build compiles the binary. make install copies it to ./heuristics/ so the daemon picks it up on next start.

Implementing Metadata()

func (h *MyHeuristic) Metadata(_ context.Context) (*pb.MetadataResponse, error) {
    return &pb.MetadataResponse{
        Name:        "my-rds-scorer",
        Description: "Flags RDS instances with no connections in 30 days",
        Version:     "1.0.0",
        // Only receive these resource types.
        ResourceTypeFilter: []string{"AWS::RDS::DBInstance"},
        // Only receive these property keys: everything else is stripped.
        RequiredProperties: []string{"DBInstanceStatus", "MultiAZ", "Engine"},
        // Preferred batch size. Engine caps at its global max (default 500).
        PreferredBatchSize: 100,
    }, nil
}

Implementing Analyze()

func (h *MyHeuristic) Analyze(
    ctx context.Context,
    send func(*pb.NodeBatch) error,
    recv func(*pb.HeuristicFinding) error,
) error {
    // Drain all node batches.
    var nodes []*pb.GraphNode
    for {
        batch := &pb.NodeBatch{}
        if err := send(batch); err == io.EOF {
            break
        } else if err != nil {
            return err
        }
        nodes = append(nodes, batch.Nodes...)
    }

    // Analyse and emit findings.
    for _, node := range nodes {
        if node.Properties["DBInstanceStatus"] != "available" {
            continue
        }
        // your detection logic here...
        if err := recv(&pb.HeuristicFinding{
            Crn:                 node.Crn,
            IsWaste:             true,
            RiskScore:           55,  // 0–79; engine caps community plugins at 79
            Reason:              "RDS instance idle: no connections in 30 days",
            ProjectedSavingsUsd: node.CostUsd,
            SuggestedAction:     "stop",
        }); err != nil {
            return err
        }
    }
    return nil
}

Security Model

Community heuristic plugins operate under four controls:

Control What it does
Binary signing SHA-256 manifest verified before subprocess spawns
Declared property access Plugin only receives the keys it listed in RequiredProperties
CRN validation Findings for nodes the plugin was never sent are silently discarded
Trust ceiling RiskScore capped at 79: plugins cannot reach RISK (80+) or CRITICAL (90+)

The ~/.cloudslash/heuristics/ path enforces binary signing. The ./heuristics/ local dev path skips it so you can iterate without signing every build.

Testing Locally

make build && make install
cs scan --mock          # your plugin runs against synthetic graph data
cs heuristics list      # confirm your plugin appears with ✓ status

Publishing to the Registry

Submit a PR to cloudslash-plugins with:

  • Pre-built binaries for linux-amd64, linux-arm64, darwin-amd64, darwin-arm64
  • A manifest.yaml with your name, version, and per-arch SHA-256 checksums
  • Your RequiredProperties list (reviewers check for over-broad declarations)

Once merged, users install with:

cs registry install my-rds-scorer