Safeguard
Application Security•

ASVS 5.0 V4.4.1: Use WSS for Every WebSocket

Plain ws:// sends everything in cleartext. Use wss:// for all WebSocket connections and make your server and clients refuse the unencrypted form.

Safeguard Security Team
3 min read

ASVS 5.0 V4.4.1 (level 1) requires WebSocket over TLS, the wss:// scheme, for all WebSocket connections. No exceptions for internal tools, staging or "it is only a chat feed".

A ws:// connection carries messages, and often session cookies or tokens, in cleartext. Anyone on the network path can read them and, worse, inject messages.

Why it matters

WebSockets tend to carry the live and sensitive part of an app: notifications, trading data, collaborative edits, admin consoles. The handshake itself is an HTTP request with cookies attached. Over ws:// those cookies are exposed. An active attacker on the path can also modify frames, so a browser can be fed false data without any cross site trick.

Browsers help in one direction. A page loaded over HTTPS will block a ws:// connection as mixed content. But native apps, scripts and services have no such guard, so the server has to enforce it. See CWE-319.

How to implement it

Terminate TLS in front of the WebSocket server and refuse plain connections.

nginx proxying to a local Node server:

server {
    listen 443 ssl;
    server_name ws.example.com;
    ssl_protocols TLSv1.2 TLSv1.3;

    location /socket {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
    }
}
server { listen 80; server_name ws.example.com; return 400; }

Bind the backend to localhost or a private network so nobody can reach it unencrypted. In client code, build the URL from a single config value and reject anything else:

const url = new URL(config.socketUrl);
if (url.protocol !== 'wss:') throw new Error('WebSocket must use wss');
const socket = new WebSocket(url);

Keep certificate validation on in server side clients. Do not set rejectUnauthorized: false.

How to test it

Try the plain scheme against every WebSocket host, including staging.

npx wscat -c ws://ws.example.com/socket

It must fail to connect. Then confirm wss:// works and that the certificate is valid. Search the front end and mobile code for the string ws:// and for URLs built by string concatenation with http replaced by ws. Finally scan the host for an open backend port, for example 8080, that is reachable from outside.

Common mistakes

  • Letting the backend port listen on all interfaces, so it can be reached without the TLS proxy.
  • Building the URL from location.protocol logic that falls back to ws: on HTTP pages.
  • Turning off certificate checks in internal clients to get past a self signed cert.
  • Exempting dev and staging, then copying the config to production.

Quick check

A page served over HTTPS opens ws://. Is that safe?

No. Browsers block it as mixed content, and even where it works the traffic is readable and modifiable. Use wss:// everywhere.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.