Security

Assume the model has already been turned.

Any channel member can write into the agent’s context, and so can the output of any tool it calls. Every mitigation here is designed to hold when the model is not on your side.

The test

What an attacker gets

These are the cases the e2e suite is written to attack. It has grown with every phase — memory, skills, and ambient mode are attacked the same way, and the sandbox is attacked with real containers on a real container runtime — and it passes against every change.

Attempt

A channel member talks the agent into printing its GitHub token.

Nothing to print.

The agent process never receives a tool credential value. Team sheets, logs, errors and tool definitions carry names. The proxy injects the secret into the outbound call and scrubs known secret values out of the result before it comes back.

Attempt

A poisoned tool result instructs the agent to call an internal admin API.

Refused in the proxy.

The server is not in the channel’s team sheet, so it is not in the tool definitions the agent fetched, and a direct call is rejected anyway. The refusal is a structured result the agent relays; the attempt is in the audit log.

Attempt

Someone asks the agent to deploy to production, urgently, at 2am.

Held for a human.

run_workflow is marked approval = "required" in the team sheet, so the call stops in the proxy and an Approve card renders in the thread. Whoever clicks is recorded. A tool whose name carries a destructive verb — delete, drop, transfer, deploy — is held whether or not anyone remembered to mark it.

Attempt

A prompt-injected loop tries to burn the month’s token budget in an hour.

Stopped at the meter.

Tokens and tool calls are metered per channel per day in the proxy. The agent loop has its own caps, but they are advisory — the proxy’s meter is the authoritative one, and it does not consult the model.

Attempt

The agent is asked to summarise what a different channel discussed.

No path to the data.

Channel state is one SQLite file per channel. The isolation is the file layout, not a WHERE clause, so there is no query that can join across channels by accident or by instruction.

Attempt

Generated code tries to POST your data to an external host.

The host is not on the list. The run ends.

Code runs in an ephemeral container with a read-only rootfs, cpu/memory/time limits, and no network at all unless the team sheet grants an egress allowlist. When it does, the container sits on a network with no route out whose only exit is a filter checking that list per host — so ignoring the proxy settings, or dialling a raw address, reaches nothing. A host outside the list does not merely fail: the run is killed and the call is refused, naming the host on the audit row. The suite proves it by sending a payload to a host the sheet allows first, then watching the same code reach nothing at one it does not.

Why the split is the whole design

Libero runs as two processes. The gateway and agent talks to Slack and runs the model loop. The tool proxy holds every tool credential and enforces what each channel may do. They speak over mutual TLS on a private network, and the only thing that crosses is a tool call and its result.

Compromise the agent completely — prompt injection, a malicious skill, a model that simply misbehaves — and the blast radius is bounded by a file in your git repository rather than by the model’s judgement.

The rule is enforced in the source as well as the deployment: packages/agent may never import packages/proxy, checked by an ESLint rule and independently by a grep-level CI job.

What we do not claim

The trust assumptions are stated rather than implied. The operator’s Slack workspace is trusted; individual channel members are not. A malicious operator, a compromised host, and Slack itself are out of scope for v1.

Libero is pre-1.0 — the changelog says which release is current — with the governed core shipped: mutual TLS, per-channel identity from the client certificate, team-sheet enforcement on both gates, an encrypted vault, credential injection, a redaction pass on results, OAuth against upstreams that require it — with tokens sender-constrained to a key the token store does not hold, where the authorization server speaks DPoP — the budget meter in calls and in dollars, the append-only audit log, and the approval broker with its Slack card — and the layers built on it since: curated memory, skills, and ambient mode, each attacked by the same suite. The end-to-end suite runs against every change, including the sandbox: real containers, a real runner, and a real egress filter. Code execution is off unless an operator starts the runner, its allowlist grants HTTP and HTTPS only, and certificate rotation is two commands with a human edit between them. The roadmap was gated so that nothing depending on the proxy shipped before the proxy could be attacked and hold.

Reporting a vulnerability

Please do not open a public issue. Use GitHub private vulnerability reporting — Security → Report a vulnerability . We aim to acknowledge within 72 hours.