findings

package
v0.27.0 Latest Latest
Warning

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

Go to latest
Published: Nov 5, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package findings holds the per-type table of finding operations, and the verbs that the CLI and the MCP server both expose from it.

Index

Constants

View Source
const AllowlistTypeWarning = "The server may not record the allowlist type for this finding type yet. The reason is recorded."

AllowlistTypeWarning is the warning for a type with AllowlistTypeUnrecorded.

View Source
const DefaultFixTimeout = 50 * time.Minute

DefaultFixTimeout is longer than the 45 minutes that the server allows one autofix run, so that a healthy run ends before the wait does.

View Source
const UnallowlistReasonWarning = "The server does not record the reason for this finding type. The finding is re-opened."

UnallowlistReasonWarning is the warning for a type with UnallowlistReasonUnrecorded.

Variables

View Source
var (
	ErrAutofixFailed       = errors.New("autofix failed")
	ErrAutofixStillRunning = errors.New("autofix is still running")
	// ErrAutofixStatusUnreadable means that the status route was failing when the
	// wait ended, so the state of the run is not known.
	ErrAutofixStatusUnreadable = errors.New("autofix status could not be read")
	ErrNoPullRequest           = errors.New("autofix finished without opening a new pull request")
	ErrAutofixDiff             = errors.New("get autofix diff")
)

Errors that FixResult.Failure holds when a fix did not deliver its result.

View Source
var (
	ErrInvalidLimit  = errors.New("limit must be greater than zero")
	ErrInvalidStatus = errors.New("invalid status")
	ErrInvalidTime   = errors.New("invalid time")
)

Errors that Search returns before it calls the API.

View Source
var (
	ErrUnsupported          = errors.New("operation is not supported for this finding type")
	ErrFindingIDRequired    = errors.New("a finding ID is required")
	ErrReasonRequired       = errors.New("a reason is required")
	ErrInvalidAllowlistType = errors.New("invalid allowlist type")
	ErrInvalidVerdict       = errors.New("invalid feedback verdict")
)

Errors that a verb returns before it calls the API.

AllowlistTypes are the allowlist types that a user can submit. Friendly commands and MCP tools require one for every finding type, so that the interface is the same for all of them. Type.AllowlistTypeUnrecorded marks the types for which the server may not record it.

View Source
var ErrInvalidTimeout = errors.New("timeout must be greater than zero")

ErrInvalidTimeout is returned before the API is called.

View Source
var ErrUnknownType = errors.New("unknown finding type")

ErrUnknownType is returned for a finding type that is not in the table.

View Source
var ErrUnreadableResponse = errors.New("unreadable findings response")

ErrUnreadableResponse is returned for a successful response without the expected shape. It must never read as an empty result.

FeedbackVerdicts are the verdicts that POST /findings/{id}/feedback accepts.

View Source
var Statuses = []string{"open", "fixed", "false_positive", "accepted_risk"}

Statuses are the values that SearchOptions.Status accepts.

