AI security with Monofor: gateways, guardrails and machine credentials
What changed
Every content update is versioned. Select a version to compare it with the one before.
Version 8 → 9
UpdateFour new lessons close the gap to the MCASP exam: approvers and lost credentials in practice, running the gateways (time limits, streaming, token audience), AI flows in operation, and choosing and scoping a machine identity. Each module quiz has two new questions. Diagrams were added to the new lessons.
4 lessons updatedduration
Content update. Enrolled learners were notified.
Duration changed from 66 to 67 minutes.
An approval policy looks simple on paper. In daily work three questions decide whether it holds: who is really asked, how do they hear about it, and what do you do when a credential secret is gone. This lesson walks through each with one example agent.
### Worked example: an agent that starts refunds
The agent `refund-bot` can start refunds. The security lead wants two named finance leads to sign off together and to explain every decision. Open the agent and its `Approvers` tab:
| Setting | Value | Why |
| --- | --- | --- |
| Mode | `Choose for this agent` instead of `Use the default` | The tenant default asks managers, then governance owners. That is too broad here |
| Step box | `These people`, with both leads | Named people, not whoever happens to manage the agent |
| `How many must approve` | `Everybody asked` | With `Any one` a single lead could decide alone |
| `Approvers must write why` | On | A decision without a reason is not accepted |
| `Also tell them by` | `Push to the authenticator app` | This is the default. `E-mail with approve and deny links` can be ticked as well |
The console bell in Management is always used, whatever you tick under `Also tell them by`. So a step left at its defaults reaches approvers in two ways: the bell and a push to the Authenticator app. E-mail is the one you add on purpose.
After every change read the panel `Right now a request would go to`. It lists the people the policy finds at this moment.
### Who counts as an approver
Only **enabled** people count, and the person the agent acts for is taken off the list. That is why an agent can have an owner on paper and still nobody to ask.
Example: an agent was promoted from `Detected` and given one governance owner. No Direct Manager was ever added. The owner leaves the company and the account is disabled. The next request for this agent is not left waiting. It is refused at once, because a request that nobody can answer would only expire later with the same result, after the caller has lost minutes.
Two habits prevent this: add a second step with `Add a fallback: who to ask when nobody above is found`, and check the panel when people change roles.
### How long a request waits
`A request waits for` on the step is 10 minutes by default. An API caller that raises a request may send its own `ExpirationSeconds`. A positive value wins over the policy and is capped at 86400 seconds, which is one day. Without it the policy decides.
### A lost credential secret
The secret of an agent credential is shown once, when it is created. It cannot be displayed again, not even to an administrator. When a developer did not store it:
1. Open the agent's `Credentials` tab and `Revoke` the credential.
2. Use `Create Credential` and hand the new secret over safely.
Revoke first, because nobody knows where a copy of the old secret might be. Remember that revoking a credential does not end tokens that were already issued. If you suspect misuse, use `Kill Switch`.
::diagram ais-lost-credential
### A budget on tokens, not on calls
`Token issuances / hour` under `Budget & Quotas` limits how often the agent may obtain a token. The request limits cap calls. An agent that is refused a token writes `AGENT_TOKEN_DENIED` with the reason on its `Audits` tab, so read that reason before you raise a limit.
Once traffic flows through the gateways, the questions change. People no longer ask "is this allowed" but "why does it behave like this". Five behaviours explain most of those questions.
| What people notice | Cause | Where you see it |
| --- | --- | --- |
| Long tasks fail after about five minutes, short ones work | `Timeout (seconds)` of the provider | Outcome HTTP 504 on the call |
| An answer arrives in one piece instead of word by word | The gateway had to read the whole answer first | `translated` or `cache` on the call, or an AI flow on the answer |
| HTTP 401 with a bearer token, agent keys work | The token was not issued for this route | Outcome `DENIED (audience)` |
| A delegated call is refused although delegation is on | The chain is too long or names an agent twice | The token request is refused |
| A smaller model answers than the one requested | `Cheaper model close to a limit` | Header `X-Monosign-Downgraded-From` |
### The time limit of a provider
Every provider has a `Timeout (seconds)`. It is 300 by default and accepts 5 to 3600. It covers the wait for the first byte and the wait for each streamed line.
Example: a reasoning model thinks for six minutes before it sends anything. With the default the gateway gives up after 300 seconds and answers HTTP 504 `The AI provider did not answer within {n} seconds.` Short prompts on the same provider work, which makes the problem look random. The fix is on the provider: raise `Timeout (seconds)`. Retrying the same call only repeats the wait.
### Why a streamed answer can arrive all at once
Normally the gateway passes a streamed answer on as it arrives. In three cases it must hold the complete answer first:
- the answer is translated between provider formats (`Let clients of the other format in`),
- the answer comes from the answer cache,
- an AI flow on the `Response` stage inspects it.
The gateway then fetches the answer whole and replays it to the client as a stream. This is expected behaviour, not a fault. A flow cannot hide a card number in text that has already left, so inspecting an answer means reading it to the end first. Tell users before you turn such a flow on.
### Tokens must be issued for the route
A route accepts `Only tokens issued for this route` by default. An OAuth client therefore names the route when it asks Monosign for a token: it sends the `resource` parameter set to the registered gateway route (RFC 8707).
- No `resource`: HTTP 401 `The token names no resource. Request it with the resource parameter set to this gateway route.`
- A token for something else: `The token was issued for another resource.`
Fix the caller, not the check. Loosening `Which tokens are accepted` would let a token made for one service be used at another. Agent keys are not subject to this check, and the route must be registered with `Register` before tokens can be issued for it.
::diagram ais-route-token-resource
### A ceiling on delegation chains
`Agent to agent delegation depth` decides how far a person's task may be handed on. Above that setting there is a fixed ceiling: a chain deeper than 8 agents, or one that names the same agent twice, is always refused. That stops two agents from passing aAn agent that is already part of the chain cannot be handed the task back and forth foreveragain.
### When a cheaper model answers
When `Cheaper model close to a limit` moves a call to a cheaper model, the response carries the header `X-Monosign-Downgraded-From` with the model that was requested, and the call record keeps the same fact. Developers can read the header and show users why the answer looks different.
Building a flow takes minutes. Running one well depends on details that are easy to miss: how to try it safely, what the sender must do after an approval, which findings can be hidden, and which traffic a flow never touches.
### Three ways to try a flow
| | `Test this flow` | `Watching` | `Enforcing` |
| --- | --- | --- | --- |
| Input | Sample text you type in the designer | Real traffic | Real traffic |
| Guard services, webhooks, the judge model | Not called | Called | Called |
| A person is asked | Never | Never | Yes |
| A workflow is started | Never | Never | Yes |
| The message is changed or stopped | No | No | Yes |
`Test this flow` runs with dry services: it shows how your steps and patterns react to a text, but it calls nobodyno guard service, no webhook, no judge model and no approver. A watching flow sees real traffic and writes what it would have done into `Runs`, with notes such as `would block`, `would ask for approval` and `would start a workflow`. Read those notes before you choose `Start enforcing`.
### After `Ask a person`: send it again
Monosign does not keep a message open while a person decides. The sender is told `This message needs approval before it can go on. Send it again once it is approved.`
How does Monosign know the second message is the approved one? It compares a fingerprint made of the flow, the step, the surface, the target, the tool, the person, the agent and the text.
Example: at 10:00 an agent sends a contract summary and the flow holds it. At 10:10 the approver approves. At 10:11 the agent sends the identical message, and it passes. Three rules follow:
- The same message within 30 minutes reuses the existing request. It does not raise a second one.
- An approved request lets the message pass once.
- A changed text is a new message and needs a new approval. A refused request blocks.
Agent developers must build this in: keep the message, wait, and resend it unchanged.
::diagram ais-flow-approval-resend
### Which findings can be hidden
`Hide what was found` needs to know **where** in the text a finding sits. Only two sources give positions:
| Source of the finding | Can block | Can feed `Hide what was found` |
| --- | --- | --- |
| Built-in `Find patterns` | Yes | Yes |
| `Ask a guard service` with `Microsoft Presidio` | Yes | Yes |
| The other guard services | Yes | No |
So a customer who already runs Presidio places an `Ask a guard service` step and a `Hide what was found` step below it. A guard service that only says "this message is harmful" can stop the message but cannot redact it.
### Scope: the `automation` exception
A flow without limits under `Where does it apply?` applies everywhere, with one exception: prompts of Monosign's own `automation` feature. Those are touched only by a flow that names `automation` under `Only in these Monosign features`. If the audit asks why automation prompts were never inspected, this is the first place to look.
### Flows created through the API
| | Designer | API without the field |
| --- | --- | --- |
| Mode | Starts as watching | `Enforce` |
| What a recorded run keeps of the text | A short excerpt with findings blanked out | Nothing |
A flow created through the API without `Mode` therefore acts on real traffic as soon as its state becomes `Active`. Teams that deploy flows from a pipeline should always send `Mode` and `RetentionMode` explicitly.
### People who keep hitting the policy
Blocks of enforcing flows count toward the detection `AI Policy Violations`, by default 5 blocked messages in 60 minutes. The detection sets `user.hasActiveThreat` for that person. Because flow conditions can read `user.hasActiveThreat`, another flow can be stricter for exactly these people, for example by asking a person where others pass.
The previous lesson introduced machine identities. This one helps you choose between the options, because each choice moves a risk from one place to another.
### Which auth method
| Auth method | Secret stored at the workload | Needs | Fits |
| --- | --- | --- | --- |
| Universal Auth | Yes, a client secret | Nothing else | Anywhere, when nothing better is available |
| OIDC | No | An issuer that is publicly discoverable | CI pipelines such as GitHub Actions and GitLab, managed clusters with an OIDC issuer |
| Kubernetes | No | Monopam must reach the cluster's API server | Any reachable cluster, including self-managed ones |
OIDC login does not work for a self-managed cluster whose issuer is only known inside the cluster. Use a Kubernetes identity there.
::diagram ais-machine-auth-choice
### OIDC for a pipeline: the bound claim
An AI coding agent runs in a CI job. The CI platform already gives every job a signed token, so no Monopam secret needs to live in the pipeline. The identity has three fields:
- `Issuer`: the token issuer of the CI platform, for example `https://token.actions.githubusercontent.com`.
- `Audience`: checked when it is set.
- `Bound claim (sub)`: which workload may log in. A wildcard at the end is supported, as in `repo:my-org/*`.
TheFill in the bound claim. The login checks the token against the issuer's keys, the audience when one is what matters mostset, and the bound claim. A public CI platform issuessigns the tokens for all of all its customers, so trusting with the same issuer alone would trust far too many pipelines. The, and `Bound claim (sub)` claim narrowsis the trustfield that ties the identity to your own repositories.
### Kubernetes: who reviews the token
Monopam does not verify a service account token itself. It sends the token to the cluster for a TokenReview. That review needs a credential, and you choose which one:
| | `Reviewer Token` set | `Reviewer Token` empty (self-review) |
| --- | --- | --- |
| Who is bound to `system:auth-delegator` | One reviewer service account | Every bound workload service account |
| Extra rights for workloads | None | A cluster-wide permission each |
| Stored in Monopam | One cluster token, encrypted | Nothing |
If the priority is that agent workloads receive no extra rights in the cluster, choose a Reviewer Token. If the priority is that Monopam stores no cluster token, choose self-review and accept the wider binding.
### Grants: project, environment, path, write
A grant has `Project`, `Environment`, `Path` and the switch `Write`.
- `Environment` is `All environments` unless you pick one. That also covers environments created later.
- `Path` is a prefix: `/db` covers `/db` and everything below it.
- `Write` is off unless you turn it on.
Example: a reporting agent needs the database settings of the `billing` project in `staging`, read only. The grant is project `billing`, environment `staging`, path `/db`, `Write` off. Leaving the environment empty would also hand it production.
### Where the read audit window lives
`Machine read audit window (minutes)` is under `System → Secrets API`, in the card `Audit`. It applies to reads through the machine API, the MCP tool `get_secret` and the Vault-compatible API. With 0 every read gets its own entry.
### Sessions through the MCP server
For work on a server, an agent with a user token uses these tools instead of a password:
| Tool | Purpose |
| --- | --- |
| `open_session` | Opens a brokered session. Answers `approval_pending` when the account needs approval |
| `session_exec` | Runs a command line in an SSH session |
| `session_query` | Runs a query in a database session |
| `close_session` | Ends the session |
The credential stays in Monopam, so the model never sees it. An SSH session is recorded, appears in Live Sessions where an operator can take over or terminate it, and every command line passes the privileged command check.