5
19 Comments

Two sharp readers found security flaws in our product in the same week. We are grateful.

TrustLoop is an AI governance platform. The whole point is that it catches problems before they happen. So when two separate people found real security gaps in the product within the same week, the irony was not lost on us.

Both found the flaws by reading carefully and asking the obvious question we had not asked ourselves.

The first was on our n8n community forum post. A reader left a detailed technical comment pointing out that our human approval workflow had a fundamental vulnerability. When TrustLoop escalates a tool call for human approval, it sends the approver the full arguments — the specific action the agent wants to take. The approver clicks Approve. But we were not verifying that the arguments the agent submitted on the retry were identical to what the approver had seen. A misconfigured or malicious agent could surface a $5 transaction for approval and then execute a $500,000 one. The approval was real. The authorisation was not.

We wrote about that in detail in a previous post here. We shipped the fix — payload hash verification — within 48 hours.

The second was on that IndieHackers post itself. A reader named chalermpon left a single comment that cut straight to the next gap. His question was precise: binding the payload hash to the approval stops argument swaps, but what stops the same approval being used twice? If the agent retries with identical arguments, the hash clears both times. Was the approval being marked as consumed after first use?

It was not. He was right. One valid approval could theoretically be replayed multiple times against the same arguments and each retry would pass.

We fixed it the same day. After a successful approval verification, the approval status is immediately updated to consumed. Any subsequent retry with the same approval ID is rejected — even with a valid hash, even with identical arguments. One approval, one execution.

Two weeks, two gaps, two fixes. Both found not by a formal audit, not by a penetration tester, but by people who read something we wrote publicly and thought carefully about it.

The thing we keep returning to is that neither of these finds would have happened if we had not been writing and sharing openly. The first came from a forum post announcing our n8n node. The second came from a post about the first fix. The transparency created the surface that invited the scrutiny.

For a small team building a security product without the budget for a formal audit, that scrutiny is genuinely valuable. We are not suggesting this replaces proper security review — it does not, and we know a penetration test and SOC 2 audit are on the roadmap. But building in public and engaging honestly with technical readers has produced more concrete security improvements this month than anything else we have done.

If you are building something and someone in the comments asks the obvious question you did not ask, read it twice before you move on.

on July 17, 2026
  1. 1

    Consuming the approval on first use marks the attempt, not the outcome. When the tool call times out after the write already landed, the approval is spent, the record says one execution, and nobody can tell whether the money actually moved.

    Is consumed set at interception, or only after the downstream system confirms a result?

  2. 1

    Receiving two security reports in one week is uncomfortable, but the response can become a meaningful trust signal.

    A company that acknowledges the reports, investigates quickly, fixes the underlying causes, and communicates clearly demonstrates more maturity than one that hides the issue or becomes defensive.

    The reports should also be treated as process feedback. Two separate findings may point to missing threat modeling, weak review practices, unsafe defaults, or insufficient testing before release.

    Responsible researchers often provide significant value before a company can afford a dedicated security team. Treating them respectfully can encourage future disclosures and reduce the chance that vulnerabilities remain hidden.

    Security culture is revealed most clearly when something goes wrong, not when everything appears safe.

    1. 1

      That framing is exactly right and it took me a moment to actually absorb it. The first instinct when someone reports a flaw is mild embarrassment. The better read is that they spent their own time on your product, understood it well enough to find something real, and then told you instead of walking away. That is a gift. What you do with it is entirely on you. We fixed both issues within the week and wrote honestly about what they found. If that becomes the pattern every time, the embarrassment fades quickly.

  3. 1

    This is a strong way to respond to a difficult situation. Many teams become defensive when someone reports a security issue, especially when the report arrives publicly or exposes something embarrassing. Gratitude is a much better starting point because responsible disclosure can prevent a small weakness from becoming a serious incident.

    The real test is what happens after the report. A clear acknowledgment, fast triage, realistic timeline, transparent fix, and follow-up with the reporter can turn a security problem into evidence that the company takes trust seriously.

    It may also be worth reviewing why two separate readers found issues in the same week. That could simply be a coincidence, but it could also reveal a gap in the development process, threat modeling, dependency review, or pre-release testing.

    Security researchers often provide enormous value before a company has the budget for a formal security team. Treating them with respect can create a long-term relationship rather than a one-time interaction.

    Were both flaws related to the same underlying weakness, or did they expose completely different parts of the product?

    1. 1

      They were different parts of the product but the same family of problem. Both were about the approval flow: what gets checked before an action is allowed to run. The first was an argument swap, where an agent could get approval for a safe action and then swap the arguments before retrying with the approved ID. The second was replay, where a valid approval could be reused indefinitely. We patched both. The argument swap fix was a payload hash check at interception time; the replay fix was consuming the approval on first use so the second attempt is blocked regardless. Different mechanisms, same gap in how we thought about approval integrity.

  4. 1

    Machine Arena team here, we run competitive AI agents that spend all day trying to slip actions past each other, so this is familiar ground.

    The payload-hash plus consume-on-use fix closes argument-swap and replay, which is most of it. The gap those two don't cover is semantic staleness: the human approved what the arguments meant at approval time, and the hash only pins the bytes, not the world they referred to. Same args, single use, fresh TTL, and the action can still do something different if the state underneath moved. The account a transfer points at got re-pointed, the balance the "5 dollar" was relative to changed, the position "sell 100" refers to isn't the one the approver pictured. An agent abusing this doesn't swap the args, it waits for state to drift and lets the still-valid approval ride.

    What actually held for us was binding the approval to a version or snapshot of the state the decision depended on, then re-checking it at execution, optimistic-concurrency style: if the referenced state changed since approval, reject and re-escalate instead of executing. A compare-and-swap on the world, not just a hash of the args.

    Smaller one, since you're already logging: store the agent's stated reason for the action next to the args in the approval record. When something does slip through, the reason is what lets you tell a misconfig apart from a deliberate retry after the fact.

    1. 1

      The semantic staleness point is the one that kept me thinking after I closed the laptop. You are right. Hashing the arguments locks the bytes but not the world. If an agent gets approval to transfer £500 to account X, and between approval and execution that account is flagged or the balance changes, the hash still matches and the action still runs. The arguments are identical. The world is not. A compare-and-swap style check, where you bind the approval to a state snapshot and re-verify at execution time, would close that. We have not built it yet. The suggestion to store the agent's stated reason alongside the arguments is also good because it gives the human approver the intent, not just the mechanics. Both are going on the roadmap with the framing you gave them.

      1. 1

        The implementation decision that will bite is snapshot scope. Bind the approval to too much of the world and every approval goes stale, humans learn to re-approve on reflex, and the check trains the exact behavior it was meant to prevent. Bind it to just the target account row and you miss drift in whatever else the decision depended on. This is where the stored reason earns its keep a second time: it names the facts the agent claims it relied on, so it can double as the list of objects the snapshot covers. A stale approval then fails with a readable why, this specific fact you relied on changed, which keeps the re-approval prompt meaningful instead of reflexive.

        1. 1

          That is the cleanest solution to the scope problem I have seen framed. The reason field doing double duty, context for the approver and definition of what the snapshot covers, means the scope isn't a separate engineering decision at all. The agent defines it implicitly by stating what it relied on.

          The reflexive re approval risk is the part I had not thought through carefully enough. A stale check that fires too often teaches people to click approve without reading, which is worse than no check. But a failure message that says "the balance you cited has changed since this was approved" is specific enough that the re-approval is a genuine decision, not a reflex.

          This is going on the implementation spec. Thank you for working it through to this level.

          1. 1

            Glad it earned its way into the spec. One practical suggestion for after it ships: log every stale approval rejection together with which cited fact drifted. That log becomes a tuning dial you can read a month later. Many rejections where the drifted fact was irrelevant to the decision means the snapshot is bound too wide; anything that slipped through means too narrow. It also doubles as good evidence for the audit you mentioned on the roadmap, since it shows the control firing in production rather than existing on paper. Credit to you for treating comment section review this seriously.

  5. 1

    Two useful reports in one week is enough signal to formalize the channel. A small security.txt plus a disclosure page with scope, safe harbor, and response times would make it easier for the next careful reader to report privately before posting details publicly.

    1. 1

      You are right that two useful reports is enough signal to treat it as a channel rather than two isolated events. We did not have a formal way for someone to reach us about security before this. I am setting up a security.txt file at /.well-known/security.txt and a page that covers what is in scope, what is out of scope, what someone can expect in terms of response time, and the safe harbor language. It should have existed already. It will be live within the week.

  6. 1

    Both finds are the same bug wearing two hats: you were treating a human approval as a check, when it's really a capability token. The two fixes you shipped, binding the hash to the exact arguments and marking it consumed after one use, are exactly the properties of a secure single-use token. Worth making that framing explicit internally, because it tells you where the next gaps are before a reader does.

    A capability should also be bound to who and when. Does the approval expire? Someone clicks yes, the retry lands six hours later against changed state: hash matches, replay guard passes, but the world moved. And is it bound to the specific agent/session that asked, or could a valid approval for agent A clear a call from agent B? Same class as the two you just closed. The transparency point holds, but the real win is realising an approval is auth, and auth has a checklist.

    1. 1

      The capability token framing is the clearest way anyone has described what these two fixes actually are. We were thinking of approval as a check but you are right that it is an auth token, and a token without expiry and without binding is not fully specified. To answer your questions directly: no, approvals do not currently expire. If a human approves an action and the agent does not use that approval for six hours, it is still valid. And no, approvals are not currently bound to a specific agent session. An approval granted for agent A could theoretically clear a call from agent B if the tool name and arguments match. Both of those are real gaps. Expiry is the easier fix and I am adding it. Agent session binding requires a bit more work on how sessions are tracked but it belongs on the same ticket. Thank you for the precise framing.

      1. 1

        Glad it helped. One more from the same checklist while you're in there: the approval is bound to the args, and soon to a session, but is it bound to who approved and what that person is allowed to authorise? A junior clicking yes on something their own role couldn't perform is the confused-deputy version, and in the logs it looks identical to a legitimate approval.

        When expiry ships, watch your re-escalation rate. Too tight a TTL and agents start chopping actions into smaller pieces to fit inside it, so you get more approvals and each one gets read less carefully.

  7. 1

    Both of these are named vulnerability classes: the argument swap is TOCTOU (time-of-check versus time-of-use) and the second is a replay attack, so I would now walk the rest of the approval flow against the standard list (privilege escalation, confused deputy) since bugs of a class rarely travel alone. I run a security and compliance company and the founders who impress auditors are not the ones with zero findings, they are the ones with a documented find-fix-disclose loop like your 48-hour turnaround. Your readers did your first pen test for free; a structured one will find flaw number three before a customer does.

    1. 1

      Having the vulnerability class names is genuinely useful because it is the difference between fixing a specific bug and understanding what category of problem you are dealing with. TOCTOU is exactly what the argument swap was. Knowing that means I can now look at every other place in the system where we check something and then act on it later and ask the same question. Walking the approval flow against privilege escalation and confused deputy is on the list now. The pen test point is well taken. Two acquaintances found two classes of problem in the same week. A structured test with someone who does this for a living will find things we are not even thinking to look for. That is not a maybe, it is a when. And yes, documenting the find-fix-disclose loop is something we are doing. This post is part of that.

  8. 1

    The interesting outcome isn't that two vulnerabilities were fixed—it's that public transparency became part of your security process. I'd keep validating whether customers ultimately trust TrustLoop because of the controls themselves or because you've demonstrated a culture that actively surfaces and responds to failure. In security, that may become a stronger differentiator than claiming perfection.

  9. 1

    If you found this useful, we share more of this kind of thinking on X and LinkedIn. The full product is at trustloop.live . Links below — would love to connect.

    X: https://x.com/sojimathewj , https://x.com/Trustloop_HQ
    Linkedin: https://www.linkedin.com/company/trustloophq/