View Source
var Types = map[string]Type{
	"sast": {
		APITypes:      []string{"Code"},
		Get:           read((*api.Client).GetSastFindingsFindingId),
		Triage:        read((*api.Client).ListSastFindingsFindingIdTriage),
		CreateTicket:  write((*api.Client).CreateSastFindingsFindingIdTicket),
		DeleteTicket:  read((*api.Client).DeleteSastFindingsFindingIdTicket),
		Allowlist:     write((*api.Client).CreateSastFindingsFindingIdAllowlist),
		Unallowlist:   writeRaw((*api.Client).CreateSastFindingsFindingIdUnallowlist),
		AutofixFix:    write((*api.Client).CreateSastFindingsFindingIdAutofixFix),
		AutofixStatus: read((*api.Client).ListSastFindingsFindingIdAutofixStatus),
		AutofixDiff:   readRaw("/sast/findings/%s/autofix/cache/diff"),
	},
	"sca_dependencies": {
		APITypes:                []string{"Dependencies"},
		AllowlistTypeUnrecorded: true,
		Get:                     read((*api.Client).GetScaDependenciesFindingsFindingId),
		Triage:                  read((*api.Client).ListScaDependenciesFindingsFindingIdTriage),
		CreateTicket:            write((*api.Client).CreateScaDependenciesFindingsFindingIdTicket),
		DeleteTicket:            read((*api.Client).DeleteScaDependenciesFindingsFindingIdTicket),
		Allowlist:               writeRaw((*api.Client).CreateScaDependenciesFindingsFindingIdAllowlist),
		Unallowlist:             writeRaw((*api.Client).CreateScaDependenciesFindingsFindingIdUnallowlist),
		AutofixFix:              write((*api.Client).CreateScaDependenciesFindingsFindingIdAutofixFix),
		AutofixStatus:           read((*api.Client).ListScaDependenciesFindingsFindingIdAutofixStatus),
		AutofixDiff:             readRaw("/sca/dependencies/findings/%s/autofix/cache/diff"),
	},
	"sca_containers": {
		APITypes:                []string{"Containers"},
		AllowlistTypeUnrecorded: true,
		Get:                     read((*api.Client).GetScaContainersFindingsFindingId),
		Triage:                  read((*api.Client).ListScaContainersFindingsFindingIdTriage),
		CreateTicket:            write((*api.Client).CreateScaContainersFindingsFindingIdTicket),
		DeleteTicket:            read((*api.Client).DeleteScaContainersFindingsFindingIdTicket),
		Allowlist:               writeRaw((*api.Client).CreateScaContainersFindingsFindingIdAllowlist),
		Unallowlist:             writeRaw((*api.Client).CreateScaContainersFindingsFindingIdUnallowlist),
		AutofixFix:              write((*api.Client).CreateScaContainersFindingsFindingIdAutofixFix),
		AutofixStatus:           read((*api.Client).ListScaContainersFindingsFindingIdAutofixStatus),
	},
	"secrets": {
		APITypes:     []string{"SecretsCredentials", "SecretsSensitiveData"},
		Get:          read((*api.Client).GetSecretsFindingsFindingId),
		Triage:       read((*api.Client).ListSecretsFindingsFindingIdTriage),
		CreateTicket: write((*api.Client).CreateSecretsFindingsFindingIdTicket),
		DeleteTicket: read((*api.Client).DeleteSecretsFindingsFindingIdTicket),
		Allowlist:    writeRaw((*api.Client).CreateSecretsFindingsFindingIdAllowlist),
		Unallowlist:  writeRaw((*api.Client).CreateSecretsFindingsFindingIdUnallowlist),
	},
	"secrets_sensitive_data": {
		APITypes:                []string{"SecretsSensitiveData"},
		AllowlistTypeUnrecorded: true,
		Get:                     read((*api.Client).GetSecretsSensitivedataFindingsFindingId),
		Triage:                  read((*api.Client).ListSecretsSensitivedataFindingsFindingIdTriage),
		CreateTicket:            write((*api.Client).CreateSecretsSensitivedataFindingsFindingIdTicket),
		DeleteTicket:            read((*api.Client).DeleteSecretsSensitivedataFindingsFindingIdTicket),
		Allowlist:               writeRaw((*api.Client).CreateSecretsSensitivedataFindingsFindingIdAllowlist),
		Unallowlist:             writeRaw((*api.Client).CreateSecretsSensitivedataFindingsFindingIdUnallowlist),
	},
	"pentest": {
		APITypes:      []string{"Pentest"},
		Get:           read((*api.Client).GetDastPentestFindingsFindingId),
		Triage:        read((*api.Client).ListDastPentestFindingsFindingIdTriage),
		CreateTicket:  write((*api.Client).CreateDastPentestFindingsFindingIdTicket),
		DeleteTicket:  read((*api.Client).DeleteDastPentestFindingsFindingIdTicket),
		Allowlist:     writeRaw((*api.Client).CreateDastPentestFindingsFindingIdAllowlist),
		Unallowlist:   writeRaw((*api.Client).CreateDastPentestFindingsFindingIdUnallowlist),
		AutofixFix:    write((*api.Client).CreateDastPentestFindingsFindingIdAutofixFix),
		AutofixStatus: read((*api.Client).ListDastPentestFindingsFindingIdAutofixStatus),
	},
	"bughunt": {
		APITypes:                []string{"BugHunt"},
		AllowOnPatch:            true,
		AllowlistTypeUnrecorded: true,
		Get:                     read((*api.Client).GetDastBughuntFindingsFindingId),
		Triage:                  read((*api.Client).ListDastBughuntFindingsFindingIdTriage),
		CreateTicket:            write((*api.Client).CreateDastBughuntFindingsFindingIdTicket),
		DeleteTicket:            read((*api.Client).DeleteDastBughuntFindingsFindingIdTicket),
		Allowlist:               write((*api.Client).PatchDastBughuntFindingsFindingIdAllowlist),
		Unallowlist:             write((*api.Client).PatchDastBughuntFindingsFindingIdAllowlist),
	},
	"cspm": {
		APITypes:                    []string{"Cloud"},
		UnallowlistReasonUnrecorded: true,
		Get:                         read((*api.Client).GetCspmFindingsFindingId),
		Triage:                      read((*api.Client).ListCspmFindingsFindingIdTriage),
		CreateTicket:                write((*api.Client).CreateCspmFindingsFindingIdTicket),
		DeleteTicket:                read((*api.Client).DeleteCspmFindingsFindingIdTicket),
		Allowlist:                   write((*api.Client).CreateCspmFindingsFindingIdAllowlist),
		Unallowlist:                 write((*api.Client).CreateCspmFindingsFindingIdUnallowlist),
		Archive:                     write((*api.Client).CreateCspmFindingsFindingIdArchive),
		Unarchive:                   write((*api.Client).CreateCspmFindingsFindingIdUnarchive),
		MarkFalsePositive:           write((*api.Client).CreateCspmFindingsFindingIdMarkFalsePositive),
		UnmarkFalsePositive:         write((*api.Client).CreateCspmFindingsFindingIdUnmarkFalsePositive),
		AutofixFix:                  write((*api.Client).CreateCspmFindingsFindingIdAutofixFix),
		AutofixStatus:               read((*api.Client).ListCspmFindingsFindingIdAutofixStatus),
	},
	"scpm": {
		APITypes:            []string{"SupplyChain"},
		Get:                 read((*api.Client).GetScpmFindingsFindingId),
		Triage:              read((*api.Client).ListScpmFindingsFindingIdTriage),
		CreateTicket:        write((*api.Client).CreateScpmFindingsFindingIdTicket),
		DeleteTicket:        read((*api.Client).DeleteScpmFindingsFindingIdTicket),
		Allowlist:           write((*api.Client).CreateScpmFindingsFindingIdAllowlist),
		Unallowlist:         writeRaw((*api.Client).CreateScpmFindingsFindingIdUnallowlist),
		UnmarkFalsePositive: write((*api.Client).CreateScpmFindingsFindingIdUnmarkFalsePositive),
		AutofixFix:          write((*api.Client).CreateScpmFindingsFindingIdAutofixFix),
		AutofixStatus:       read((*api.Client).ListScpmFindingsFindingIdAutofixStatus),
	},
}

