How SocialHandoff keeps agents in check
SocialHandoff exists to put a person between AI agents and your social accounts. This page describes the controls that do that, how your credentials are stored, and how to report a problem. Everything here describes the open-source core, so you can read the code that enforces it.
The short version
- Nothing an agent drafts is published until a person approves it, unless you have promoted that agent.
- An agent can never approve its own draft. Approval needs an owner key.
- Social account tokens are encrypted at rest with AES-256-GCM.
- Agent API keys are stored only as SHA-256 hashes.
- Every action lands in an append-only, hash-chained audit log you can verify.
- One kill switch pauses every agent and holds the queue.
Approval is enforced on the server
The approval rule is not a setting in the agent's prompt. It is enforced by the API. When an agent calls draft_post or schedule_post, the post enters the approval inbox. The approve_post tool is only accepted from an owner key, so an agent that tries to approve its own work gets an error, whatever its instructions say. That matters because prompt injection is a real risk: a malicious web page or email can tell an agent to post something. With SocialHandoff, the worst a hijacked agent can do is add a draft you then reject.
Publish-now posts also have a 60-second undo window, and a dry-run preview shows exactly what each network will receive before anything is sent. See approving posts.
One key per agent, least privilege by default
Every agent gets its own API key. Each key is scoped two ways:
| Scope | What it limits |
|---|---|
| Networks | Which connected accounts the agent may target |
| Permissions | draft, schedule, and analytics, each granted separately |
If a key leaks, you revoke that one key without touching your other agents. Keys are shown once when you create them and then stored only as SHA-256 hashes, so a copy of the database does not contain usable keys. Read more in agent keys and permissions.
The trust ladder
A new agent starts draft-only. After a run of clean approvals, an owner can promote it to auto-publish on specific networks, either by hand or by policy with autoPromote. Promotion is narrow: an agent trusted on Bluesky is still draft-only on LinkedIn. If any guardrail fails, the agent is demoted immediately and goes back to draft-only.
The policy lives in .socialhandoff/policy.json in your own repository, so a change to what agents may do goes through the same code review as any other change. See the trust ladder guide.
Guardrails on every draft
Guardrails check every draft before it can publish:
- Banned topics from your brand memory
- Per-network length limits
- Missing alt text on images
- Per-account daily post limits
- Blocked link shorteners, which hide where a link goes
Details are in the guardrails guide.
The kill switch
If something looks wrong, one action pauses every agent and holds every queued post. Nothing publishes while the switch is on. When you resume, held posts are released. See the kill switch guide.
Credentials and data at rest
- Social account tokens are encrypted with AES-256-GCM before they are written to the database. The key comes from
SOCIALHANDOFF_ENCRYPTION_KEYand is never stored alongside the data. - Agent API keys are stored as SHA-256 hashes only.
- Model keys for brand-voice rewrites are your own, and you can remove them at any time.
A verifiable audit log
Drafts, approvals, rejections, publishes, and other key actions are appended to the audit log. Each entry includes a hash of the entry before it, forming a chain. Calling GET /v1/audit/verify recomputes the chain and reports whether any entry was changed or removed. Entries also carry citations, such as the GitHub release a draft was created from, so you can trace a post back to its source. See the audit log guide.
Hardening of the service
- Owner sign-in protects the dashboard and approval inbox.
- A one-time setup token is required to claim a new install, so a freshly deployed server cannot be taken over by whoever finds it first.
- Private-network URLs are refused, which blocks server-side request forgery against your internal network.
- The container runs as a non-root user.
- Duplicate protection and idempotency keys stop retries from posting the same thing twice.
Self-hosting and the hosted service
If you self-host, your tokens, drafts, and logs never leave your server, and you control the encryption key. If you use the hosted service, SocialHandoff runs the same open-source core for you. Our privacy policy explains what we store and why. The open-source page explains what is and is not in the AGPL core.
Reporting a vulnerability
Email support@socialhandoff.com with the details and steps to reproduce. Please give us a reasonable time to fix the issue before you disclose it publicly. We will confirm receipt, keep you updated, and credit you if you would like.