Security checklist
A short, actionable list for three moments: before your first live send, before each release, and when something goes wrong. It summarizes controls described in SECURITY.md and THREAT-MODEL.md; read those for the reasoning and the residual risks. Ticking a box is your own check, not a guarantee.
Nothing in this list changes or bypasses the human approval, the dry-run default, the emergency pause, credential isolation or ambiguous-send reconciliation. Changes to those rules need a separate security review.
A. Before your first live send (account owner)
Section titled “A. Before your first live send (account owner)”Offline preparation:
- You completed the quickstart dry run and know what
show,approve,publish --post-idandpausedo. -
config/accounts.jsonholds public metadata only: ids, handles, servers,env_prefix. ReachWeaver refuses secret-like keys there, and git ignores the file. - Each account in it is one you control and are allowed to post to. You never configure a client’s, employer’s or friend’s account without their written authorization.
- You use least-privilege credentials: a Bluesky App Password (never the main password), a Mastodon token limited to
read:accountsandwrite:statuses, a Telegram bot that is admin of that one channel, a LinkedIn member token for your own profile. - Credentials live only in your environment or a local secret manager (account onboarding). They were never typed into Claude, a chat, an issue, a screenshot, a committed file or a shell command that lands in history.
-
reachweaver credentials-audit --history ~/.bash_history(or your shell’s history file) exits 0. It prints variable names and line numbers only. -
reachweaver doctorandreachweaver healthshow the account, adapter and credential names you expect. - The state directory is private (
state/mode 0700, database 0600) and on a persistent disk you back up. CI runners and shared machines are not suitable. -
reachweaver capabilities --platform Pshows a live text adapter for the platform you plan to use. Every other platform is manual: see troubleshooting.
Online, on your own machine:
-
reachweaver connect-check --account ID --onlineconfirms the exact remote identity (handle,sub, bot and channel). It authenticates and never posts.
The send itself:
- You ran
show POST_IDand read the exact text, account and link, and every claim is backed by a source you opened today. - You approved this post with
approve POST_ID(or answeredafor it inreachweaver review, which runs the sameapprovefor that one post). A plan, a campaign file, a calendar or a Claude Code session is never approval. - Recommended (owner decision 2026-10-21) on every host where you approve yourself:
reachweaver approval-mode --confirmation tty_hash_prefix, soapprove,reviewapprovals andpublish --liveneed a terminal and the typed hash prefix, andreachweaver approval-mode --max-age-hours N(for example 168, at least your longest scheduling lead), so a forgotten approval is not sent weeks later.reachweaver approval-modeshows both as on. Both add friction; neither proves a human is present. - Deferred until you run a separate worker host: set up the owner approval key (
REACHWEAVER_APPROVAL_SECRET) there, where the agent cannot read it, and runapproval-mode --require-key. - Wave 9 item 1: the worker runs as its own system user, so
approval-ledger.jsonl(next to the database, mode 0600) is not writable from the agent’s account, and the ledger is backed up together with the database.reachweaver healthshows noapproval_ledger_*orapproval_nonce_missing_reapproval_requiredreason, and a post you did not expect never printsapproval_replay_reapproval_required. -
publish --post-id POST_ID(dry run) shows exactly one post. - Only then, on the production host:
REACHWEAVER_ENV=prod REACHWEAVER_ALLOW_LIVE=1 reachweaver publish --live --post-id POST_ID, withreachweaver pauseready.REACHWEAVER_ENV=prodis set nowhere else (not on dev or staging machines, not in a shell an AI agent drives). - Afterwards you opened the native post. If the post is in
sendingorneeds_review, you checked the account by hand and ranreconcile. You never re-send blindly.
Follow FIRST-LIVE-PILOT.md for the full supervised sequence and the evidence to keep privately.
Public/private visibility decision — separate from a tagged release
Section titled “Public/private visibility decision — separate from a tagged release”Before turning ReachWeaver from a private repository into a public repository, follow PUBLIC-RELEASE-CHECKLIST.md. It requires review of all Git history/branches/tags, rights to redistribute source and media, accidental personal data and historical credentials, and an independent owner decision. Current-file CI secret scans and a passing release gate do not prove that repository history is safe. The About-metadata helper does not change visibility.
B. Before each release (maintainer)
Section titled “B. Before each release (maintainer)”-
Approval replay ledger (wave 9 item 1):
tests/test_approval_ledger.pypasses,claim_sendingstill callsledger.consumebefore it flips the row tosending,check_approval_bindingstill repeats the read-only ledger check, and no code path writesapproval_nonceother thantransition. -
Send gate (wave 9 item 3):
tests/test_send_gate.pypasses,publishers.publishandpublish_videostill start withsendgate.consume, no module other thanstorage.pycallssendgate.register_claimand no env var or flag disables the gate. A script that importsreachweaver.publishersand callspublish()directly must fail with asend_*code; this stops accidents, not a determined process running as the same OS user. -
make release-gateexits 0: versions agree,CHANGELOG.mdhas the version section, required docs exist, README names every CLI subcommand, relative links resolve, the skill bundle is in sync, every blocked platform has an alternative, the release-readiness scorecard is valid, and the offline unit tests pass. For the tag itself:make release-gate TAG=vX.Y.Z(also requires a dated CHANGELOG section). The tag-triggered release workflow (.github/workflows/release.yml) runs the same gate with the tag before it builds or publishes the bundle and refuses to publish if it fails (tests/test_release_workflow.pypins this). -
make check,make ruffandmake typecheckexit 0, the same hygiene jobs CI runs (secret scan, stdlib lint, runtime-dependency guard). -
The tag workflow still has a read-only
verifyjob and a separatepublishjob that is the only one withcontents: write(the optionalpypijob’s only write is theid-tokenit needs for PyPI trusted publishing; it stays off unless the owner setsPYPI_PUBLISH, and it needs the owner’s approval of thepypienvironment), runs only afterverifyand only for the repository owner, and creates the release with--verify-tag. Both jobs runscripts/release_ref_guard.py(owner’svMAJOR.MINOR.PATCHtag at the currentorigin/maincommit), andverifyruns the secret scan, lint, byte compilation, dependency guard, pinned Ruff and mypy,sync_skill.py --checkandrelease_gate.py --tagwith tests (tests/test_release_workflow.pyandtests/test_release_ref_guard.pypin this). See the maintainer release checks. -
You checked
main’s branch protection or ruleset (required CI checks, pull-request review, no force pushes) in GitHub’s own settings. The workflow cannot turn it on. If the plan offers no branch rules, write down that limitation and review each commit yourself, with green CI on it, before tagging. -
You tag only the current, reviewed
maincommit. Green CI and a green release run are technical checks, not legal, rights or privacy clearance, and not proof of any social-platform post. -
No test, script or CI step publishes, uses a real credential or contacts a platform.
tests/test_no_network.pyandtests/test_onboarding.pyblock sockets. -
New fake tokens in fixtures are built at runtime, or have a narrow, reasoned
.secret-scan-allowentry. -
pyproject.tomlstill hasdependencies = []. A first runtime dependency needspip-audit, a lock file and an ADR-0002 amendment in the same change. -
The capability registry was re-verified within 120 days (
reachweaver capabilitieswarns), and no live adapter was added without official docs, mock tests and a security review. -
python3 scripts/release_gate.py --scorecardshows each platform and area at its true status. Promote an area tolive_receipt_verifiedonly by checking in the owner’s first-party receipt file and naming it inreceipt; the gate rejects the status without one. -
The release notes claim no live account connection, delivered post, view count or growth result without first-party evidence.
C. When something goes wrong
Section titled “C. When something goes wrong”- Stop new sends:
reachweaver pause. This blocks every new claim. - Leaked approval key (
REACHWEAVER_APPROVAL_SECRET, #7 D7):pause, replace the key everywhere, then re-approve every queued post. Rows signed with the old key are refused (approval_mac_mismatch_reapproval_required). Treat any post approved while the key was exposed as unreviewed. - Leaked credential: revoke it at the provider first (revocation per provider), then remove it from the environment, history and any file, and rotate.
- Wrong or unexpected post: delete it in the native app if needed, then
reconcilethe local record to match what really happened. - Unknown outcome:
reachweaver dead-letterandreachweaver queue-healthlistneeds_reviewand stalesendingposts. Check each one by hand beforereconcile. - Vulnerability in ReachWeaver: report it privately (SECURITY.md), never in a public issue.
- Resume only after you know the cause:
reachweaver resume.
D. If you run the local dashboard (reachweaver ui)
Section titled “D. If you run the local dashboard (reachweaver ui)”- Keep the printed address (it contains a per-run token that is valid until you stop the dashboard) out of chat, issues, screenshots and shared terminals; stop the dashboard with Ctrl-C when you are done.
- Prefer pasting the address into your browser over
ui --open:--openhands the address to the browser on its command line, where another user on a shared computer could see it withps. - Run it only on a computer you alone use. It has no login; another program running as you could read the same database file anyway.
- Do not forward or tunnel its port (SSH
-L/-R, ngrok, a reverse proxy). It is meant for127.0.0.1only, and the server refuses other hosts. - Remember it is read-only: approve, schedule, pause and publish stay terminal commands, and the commands it shows are suggestions. A page that offers a button to approve or send is not this dashboard.