Types is the table of finding types, keyed by the slug that users pass.

View Source
var Verbs = []Verb{
	{
		Name:        "get",
		Tool:        "nullify_get_finding",
		Description: "Get details of a specific finding by type and ID.",
		// contains filtered or unexported fields
	},
	{
		Name:        "triage",
		Tool:        "nullify_get_finding_triage",
		Description: "Get the AI-triage analysis for a finding: the model's assessment of exploitability, severity, and recommended disposition.",
		// contains filtered or unexported fields
	},
	{
		Name:        "fix-status",
		Tool:        "nullify_get_fix_status",
		Description: "Get the state of the autofix run of a finding: its state, whether it has ended, and its pull requests.",
		// contains filtered or unexported fields
	},
	{
		Name:        "fix-diff",
		Tool:        "nullify_get_fix_diff",
		Description: "Get the proposed change of a finished autofix run.",
		// contains filtered or unexported fields
	},
	{
		Name:               "allowlist",
		Write:              true,
		Tool:               "nullify_allowlist_finding",
		Description:        "Allowlist a finding: suppress it as fixed, accepted risk, false positive, or other. This is how a finding is triaged out of the open queue.",
		NeedsReason:        true,
		NeedsAllowlistType: true,
		// contains filtered or unexported fields
	},
	{
		Name:        "unallowlist",
		Write:       true,
		Tool:        "nullify_unallowlist_finding",
		Description: "Remove a finding from the allowlist, re-opening it.",
		NeedsReason: true,
		// contains filtered or unexported fields
	},
	{
		Name:        "archive",
		Write:       true,
		OptIn:       true,
		Tool:        "nullify_archive_finding",
		Description: "Archive a finding, removing it from the open queue without recording a verdict.",
		// contains filtered or unexported fields
	},
	{
		Name:        "unarchive",
		Write:       true,
		OptIn:       true,
		Tool:        "nullify_unarchive_finding",
		Description: "Restore an archived finding to the open queue.",
		// contains filtered or unexported fields
	},
	{
		Name:        "mark-false-positive",
		Write:       true,
		OptIn:       true,
		Tool:        "nullify_mark_false_positive",
		Description: "Mark a finding as a false positive.",
		NeedsReason: true,
		// contains filtered or unexported fields
	},
	{
		Name:        "unmark-false-positive",
		Write:       true,
		OptIn:       true,
		Tool:        "nullify_unmark_false_positive",
		Description: "Remove the false positive mark from a finding.",
		// contains filtered or unexported fields
	},
	{
		Name:        "create-ticket",
		Write:       true,
		Tool:        "nullify_create_ticket",
		Description: "Create a ticket (e.g. Jira, GitHub issue) for a finding.",
		// contains filtered or unexported fields
	},
	{
		Name:        "delete-ticket",
		Write:       true,
		OptIn:       true,
		Tool:        "nullify_delete_ticket",
		Description: "Close the ticket of a finding on its integration and clear the link to it. The API does not record a reason for this.",
		// contains filtered or unexported fields
	},
}

