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
- Variables
- func APITypesFor(name string) ([]string, error)
- func AcceptedNames(pick func(Type) bool) []string
- func HasRepository(apiTypes []string) bool
- func ListFeedback(ctx context.Context, c *api.Client, findingID string) (json.RawMessage, error)
- func Names() []string
- func NamesWith(pick func(Type) bool) []string
- func SubmitFeedback(ctx context.Context, c *api.Client, findingID, verdict, comment string) (json.RawMessage, error)
- type Action
- type AutofixPullRequest
- type AutofixStatus
- type FixOptions
- type FixResult
- type FixStart
- type Params
- type PullRequestStatus
- type SearchOptions
- type SearchResult
- type Type
- type Verb
Constants ¶
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.
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.
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 ¶
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.
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.
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.
var AllowlistTypes = []string{ string(models.ModelsAllowlistTypeUserfixed), string(models.ModelsAllowlistTypeUserassumerisk), string(models.ModelsAllowlistTypeUserfalsepositive), string(models.ModelsAllowlistTypeUserother), }
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.
var ErrInvalidTimeout = errors.New("timeout must be greater than zero")
ErrInvalidTimeout is returned before the API is called.
var ErrUnknownType = errors.New("unknown finding type")
ErrUnknownType is returned for a finding type that is not in the table.
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.
var FeedbackVerdicts = []string{ string(models.EndpointsFindingFeedbackVerdictUp), string(models.EndpointsFindingFeedbackVerdictDown), string(models.EndpointsFindingFeedbackVerdictHelpful), string(models.EndpointsFindingFeedbackVerdictWrong), string(models.EndpointsFindingFeedbackVerdictNoisy), string(models.EndpointsFindingFeedbackVerdictFixGood), string(models.EndpointsFindingFeedbackVerdictFixBad), string(models.EndpointsFindingFeedbackVerdictWorthFixing), string(models.EndpointsFindingFeedbackVerdictExplanationClear), string(models.EndpointsFindingFeedbackVerdictExplanationUnclear), string(models.EndpointsFindingFeedbackVerdictSeverityRight), string(models.EndpointsFindingFeedbackVerdictSeverityWrong), }
FeedbackVerdicts are the verdicts that POST /findings/{id}/feedback accepts.
var Statuses = []string{"open", "fixed", "false_positive", "accepted_risk"}
Statuses are the values that SearchOptions.Status accepts.
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.
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 ¶
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 ¶
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 ¶
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 ¶
ListFeedback returns the feedback recorded on a finding.
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 ¶
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 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 ¶
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.
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 ¶
TypeValues returns the values that the verb accepts for a type: the slugs, the aliases and the type values of listed findings.