Skip to content

feat: bring TLS to SASL-level support - #134

Merged
jaredwray merged 3 commits into
mainfrom
cursor/tls-sasl-parity-0d9f
Aug 19, 2026
Merged

feat: bring TLS to SASL-level support#134
jaredwray merged 3 commits into
mainfrom
cursor/tls-sasl-parity-0d9f

Conversation

@jaredwray

@jaredwray jaredwray commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Please check if the PR fulfills these requirements

  • Followed the Contributing and Code of Conduct guidelines.
  • Tests for the changes have been added (for bug fixes/features) with 100% code coverage.

What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)

Feature / completeness: TLS now matches SASL in API surface, Auto Discovery, tests, and docs.

Audit (TLS vs SASL)

SASL already had client + node options, Auto Discovery wiring, introspection (hasSaslCredentials / isAuthenticated), factory tests, a dedicated compose service, and a full README (options, per-node, events, server setup). TLS had the socket handshake and memcaches:// URIs, but Auto Discovery’s config endpoint ignored TLS, there was no node introspection, no TLS+SASL path, thin operation coverage, and the README called out Auto Discovery as unfinished.

Changes

  • Pass tls into Auto Discovery the same way as sasl; memcaches:// config endpoints enable TLS
  • Discovered nodes inherit TLS; when discovery returns hostname+IP, connect to the IP and set SNI servername to the hostname (ElastiCache in-transit certs)
  • node.tlsEnabled / node.tls; node identity (host/port/path) always wins over tls.connect() overrides; Unix memcaches:// URIs round-trip
  • Compose service memcached-tls-sasl on 21215 for combined encryption + AUTH
  • Tests expanded to SASL-level coverage (node, factory, text ops, Auto Discovery, TLS+SASL)
  • README TLS section mirrors SASL (options, per-node, properties, Auto Discovery, TLS+SASL, server flags)

Testing

  • pnpm build succeeded
  • TLS / SASL / Auto Discovery TLS tests passed against local compose (TLS 21211, SASL 11215, TLS+SASL 21215)
  • CI hang: three startAutoDiscovery TLS tests timed out because client.connect() TLS-handshakes a new config-endpoint node against plaintext FakeConfigServer; instance spies on client.nodes did not cover that node. Fixed by mocking MemcacheNode.prototype.connect.
  • Full suite locally: 665 passed plus the known sandbox timeout pair; auto-discovery file 83/83 after the mock fix.
Open in Web Open in Cursor 

cursoragent and others added 2 commits August 19, 2026 04:35
TLS now flows through Auto Discovery (config endpoint and discovered
nodes), exposes node.tls/tlsEnabled like SASL credentials, composes
with SASL on a dedicated compose service, and documents per-node
setup, SNI, and server flags at the same depth as SASL.

Co-authored-by: Jared Wray <me@jaredwray.com>
Mock data-node connect() so client.connect() does not TLS-handshake
10.0.0.1 before the config endpoint is polled, and cover empty-IP
discovery for SNI host selection.

Co-authored-by: Jared Wray <me@jaredwray.com>
@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (b8a16c9) to head (2ee786d).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #134   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            8         8           
  Lines         1411      1471   +60     
  Branches       310       333   +23     
=========================================
+ Hits          1411      1471   +60     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

client.connect() creates a new MemcacheNode for the configuration
endpoint. Instance spies on client.nodes never wrap that handshake,
so TLS against plaintext FakeConfigServer hung until Vitest's 5s
timeout on CI. Spy MemcacheNode.prototype.connect instead.

Co-authored-by: Jared Wray <me@jaredwray.com>
@jaredwray
jaredwray marked this pull request as ready for review August 19, 2026 05:11
@jaredwray
jaredwray merged commit 769ac99 into main Aug 19, 2026
17 checks passed
@jaredwray
jaredwray deleted the cursor/tls-sasl-parity-0d9f branch August 19, 2026 05:12

@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: 2ee786d0ad

ℹ️ 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".

