Skip to main content

What is an Issue?

An issue in Garnet represents a security concern that requires attention. Issues are created by aggregating related events into actionable items, making it easier for security teams to understand and respond to threats. While events are raw telemetry, issues are the analyzed, prioritized, and contextualized security incidents that your team should investigate.

Issue Structure

type Issue struct {
    ID                  string              `json:"id"`
    ProjectID           string              `json:"-"`
    Class               IssueClass          `json:"class"`
    Description         string              `json:"description"`
    State               IssueState          `json:"state"`
    Priority            IssuePriority       `json:"priority"`
    Labels              IssueLabels         `json:"labels"`
    Ignored             bool                `json:"ignored"`
    IgnoredReason       string              `json:"ignored_reason,omitempty"`
    IgnoredBy           string              `json:"ignored_by,omitempty"`
    IgnoredAt           *time.Time          `json:"ignored_at,omitempty"`
    PolicyScope         *NetworkPolicyScope `json:"policy_scope,omitempty"`
    NetworkPolicyID     *string             `json:"network_policy_id,omitempty"`
    NetworkPolicyRuleID *string             `json:"network_policy_rule_id,omitempty"`
    LastActionBy        *string             `json:"last_action_by,omitempty"`
    LastActionAt        *time.Time          `json:"last_action_at,omitempty"`
    Events              []Event             `json:"events"`
    CreatedAt           time.Time           `json:"created_at"`
    UpdatedAt           time.Time           `json:"updated_at"`
    DeletedAt           *time.Time          `json:"deleted_at,omitempty"`
}

Issue Classification

Issue Classes

Issues are categorized into classes based on the type of threat:
const (
    IssueClassNetworkExfiltration IssueClass = "network_exfiltration"
    IssueClassCryptoMiner         IssueClass = "crypto_miner"
    IssueClassNetworkAnomaly      IssueClass = "network_anomaly"
)

Network Exfiltration

Suspicious outbound network traffic to potentially malicious destinations.Examples:
  • Connections to known threat domains
  • Data transfer to suspicious IPs
  • Unauthorized external communications

Crypto Miner

Cryptocurrency mining activity detected on your infrastructure.Examples:
  • Mining pool connections
  • Mining binary execution
  • High CPU usage patterns

Network Anomaly

Unusual network behavior that doesn’t fit normal patterns.Examples:
  • Unexpected protocol usage
  • Connections to unusual ports
  • Abnormal traffic patterns

Issue States

Issues can be in one of two states:
const (
    IssueStateAllowed IssueState = "allowed"
    IssueStateBlocked IssueState = "blocked"
)
  • Allowed: The issue has been reviewed and the activity is permitted
  • Blocked: The issue has been blocked via a network policy rule
Changing an issue’s state requires providing a reason to maintain an audit trail.

Priority Levels

const (
    IssuePriorityLow      IssuePriority = "low"
    IssuePriorityMedium   IssuePriority = "medium"
    IssuePriorityHigh     IssuePriority = "high"
    IssuePriorityCritical IssuePriority = "critical"
)
Minor security concerns that should be reviewed but don’t require immediate action.
  • Tracking domain access
  • Informational fingerprinting
  • Low-risk network activity
Potentially suspicious activity that warrants investigation.
  • Unusual network patterns
  • Access to uncommon services
  • Moderate risk indicators
Significant security concerns requiring prompt attention.
  • Known malicious domains
  • Suspicious tool execution
  • Privilege escalation attempts
Severe security threats requiring immediate response.
  • Active crypto mining
  • Confirmed malware execution
  • Data exfiltration attempts
  • Known threat actor infrastructure

Creating an Issue

Issues are typically created automatically by Garnet, but can also be created manually:
import (
    "context"
    "github.com/garnet-org/api/client"
    "github.com/garnet-org/api/types"
)

func createIssue() {
    c := client.New("your-api-key")
    
    issue := types.CreateIssue{
        Class:       types.IssueClassCryptoMiner,
        Description: "Crypto miner detected on production server",
        State:       types.IssueStateBlocked,
        Priority:    types.IssuePriorityCritical,
        Labels: types.IssueLabels{
            "env":     "production",
            "severity": "critical",
        },
        EventIDs: []string{
            "evt_1234567890",
            "evt_0987654321",
        },
    }
    
    result, err := c.CreateIssue(context.Background(), issue)
    if err != nil {
        // Handle error
    }
    
    fmt.Printf("Created issue: %s\n", result.ID)
}
An issue must be associated with at least one event. The EventIDs field cannot be empty.

