security: remove internal infrastructure details from public repo - #4
Conversation
seo-tools is a public repository, but .env.example, several docs and the mail-sending code published production details of the private ecosystem: the internal Docker service name, port and route of the sales-assistant mail API, the operator mailbox used as SMTP_FROM/BCC, the production VPS IP and SMTP relay, and a list of still-open security gaps. None of this is a credential (.env was never committed and no real token ever reached git), but it hands an attacker the internal topology for free — and this is a crawler, an SSRF-prone class of app. - mcp-server.ts: drop the hardcoded production fallbacks in sendSeoEmail(). MAIL_API_URL/MAIL_API_TOKEN/SMTP_FROM are now required with no defaults, so a misconfigured deploy fails loudly instead of silently posting to another host or sending from an unintended address. Warn at boot rather than exit, since emailing reports is optional. - SEO_BCC_EMAIL is now opt-in. BEHAVIOUR CHANGE: it previously defaulted to a hardcoded address, so every report was blind-copied. Unset now means no copy is taken — set it explicitly in production .env to keep the archive. - .env.example: neutral placeholders, explanatory comments kept. - docs/: move SECURITY-KEY-ROTATION.md, memory-optimization.md, todo.md and business-plan.md to the private lkv repo; they are internal ops/roadmap documents. Rewrite the references they leave behind in security.md and in code comments. security.md itself stays public on purpose. - scripts/check-no-internal-refs.sh: pre-commit + CI guard so internal hostnames, the VPS IP and operator mailboxes cannot reappear. Deliberately published GDPR contact addresses are excluded. Also closes the open "add a pre-commit hook or CI check" backlog item. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b91a419afe
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| case "$f" in | ||
| package-lock.json | *.lock) continue ;; | ||
| esac | ||
| hits=$(grep -nEI "$PATTERN" "$f" 2>/dev/null | |
There was a problem hiding this comment.
Read staged file contents in the hook
When a developer partially stages a file, or stages a leak and then edits the worktree to remove it before committing, git diff --cached --name-only only supplies the staged filename while this grep "$f" reads the current worktree copy. In that scenario the pre-commit hook exits successfully even though the staged blob still contains the internal reference, so the leak can be committed and pushed to the public repo before the CI --all job rejects it. Scan the index contents instead (for example via git grep --cached or git show :$f).
Useful? React with 👍 / 👎.
Why
seo-toolsis a public repository..env.example, several docs and the mail-sending code published production details of the private ecosystem.Raised by the question: why does a git user need to know that sales-assistant sends emails, and on which docker container? They don't —
.env.exampleshould document the contract (a variable exists, what shape its value has), not the deployment.What was disclosed
sales-assistant-assistant-1:8000/api/mail/senddocs/security.mdalready acknowledges. No enumeration needed.seo@ludekkvapil.czasSMTP_FROMdocs/SECURITY-KEY-ROTATION.mdMAIL_API_TOKENis shared across three projects → blast radius of a single token.docs/todo.mddocs/memory-optimization.mdapi-ludekkvapil-assistant-1container. Worth rotating."Severity: Low (CWE-200). Verified that no credential ever reached git:
.envwas never committed, and the onlyMAIL_API_TOKENvalues in history are the placeholderschange-me-mail-tokenandgenerate-with-openssl-rand-base64-32. No history rewrite and no token rotation needed — with one exception, see below.Changes
src/mcp-server.ts— hardcoded production fallbacks removed fromsendSeoEmail().MAIL_API_URL/MAIL_API_TOKEN/SMTP_FROMare now required with no defaults. Boot-time warning rather than exit, since emailing is optional..env.example— neutral placeholders; explanatory comments kept.docs/— four internal ops/roadmap docs moved to the privatelkvrepo; the references they left behind insecurity.mdand in code comments are rewritten.security.mdstays public deliberately (transparent threat model is good practice).scripts/check-no-internal-refs.sh— pre-commit + CI guard so internal hostnames, the VPS IP and operator mailboxes cannot reappear. Deliberately published GDPR contact addresses (info@,privacy@, …) are excluded. Also closes the previously-open "add a pre-commit hook or CI check" backlog item.memory-optimization.mdis out of the public tree now, but the sentence naming the container and stating the password sits there in plaintext remains in public git history. This is the one credential concern the audit turned up.SEO_BCC_EMAILis now opt-in — behaviour change. It previously defaulted to a hardcoded address, so every report was blind-copied. Unset now means no copy. Set it explicitly in the production.envor the archive copies stop silently.Verification
/health200throwinsidesendSeoEmail()— the function isn't exported and I didn't export it just to test it; verified by reading🤖 Generated with Claude Code