Skip to content

Latest commit

 

History

History
422 lines (272 loc) · 8.17 KB

File metadata and controls

422 lines (272 loc) · 8.17 KB

SPEC.mdhotenv


1. Overview

hotenv caches an environment (a set of KEY=VALUE pairs) in a per-session local server and allows commands to run with that environment without reloading secrets repeatedly.

Core model:

  • hotenv serve reads dotenv-style lines from stdin until EOF, stores them as an immutable environment snapshot, then serves it over a Unix domain socket.
  • hotenv run -- <cmd...> fetches the cached environment from the server and executes <cmd...> locally with those variables set.
  • hotenv dump prints the cached environment.

There is no authentication layer beyond filesystem permissions.

Sessions are intentionally short‑lived. Users may terminate the server via normal process control (e.g. Ctrl-C or pkill hotenv).


2. Security Model

hotenv relies entirely on Unix filesystem isolation:

  • Runtime base directory is created with mode 0700.
  • Socket files are created inside that directory.
  • .hotenv is created with mode 0600.

If a process can connect to the Unix socket, it is trusted.

There is:

  • No token
  • No per-request authentication
  • No confirmation mode
  • No runtime permission re-validation

The primary security measure is the idle timeout, which limits how long secrets remain available in memory.


3. Identifiers

Each session has a session_id.

Requirements:

  • 32-bit random value
  • 8 lowercase hexadecimal characters
  • Used only to avoid filename conflicts

Example:

a3f19c2d

No cryptographic strength is required beyond collision avoidance.


4. Environment Input Format

hotenv serve reads dotenv-style lines from stdin until EOF.

Parsing MUST be delegated to a standard dotenv-compatible parsing library. The implementation MUST follow conventional dotenv semantics.

Example input:

# comment
API_KEY=abc123
EMPTY=
DATABASE_URL=postgres://localhost/db

General rules:

  • One entry per line: VAR=VALUE
  • Blank lines are ignored
  • Lines starting with # are ignored
  • Split on the first =
  • VALUE may be empty
  • If a variable appears multiple times, the last occurrence wins
  • Behavior otherwise follows the chosen dotenv library

4.1 Input Parsing Failures (User Input)

If the dotenv parser reports an error:

  • serve MUST:
    • Print a clear, helpful error message to stderr.
    • Include enough context to identify the problem (e.g., line number if available).
    • Exit immediately.
  • No socket MUST be created.
  • No .hotenv file MUST be created.

This is considered a user input parsing error and MUST use exit code 7.


5. Filesystem Layout

5.1 Runtime Base Directory

Chosen as:

  1. $XDG_RUNTIME_DIR/hotenv/ if set
  2. Otherwise /tmp/hotenv-<uid>/

Created with:

mode 0700
owned by current user

No runtime re-validation is required.


5.2 Socket File

Per session:

<runtime_base>/<session_id>.sock
  • Unix domain stream socket
  • Created inside the runtime base directory
  • Removed during cleanup

5.3 Marker File .hotenv

Created in the directory where serve is started:

<root>/.hotenv

Mode: 0600

Must be created safely (e.g., O_CREAT | O_EXCL).

Contents:

socket=<absolute path>

Example:

socket=/tmp/hotenv-1000/a3f19c2d.sock

6. Session Artifact Parsing

Session artifacts include:

  • .hotenv
  • The socket= entry inside .hotenv
  • Socket protocol JSON messages

If parsing of any session artifact fails (e.g., malformed .hotenv, invalid socket= entry, malformed JSON protocol):

  • The command MUST:
    • Print a clear error message to stderr.
    • Exit with exit code 9.
  • No undefined behavior is permitted.

7. Socket Protocol

7.1 Transport

  • Unix domain stream socket at <runtime_base>/<session_id>.sock
  • One request per connection:
    1. Client connects
    2. Client sends one JSON line
    3. Server replies with one JSON line
    4. Connection closes

All messages are UTF‑8 encoded JSON objects terminated by a single newline.


7.2 Requests

All requests are single-line JSON objects.

Dump environment