Listing Issues

Query issues with comprehensive filtering:
func listIssues() {
    c := client.New("your-api-key")
    
    classFilter := types.IssueClassCryptoMiner
    priorityFilter := types.IssuePriorityCritical
    stateFilter := types.IssueStateBlocked
    
    params := types.ListIssues{
        Filters: &types.IssueFilters{
            Class:    &classFilter,
            Priority: &priorityFilter,
            State:    &stateFilter,
        },
        Labels: types.IssueLabels{
            "env": "production",
        },
        PageArgs: types.PageArgs{
            Page:    intPtr(1),
            PerPage: intPtr(50),
        },
        Sort: &types.Sort{
            Field: "created_at",
            Order: types.SortOrderDesc,
        },
        IncludeIgnored: false, // Exclude ignored issues
    }
    
    page, err := c.Issues(context.Background(), params)
    if err != nil {
        // Handle error
    }
    
    for _, issue := range page.Data {
        fmt.Printf("Issue: %s - %s (%s)\n",
            issue.ID, issue.Description, issue.Priority)
    }
}

Available Filters

Issue Attributes

  • Class: Issue classification
  • State: Allowed or blocked
  • Priority: Priority level
  • Labels: Key-value labels

Context Filters

  • AgentKind: Type of agent (GitHub, K8s, Vanilla)
  • AgentID: Specific agent
  • RepositoryID: GitHub repository
  • Repository: Repository name
  • WorkflowName: GitHub workflow
  • CreatedAfter: Time filter

Updating Issues

Modify issue properties as your investigation progresses:
func updateIssue(issueID string) {
    c := client.New("your-api-key")
    
    newPriority := types.IssuePriorityMedium
    newState := types.IssueStateAllowed
    reason := "Verified as legitimate development activity"
    
    update := types.UpdateIssue{
        Priority: &newPriority,
        State:    &newState,
        Reason:   &reason,
    }
    
    result, err := c.UpdateIssue(context.Background(), issueID, update)
    if err != nil {
        // Handle error
    }
    
    fmt.Printf("Updated issue: %s\n", result.ID)
}
When changing the state of an issue, you must provide a reason. This creates an audit trail for security decisions.

Ignoring Issues

Mark an issue as ignored to suppress it from default queries:
func ignoreIssue(issueID string) {
    c := client.New("your-api-key")
    
    ignored := true
    reason := "False positive - internal testing activity"
    
    update := types.UpdateIssue{
        Ignored:       &ignored,
        IgnoredReason: &reason,
    }
    
    result, err := c.UpdateIssue(context.Background(), issueID, update)
    if err != nil {
        // Handle error
    }
}
Ignored issues can still be retrieved by setting IncludeIgnored: true in your list query.

Taking Action on Issues

Garnet provides two primary actions for responding to issues: allowing and blocking.

Allowing an Issue

Create a network policy rule to explicitly allow the traffic:
func allowIssue(issueID string) {
    c := client.New("your-api-key")
    
    action := types.IssueAction{
        Scope:  types.NetworkPolicyScopeGlobal,
        Reason: "Verified safe - company API endpoint",
    }
    
    result, err := c.AllowIssue(context.Background(), issueID, action)
    if err != nil {
        // Handle error
    }
    
    fmt.Printf("Created allow rule: %s\n", result.NetworkPolicyRule.ID)
    fmt.Printf("Policy ID: %s\n", result.NetworkPolicyID)
}

Blocking an Issue

Create a network policy rule to block the traffic:
func blockIssue(issueID string) {
    c := client.New("your-api-key")
    
    action := types.IssueAction{
        Scope:  types.NetworkPolicyScopeWorkflow,
        Reason: "Known malicious domain - blocking at workflow level",
    }
    
    result, err := c.BlockIssue(context.Background(), issueID, action)
    if err != nil {
        // Handle error
    }
    
    fmt.Printf("Blocked at scope: %s\n", action.Scope)
    fmt.Printf("Rule ID: %s\n", result.NetworkPolicyRule.ID)
}

Network Policy Scopes

Actions can be scoped at different levels:

Global

System Global: Applies across all projects (admin only)Project Global: Applies to all agents in a projectUse for: Organization-wide policies

GitHub Context

