> ## Documentation Index
> Fetch the complete documentation index at: https://fentaris.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Security

> Security practices and patterns for Fentaris proxies.

> Keep Fentaris deployments safe with centralized identity, policy, secrets, and audit controls.

Start with allow-list policy, authenticated users, scoped upstream servers, and structured logging. Add runtime middleware only for checks that depend on request data.

## Quick Start

Declare the proxy with high-level helpers and keep security rules close to the upstream MCP server they govern.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { fentaris, group, mcp, policy, stdio, user } from "@fentaris/core";

const productionPolicy = policy("production")
  .mcp("filesystem")
  .allow("read_file")
  .mcp("filesystem")
  .deny("write_file")
  .mcp("filesystem")
  .deny("delete_file");

const proxy = fentaris({
  groups: [
    group({
      id: "readers",
      users: [user("alice", { tenantId: "acme" })],
      policy: productionPolicy,
    }),
  ],
  servers: [
    mcp("filesystem", {
      transport: stdio({
        command: "npx",
        args: ["-y", "@modelcontextprotocol/server-filesystem", "/srv/shared"],
      }),
    }),
  ],
  autoLog: true,
});
```

Use allow-list policy in production. Deny rules are useful for explicit exceptions, but the baseline should be the smallest set of tools and capabilities each group needs.

## Guard Destructive Tools

Policy should block known destructive tools before runtime middleware runs.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
const supportPolicy = policy("support")
  .mcp("github")
  .allow("search_issues")
  .mcp("github")
  .allow("create_issue")
  .mcp("github")
  .deny("delete_repo");
```

Add middleware when the decision depends on request arguments, tenant state, or a live approval system.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
proxy.mcp("github").tool("create_issue", async (ctx, next) => {
  if (typeof ctx.args?.title !== "string") {
    return ctx.deny("Issue title is required.");
  }

  ctx.log.info("github.create_issue.validated", {
    subject: ctx.subject?.id,
    groups: ctx.subject?.groups.map((group) => group.id),
  });

  return next();
});
```

## Govern All MCP Capabilities

Tools are only one MCP surface. Resources, resource templates, prompts, and completion can expose data or shape model behavior.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
const leastPrivilege = policy("least-privilege")
  .mcp("filesystem")
  .allow("read_file")
  .mcp("filesystem")
  .allowCapability({
    operation: "resources:list",
    targetKind: "resource",
  })
  .mcp("filesystem")
  .denyCapability({
    operation: "resource:read",
    target: "file:///secrets.env",
    targetKind: "resource",
  });
```

Use list permissions for discovery and direct-operation permissions such as `resource:read`, `prompt:get`, and `completion:complete` before Fentaris forwards a request upstream.

## Scope Servers Per Group

Use groups to make server access explicit. This keeps tool visibility, credentials, and policy tied to the caller.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
const proxy = fentaris({
  groups: [
    group({
      id: "support",
      users: [user("alice")],
      servers: [
        mcp("github", {
          transport: stdio({ command: "github-mcp-server" }),
        }),
      ],
      policy: policy("support")
        .mcp("github")
        .allow("search_issues")
        .mcp("github")
        .allow("create_issue"),
    }),
  ],
});
```

Server-scoped policy also reduces accidental exposure when a new upstream MCP server is added to the proxy.

## Compose App-Level Policy And Groups

For incremental proxy setup, declare policies and groups from the app handle before `start()`.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { fentaris, stdio, user } from "@fentaris/core";

const app = fentaris({ autoLog: true });

app.policy("readonly")
  .mcp("github")
  .allow("search_issues");

app.policy("maintainers")
  .mcp("github")
  .allow("*")
  .mcp("github")
  .deny("delete_repo");

app.group("guests")
  .users(user("guest"))
  .policy("readonly");

app.group("maintainers")
  .users(user("alice"), user("bob"))
  .policy("maintainers");

app.mcp("github", {
  transport: stdio({ command: "github-mcp-server" }),
});
```

