The goal every security leader is actually chasing is simple to state and brutal to reach: get security work done end to end — assigned, tracked, verified, closed — efficiently, with automation you can control and measure. Said differently, decouple your program's outcomes from its headcount. A security program shouldn't have to hire a program manager for every N engineers just to keep a spreadsheet honest, and yet that is the tax most programs quietly pay.
The pressure to break that coupling is only getting worse, because the other side isn't paying it. Attackers are scaling their own operations without scaling their teams — cheap, capable open-weight models now do the reconnaissance, triage, and exploit-drafting that used to require people. The cost of coming at you is falling. If the defense still scales linearly with human bookkeepers while the offense scales with GPUs, the math doesn't work. The bar on how efficiently a defensive program has to run just went up, and it went up for structural reasons that aren't going to reverse.
This post is about the piece of the program that has stubbornly stayed manual: not detection, but the operational layer around it — who owns a finding, what its real priority is, whether it's actually fixed. That layer is almost always run as human clerical work, and clerical work is exactly what caps a program at the size of its ops staff.
Ask a security leader how they track finding ownership, remediation SLAs, and accepted-risk exceptions across their org, and the honest answer is almost always the same: a patchwork. A Jira board here, a Slack channel there, a compliance dashboard that reports SLA status, and — somewhere in the middle of it — a spreadsheet. One person owns the spreadsheet. It maps GitHub authors to Slack handles, or teams to repositories, or which findings someone has decided to live with for now. Everyone else trusts that it's current. It isn't.
We've watched this pattern break in the field. (The customer stories throughout this post are illustrative composites drawn from real deployments — details are blended and anonymized, but every failure mode described is one we've actually seen.) One security team we spoke with was manually moving Jira tickets between team-specific boards because findings all landed on a single org-wide board and nothing routed them onward. Their lead called the situation unacceptable — it defeated the purpose of having a tool at all. And the data needed to route work automatically, the GitHub-to-Slack identity mapping, was sitting in a spreadsheet held by a single team member. The tool had become a passive filing cabinet, and the real system of record was a file on one laptop.
That's the failure mode. Not spreadsheets specifically — spreadsheets are just the most honest version of the problem. The problem is that program management for security is treated as manual bookkeeping: humans deciding who owns a finding, humans typing an SLA into a cell, humans clicking "done," humans chasing other humans for action. That model doesn't scale — every new engineer adds work the spreadsheet-owner has to absorb — and worse, it produces records nobody can trust. This post is about the alternative: a native ops layer driven by context rather than clerical labor, so the program can grow without the ops team growing under it.
The instinct, when the spreadsheet buckles, is to buy a better tool. A nicer ticketing integration, a dashboard with SLA countdowns, a fancier board. But the tool was never the bottleneck. The judgment behind the tool was.
Consider the three things a security program actually has to know about every finding:
A spreadsheet holds assertions about these three things. It can't verify any of them. The ownership column is right until someone changes teams. The SLA column is right until nobody looks at the row. The status column says "closed" whether or not the vulnerability is actually gone. Every column is a promise that decays the moment a human stops maintaining it — and at scale, humans always stop maintaining it.
We've seen exactly what that decay costs. A security team told us about a critical RCE fix that sat unreviewed for 35 days, well past any reasonable SLA, and another finding that sat 20 days past SLA and was, in their own words, unlikely to ever be reviewed. Those aren't tooling failures. The SLA was tracked. The dashboard showed it as breached. What was missing was anyone — or anything — whose job was to notice the stale row and act on it. That's the gap a native ops layer fills, and it's a judgment gap, not a UI gap.
Inside Nullify, program management is a service — internally manager, exposed to customers as Campaigns. Its job is to run the assign → track → verify → close loop for a whole org without a human doing the bookkeeping. The primitive underneath it is called an Action Item, and its defining rule is the opposite of how Jira works: in Jira or a spreadsheet, a human creates the ticket, sets the priority, decides who it goes to, and clicks done. In our model, a Nullify agent creates the item — because it hit a wall it genuinely can't clear on its own — routes it, does the state-changing work, and closes it. The human supplies exactly one missing piece of judgment: an answer, a name, a decision, in one sentence of chat. There is no human-facing "create ticket" or "mark done" endpoint at all.
That inversion is the whole point at the concept level: the system doesn't ask you to do bookkeeping, it asks you the one question it can't answer for itself, and then it does the bookkeeping. Each item corresponds to a specific judgment gap — an escalated finding to decide on, a clarifying question blocking a low-confidence triage, an unknown GitHub-author-to-Slack identity, an unowned repo — rather than a generic "task," so answering one question can fan out to every finding it touches. Notice what the last two of those replace: the spreadsheet held by one person mapping GitHub handles to Slack, and the ownership data nobody was maintaining. Those stop being files and become resolvable questions the system raises exactly when it needs the answer, and never again once it has it.
The detailed mechanics of that loop — the four typed item kinds, how each surface resolves an item, and how completion is gated on the side effect actually succeeding — are the subject of a companion post, Action Items, Not Alerts. This post stays at the altitude of why a program needs this layer at all.
The heart of the differentiation is the escalation itself. When the ops layer routes a finding to a developer, it doesn't drop a ticket in a queue with a name attached. Every escalation carries a structured, generated justification for why this human:
<severity> risk with a <duration> SLA"), not typed into a cell.Crucially, the facts — finding type, repo, file, dependency version, assignee name, campaign, fix-PR URL — are resolved server-side from the system of record. The agent doesn't retype them, which eliminates the transcription-error surface that every spreadsheet carries. The agent supplies only the judgment: severity, category, ownership reasoning, business impact. The message is then assembled deterministically from those structured fields, so if the wording ever looks off, it's the template — not a model hallucinating a status.
That "why this person" reasoning isn't decoration. It's what makes the loop auditable and overridable at the same time. A human reviewer can read the ownership reason and disagree — and re-route the item to a different team — rather than silently inheriting a routing decision they can't see the basis for.
How does the system know who owns code in the first place? Through a continuously-synced ownership index, resolved by a cascade of fallbacks rather than a manual assignment: CODEOWNERS for the file path, the team that owns the repo, git blame on the affected lines, the most active committer in the directory, and so on down. This is where the payoff is most measurable. When we re-ran ownership resolution across a set of production tenants, the share of unmapped findings dropped dramatically — in one case a customer's backlog went from more than 90% unowned to roughly 35% unowned, purely by consuming CODEOWNERS and team data the org already had, sitting unused. The ownership answer was never missing. It was just never being read. A spreadsheet can't read it; an ops layer can.
The single most important property of a trustworthy ops layer is that it can't lie to you about what's done. This is precisely where spreadsheets fail: the "closed" checkbox is a human assertion, decoupled from reality.
Our resolution model is fail-loud. An item reaches completed only after both the item write and its real-world side effect succeed; if the side effect fails, the item stays open. There is no "completed-with-failed-side-effect" state — every item is either open (still needs action) or terminal (action verified). And the system watches the underlying state continuously: if a finding gets fixed through some other path, or a repo is deleted, or a user gets mapped elsewhere, the item auto-resolves because its need genuinely evaporated. But it will never auto-close an item just because nobody answered — silence is not resolution. A spreadsheet row goes stale and no one notices; here the system both refuses to fake a resolution and proactively clears the ones whose need is really gone. (The companion post walks through exactly how that gate is enforced per item kind.)
None of this matters if the ops layer just generates more noise than a human ever could. So the design is aggressively biased toward doing the least work — human and machine — to get the outcome.
The point of all of this is a program that produces outcomes — findings found, triaged, tracked, and fixed — for the least human time and the least token spend. That is the headcount decoupling made concrete: outcomes scale with context and compute, not with how many people you can put on the problem.
Autonomy that you can't steer is its own kind of black box, so the loop is built to stay open to a human without depending on one.
The dashboard is always the system of record. Most orgs don't wire up a messaging platform to their security tooling, so the entire feature is fully usable from the operator UI alone. A Slack or Teams thread is an optional mirror, never a load-bearing path — the feature is never silently disabled because a channel is missing. When no channel exists, an item is explicitly counted as "unrouted" in the summary rather than quietly dropped.
Where a human can reach in, the intervention points are deliberate and narrow:
None of this assumes the automation is always right — it won't be. An ownership cascade can point at the wrong team when CODEOWNERS is stale; a generated justification can misread why a finding matters; a priority can land too high or too low. The design's answer isn't to claim it never happens, it's to make every one of those failures cheap to catch and reverse: the "why this person" reasoning is shown so a wrong routing decision is visible rather than silent, re-route and reopen exist precisely so a human can correct it, and the fail-loud model means the worst case is an item that stays open one beat too long — never one that reports a fix that didn't happen. A mis-routed item is an annoyance you can see and fix; a fabricated "done" is the failure that actually erodes trust, and that's the one the system is built to make impossible.
And everything the system does is measurable. A summary endpoint reports counts by status, kind, and team, plus the unrouted count, powering per-team unread badges. Every escalation is recorded as an event with success or error status, independent of whether the notification actually delivered — so "the escalation isn't in Slack" is never the same as "the escalation was lost." Every action the ops layer takes against a finding — a fix PR, a ticket, a message, an escalation — emits its own auditable event. There is no manual status update someone forgot to make, because there are no manual status updates.
That measurability is also what lets a leader trust the automation enough to hand it real work — but there's a precondition underneath all of it that no dashboard can supply. None of this control matters if the rows themselves aren't believable.
We built all of this on top of validated, triaged findings rather than raw scanner output for a reason. A tracked item is only as good as whether people believe it's real. One customer told us they'd evaluated a competitor that was genuinely good, but the constant battle over false positives and triage was the deal-breaker — a battle they didn't have with us. Their lead developer initially waved findings off as false positives, until he saw a proof of exploit and said, in effect, okay, I'm going to fix these.
That's the whole chain. A spreadsheet row nobody trusts is a row nobody acts on. Automation nobody trusts is automation everyone routes around — and we've seen exactly that, developers hand-fixing findings outside the flow because the system's judgment hadn't earned their confidence yet. The ops layer only scales the loop when the loop is trustworthy end to end: the finding is proven, the priority is right, the owner is correctly identified, and "closed" is verified rather than asserted.
Program management for security has been stuck as clerical work — assign, track, chase, update, repeat — pasted across a ticket board, a chat tool, and a spreadsheet nobody can quite trust. The way out isn't a better spreadsheet. It's moving the judgment into the system: let context decide who owns the code and what the real risk is, let the agents do the assigning and tracking and verifying and closing, and keep a human in the loop for exactly the decisions that need one. That's how a program's outcomes stop scaling with its headcount — which, with attackers scaling on cheap compute and no headcount at all, is no longer a nice-to-have.