# rules/eslint/no-plaintext-transport.eslint.rule.ts

> 87 lines of code and 16 definitions.

Tree: Governance tree
Language: typescript
Canonical: https://banes-lab.com/anatomy/governance#file-governance-rules-eslint-no-plaintext-transport-eslint-rule-ts
Source text: https://banes-lab.com/source/governance/rules/eslint/no-plaintext-transport.eslint.rule.ts.txt

Listed in [rules/eslint](https://banes-lab.com/api/source/governance/rules/eslint.md), after [rules/eslint/no-literal-strings.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/no-literal-strings.eslint.rule.ts.md) and before [rules/eslint/no-port-literal.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/no-port-literal.eslint.rule.ts.md).

## Definitions

- `isPlaintextUrl` (lexical_declaration, line 22)
- `serverBlocksOf` (lexical_declaration, line 35)
- `property` (method_definition, line 59, exported)
- `templateLiteral` (method_definition, line 67, exported)
- `keysOf` (lexical_declaration, line 29)
- `create` (method_definition, line 51, exported)
- `literal` (method_definition, line 53, exported)
- `PLAINTEXT_SCHEMES` (lexical_declaration, line 17)
- `SERVER_KEY` (lexical_declaration, line 18)
- `PORT_KEY` (lexical_declaration, line 19)
- `TRANSPORT_KEY` (lexical_declaration, line 20)
- `BRANCH_KEYS` (lexical_declaration, line 33)
- `isPlaintextListener` (lexical_declaration, line 45)
- `keys` (lexical_declaration, line 46)
- `[head]` (lexical_declaration, line 71, exported)
- `text` (lexical_declaration, line 72, exported)

## Uses

- [shared/factories/listener.factory.ts](https://banes-lab.com/source/governance/shared/factories/listener.factory.ts.md)
- [shared/selectors/literal.selector.ts](https://banes-lab.com/source/governance/shared/selectors/literal.selector.ts.md)
- [shared/selectors/syntax.selector.ts](https://banes-lab.com/source/governance/shared/selectors/syntax.selector.ts.md)

## Records this file grounds

- [Encryption in Transit](https://banes-lab.com/records/architecture/encryption-in-transit.md)

## Records this file checks

- [Encryption in Transit](https://banes-lab.com/records/architecture/encryption-in-transit.md)

## Source

```typescript
import { EXTERNAL_PLAINTEXT_ENDPOINTS, URL_SHAPED_IDENTIFIERS } from "../../shared/manifests/identifier.manifest.ts";
import type { LocalRule, RuleContext, RuleListener } from "../../types/rule.types.ts";
import {
    isType,
    literalString,
    nodeAt,
    nodesAt,
    propertyKeyName,
    recordAt,
    stringIn,
} from "../../shared/selectors/syntax.selector.ts";
import type { AstNode } from "../../types/syntax.types.ts";
import { defineCheck } from "@govlab/context/check";
import { listener } from "../../shared/factories/listener.factory.ts";
import { templateShapeOf } from "../../shared/selectors/literal.selector.ts";

const PLAINTEXT_SCHEMES: readonly string[] = ["http://", "ws://"];
const SERVER_KEY = "server";
const PORT_KEY = "port";
const TRANSPORT_KEY = "https";

const isPlaintextUrl = function isPlaintextUrl(text: string, followed: boolean): boolean {
    if (URL_SHAPED_IDENTIFIERS.has(text) || EXTERNAL_PLAINTEXT_ENDPOINTS.has(text)) {
        return false;
    }
    return PLAINTEXT_SCHEMES.some((scheme) => text.startsWith(scheme) && (followed || text.length > scheme.length));
};

const keysOf = function keysOf(object: AstNode | null): ReadonlySet<string> {
    return new Set(nodesAt(object, "properties").map(propertyKeyName));
};

const BRANCH_KEYS: readonly string[] = ["consequent", "alternate", "left", "right"];

const serverBlocksOf = function serverBlocksOf(value: AstNode | null): AstNode[] {
    if (isType(value, "ObjectExpression") && value !== null) {
        return [value];
    }
    if (isType(value, "ConditionalExpression") || isType(value, "LogicalExpression")) {
        return BRANCH_KEYS.flatMap((key) => serverBlocksOf(nodeAt(value, key)));
    }
    return [];
};

const isPlaintextListener = function isPlaintextListener(block: AstNode): boolean {
    const keys = keysOf(block);
    return keys.has(PORT_KEY) && !keys.has(TRANSPORT_KEY);
};

export default {
    create(context: RuleContext): RuleListener {
        return listener({
            literal(view, node) {
                const text = literalString(view);
                if (text !== null && isPlaintextUrl(text, false)) {
                    context.report({ messageId: "plaintextUrl", node });
                }
            },
            property(view, node) {
                if (propertyKeyName(view) !== SERVER_KEY) {
                    return;
                }
                if (serverBlocksOf(nodeAt(view, "value")).some(isPlaintextListener)) {
                    context.report({ messageId: "plaintextListener", node });
                }
            },
            templateLiteral(view, node) {
                if (EXTERNAL_PLAINTEXT_ENDPOINTS.has(templateShapeOf(view) ?? "")) {
                    return;
                }
                const [head] = nodesAt(view, "quasis");
                const text = stringIn(recordAt(head ?? null, "value"), "cooked");
                if (isPlaintextUrl(text, nodesAt(view, "expressions").length > 0)) {
                    context.report({ messageId: "plaintextUrl", node });
                }
            },
        });
    },
    meta: {
        docs: {
            checks: defineCheck({ detects: [], enforces: ["architecture:encryption-in-transit"] }),
            description:
                "Every locally served surface speaks encrypted transport, development included. A plaintext URL, page or socket, or a server block that declares a listening port without declaring its encrypted transport, is reported at the source. The one exception is a loopback endpoint a third-party process serves with no encrypted form, recorded by its template shape in a classified registry.",
        },
        messages: {
            plaintextListener:
                "This server block listens on a port without declaring encrypted transport. Declare the transport with the generated local certificate so the surface is served encrypted, the same way production serves it.",
            plaintextUrl:
                "This URL uses the plaintext scheme. Every served surface is encrypted, local ones included; address it with the encrypted scheme and serve it with the generated local certificate.",
        },
        schema: [],
        type: "problem",
    },
} satisfies LocalRule;
```