Comment thread src/index.ts
Comment on lines 1706 to +1708
if (!currentNodeIds.has(id)) {
try {
const host = node.ip || node.hostname;
const wrappedHost = host.includes(":") ? `[${host}]` : host;
await this.addNode(`${wrappedHost}:${node.port}`);
await this.addDiscoveredNode(node);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Refresh matching seed nodes with inferred TLS

When TLS is inferred solely from a memcaches:// configuration endpoint, any preconfigured node whose ID also appears in the discovered topology is skipped here and retains its original plaintext configuration. For example, a client seeded with 10.0.0.1:11211 will leave that node plaintext while newly discovered peers use TLS/SNI, so keys hashed to the seed fail against a TLS-only cluster. Existing matching nodes need to be replaced or upgraded when their effective TLS/SNI settings differ.

Useful? React with 👍 / 👎.

Comment thread README.md
Comment on lines +1226 to +1227
Auto Discovery uses the same client-level `tls` (and `sasl`) options as data
nodes, including the configuration-endpoint connection. Set `tls: true` (or a

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid promising SASL support for Auto Discovery

This claim is not supported for a SASL-protected configuration endpoint: AutoDiscovery.fetchConfig() sends the ASCII config get cluster/legacy command through MemcacheNode.command(), while a SASL node requires the binary protocol and its permanent data handler explicitly skips handleData() whenever _sasl is set. Consequently, configuring the advertised TLS+SASL Auto Discovery flow cannot obtain a topology and may leave the command pending until the connection closes; either implement the discovery exchange over the authenticated protocol or remove the SASL claim and avoid forwarding credentials to this connection.

Useful? React with 👍 / 👎.

Comment thread src/node.ts
Comment on lines +365 to +369
const options: TlsConnectionOptions = tls === true ? {} : { ...tls };
// Node identity always wins over any host/port/path in user options.
options.host = undefined;
options.port = undefined;
options.path = undefined;

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 Reject supplied sockets when enforcing the node target

A tls.ConnectionOptions object can contain a preconnected socket, and spreading it here preserves that field. Node's tls.connect() wraps the supplied socket and ignores the subsequently assigned host, port, and path, so callers can still retarget the connection despite the new invariant and may send SASL credentials or commands to the socket's actual peer. Remove or reject options.socket when the node identity is intended to always win.

Useful? React with 👍 / 👎.

Comment thread src/auto-discovery.ts
Comment on lines +221 to +232
const { host, port, secure } = this.parseEndpoint(this._configEndpoint);
const tls = secure ? this._tls || true : this._tls;
if (this._tls === undefined && tls) {
this._tls = tls;
}

this._configNode = new MemcacheNode(host, port, {
timeout: this._timeout,
keepAlive: this._keepAlive,
keepAliveDelay: this._keepAliveDelay,
sasl: this._sasl,
tls,

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 Set SNI for DNS configuration endpoints

When the configuration endpoint is a DNS name and TLS is enabled with true or inferred from memcaches://, this passes a bare TLS option to MemcacheNode. Node's tls.connect() does not infer servername from host, so a configuration service that selects its certificate through SNI returns its default certificate and the discovery handshake fails. For non-IP endpoint hosts, merge servername: host unless the caller already supplied a server name, as is done for discovered data nodes.

Useful? React with 👍 / 👎.

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but it could not run because the branch was deleted or merged before autofix could start.

Reviewed by Cursor Bugbot for commit 2ee786d. Configure here.

Comment thread src/auto-discovery.ts
const tls = secure ? this._tls || true : this._tls;
if (this._tls === undefined && tls) {
this._tls = tls;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

False TLS loses to memcaches scheme

Medium Severity

this._tls || true treats an explicit tls: false as unset when the configuration endpoint is memcaches://, so the config node still does a TLS handshake. effectiveTls uses ??, so that same false keeps discovered data nodes on plaintext. The cluster can end up with a TLS config connection and unencrypted node connections.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2ee786d. Configure here.

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.

2 participants