Skip to content

Output Gate

The Output Gate is ToolMesh’s content control layer. JavaScript policies run before (pre) and after (post) every tool execution, enabling input validation, output filtering, and PII redaction.

Gate policies are top-level JavaScript files in the policies/ directory. They are executed by goja, a Go-native JavaScript engine, with a 5-second timeout per policy.

Each policy receives a single global variable named ctx and runs in one of two phases:

  • pre — before the tool executes. Mutate ctx.params to rewrite the request, or throw to reject it.
  • post — after the tool executes. Mutate ctx.response.content to filter the response.

Throwing a string or Error rejects the call. If the script completes without throwing, the call is allowed.

FieldDescription
ctx.phase"pre" or "post"
ctx.toolTool name with backend prefix (e.g. github_merge_pull)
ctx.toolAccessDADL access tag: "read", "write", "admin", "dangerous", or empty
ctx.paramsTool parameters (mutate in pre to rewrite)
ctx.response.contentTool response (mutate in post to filter; only populated in post)
ctx.user.userIDAuthenticated user identifier
ctx.user.callerIdCaller identity (e.g. cli, claude-desktop)
ctx.user.callerClass"trusted", "standard", or "untrusted"
ctx.user.rolesArray of user role strings
ctx.rateLimitExceeded(n)Function — returns true if the user exceeded n calls/hour
policies/block-force-delete.js
if (ctx.phase === "pre" && ctx.params.force_delete === true) {
throw "force_delete is blocked by policy";
}
policies/redact-email.js
if (ctx.phase === "post" && ctx.response && ctx.response.content) {
for (var i = 0; i < ctx.response.content.length; i++) {
var block = ctx.response.content[i];
if (block && block.type === "text" && block.text) {
block.text = block.text.replace(
/[\w.-]+@[\w.-]+\.\w+/g,
"[REDACTED]"
);
}
}
}

Mutations on ctx.response.content propagate back to the live response — downstream evaluators and the executor see the redacted content.

Strip Co-Authored-By from GitHub PRs (pre)

Section titled “Strip Co-Authored-By from GitHub PRs (pre)”

Many AI coding agents append Co-Authored-By trailers to commit messages and PR descriptions. A pre-gate policy can strip them before the GitHub API call:

policies/strip-co-authored-by.js
if (ctx.phase === "pre") {
var coAuthorPattern = /^[Cc]o-[Aa]uthored-[Bb]y:.*$/gm;
function stripTrailer(text) {
if (!text) return text;
return text
.replace(coAuthorPattern, "")
.replace(/\n{3,}/g, "\n\n")
.replace(/\n+$/, "\n");
}
if (ctx.tool === "github_create_pull" || ctx.tool === "github_update_pull") {
if (ctx.params.body) {
ctx.params.body = stripTrailer(ctx.params.body);
}
}
if (ctx.tool === "github_merge_pull" && ctx.params.commit_message) {
ctx.params.commit_message = stripTrailer(ctx.params.commit_message);
}
if (ctx.tool === "github_create_git_commit" && ctx.params.message) {
ctx.params.message = stripTrailer(ctx.params.message);
}
}

The AI agent never notices the change, and no Co-Authored-By line reaches your repository.

Read-only enforcement for untrusted callers (pre)

Section titled “Read-only enforcement for untrusted callers (pre)”
policies/untrusted-readonly.js
if (ctx.phase === "pre" &&
ctx.user.callerClass === "untrusted" &&
ctx.toolAccess !== "read") {
throw "Caller class 'untrusted' may only execute read-tagged tools";
}

This relies on the DADL access tag (read, write, admin, dangerous) declared per tool — see the DADL spec.

The gate receives the CallerClass, enabling tiered policies:

CallerClassTypical Filtering
trustedCredentials only
standardHigh-risk PII + credentials
untrustedAll PII patterns, read-only tools

Policies can enforce a sliding-window rate limit per user:

if (ctx.phase === "pre" && ctx.rateLimitExceeded(100)) {
throw "Rate limit exceeded (100/hour)";
}
Terminal window
GATE_EVALUATORS=goja # Enable goja evaluator (default)

Place policy files in the policies/ directory. ToolMesh loads them at startup.

The enterprise extension adds an LLM-based gate evaluator that classifies content against compliance rules. This enables policies like “block responses containing financial advice” without writing regex patterns.