Verbs lists the finding operations in the order that help shows them.

Functions

func APITypesFor

func APITypesFor(name string) ([]string, error)

APITypesFor resolves a slug, an alias or a raw API value to the values of the /admin/findings "type" filter. "sca" is both SCA types here, because a filter that keeps only one would hide the other. A verb acts on one finding, so for a verb "sca" is the dependency route (see Lookup).

func AcceptedNames

func AcceptedNames(pick func(Type) bool) []string

AcceptedNames returns every sorted name that Lookup resolves to a type for which pick reports support: slugs, aliases and the type values of listed findings.

func HasRepository

func HasRepository(apiTypes []string) bool

HasRepository reports whether findings of the API types can belong to a repository. Cloud and bug hunt findings have none, so a repository filter removes all of them. A repository detected from the working directory must not filter a search for them, because that search would then always be empty.

func ListFeedback

func ListFeedback(ctx context.Context, c *api.Client, findingID string) (json.RawMessage, error)

ListFeedback returns the feedback recorded on a finding.

func Names

func Names() []string

Names returns the sorted slugs of all finding types.

func NamesWith

func NamesWith(pick func(Type) bool) []string

NamesWith returns the sorted slugs of the types for which pick reports support.

func SubmitFeedback

func SubmitFeedback(ctx context.Context, c *api.Client, findingID, verdict, comment string) (json.RawMessage, error)

SubmitFeedback records a verdict on a finding of any type. It never changes the finding.

Types

type Action

type Action func(ctx context.Context, c *api.Client, findingID string, body []byte) (json.RawMessage, error)

Action calls one finding operation. Operations that take no request body ignore body. The result is the JSON response of the API.

type AutofixPullRequest

type AutofixPullRequest struct {
	URL    string `json:"url"`
	Status string `json:"status"`
}

AutofixPullRequest is one entry in the pull-request history of a finding.

type AutofixStatus

type AutofixStatus struct {
	State       string `json:"state"`
	Terminal    bool   `json:"terminal"`
	Explanation string `json:"autofixVerdictExplanation"`
	// PullRequests is the history of autofix pull requests on the finding, most
	// recent first. The pullRequestUrl field is not read, because it can name a
	// pull request of an earlier run.
	PullRequests []AutofixPullRequest `json:"pullRequests"`
}

AutofixStatus is the part of the autofix status payload that polling reads.

type FixOptions

type FixOptions struct {
	// WaitForPR also waits for a pull request that the run opens.
	WaitForPR bool
	// Timeout is the longest that Fix waits for the run.
	Timeout time.Duration
}

FixOptions control how long Fix waits and for what.

type FixResult

type FixResult struct {
	// State is the last autofix state that the run reported.
	State string
	// RunID identifies the run. It is empty when the server does not report one.
	RunID string
	// CheckWith is the command that reports a run that continues on the server. It
	// is set when the wait ended before the run did.
	CheckWith string
	// Diff is the proposed change. It is empty for a type with no diff route.
	Diff json.RawMessage
	// PullRequest is set when the run opened a pull request, or when its latest
	// iteration failed and left a pull request open.
	PullRequest *PullRequestStatus
	// Failure is why the run did not deliver its result. It is nil on success.
	Failure error
}

FixResult is the outcome of Fix.

func Fix

func Fix(ctx context.Context, c *api.Client, typeName, findingID string, opts FixOptions) (FixResult, error)

Fix starts the autofix agent for a finding, waits for it to end and returns the diff. The fix run opens any pull request itself, so WaitForPR only waits for a pull request that was not on the finding before the run. The returned error is a failure to call the API; FixResult.Failure is a run that failed. When the run started before the failure, the result still holds what is known about it, and CheckWith is set.