Repo: Applies to specific repositoryWorkflow: Applies to specific workflowUse for: Repository or workflow-specific rules

Kubernetes Context

Cluster: Applies to entire clusterNode: Applies to specific nodeUse for: Cluster or node-specific rules

Choosing Scope

Narrow scope: More targeted, less impactBroad scope: Organization-wide protectionUse for: Balance security and operational needs
The network policy rule is automatically created from the network destination extracted from the issue’s events. For domain-based events, a domain rule is created. For IP-based events, a CIDR rule is created.

Issue Action History

Track all actions taken on an issue:
func getActionHistory(issueID string) {
    c := client.New("your-api-key")
    
    history, err := c.IssueActionHistory(context.Background(), issueID)
    if err != nil {
        // Handle error
    }
    
    for _, action := range history {
        fmt.Printf("%s: %s at %s (scope: %s)\n",
            action.ActionType,
            action.Reason,
            action.CreatedAt,
            action.Scope,
        )
    }
}
Action history includes:
  • Action type (allow/block)
  • Scope where rule was created
  • Reason provided
  • User who performed the action
  • Network policy and rule IDs
  • Destination type and value
  • Timestamp

Issue Labels

Labels help organize and categorize issues:
type IssueLabels map[string]string
Common labeling patterns:
labels := types.IssueLabels{
    "env":        "production",
    "team":       "platform",
    "severity":   "high",
    "reviewed":   "true",
    "jira":       "SEC-1234",
}
Use labels to integrate issues with your existing workflows, ticketing systems, and organizational structure.

Network Destination Extraction

Issues automatically extract network destination information from their events:
// Extract the network destination from an issue
ruleType, value, err := issue.ExtractNetworkDestination()
if err != nil {
    // Handle error
}

switch ruleType {
case types.NetworkPolicyRuleTypeCIDR:
    fmt.Printf("IP/CIDR: %s\n", value)
case types.NetworkPolicyRuleTypeDomain:
    fmt.Printf("Domain: %s\n", value)
}
The extraction logic (types/issue.go:364-442):
  1. Examines all events in the issue
  2. For DropIP events: Prefers domain if available, falls back to IP
  3. For domain access events: Extracts domain from flow data
  4. Returns the rule type (CIDR or domain) and value
Domain extraction prefers the Name field (original requested domain) over the Names array (DNS resolution chain) to ensure the rule targets the intended destination.

Deleting Issues

Soft-delete an issue:
func deleteIssue(issueID string) {
    c := client.New("your-api-key")
    
    err := c.DeleteIssue(context.Background(), issueID)
    if err != nil {
        // Handle error
    }
}
Deleted issues are soft-deleted (marked with DeletedAt timestamp) and not returned in normal queries.

Best Practices

  • Review critical and high priority issues daily
  • Investigate medium priority issues weekly
  • Use labels to track review status
  • Document decisions in the reason field
  • Set up alerting for critical issues
  • Start with narrow scopes (workflow/node) before broad (global)
  • Test allow rules before deploying broadly
  • Block known threats immediately
  • Document the business justification for allows
  • Review action history regularly
  • Don’t ignore issues without investigation
  • Use priority levels consistently
  • Update issue state as investigation progresses
  • Link issues to tickets in external systems via labels
  • Regularly review and cleanup old issues
  • Assign issues to team members via labels
  • Track progress with custom labels
  • Use filters to create team views
  • Integrate with incident response workflows
  • Maintain audit trail with detailed reasons

Error Handling

Common Errors

ErrInvalidIssueID (types/issue.go:73)
ErrInvalidIssueID = errs.InvalidArgumentError("invalid issue ID")
Returned when the issue ID format is invalid. ErrUnauthorizedIssue (types/issue.go:82)
ErrUnauthorizedIssue = errs.UnauthorizedError("permission denied")
Returned when accessing an issue without proper permissions. ErrIssueHasNoNetworkDestination (types/issue.go:71)
ErrIssueHasNoNetworkDestination = errs.InvalidArgumentError("issue has no network destination")
Returned when trying to create a network policy rule from an issue that doesn’t contain network information. ErrNoAssociatedEvents (types/issue.go:94)
ErrNoAssociatedEvents = errs.InvalidArgumentError("issue has no associated events")
Returned when an issue has no linked events.
  • Events - Raw security events that form issues
  • Agents - Components that detect and report events
  • Network Policies - Rules created from issue actions

Build docs developers (and LLMs) love