Where a key may be sent
Each key goes only to the hosts it is pinned to, and only over https. A tool cannot send it anywhere else, whoever wrote the tool.
The rule
A tool uses a key by writing {{secrets.NAME}} in its address, headers or body. Every call is
checked after the placeholders are filled, just before it goes out:
Redirects are checked the same way. A redirect is followed only to a listed host, five hops at most.
How a key gets its hosts
You rarely set the list by hand. The first time you save a tool that uses a key, the key is pinned to every host it is already sent to in the workspace:
- the hosts in this save,
- this agent's draft and every stored version of it, so a conversation still on an older version keeps working,
- and, for a workspace key, every other agent that sends it, archived agents included.
So the first pin never cuts off a tool that was working. You typed those hosts, so saving is the consent: there is no extra dialog. The pin is written only after the save succeeds, and two first saves at the same moment keep both their hosts.
| Moment | What the key may reach |
|---|---|
| Not pinned yet | Only the host its tool writes out in full |
| First save of a tool that uses it | Pinned to every host already in use |
| Any later save | Only the pinned hosts. A save that sends it to a new host is refused |
| You add a host on purpose | The list you set with setAllowedHosts |
Only a destination the save adds is checked. An unrelated edit to an agent is never blocked by a key it already uses.
Write the host out in full
The host decides where a key goes, so a tool that sends a key must start its address with the scheme and a written-out host. A placeholder cannot build them.
| Address of a tool that sends a key | |
|---|---|
https://api.helpdesk.example.com/v2/tickets/{{params.id}} | Saved |
https://{{params.region}}.helpdesk.example.com/v2/tickets | Refused: a placeholder builds the host |
{{secrets.BASE_URL}}/v2/tickets | Refused: a placeholder builds the scheme and host |
http://api.helpdesk.example.com/v2/tickets | Saved, but never called: a key goes only over https |
A tool that sends no key may keep placeholders anywhere, as before. Only a new or changed tool is checked when you save. An older tool you have not touched still saves, but its calls that use a key are refused until its address is written out in full.
Addresses are saved in one spelling: trimmed, the scheme and host in lower case, and a default port
(:443 for https) dropped. So HTTPS://API.Example.com:443/x and
https://api.example.com/x are the same host.
What gets refused, and what it says
Every refusal names the fix. The messages are exactly these:
| When, and where you see it | The message |
|---|---|
| A save sends a pinned key to another host. The save fails with 400, code secret_host_not_allowed. The SDK throws SecretHostNotAllowed | HELPDESK_TOKEN is only sent to api.acme.example. Use a separate secret for api.other.example. |
| A save gives a tool that sends a key a placeholder host. The save fails with 400 | Type the host in the URL yourself. The agent cannot choose where a secret goes. |
| A call goes to a host not in the list. The tool's answer to the agent | The key HELPDESK_TOKEN is only sent to api.acme.example, so the call to api.other.example was not made. |
| A call uses a key that has no host it may go to. The tool's answer | The key HELPDESK_TOKEN is not set up to be sent to any host, so the call to api.other.example was not made. |
| A call's key has no value (never given, or the attached secret was deleted). The tool's answer, which names no key | This tool is not available right now. Tell the person, in plain words, that you cannot do this at the moment, without a reason or technical detail, and carry on with what you can do. |
| A pushed sign-in has expired (a value your app pushed for the conversation, past its expiry). The tool's answer, which names no key | The sign-in this tool uses has expired, so the call was not made. Tell the person, in plain words, that their sign-in expired and the app needs to sign them in again. |
| A call that uses a key is not https. The tool's answer | This tool sends a key, and a key is only sent over https, so the call was not made. |
| A secret value is shorter than 8 characters. Creating or replacing the secret fails with 400, code secret_value_too_short | value must be at least 8 characters: a shorter one cannot be hidden when an API echoes it back |
| An import cannot find a tool's base address. In draft.warnings, and in Import's skipped list in the app | Skipped GET {{baseUrl}}/tickets: This collection does not include its base address (baseUrl). Add it to the collection's variables in Postman, then import again. |
A refused call is an ordinary tool answer, so the agent explains it to the person and the conversation goes
on. The key's value never appears in it, and the turn's tool event for that call ends with
ok: false.
We hide a key's value if an API ever echoes it back in a reply, including its usual re-spellings:
URL-encoded, base64, and without a Bearer prefix. A value shorter than 8 characters could not be
hidden without mangling ordinary text, so it is not accepted as a secret.
Send a key to another host
Usually, use a separate key. One key per service keeps each key's reach small, and a leak of one does not expose the other. Create the second key under its own name and use that name in the new tool.
When one key really serves two hosts, add the host on purpose from the SDK. Saving a tool never adds one.
// pin it when you create it
const key = await ws.secrets.create({
name: 'HELPDESK_TOKEN',
value: process.env.HELPDESK_TOKEN,
allowedHosts: ['api.helpdesk.example.com'],
});
// later: replace the list (it is not a merge, so name every host)
await key.setAllowedHosts(['api.helpdesk.example.com', 'uploads.helpdesk.example.com']);
key.allowedHosts; // ['api.helpdesk.example.com', 'uploads.helpdesk.example.com']
// [] unpins it: the next tool saved with it pins its own host again
await key.setAllowedHosts([]);| An entry | Matches |
|---|---|
api.example.com | Exactly that host |
*.example.com | One more label: api.example.com, not a.b.example.com and not example.com |
api.example.com:8443 | That host on that port |
Write the host alone, with no https:// and no path. At most 20 hosts per key. A wrong entry is
refused with the reason, for example: allowedHosts: "https://api.example.com" is not a host. Write the
host alone, like "api.example.com", with no https:// and no path.
Handling the refusal in code
import { SecretHostNotAllowed } from '@cerebro-labs/agentnava-sdk';
try {
await agent.update({ httpTools });
} catch (err) {
if (err instanceof SecretHostNotAllowed) {
err.code; // 'secret_host_not_allowed'
err.secret; // 'HELPDESK_TOKEN'
err.host; // 'api.other.example.com'
err.allowedHosts; // ['api.helpdesk.example.com']
}
throw err;
}SecretHostNotAllowed is an AgentNavaError, so code that branches on
err.code keeps working.
An end user's token
| The person's token is | It may be sent to |
|---|---|
Stored for them and attached, with allowedHosts | Only the hosts in that list. It is read from the stored secret at every call, so a list you change applies to running conversations too |
| Stored for them and attached, with no list | Only the host written out in the tool that names it |
Pushed with the conversation (secrets on start, ask or attach) | Only the host written out in the tool that names it |
const key = await ws.endUser(user.id).secrets.create({
name: 'HELPDESK_TOKEN',
value: token,
allowedHosts: ['api.helpdesk.example.com'],
});In the app
- Where a key may go is fixed the first time a tool that uses it is saved. The app cannot change it.
- To send a key to another host, add a separate key on the Secrets tab and use it in the new tool.
- The Agent Builder never changes where a key goes. If a change it makes is refused because a key is only sent to another host, it tells you to add a separate key.
- A token you type into an HTTP tool must be at least 8 characters.