Selected work

Chapter 04 / Security research

SSH Honeypot.

Observe an SSH session without exposing the host shell.

Status

Lab implementation

My role

Personal security lab

View source on GitHub

Source-verified session boundaries

Disposable container per shell session
1
Default container memory limit
256 MiB
Default command execution timeout
30 s
Evidence files per session
3

Each session produces metadata, structured events and a transcript, so a reviewer can connect attempted commands to returned output. The configured 256 MiB memory limit bounds each container, and the 30-second subprocess timeout limits how long the manager waits for a command.

Measurement scope & source

These are configurable code defaults and evidence outputs, not measured throughput or containment results. A subprocess timeout does not prove the command inside the container was terminated. Network isolation needs explicit configuration; the project remains a controlled lab implementation.

Inspect limits and session records ↗

The idea

A session manager, a disposable container, and a record of what happened.

Python / Paramiko / Docker

Animated architecture

A session and its evidence

A lab architecture. Docker network isolation is a configuration requirement, not a default guarantee.

01 / Connect

02 / Prepare

03 / Interact

04 / Record and close

Control flow / Decisions & data

The lifecycle of an SSH session

The manager owns the connection and evidence. A per-session container handles shell execution, while the command loop maintains the working directory.

Decision branchesSolid arrows show routing and return paths.

Scroll across the diagram to follow each branch

The lifecycle of an SSH sessionThe manager owns the connection and evidence. A per-session container handles shell execution, while the command loop maintains the working directory. A text explanation follows the diagram.NoYesExitcd / blankOther commandNext promptContinueLoop error → cleanupPersist end timestampCommand result eventsIncoming SSH connectionCreate directory + metadataParamiko transportHost key, auth events, shell channelContainer starts?Limits + tmpfs + no-new-privilegesStartup failureLog error; close channel/transportRead commandAppend command event + input textExit / EOFEnd interactioncd or blank commandUpdate cwd or redraw promptdocker execRun command at tracked cwdRecord & replyExit code, duration, stdout/stderrSession cleanupStop container; close transportSession evidencemetadata / JSONL / transcript

Reading the flow

  1. The manager creates metadata and records authentication attempts. Failed transport startup or a missing channel logs an error and closes the transport.
  2. A writable container uses memory, PID and CPU limits, no-new-privileges, and tmpfs mounts. Network isolation only applies when a network is explicitly configured.
  3. Exit/logout/quit or end-of-input ends the loop. Blank input redraws the prompt. The manager tracks cd state; ordinary commands execute inside the container.
  4. Command result events record exit code, duration, and output lengths; stdout/stderr return to the SSH client. Session cleanup stops the container, closes transport, and updates metadata.

Why / What / How

The thinking behind the system.

Why observe a session rather than a connection count?

An SSH connection log tells you that someone connected. It does not explain the sequence of commands they attempted or the output they saw. This lab explores collecting that richer interaction as a session that can be reconstructed later.

The system separates an observer, responsible for the SSH connection and evidence, from a disposable execution environment. The aim is to study behavior while keeping the manager’s own shell out of the normal command path.

What lives on each side of the boundary

The Python manager owns Paramiko transport, authentication events, shell channels and session records. Docker supplies a container for a session, and commands are executed through docker exec. Metadata, event records and a transcript preserve different views of the interaction.

A container is a useful lifecycle and resource boundary, not proof of safe isolation. Resource limits and no-new-privileges constrain execution, while network isolation still depends on explicit configuration. The intended environment is an authorized, controlled lab.

How the command loop works

A connection creates a session record and establishes the SSH transport. Once a shell channel is available, the manager prepares the session container. It reads a command, handles working-directory changes, executes the command inside the container and returns output to the SSH client.

The loop repeats until the session ends. Recording both command events and visible output helps distinguish what was requested from what the environment returned. Container cleanup ends the execution environment while the evidence remains available locally for inspection.

What an investigation can learn

A useful review follows the session timeline: authentication attempts, commands, changes of directory and returned output. For example, a command that tries to read a file is more meaningful when its response is visible alongside it. A transcript supports that reconstruction without assuming every attempted command succeeded.

The next hardening questions concern enforced network restrictions, access to the manager and its Docker privileges, and reliable cleanup under failure. The project demonstrates the session and evidence pipeline; it does not claim that exposing a container-backed honeypot publicly is automatically safe.

The starting point

Why this project?

Understanding interactive SSH behavior requires more than a connection count. This project records authentication attempts, commands, and session output for inspection.

My contribution

The work I brought to it.

Built a Python SSH honeypot with a per-session Docker execution environment and structured session records.

How it works

From input to output.

Paramiko handles authentication and shell channels. The manager starts a container, reads commands, executes them with docker exec, and sends output back over SSH. Metadata, events, and transcripts are recorded locally; the container is stopped at session end.

  1. SSH connection
  2. Paramiko session
  3. Container shell
  4. Session evidence

A design decision

Separate the shell from the observer.

A container per session separates the interaction from the Python manager. Resource limits and no-new-privileges constrain the session, but network isolation still requires explicit configuration.

The evidence

Look under the surface.

These links point to the reviewed source revision, so the implementation behind this story stays inspectable.

Current boundaries

Useful work. Honest limits.

  • Network isolation depends on explicit configuration; the manager does not enforce an internal network by default.
  • Intended for an authorized, controlled lab. Containerization alone does not establish safe public deployment.

Source reviewed October 3, 2026.

Keep exploring / Chapter 05

Financial RAG Analyst

Ask questions of company filings with the source close at hand.

Let’s build something
worth protecting.

“A thoughtful conversation can be the beginning of something worth building.”
Connect on LinkedIn