Open a private security advisory rather than a public issue. Expect an acknowledgement within a few days.
Treat it as production data. It holds:
- Credentials —
.env(provider API keys) and.secrets/(platform logins, and a browser profile containing live session cookies). - Personal data —
crm.dbandtargets/*.jsonhold real journalists' names and contact addresses, andmessagesholds the full text of correspondence with them. - The client's private positioning — often including figures and customer details that are not public.
A new campaign's .gitignore excludes .env, .secrets/, crm.db, logs/
and inbox/. publicist doctor re-checks that none of them are tracked, and
fails if they are.
A browser profile is a credential. It contains session cookies that grant
account access without a password. .secrets/ is created 0700 and doctor
checks it stayed that way.
Rotate first, clean history second. Removing a file from git history does not un-publish it — assume anything pushed to a public remote was captured.
- API key → revoke it at the provider.
- Platform session or browser profile → change the account password, which
invalidates the sessions, then delete
.secrets/browser-profile.
Depending on where you and your targets are, a target list is personal data under the GDPR, the CCPA, or both. This project gives you the mechanics; the legal basis for holding those addresses is yours.
Practically:
- Do not publish a target file. Do not put one in a public repository.
- Honour deletion requests:
publicist crm sql "DELETE FROM ..."reaches everything, and a target's messages are inmessages. - Do not add a target that asked not to be contacted. Mark them
deadso dedup keeps blocking future approaches — deleting the row lets a later discovery pass re-add them.
These are security-relevant properties, not missing features:
- No bot-protection bypass. See docs/browser-lane.md.
- Sending is opt-in. New campaigns cannot send mail until the provider is changed by hand.
- Lanes are serialized, so no concurrent writer can corrupt campaign state.
doctornever prints a secret's value, only whether it is set.
publicist run defaults to acceptEdits. Unattended cron runs usually need
--permission-mode bypassPermissions, which lets the agent run shell commands
in the campaign directory without asking. That is a real grant of authority:
make it deliberately, on a machine where the campaign directory is the blast
radius you accept, not by copying a command from a tutorial.