Skip to content

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 09 Oct 07:43
· 59 commits to main since this release
b7a7eee

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 the mcp-remote bridge pinned to an exact version, and a regression test rejects socialos-mcp and unpinned npx packages.

Added

  • Plan limits (D-014): opt-in limits for one free plan: 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 with QUOTA_ACCOUNTS, QUOTA_POSTS_PER_MONTH and QUOTA_MEDIA_MB; -1 switches a limit off. Over a limit the action is refused with 403 QUOTA_EXCEEDED and 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 (scope analytics: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.css and is disabled under prefers-reduced-motion. Empty states gained an icon and page headers a clearer hierarchy.

  • Public pages /privacy and /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's OPERATOR_NAME and OPERATOR_CONTACT; without them the pages say so.

  • Breaking: POST /auth/register requires accept_terms: true (otherwise 400 VALIDATION_ERROR with fields.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 get 403 EMAIL_NOT_VERIFIED on 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-password and /reset-password, an email banner, and a Password section in Settings.

  • Database migration 00003 also 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/token and the provider capability fields connect_fields, max_image_bytes and requires_title. It needs the new critical API-key scope social: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 secret flag (connect_fields[].secret in GET /social/providers) for credentials that are not of kind secret, 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. See docs/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 answers 428 APPROVAL_REQUIRED and waits for you. Approve or deny in the dashboard under Approvals (GET /approvals, POST /approvals/{id}/approve|deny); the agent repeats the same call with X-Approval-Id and it works once. New settings APPROVAL_TTL, APPROVAL_MAX_PENDING (per key), APPROVAL_RETENTION, AGENT_MIN_SCHEDULE_LEAD (also gates edits of posts that run soon); new audit entries approval.requested|approved|denied|used.

  • API keys have a dangerous_policy: approve (default, also for existing keys) or trusted (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_post and disconnect_account no longer take confirm; they take an optional approval_id, and schedule_post and update_post take 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 within PASSWORD_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 get 429 RATE_LIMITED and should retry.
  • The SSRF guard also blocks site-local fec0::/10, IPv4-compatible ::/96 and local-use NAT64 64: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), /developer no 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 getting 429 RATE_LIMITED with Retry-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's stop_grace_period is now 45s instead of Docker's 10s), stops its background jobs and only then closes the database. The worker's health address is configurable as WORKER_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-contrast is 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).