Fentaris validates the final app before serving. Missing named policies, empty groups, duplicate declarations, and policies that still reference missing upstream MCP servers fail startup with configuration diagnostics.

## Protect Secrets

Bind secrets through credentials or environment injection. Do not pass secrets through user-controlled metadata or tool arguments.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { bearer, credential, credentialEnv, fentaris, group, mcp, policy, streamableHttp, user } from "@fentaris/core";

const proxy = fentaris({
  groups: [
    group({
      id: "maintainers",
      users: [user("bob")],
      credentials: {
        "github.token": credentialEnv("GITHUB_TOKEN"),
      },
      policy: policy("maintainers").mcp("github").allow("*"),
    }),
  ],
  servers: [
    mcp("github", {
      transport: streamableHttp({ url: "https://github.example/mcp" }),
      auth: bearer(credential("github.token")),
    }),
  ],
});
```

<Note>
  Store production secrets in your infrastructure secret manager and inject them at deploy time. Keep `.fentaris/credentials.enc.json` private when using local encrypted credentials.
</Note>

Do not forward whole environment maps into HTTP headers. Use `auth`, `resolveHeaders`, or an explicit `envHeaderMap` so token-like variables such as `GITHUB_TOKEN` are not sent upstream unless you map them deliberately.

## Validate Identity

When users declare API keys, Fentaris uses API-key identity by default with the `x-fentaris-api-key` header. Store API keys as credential sources, such as environment variables or encrypted local credentials, instead of literal strings in code.

For local encrypted credentials, register a downstream client API key with the CLI:

```bash theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
printf '%s' "$ADMIN_API_KEY" | fentaris auth api-key add alice --value-stdin
```

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { credentialEnv, fentaris, group, mcp, policy, stdio, user } from "@fentaris/core";

const proxy = fentaris({
  groups: [
    group({
      id: "admins",
      users: [
        user("alice", {
          apiKeys: [credentialEnv("ADMIN_API_KEY")],
        }),
      ],
      policy: policy("admins").mcp("*").allow("*"),
    }),
  ],
  servers: [
    mcp("admin", {
      transport: stdio({ command: "admin-mcp-server" }),
    }),
  ],
});
```

Trusted identity headers should only be used behind an internal gateway that already authenticated the caller. Do not expose direct user-id headers to untrusted clients.

## Audit Decisions

Enable `autoLog` and add stable tags for traceability. Logs should include metadata, not raw payloads or secret values.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
const proxy = fentaris({
  servers: [
    mcp("github", {
      transport: stdio({ command: "github-mcp-server" }),
    }),
  ],
  autoLog: true,
});

proxy.use(async (ctx, next) => {
  ctx.log.setTag("subject", ctx.subject?.id ?? "anonymous");
  ctx.log.setTag("tenant", String(ctx.subject?.tenant?.id ?? "unknown"));
  return next();
});
```

Review denied calls regularly. They show missing policy, unexpected client behavior, or attempted abuse.

## Harden Network Exposure

Run public Fentaris deployments behind a reverse proxy or cloud load balancer.

* Terminate TLS at the edge and forward only HTTPS traffic.
* Restrict inbound access with private networks or IP allow-lists.
* Keep upstream MCP servers private when possible.
* Rotate API keys and upstream credentials regularly.

## Low-Level API

The class constructors remain available for compatibility and advanced embedding. Prefer `fentaris(...)`, `mcp(...)`, `stdio(...)`, `streamableHttp(...)`, `group(...)`, `user(...)`, and `policy(...)` for new applications.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { McpProxy, Policy } from "@fentaris/core";

const legacyPolicy = new Policy({ name: "legacy" })
  .mcp("filesystem")
  .allow("read_file");

const proxy = new McpProxy({
  policy: legacyPolicy,
  servers: [filesystemServer],
});
```

## Related Documentation

* [Governance auth](/guides/governance-auth)
* [Multi-server routing](/guides/multi-server)
* [Observability](/guides/observability)