type FixStart

type FixStart struct {
	State string `json:"autoFixState"`
	RunID string `json:"runId"`
}

FixStart is the answer to a request that starts an autofix run.

func StartFix

func StartFix(ctx context.Context, c *api.Client, typeName, findingID string) (FixStart, error)

StartFix starts the autofix agent for a finding and returns at once. The run continues on the server, and the fix-status verb reports it.

type Params

type Params struct {
	Reason        string
	AllowlistType string
}

Params are the inputs that a verb can take in addition to the finding.

type PullRequestStatus

type PullRequestStatus struct {
	State string `json:"state,omitempty"`
	URL   string `json:"url,omitempty"`
}

PullRequestStatus is the pull-request phase of an autofix run.

type SearchOptions

type SearchOptions struct {
	// APITypes are FindingType values. Empty means all types.
	APITypes []string
	// Severity is a lower-case value, which is sent upper-case.
	Severity   string
	Repository string
	Status     string
	// From and To are RFC 3339 bounds on the finding date.
	From string
	To   string
	// Limit is the most findings to collect across pages.
	Limit int
	// Debug receives one line per page when it is not nil.
	Debug io.Writer
}

SearchOptions are the filters of the unified /admin/findings search.

type SearchResult

type SearchResult struct {
	Findings []json.RawMessage `json:"findings"`
	// Total is the count that the server reports for the filters.
	Total int `json:"total"`
	// Returned is the number of entries in Findings.
	Returned int `json:"returned"`
	// Truncated is true when the server holds more findings than Findings has.
	Truncated bool `json:"truncated"`
}

SearchResult holds the collected findings and their completeness signals. An empty result has Total 0 and Truncated false. A failure to read is an error and never an empty result.

func Search(ctx context.Context, c *api.Client, opts SearchOptions) (SearchResult, error)

Search collects findings from POST /admin/findings up to opts.Limit. The page size stays constant across pages, because the server computes its offset from page and pageSize. The number of pages is bounded because every page must add a finding.

type Type

type Type struct {
	// APITypes are the values of the /admin/findings "type" filter.
	APITypes []string
	// AllowOnPatch marks a type whose allowlist state is set by one PATCH with
	// the body {allow, reason}. Other types use separate POST routes.
	AllowOnPatch bool
	// AllowlistTypeUnrecorded marks a type for which the server may not record the
	// allowlist type.
	AllowlistTypeUnrecorded bool
	// UnallowlistReasonUnrecorded marks a type whose unallowlist route ignores the reason.
	UnallowlistReasonUnrecorded bool

	Get                 Action
	Triage              Action
	CreateTicket        Action
	DeleteTicket        Action
	Allowlist           Action
	Unallowlist         Action
	Archive             Action
	Unarchive           Action
	MarkFalsePositive   Action
	UnmarkFalsePositive Action
	AutofixFix          Action
	AutofixStatus       Action
	AutofixDiff         Action
}

Type holds the operations of one finding type. A nil Action means that the API does not support the operation for the type.

func Lookup

func Lookup(name string) (Type, error)

Lookup resolves a slug, an alias or the type value of a listed finding to its type.

type Verb

type Verb struct {
	Name        string
	Tool        string
	Description string
	// NeedsReason makes Params.Reason mandatory.
	NeedsReason bool
	// NeedsAllowlistType makes Params.AllowlistType mandatory.
	NeedsAllowlistType bool
	// Write marks a verb that changes the finding or its ticket.
	Write bool
	// OptIn keeps the MCP tool out of the default tool set.
	OptIn bool
	// contains filtered or unexported fields
}

Verb is one finding operation, exposed as a `nullify findings` subcommand and as an MCP tool.

func (Verb) Run

func (v Verb) Run(ctx context.Context, c *api.Client, typeName, findingID string, params Params) (json.RawMessage, error)

Run calls the verb for one finding after it validates the inputs.

func (Verb) TypeValues

func (v Verb) TypeValues() []string

TypeValues returns the values that the verb accepts for a type: the slugs, the aliases and the type values of listed findings.

func (Verb) Types

func (v Verb) Types() []string

Types returns the finding types that support the verb.

func (Verb) Warning

func (v Verb) Warning(typeName string) string

Warning returns what a successful run of the verb for the type must tell the caller, or an empty string.

Jump to

Keyboard shortcuts

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