Skip to content

security: remove internal infrastructure details from public repo - #4

Merged
siva01c merged 1 commit into
mainfrom
security/remove-internal-topology
Aug 26, 2026
Merged

siva01c merged 1 commit into
mainfrom
security/remove-internal-topology

Conversation

@siva01c

@siva01c siva01c commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Why

seo-tools is 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.example should document the contract (a variable exists, what shape its value has), not the deployment.

What was disclosed

Leak Why it matters
sales-assistant-assistant-1:8000/api/mail/send Name + port + route of an internal service belonging to a private product. This repo is a crawler — an SSRF-prone class of app, a risk docs/security.md already acknowledges. No enumeration needed.
seo@ludekkvapil.cz as SMTP_FROM Real operator mailbox, plus which From address clients are conditioned to trust (phishing pretext).
Silent BCC on every report Belongs in a privacy notice, not a config sample.
docs/SECURITY-KEY-ROTATION.md Stated publicly that one MAIL_API_TOKEN is shared across three projects → blast radius of a single token.
docs/todo.md A list of still-open security gaps.
docs/memory-optimization.md Production VPS IP, SMTP relay, and "The SMTP password is present in plaintext in the environment of the api-ludekkvapil-assistant-1 container. Worth rotating."

Severity: Low (CWE-200). Verified that no credential ever reached git: .env was never committed, and the only MAIL_API_TOKEN values in history are the placeholders change-me-mail-token and generate-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 from sendSeoEmail(). MAIL_API_URL / MAIL_API_TOKEN / SMTP_FROM are 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 private lkv repo; the references they left behind in security.md and in code comments are rewritten. security.md stays 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.

⚠️ Two things needing a decision

  1. Rotate the SMTP password. memory-optimization.md is 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.
  2. 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. Set it explicitly in the production .env or the archive copies stop silently.

Verification

  • 247 tests pass, typecheck and lint clean
  • guard passes over all tracked files, and fails as expected on a planted leak
  • server without mail config boots with a warning and still serves /health 200
  • Not executed: the throw inside sendSeoEmail() — the function isn't exported and I didn't export it just to test it; verified by reading

🤖 Generated with Claude Code

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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@siva01c
siva01c merged commit 048d67e into main Aug 26, 2026
4 checks passed
@siva01c
siva01c deleted the security/remove-internal-topology branch August 26, 2026 06:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant