Environment variables

A process websocketd spawns for a WebSocket connection receives the variables below, in CGI style. A process spawned for a --cgidir request receives a different set, built by Go's net/http/cgi; the differences are listed at the end of this page.

Client-controlled means the value comes from data the client sent and can therefore be anything the client chooses.

RFC 3875 variables

VariablePopulated fromClient-controlled
SERVER_SOFTWAREwebsocketd/ followed by the version stringNo
REMOTE_ADDRThe client's source IP address. Literally unix-socket for a client connected over --unixsocketNo
REMOTE_HOSTThe reverse DNS name of the client's address when --reverselookup is given, otherwise the same value as REMOTE_ADDRNo
SERVER_NAMEThe host part of the request's Host headerYes
SERVER_PORTThe port part of the request's Host header, or 80, or 443 under --ssl, when the header carries no portYes
SERVER_PROTOCOLThe request's HTTP version, for example HTTP/1.1No
GATEWAY_INTERFACEThe constant CGI/1.1No
REQUEST_METHODThe request method, GET for a WebSocket upgradeNo
SCRIPT_NAMEUnder --dir, the leading path segments that resolved to a file. With a single COMMAND, always /Partly: derived from the request path, but only ever a path that resolves to a file under --dir
PATH_INFOUnder --dir, whatever path is left after SCRIPT_NAME. With a single COMMAND, the whole request pathYes
PATH_TRANSLATEDThe whole request path, before the query stringYes
QUERY_STRINGEverything after ? in the request URL, raw and still percent-encodedYes

SERVER_NAME and SERVER_PORT come from the Host header, not from the address websocketd is bound to, which matches net/http/cgi and virtual-hosted web servers. This is settled behaviour, not a defect (issue #475).

Variables set to an empty string

Each of these is set, and set to an empty string. websocketd sets them explicitly so that a value cannot leak in from its own environment.

VariableWhy it is empty
AUTH_TYPEwebsocketd performs no authentication
REMOTE_USERwebsocketd performs no authentication
REMOTE_IDENTwebsocketd performs no authentication
CONTENT_LENGTHA WebSocket upgrade carries no request body
CONTENT_TYPEA WebSocket upgrade carries no request body

Non-standard variables

Not part of RFC 3875, and commonly provided by CGI-style servers.

VariablePopulated fromClient-controlled
UNIQUE_IDA random per-connection identifier, modelled on Apache's mod_unique_idNo
REMOTE_PORTThe client's source TCP port. Empty for a --unixsocket client, which has noneNo
REQUEST_URIThe full request target, path and query string, for example /foo/blah?a=bYes
HTTPSThe constant on. Set only under --ssl; otherwise the variable is absent, not emptyNo

Request headers

Every request header becomes one variable named HTTP_ followed by the header name uppercased with each - replaced by _. X-Forwarded-For becomes HTTP_X_FORWARDED_FOR. All of them are client-controlled.

Repeated headers of the same name are joined with , into one value. Carriage returns and newlines within a value are replaced with spaces, and the result is trimmed.

There is no HTTP_HOST. Go's HTTP server moves the Host header out of the header map before websocketd sees it; the host reaches the process as SERVER_NAME and SERVER_PORT instead.

One header is dropped: a request header named Proxy never becomes HTTP_PROXY. Many HTTP client libraries route outbound requests through whatever HTTP_PROXY names, so forwarding it would let a remote caller redirect a spawned program's own traffic. This is the httpoxy vulnerability, CVE-2016-5385. The comparison uses the header's canonical form, so proxy, Proxy, and PROXY are all dropped. Go's net/http/cgi drops it for the same reason.

Variables forwarded from websocketd's own environment

--passenv names, comma-separated, which of websocketd's own environment variables are copied into every spawned process. Nothing else from websocketd's environment reaches the process: on every platform except Windows, websocketd clears its own environment after reading this list.

PlatformDefault --passenv
LinuxPATH,LD_LIBRARY_PATH
macOSPATH,DYLD_LIBRARY_PATH
WindowsPATH,SystemRoot,COMSPEC,PATHEXT,WINDIR

Four facts govern the list:

  • Passing --passenv replaces the default rather than adding to it. --passenv=API_KEY alone leaves the spawned process with no PATH.
  • A named variable that is unset, or set to an empty string, in websocketd's environment is dropped rather than forwarded empty.
  • HTTPS is skipped even when named, because that variable is websocketd's own --ssl signal.
  • The list has no effect on the request-derived variables above. They are built per request and are always present.

Differences under --cgidir

A --cgidir request is handed to Go's net/http/cgi, which builds its own environment. SERVER_SOFTWARE is overridden to websocketd's value and the --passenv list is added, but the rest comes from Go.

GATEWAY_INTERFACE, SERVER_PROTOCOL, REQUEST_METHOD, QUERY_STRING, REQUEST_URI, SERVER_NAME, SERVER_PORT, REMOTE_ADDR, REMOTE_PORT, and the HTTP_<NAME> mapping carry the same meaning in both modes, and Proxy is dropped in both. These differ:

VariableWebSocket (COMMAND or --dir)--cgidir
SCRIPT_NAMEThe path that resolved to a file, or /Always empty
PATH_INFOThe path left after SCRIPT_NAMEThe whole request path
PATH_TRANSLATEDThe whole request pathNot set
UNIQUE_IDA random per-connection identifierNot set
AUTH_TYPE, REMOTE_USER, REMOTE_IDENTSet to an empty stringNot set
CONTENT_LENGTHSet to an empty stringSet only when the request has a body
CONTENT_TYPESet to an empty stringSet only when the request carries a Content-Type header
REMOTE_HOSTHonours --reverselookupAlways the client IP address
SCRIPT_FILENAMENot setThe resolved path of the script on disk
HTTP_HOSTNot setThe request's Host header
HTTP_COOKIE with repeated headersJoined with ,Joined with ;
PATH when --passenv does not name itNot set/bin:/usr/bin:/usr/ucb:/usr/bsd:/usr/local/bin, a fallback net/http/cgi supplies

A --cgidir request path must name the script file exactly. There is no trailing path information to split off, which is why PATH_INFO is the whole path and SCRIPT_NAME is empty.

See also