bulkUpdateFindings
Change the status and/or severity of a SET of findings in the caller's own organization, selected either by filter or by an explicit list of uuids. Use it for "dismiss all the unrotated-secret findings", "mark everything on foo/bar medium". For ONE finding you already hold a uuid for, use updateFinding instead; this tool is for sets you can only describe.
READ THIS BEFORE CALLING — IT WRITES THE MOMENT IT IS CALLED. There is no dry run, no preview mode and no confirmation step inside this tool, and no single call that puts back what it changed. The check has to happen BEFORE the call, in conversation with the user:
Work out the selection, and resolve it yourself with listFindings using the SAME filters.
Show the user what it covers — the count, and the findings themselves (name them if there are only a few).
Say in one sentence what will change and how many findings it affects.
Get a plain yes. Then call this ONCE. Never call this tool to find out what would match: listFindings and queryApi answer that and write nothing. Never call it on your own initiative, and never as a tidy-up you decided on yourself.
SCOPE is required and has no default. Over MCP it must be "tenant" — every finding in the organization, including ones filed months ago by people who are not in this conversation. "this_thread" only means anything inside the Mallory chat app, which is the only place a conversation thread exists; here it can only fail with MISSING_THREAD. If you do get MISSING_THREAD, do not "fix" it by re-sending with "tenant": that is a far larger write than the one that just failed, and the user has not agreed to it.
SELECTING
Pass
filtersoruuids, never both, and never invent a uuid — use only uuids a previous tool result actually returned (at most 500).A tenant selection needs at least one filter, or a uuid list — there is no "everything we have" call. A selection filtered ONLY by status and/or severity still covers essentially the whole organization; the result flags that as
broad, and you must say so explicitly when you report back.A phrase like "the unrotated-secret findings" names a problem TYPE, not an asset: find the definition first with queryApi, path "/v1/finding_definitions" (slug__contains / title__contains), then filter on definition_slug, or definition_slug__in when several definitions match the phrase. definition_category is a whole category and much broader than a slug — confirm before using it.
An asset is
asset_identifier(e.g. "foo/bar" for a GitHub repo). Try the exact value first and fall back to asset_identifier__contains only if it matched nothing.Add
status: "open"unless the user said otherwise: re-dismissing already-dismissed findings is churn they did not ask for.searchis fuzzy full text over title, asset_identifier, body, summary, remediation and asset_details — the only way to reach a cloud account id, region or owner, none of which are filterable columns. It is also the easiest way to hit findings the user did not mean, so withsearchespecially, show them the listFindings result before you call.
BOUNDS AND REFUSALS — each is an answer, not an obstacle to route around:
TOO_MANY_MATCHES: the selection is over the 500-finding limit for one update. Narrow it (a definition_slug, a category, an asset filter, a created_at range). Do NOT page through it, and do NOT split it into several calls to get under the limit — each call is its own write, and the user agreed to one change rather than to a sequence of them.
NO_MATCHES: nothing matched, or everything matched already holds the value you are setting. When you set ONE field on a
filtersselection, findings already at that value are excluded from the selection, so the count you get back is the real blast radius and repeating the call for the same request is inert. Setting status AND severity together drops that exclusion — the selector cannot express "not both at once" — so there the count can include findings that were already correct, and a repeat call writes them all again. Auuidsselection is never narrowed that way — the uuid list IS the selection — so a repeat call there rewrites every finding you named. Never re-issue a call to find out whether the first one landed: the result told you. Tell the user there was nothing to change and stop. Do NOT loosen the filters and retry until something matches: a selection that only appeared after you widened it is not the one the user described.PLAN_LIMIT / FORBIDDEN: nothing was written and nothing is broken. Say so plainly and do not retry.
In user-facing status messages, use "fixed" and "mark as fixed" consistently.
ENUM VALUES ARE CASE-SENSITIVE AND EACH FIELD HAS ITS OWN SET. severity is UPPERCASE (CRITICAL, HIGH, MEDIUM, LOW, INFO). status is lowercase with exactly three values: open, fixed, dismissed — "resolved", "closed" and "assigned" are not statuses. The resolution reasons (false_positive, accepted_risk, not_applicable, duplicate, fixed_externally, other) are a SEPARATE field that this tool cannot set: false_positive is not a status. definition_category is lowercase-hyphenated.
WHAT IT CANNOT DO: title, summary, body, remediation, confidence and qualifier — those are updateFinding, one finding at a time, because they are per-asset prose or part of core's dedupe key. resolution_reason is settable by NEITHER tool: it is recorded in the Mallory UI when someone closes a finding there, so listFindings can read and filter on it but nothing here writes it. It cannot create findings (createFinding). Setting status back to "open" is allowed but unusual; reopening two findings onto the same asset trips core's open-finding uniqueness constraint and rolls the whole batch back, so do not retry that — reopen one at a time.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| set | Yes | The change to apply to every selected finding. At least one field must be set. | |
| scope | Yes | REQUIRED, and a real decision — there is no default and you must not guess. 'this_thread' restricts the update to findings created in THIS conversation; that is what a user means by 'those findings', 'the ones you just filed', 'these'. 'tenant' searches every finding in the organisation, including ones filed months ago by someone else — use it only when the user clearly means their whole findings backlog. If the wording could mean either, ASK before calling this tool. | |
| uuids | No | Exact findings to update, by uuid. Use ONLY when you already have real uuids from a previous tool result — never invent or guess one. Mutually exclusive with `filters`, and requires scope 'tenant': a uuid list is already exact, so thread scoping could only silently drop findings you named. Core still enforces that every uuid belongs to the caller's tenant. | |
| filters | No | Select findings by attribute instead of by uuid. Mutually exclusive with `uuids`. Required when scope is 'tenant'; optional when scope is 'this_thread' (omit it to mean every finding from this conversation). |