Repository navigation
v0.2.0
Security
- The generated Claude Desktop config no longer runs
npx -y socialos-mcp: that npm package does not exist, and whoever registered the name would have received users' API keys. Configs, the MCP connections page and the docs now use Claude Desktop connectors or themcp-remotebridge pinned to an exact version, and a regression test rejectssocialos-mcpand unpinnednpxpackages.
Added
-
Plan limits (D-014): opt-in limits for one
freeplan: connected accounts, scheduled or published posts per UTC month and media storage, all unlimited by default so updates never cap an existing install. A public instance sets them withQUOTA_ACCOUNTS,QUOTA_POSTS_PER_MONTHandQUOTA_MEDIA_MB;-1switches a limit off. Over a limit the action is refused with403 QUOTA_EXCEEDEDand nothing changes. Drafts are free, a post counts once per month, reconnecting an account you already have is free. Counting is race-free, so parallel requests cannot overshoot. -
GET /account/usage(scopeanalytics:read) shows the plan, the period and what you have used against each limit. -
Dashboard motion: route transitions through the View Transitions API (with a CSS fade-and-rise fallback), a sidebar highlight that slides between items, dialog and toast enter/exit, staggered lists on first load, skeleton shimmer, button press feedback and card hover lift. All motion uses transform and opacity with tokens from
tokens.cssand is disabled underprefers-reduced-motion. Empty states gained an icon and page headers a clearer hierarchy. -
Public pages
/privacyand/terms(no sign-in, linked from the sign-in and register forms and from the app sidebar and Settings), written for a self-hosted, single-operator instance and usable as the LinkedIn app's Privacy Policy URL. They are a template, not legal advice. The operator's name and contact come from the frontend container'sOPERATOR_NAMEandOPERATOR_CONTACT; without them the pages say so. -
Breaking:
POST /auth/registerrequiresaccept_terms: true(otherwise400 VALIDATION_ERRORwithfields.accept_terms). The register form has the matching checkbox. The accepted version and time are stored. Existing accounts are not blocked (D-016). -
A new landing page: full-viewport hero with an animated flow of posts from agent to approval to networks, a real recorded video of the demo (WebM and MP4, light and dark, poster only on phones and with reduced motion), a sticky "agent, approval, network" walkthrough, parallax screenshots, true-fact counters, a marquee of the networks that publish today and an honest list of those that do not. Cross-document View Transitions between the landing page and the docs. Docs keep their own calm layout. See D-017.
-
Email verification, password reset and password change. Mailed links carry a single-use token in the URL fragment (48 h to verify, 30 min to reset). A reset signs out every session; a change keeps the current one. API keys and MCP connections are revoked only if you tick "Also revoke all API keys and MCP connections".
-
When
MAIL_PROVIDER=smtp, unverified accounts get403 EMAIL_NOT_VERIFIEDon connecting networks, scheduling, publishing, editing scheduled posts and creating API keys. With the log provider nothing is restricted and the dashboard says mail is off. Existing accounts start unverified. -
New pages
/verify-email,/forgot-passwordand/reset-password, an email banner, and a Password section in Settings. -
Database migration
00003also prepares plans, quotas, data export and account deletion; it needs no manual step. -
Connect a network with a pasted credential:
POST /api/v1/social/accounts/tokenand the provider capability fieldsconnect_fields,max_image_bytesandrequires_title. It needs the new critical API-key scopesocial:connect, which is never in a default set (D-009). -
"Connect with a token" on the Accounts page: Discord, Mastodon and Bluesky open a form built from the provider's
connect_fields(secrets as password inputs with show/hide, never stored or put in a URL), with the how-to linked and plain-English errors per field. The demo and mock API answer the same fields and the token endpoint. -
Posts are checked against the stricter of the network limits and the account's own limits, and networks that need a title reject posts without one.
-
SSRF-safe HTTP client for hosts users supply (D-010).
-
Discord: connect a channel with its webhook URL and publish text and up to 10 images, or delete a post. The URL is stored encrypted and never returned; a timeout after sending goes to review instead of risking a duplicate. See
docs/integrations/discord.md. -
Connect fields have a
secretflag (connect_fields[].secretinGET /social/providers) for credentials that are not of kindsecret, such as a webhook URL; forms must render them as password inputs. -
Mastodon (and API-compatible Fediverse servers): connect with an instance URL and an access token, publish text and images (public), delete. Limits are read from the instance. A repeated publish after a timeout is safe because of the
Idempotency-Key. Seedocs/integrations/mastodon.md. -
Bluesky: connect with a handle and an app password, publish text with link and hashtag facets and up to 4 images, delete, and resolve unknown outcomes through a deterministic record key. See
docs/integrations/bluesky.md. -
Stub notes for Reddit, Medium and Hashnode; the X note now says the API is pay-per-use.
-
Sharing a host safely (host-proxy mode): every SocialOS container runs in the systemd slice
socialos.slice(1 CPU, 664 MB, no swap, 512 tasks for the whole stack), may not swap and has a process cap; drop-ins cap dockerd and containerd; a guard timer stops the SocialOS worker, then the worker, MCP and UI, only when the host is under pressure and SocialOS is a real contributor (anonymous memory, not page cache, or its own CPU or IO), and resumes them once the host is calm; it also alerts on disk, data budget and the other services' health. A Caddy pre-check keeps a broken SocialOS snippet from stopping the host's Caddy. Runbook:deploy/host-proxy/apply-guardrails.md. -
Approvals for dangerous actions (D-013): publishing now, retrying now, deleting a post, disconnecting an account, connecting with a pasted token, or scheduling less than 5 minutes ahead (
AGENT_MIN_SCHEDULE_LEAD) with an API key or an MCP connection answers428 APPROVAL_REQUIREDand waits for you. Approve or deny in the dashboard under Approvals (GET /approvals,POST /approvals/{id}/approve|deny); the agent repeats the same call withX-Approval-Idand it works once. New settingsAPPROVAL_TTL,APPROVAL_MAX_PENDING(per key),APPROVAL_RETENTION,AGENT_MIN_SCHEDULE_LEAD(also gates edits of posts that run soon); new audit entriesapproval.requested|approved|denied|used. -
API keys have a
dangerous_policy:approve(default, also for existing keys) ortrusted(set when creating the key, skips approvals).
Changed
- Breaking for API-key integrations: dangerous actions made with a key now need your approval (see above). The MCP tools
publish_post,delete_postanddisconnect_accountno longer takeconfirm; they take an optionalapproval_id, andschedule_postandupdate_posttake one too. An agent that only schedules at least 5 minutes ahead is not affected. - Password hashing is bounded in memory: at most
PASSWORD_HASH_CONCURRENCY(default 2) hashes run at once withinPASSWORD_HASH_MEMORY_MIB(default 48) of argon2 memory, new hashes use argon2id m=19 MiB, t=2, p=1 (OWASP), and existing hashes are upgraded at the next successful login. Under a burst, logins and registrations may get429 RATE_LIMITEDand should retry. - The SSRF guard also blocks site-local
fec0::/10, IPv4-compatible::/96and local-use NAT6464:ff9b:1::/48(the whole range is blocked). - Browser sessions now hold ten scopes (the new
social:connect); the "Dangerous" list in the dashboard shows it. - Host-proxy mode: memory caps rebalanced to fit the slice (backend and worker 160m with
GOMEMLIMIT=100MiB, frontend 160m, postgres 112m, minio 80m, mcp 56m, redis 32m, migrate 64m). Automatic updates do not deploy while the guard has shed load. - One shared table for post attempts, analytics, API usage, API keys and the audit log. On phones each row becomes a stacked card showing every column (the Error column is no longer hidden),
/developerno longer scrolls sideways at 390 px, and wide tables can be scrolled with the keyboard.
Fixed
- Large uploads no longer exhaust memory on a small host: media is streamed to object storage in 5 MiB parts (no temp files, no whole-file buffering), the size and type limits are enforced while streaming and the partial object is removed on failure, and at most
MEDIA_UPLOAD_CONCURRENCY(2) uploads run at once, the rest waiting 5 s and then getting429 RATE_LIMITEDwithRetry-After. The web UI uploads directly to the API host, so videos are no longer cut at the Next.js proxy's 10 MB (D-015). - Cancelling, unscheduling or publishing a post while the worker publishes it can no longer deadlock and fail with a 500: the API and the worker now lock rows in the same order (post, then target). A database deadlock or serialization failure that still happens is reported as a retryable
CONFLICT(#38). - Deploying or restarting the worker during a publish no longer sends that post to review: on SIGTERM the worker stops taking tasks, lets in-flight publishes finish (up to
WORKER_SHUTDOWN_TIMEOUT, default 30s, at most 35s; the worker'sstop_grace_periodis now 45s instead of Docker's 10s), stops its background jobs and only then closes the database. The worker's health address is configurable asWORKER_HTTP_ADDR(#39). - The Posts date filter now uses the timezone chosen in Settings instead of the browser's, so "From" and "To" cover whole days where you expect them.
- Colour contrast: unsupported-network badges and the dark-mode "Scheduled" badge now meet WCAG AA (axe
color-contrastis clean in light and dark). The dark accent is slightly lighter, in the app and on the site. - A failed load no longer looks like an empty list: Posts, post detail and the audit log show a titled error with a retry button, "Load more" failures keep the list on screen, and error messages are plain English (no raw codes, JSON or stack traces).