{"command":"dump"}

Run command

{"command":"run","args":["cmd","arg1","arg2"]}

Fields:

  • command MUST be "dump" or "run".
  • For "run":
    • args MUST be a non-empty array of strings.
    • args[0] is the executable name.
    • Remaining elements are arguments.
    • The server does not execute the command.
    • The args field exists to support server-side logging only.

Malformed JSON or invalid fields → protocol error (exit code 9 on client side).


7.3 Responses

The response format is identical for all successful requests.

Success

{"env":{"KEY":"VALUE"}}
  • env is the complete immutable environment snapshot.
  • Order of keys is unspecified.

Error

{"error":"BAD_REQUEST","message":"..."}

Error codes:

  • BAD_REQUEST
  • INTERNAL

8. Logging

Logging is performed by the server process (hotenv serve).

8.1 Default Behavior

By default, the server MUST log one line per successful request to stderr.

Log format (human-readable):

<timestamp> <command> <summary>

Where:

  • <timestamp> is local time in a human-readable format (e.g., 2026-02-14 15:04:05).
  • <command> is run or dump.
  • <summary> is:
    • For dump: -
    • For run: the value of args[0] only.

Example:

2026-02-14 15:04:05 run python
2026-02-14 15:05:12 dump -

8.2 --verbose

If hotenv serve --verbose is specified:

  • For run, the log line MUST include all arguments, space-separated, instead of only args[0].

Example:

2026-02-14 15:04:05 run python script.py --debug

8.3 --quiet

If hotenv serve --quiet is specified:

  • No per-request logs are emitted.
  • Startup errors and fatal errors MUST still be printed.

If both --quiet and --verbose are specified, this is a CLI usage error (exit code 2).


9. Command-Line Interface

9.1 hotenv serve [-t timeout] [-f] [-q] [-v]

Shorthands:

  • -t, --timeout
  • -f, --force
  • -q, --quiet
  • -v, --verbose

Behavior:

  1. Read environment from stdin.
  2. Parse via dotenv library.
  3. Generate session_id.
  4. Create socket at <runtime_base>/<session_id>.sock.
  5. Write .hotenv.
  6. Serve requests until:
    • Idle timeout expires, or
    • Graceful termination signal is received.

Existing .hotenv

Without --force:

  • Refuse to start (exit code 10).

With --force:

  • Overwrite .hotenv.
  • Best-effort cleanup of the old socket file.

9.2 hotenv run -- <cmd...>

  1. Walk upward to find .hotenv.
  2. Parse .hotenv.
  3. Read socket path.
  4. Connect to socket.
  5. Send:
{"command":"run","args":["cmd","arg1","arg2"]}
  1. Receive environment snapshot.
  2. Overlay returned environment onto current process environment.
  3. Execute <cmd...> locally.
  4. Exit with the child’s exit status.

9.3 hotenv dump

Same lookup and artifact parsing as run.

Sends:

{"command":"dump"}

Receives environment snapshot and prints:

KEY=VALUE

Order unspecified.


10. Idle Timeout (Core Security Property)

  • Default: 5 minutes
  • Configurable via --timeout
  • Timer resets after each successful request
  • When expired:
    • Server exits
    • Cleanup occurs

This is the primary security control limiting exposure of secrets.


11. Cleanup Semantics

Cleanup occurs on:

  • Idle timeout
  • Graceful signal (SIGTERM, SIGINT)

Cleanup consists of:

  • Removing the socket file
  • Removing .hotenv only if it still points to this socket

On non-graceful termination (SIGKILL):

  • No cleanup is required.

12. Exit Codes

Code Meaning
0 Success
2 CLI usage error
3 No .hotenv found
4 Session unreachable
7 User input parse error (dotenv input)
8 OS operation failure
9 Session artifact parse error
10 serve refused (existing marker, no --force)
  • Exit code 7 is reserved exclusively for dotenv parsing failures during serve.
  • Exit code 9 is reserved for parsing failures of session artifacts (.hotenv, protocol JSON, etc.).

hotenv run exits with the child process’s exit code if execution occurs.