Skip to content

feat(security): optional SOCKS source-IP access control via SocksPolicy (ISSUE-18c) - #112

Merged
hapheus merged 2 commits into
mainfrom
feat/issue-108-socks-policy
Nov 7, 2026
Merged

hapheus merged 2 commits into
mainfrom
feat/issue-108-socks-policy

Conversation

@hapheus

@hapheus hapheus commented Nov 7, 2026

Copy link
Copy Markdown
Owner

Summary

Implements ISSUE-18c (#108) with a researched scope correction (see the comment on #108):

Finding: Tor's SocksPort is unauthenticated by design (torrc manual: "unencrypted and (as we use it) unauthenticated"). Client-supplied username/password is only stream isolation (IsolateSOCKSAuth), never a credential check — so TORPROXY_SOCKS_USER/PASSWORD env vars in torrc would be a misleading no-op. True username/password auth would need an additional front-proxy daemon (separate ADR if ever desired).

Implemented instead — the control Tor itself offers: optional, additive TORPROXY_SOCKS_ALLOWED_CIDRS:

  • Comma-separated CIDRs, validated via ipaddress.ip_network() in Settings (invalid → ValueError in the existing style).
  • render_torrc emits SocksPolicy accept 127.0.0.0/8, accept ::1/128, accept <cidrs>, reject * when set (Tor accepts anything unmatched, so the final reject * is required). Loopback is always accepted for the internal SOCKS readiness probe and Privoxy.
  • Default unset = accept all (legacy behavior). Placeholders in .env.example (no real secrets), docs in docs/configuration.md / docs/compatibility.md including the Tor auth limitation.

Hermetic tests (tests/unit/test_socks_policy.py, 6 tests) cover env parsing, CIDR validation (v4/v6/invalid), default no-policy rendering and policy rendering.

Compatibility: additive only — no env var set means exactly the current behavior.

Verification

  • TDD: new tests failed before the implementation, pass after.
  • uv run pytest tests/unit/ -q --cov=torproxy --cov-fail-under=85 → 323 passed, coverage 85.74%
  • uv run ruff check ., uv run ruff format --check ., uv run mypy, shellcheck docker/*.sh tests/integration/*.sh → clean

Not run: live container test with SocksPolicy enforced (requires Docker daemon + live Tor; CI covers the image build).

Closes #108

ISSUE-18c (#108): Tor's SocksPort is unauthenticated by
design (torrc manual: 'unencrypted and (as we use it)
unauthenticated'); client username/password is only stream
isolation, never a credential check. So username/password
env vars rendered into torrc would be a misleading no-op.

Implementable control is Tor's SocksPolicy (source IP):

- Add optional TORPROXY_SOCKS_ALLOWED_CIDRS (comma-separated
  CIDRs, validated with ipaddress.ip_network). When set,
  render_torrc emits 'SocksPolicy accept loopback, accept
  <cidrs>, reject *' — Tor accepts anything unmatched, so
  the final reject is required. Loopback is always accepted
  for the internal probes and Privoxy.
- Default unset = accept all (legacy behavior, additive).
- Document the Tor auth limitation in .env.example,
  docs/configuration.md and docs/compatibility.md.

Closes #108

@MustermenschAlex MustermenschAlex left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks — the scope correction is the most valuable part of this PR.

Verifying against the torrc manual that SocksPort is "unencrypted and unauthenticated" and that client username/password is only stream isolation — then shipping the control Tor actually offers (SocksPolicy) instead of a misleading no-op env var — is exactly right. The issue comment documents the reasoning.

Implementation-wise: CIDR validation via ip_network, loopback always accepted (probes/Privoxy keep working), final reject * present so the policy actually restricts, default unset = legacy behavior. Docs and .env.example placeholders included, no real secrets. All checks green. Approving.

@kuenstlichedummheit-sys kuenstlichedummheit-sys left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is how you handle a feature request that turns out to be impossible as written: check the manual, say so plainly, ship the thing that actually works. "Tor SOCKS auth is isolation, not authentication" is the kind of sentence that saves the next person a week.

The SocksPolicy rendering looks right — final reject *, loopback pinned open for the probes. The one thing I'd keep an eye on is operators misunderstanding this as password auth, but the docs say it three times, so that's on them now.

Approving.

@majorungustl majorungustl left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Good call not faking username/password. Source-IP policy is honest, additive, and actually enforces something. Merge it. Approving.

@hapheus
hapheus merged commit 96c7202 into main Nov 7, 2026
11 checks passed
@hapheus
hapheus deleted the feat/issue-108-socks-policy branch November 10, 2026 08:12
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.

[ISSUE-18c] Optional SOCKS5 username/password authentication

4 participants