// ai security · 8 min read

How to secure AI driven workflows

When an agent writes and runs your software, the security questions change. The risk is not that the model is malicious. It is that it is confident, fast, and wrong in ways that look right. Here is where AI workflows actually break, and what to put in place so they do not.

The threat model is different, not just bigger

Traditional application security assumes a human wrote the code and a human can be held to a standard. An AI workflow breaks both halves of that. The code appears faster than anyone can review it, and there is often no clear author to ask why a line exists.

That does not make AI software uniquely insecure. It makes it uniquely likely to be shipped before it is checked, because it looks done the moment it runs. So the job is not to distrust the model. It is to assume the output is unverified by default and build the verification into the workflow, the same way a compiler assumes your types are wrong until proven otherwise.

Pitfall: secrets in the code

The single most common and most damaging failure. An agent needs an API key to make something work, so it puts the key in the source. Sometimes in a file that gets committed. Sometimes in client-side code that ships to every visitor. Sometimes in a log line nobody reads until the bill arrives.

The fix is not to tell the agent to be careful. It is to make the secret impossible to reach in the first place.

  • Keep secrets in the platform environment, never in the repo. A secret that is only in a dashboard cannot be committed by accident.
  • Run a secret scanner in CI that fails the build on a key-shaped string, a private key block, or a token literal. Ours is one of the attacks in our adversarial pack, and it reads the real build artifact rather than the source.
  • Rotate anything that ever touched a commit. A key in git history is a key that is public, even after you delete the file, because history keeps it.
  • Give the agent least privilege. If it only needs to read, do not hand it a token that can write, and never hand it a key that can move money.

Pitfall: handlers that throw instead of refusing

An agent writes a handler for the input you described. It works perfectly for that input. Then someone sends an empty field, a very long string, a strange character, or a shape the handler never considered, and instead of refusing the request cleanly, it throws.

A thrown error is not a refusal. It is a failure that leaks. It can expose a stack trace, drop a connection mid-transaction, or leave data half-written. The rule we hold to is simple: handlers refuse, never fail.

The way to hold that rule is not review, it is repetition. Run a fixed set of hostile inputs against the real handlers on every change, and fail the build when one that used to be refused starts throwing instead.

  • Test the handlers, not a mock. A mocked API hides the exact bug you are trying to catch, because the mock answers politely no matter what you send.
  • Keep a ledger of findings and fail CI when a previously refused attack starts passing. That turns security from a one-time audit into a regression gate.
  • Run the campaign against the built artifact, so you are testing what ships, not what you meant to ship.

Pitfall: changes that ship quietly

An agent makes fifty changes across a codebase in an afternoon. Most are correct. One breaks a layout, one drops a heading, one removes the label a screen reader needed. Nothing fails, because nothing was checking, and the regression reaches production dressed as an improvement.

The fix is a set of gates that compare what shipped against what was expected, on every change, so a silent regression becomes a loud one.

  • Visual regression. Capture the key pages deterministically, compare them pixel by pixel, and fail when a page moves beyond tolerance. Ours freezes animation so the comparison is stable, and keeps a committed visual memory in the repo so the canon is reviewable.
  • SEO and structure. Audit the built HTML for titles, canonical tags, structured data, and real content, because a generated single-page app can look perfect in a browser and ship an empty page to a crawler.
  • Accessibility. Scan every route with an automated tool and fail on serious findings. Keyboard traps, missing labels, and low contrast are the ones generation gets wrong most often.
  • Performance and link integrity. Catch the broken link and the bloated bundle before a visitor does.

Pitfall: a family of repos that drifts apart

AI makes it cheap to spin up new repositories. Ten repos later, each one has its own idea of the shared config, its own version of the same utility, and its own way of doing the thing that was supposed to be uniform. Nothing is broken, and nothing agrees.

Left alone, this is where security drift hides: one repo gets the header fix, the other nine do not, and you cannot tell which is which by looking. The fix is governance that is enforced rather than remembered.

  • Keep one manifest of members and one command that verifies every member serves the same truth, so drift is a failing check rather than a surprise.
  • Stamp new repos from proven blocks instead of starting blank, so the secure default is the path of least resistance.
  • Never ship a placeholder. A generated green check on a repo that was never really configured is worse than no check, because it is a lie you will trust.

Pitfall: you cannot leave

The last risk is not a bug in the code. It is a bug in where the code lives. If your application runs only inside a platform you cannot export from, then the platform holds your security posture, your uptime, and your future. A change to their terms is a change to your business, and you have no vote.

The fix is ownership from day one. The code lives in your repository, the secrets live in your environment, the data lives in your database, and the domain is registered to you. The platform you built with becomes a tool you used, not a place you are trapped.

The short version

If you remember nothing else, remember this: the danger of AI workflows is not the model, it is the gap between looking finished and being finished. Close that gap with gates, not with hope.

Keep secrets out of the code. Make handlers refuse instead of fail. Compare what shipped against what you expected, visually, structurally, and for accessibility. Govern a family of repos instead of trusting memory. And make sure the code is yours, so you keep the option to leave.

None of that requires slowing down. It requires putting the checks in the workflow so the fast path and the safe path are the same path. That is what our tools are for, and it is what we do for clients who would rather find the problem in CI than in production.

Want help with this?

Tell us the situation you are in. We will give you an honest read, with the reasoning included.

Start a Conversation