cyber-shell-wrapper 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,196 @@
1
+ Metadata-Version: 2.4
2
+ Name: cyber-shell-wrapper
3
+ Version: 0.1.0
4
+ Summary: Local PTY shell wrapper for cyber range AI telemetry
5
+ Author: Nguyen Nhan M.M
6
+ License: Proprietary
7
+ Classifier: Environment :: Console
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Operating System :: POSIX :: Linux
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Topic :: Security
14
+ Classifier: Topic :: System :: Shells
15
+ Requires-Python: >=3.11
16
+ Description-Content-Type: text/markdown
17
+
18
+ # Cyber Shell
19
+
20
+ `cyber-shell` is a local CLI wrapper for interactive Bash on Linux/Kali. It runs a real shell inside a PTY, preserves normal terminal behavior, captures command/output/exit code/cwd, and sends telemetry to a configurable HTTP endpoint in fail-open mode.
21
+
22
+ `endpoint_url` is the URL of the receiving server. For local testing, that can be the same machine running the mock endpoint. For real deployment, it should point to your backend or AI ingestion server, not to the student machine unless that machine is intentionally hosting the receiver.
23
+
24
+ ## Features
25
+
26
+ - Runs a real interactive Bash session inside a PTY.
27
+ - Relays stdin/stdout in raw terminal mode with resize support.
28
+ - Uses shell hooks over a dedicated control pipe instead of printing markers into the terminal.
29
+ - Captures one logical event per command with:
30
+ - `cmd`
31
+ - `output`
32
+ - `exit_code`
33
+ - `cwd`
34
+ - `started_at`
35
+ - `finished_at`
36
+ - `is_interactive`
37
+ - Sends telemetry asynchronously with timeout, retry, and fail-open behavior.
38
+ - Truncates oversized command output with `max_output_bytes`.
39
+ - Includes a built-in mock endpoint and dashboard for local testing.
40
+ - Strips ANSI color/control sequences from telemetry output while keeping the local terminal unchanged.
41
+
42
+ ## Install
43
+
44
+ Assume Python 3 and `pip` are already installed and working normally.
45
+
46
+ ```bash
47
+ python3 -m pip install cyber-shell-wrapper
48
+ ```
49
+
50
+ ## Quick Start
51
+
52
+ The fastest test flow uses two terminals.
53
+
54
+ Terminal 1: start the mock endpoint
55
+
56
+ ```bash
57
+ cyber-shell mock-endpoint --host 0.0.0.0 --port 8080 --api-key replace-me
58
+ ```
59
+
60
+ Open the dashboard locally:
61
+
62
+ ```text
63
+ http://127.0.0.1:8080/
64
+ ```
65
+
66
+ If you are accessing it from another machine:
67
+
68
+ ```text
69
+ http://<server-ip>:8080/
70
+ ```
71
+
72
+ Check that the endpoint is alive:
73
+
74
+ ```bash
75
+ curl -i http://127.0.0.1:8080/health
76
+ ```
77
+
78
+ Terminal 2: start the wrapped shell and point it at the mock endpoint
79
+
80
+ ```bash
81
+ cyber-shell --endpoint-url http://127.0.0.1:8080/api/terminal-events --api-key replace-me
82
+ ```
83
+
84
+ Alternative: export the variables first, then start `cyber-shell`
85
+
86
+ ```bash
87
+ export CYBER_SHELL_ENDPOINT_URL=http://127.0.0.1:8080/api/terminal-events
88
+ export CYBER_SHELL_API_KEY=replace-me
89
+ cyber-shell
90
+ ```
91
+
92
+ Inside that wrapped shell, run a few commands:
93
+
94
+ ```bash
95
+ whoami
96
+ pwd
97
+ ls -la
98
+ ```
99
+
100
+ To confirm that you are inside the wrapped session:
101
+
102
+ ```bash
103
+ echo $CYBER_SHELL_SESSION_ID
104
+ ```
105
+
106
+ If it prints a value like `sess-...`, you are inside a `cyber-shell` session.
107
+
108
+ ## Configuration
109
+
110
+ By default, `cyber-shell` reads:
111
+
112
+ ```text
113
+ ~/.config/cyber-shell/config.yaml
114
+ ```
115
+
116
+ The config format is intentionally simple YAML with optional environment variable overrides.
117
+
118
+ Runtime precedence is:
119
+
120
+ - CLI arguments
121
+ - environment variables
122
+ - config file
123
+ - built-in defaults
124
+
125
+ When you start the wrapped shell with runtime overrides such as `--endpoint-url`, `--api-key`, or exported `CYBER_SHELL_*` variables, `cyber-shell` writes the effective values back into `~/.config/cyber-shell/config.yaml`. In this project, that file acts as a temporary session cache so later terminals can reuse the same lab settings without retyping them.
126
+
127
+ Sample config:
128
+
129
+ ```yaml
130
+ endpoint_url: "http://127.0.0.1:8080/api/terminal-events"
131
+ api_key: "replace-me"
132
+ timeout_ms: 3000
133
+ retry_max: 3
134
+ retry_backoff_ms: 1000
135
+ max_output_bytes: 262144
136
+ queue_size: 256
137
+ shell_path: "/bin/bash"
138
+ metadata:
139
+ hostname_group: "kali-lab"
140
+ ```
141
+
142
+ Print the default template:
143
+
144
+ ```bash
145
+ cyber-shell print-default-config
146
+ ```
147
+
148
+ Supported environment overrides:
149
+
150
+ - `CYBER_SHELL_CONFIG`
151
+ - `CYBER_SHELL_ENDPOINT_URL`
152
+ - `CYBER_SHELL_API_KEY`
153
+ - `CYBER_SHELL_TIMEOUT_MS`
154
+ - `CYBER_SHELL_RETRY_MAX`
155
+ - `CYBER_SHELL_RETRY_BACKOFF_MS`
156
+ - `CYBER_SHELL_MAX_OUTPUT_BYTES`
157
+ - `CYBER_SHELL_QUEUE_SIZE`
158
+ - `CYBER_SHELL_SHELL_PATH`
159
+
160
+ ## Telemetry Flow
161
+
162
+ The wrapper sends telemetry with `POST` requests to `endpoint_url`.
163
+
164
+ - Local test flow:
165
+ - `cyber-shell` posts JSON to `http://127.0.0.1:8080/api/terminal-events`
166
+ - the mock endpoint displays those events in its dashboard
167
+ - Production flow:
168
+ - `cyber-shell` posts JSON to your backend or AI ingestion server
169
+ - that server stores the events, forwards them, or exposes them to downstream AI components
170
+
171
+ The wrapper does not need to `GET` logs back from the AI server for the core design. The primary contract is outbound `POST` from the PTY wrapper to the server. Optional `GET` endpoints such as `GET /events` are only for debugging, review, or dashboards.
172
+
173
+ ## Manual Endpoint Test
174
+
175
+ You can test the dashboard without starting the wrapped shell:
176
+
177
+ ```bash
178
+ curl -i -X POST http://127.0.0.1:8080/api/terminal-events \
179
+ -H "Content-Type: application/json" \
180
+ -H "Authorization: Bearer replace-me" \
181
+ -d '{"session_id":"s1","seq":1,"cmd":"whoami","cwd":"/home/kali","exit_code":0,"output":"kali","output_truncated":false,"started_at":"2026-03-21T10:00:00Z","finished_at":"2026-03-21T10:00:01Z","is_interactive":false,"hostname":"kali","shell":"bash","metadata":{}}'
182
+ ```
183
+
184
+ ## Notes
185
+
186
+ - This tool targets POSIX/Linux, with Kali as the primary environment.
187
+ - V1 does not semantically parse `vim`, `top`, `nano`, `less`, or `man`; it only preserves terminal behavior and finalizes the event when the prompt returns.
188
+ - Nested shells and remote shells are treated as opaque terminal streams.
189
+ - The wrapper does not capture raw keystrokes for the entire session.
190
+
191
+ ## Dev Checks
192
+
193
+ ```bash
194
+ python -m unittest discover -s tests -v
195
+ python -m compileall src tests
196
+ ```
@@ -0,0 +1,179 @@
1
+ # Cyber Shell
2
+
3
+ `cyber-shell` is a local CLI wrapper for interactive Bash on Linux/Kali. It runs a real shell inside a PTY, preserves normal terminal behavior, captures command/output/exit code/cwd, and sends telemetry to a configurable HTTP endpoint in fail-open mode.
4
+
5
+ `endpoint_url` is the URL of the receiving server. For local testing, that can be the same machine running the mock endpoint. For real deployment, it should point to your backend or AI ingestion server, not to the student machine unless that machine is intentionally hosting the receiver.
6
+
7
+ ## Features
8
+
9
+ - Runs a real interactive Bash session inside a PTY.
10
+ - Relays stdin/stdout in raw terminal mode with resize support.
11
+ - Uses shell hooks over a dedicated control pipe instead of printing markers into the terminal.
12
+ - Captures one logical event per command with:
13
+ - `cmd`
14
+ - `output`
15
+ - `exit_code`
16
+ - `cwd`
17
+ - `started_at`
18
+ - `finished_at`
19
+ - `is_interactive`
20
+ - Sends telemetry asynchronously with timeout, retry, and fail-open behavior.
21
+ - Truncates oversized command output with `max_output_bytes`.
22
+ - Includes a built-in mock endpoint and dashboard for local testing.
23
+ - Strips ANSI color/control sequences from telemetry output while keeping the local terminal unchanged.
24
+
25
+ ## Install
26
+
27
+ Assume Python 3 and `pip` are already installed and working normally.
28
+
29
+ ```bash
30
+ python3 -m pip install cyber-shell-wrapper
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ The fastest test flow uses two terminals.
36
+
37
+ Terminal 1: start the mock endpoint
38
+
39
+ ```bash
40
+ cyber-shell mock-endpoint --host 0.0.0.0 --port 8080 --api-key replace-me
41
+ ```
42
+
43
+ Open the dashboard locally:
44
+
45
+ ```text
46
+ http://127.0.0.1:8080/
47
+ ```
48
+
49
+ If you are accessing it from another machine:
50
+
51
+ ```text
52
+ http://<server-ip>:8080/
53
+ ```
54
+
55
+ Check that the endpoint is alive:
56
+
57
+ ```bash
58
+ curl -i http://127.0.0.1:8080/health
59
+ ```
60
+
61
+ Terminal 2: start the wrapped shell and point it at the mock endpoint
62
+
63
+ ```bash
64
+ cyber-shell --endpoint-url http://127.0.0.1:8080/api/terminal-events --api-key replace-me
65
+ ```
66
+
67
+ Alternative: export the variables first, then start `cyber-shell`
68
+
69
+ ```bash
70
+ export CYBER_SHELL_ENDPOINT_URL=http://127.0.0.1:8080/api/terminal-events
71
+ export CYBER_SHELL_API_KEY=replace-me
72
+ cyber-shell
73
+ ```
74
+
75
+ Inside that wrapped shell, run a few commands:
76
+
77
+ ```bash
78
+ whoami
79
+ pwd
80
+ ls -la
81
+ ```
82
+
83
+ To confirm that you are inside the wrapped session:
84
+
85
+ ```bash
86
+ echo $CYBER_SHELL_SESSION_ID
87
+ ```
88
+
89
+ If it prints a value like `sess-...`, you are inside a `cyber-shell` session.
90
+
91
+ ## Configuration
92
+
93
+ By default, `cyber-shell` reads:
94
+
95
+ ```text
96
+ ~/.config/cyber-shell/config.yaml
97
+ ```
98
+
99
+ The config format is intentionally simple YAML with optional environment variable overrides.
100
+
101
+ Runtime precedence is:
102
+
103
+ - CLI arguments
104
+ - environment variables
105
+ - config file
106
+ - built-in defaults
107
+
108
+ When you start the wrapped shell with runtime overrides such as `--endpoint-url`, `--api-key`, or exported `CYBER_SHELL_*` variables, `cyber-shell` writes the effective values back into `~/.config/cyber-shell/config.yaml`. In this project, that file acts as a temporary session cache so later terminals can reuse the same lab settings without retyping them.
109
+
110
+ Sample config:
111
+
112
+ ```yaml
113
+ endpoint_url: "http://127.0.0.1:8080/api/terminal-events"
114
+ api_key: "replace-me"
115
+ timeout_ms: 3000
116
+ retry_max: 3
117
+ retry_backoff_ms: 1000
118
+ max_output_bytes: 262144
119
+ queue_size: 256
120
+ shell_path: "/bin/bash"
121
+ metadata:
122
+ hostname_group: "kali-lab"
123
+ ```
124
+
125
+ Print the default template:
126
+
127
+ ```bash
128
+ cyber-shell print-default-config
129
+ ```
130
+
131
+ Supported environment overrides:
132
+
133
+ - `CYBER_SHELL_CONFIG`
134
+ - `CYBER_SHELL_ENDPOINT_URL`
135
+ - `CYBER_SHELL_API_KEY`
136
+ - `CYBER_SHELL_TIMEOUT_MS`
137
+ - `CYBER_SHELL_RETRY_MAX`
138
+ - `CYBER_SHELL_RETRY_BACKOFF_MS`
139
+ - `CYBER_SHELL_MAX_OUTPUT_BYTES`
140
+ - `CYBER_SHELL_QUEUE_SIZE`
141
+ - `CYBER_SHELL_SHELL_PATH`
142
+
143
+ ## Telemetry Flow
144
+
145
+ The wrapper sends telemetry with `POST` requests to `endpoint_url`.
146
+
147
+ - Local test flow:
148
+ - `cyber-shell` posts JSON to `http://127.0.0.1:8080/api/terminal-events`
149
+ - the mock endpoint displays those events in its dashboard
150
+ - Production flow:
151
+ - `cyber-shell` posts JSON to your backend or AI ingestion server
152
+ - that server stores the events, forwards them, or exposes them to downstream AI components
153
+
154
+ The wrapper does not need to `GET` logs back from the AI server for the core design. The primary contract is outbound `POST` from the PTY wrapper to the server. Optional `GET` endpoints such as `GET /events` are only for debugging, review, or dashboards.
155
+
156
+ ## Manual Endpoint Test
157
+
158
+ You can test the dashboard without starting the wrapped shell:
159
+
160
+ ```bash
161
+ curl -i -X POST http://127.0.0.1:8080/api/terminal-events \
162
+ -H "Content-Type: application/json" \
163
+ -H "Authorization: Bearer replace-me" \
164
+ -d '{"session_id":"s1","seq":1,"cmd":"whoami","cwd":"/home/kali","exit_code":0,"output":"kali","output_truncated":false,"started_at":"2026-03-21T10:00:00Z","finished_at":"2026-03-21T10:00:01Z","is_interactive":false,"hostname":"kali","shell":"bash","metadata":{}}'
165
+ ```
166
+
167
+ ## Notes
168
+
169
+ - This tool targets POSIX/Linux, with Kali as the primary environment.
170
+ - V1 does not semantically parse `vim`, `top`, `nano`, `less`, or `man`; it only preserves terminal behavior and finalizes the event when the prompt returns.
171
+ - Nested shells and remote shells are treated as opaque terminal streams.
172
+ - The wrapper does not capture raw keystrokes for the entire session.
173
+
174
+ ## Dev Checks
175
+
176
+ ```bash
177
+ python -m unittest discover -s tests -v
178
+ python -m compileall src tests
179
+ ```
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "cyber-shell-wrapper"
7
+ version = "0.1.0"
8
+ description = "Local PTY shell wrapper for cyber range AI telemetry"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = { text = "Proprietary" }
12
+ authors = [
13
+ { name = "Nguyen Nhan M.M" }
14
+ ]
15
+ classifiers = [
16
+ "Environment :: Console",
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: POSIX :: Linux",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Topic :: Security",
23
+ "Topic :: System :: Shells",
24
+ ]
25
+
26
+ [project.scripts]
27
+ cyber-shell = "cyber_shell.cli:main"
28
+
29
+ [tool.setuptools]
30
+ package-dir = { "" = "src" }
31
+
32
+ [tool.setuptools.packages.find]
33
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ """Cyber Shell package."""
2
+
3
+ __all__ = ["__version__"]
4
+
5
+ __version__ = "0.1.0"
@@ -0,0 +1,174 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import shlex
5
+ from dataclasses import dataclass
6
+
7
+ from .config import AppConfig
8
+ from .models import ShellEvent
9
+
10
+
11
+ INTERACTIVE_COMMANDS = {
12
+ "alsamixer",
13
+ "ftp",
14
+ "htop",
15
+ "less",
16
+ "man",
17
+ "more",
18
+ "mongo",
19
+ "mysql",
20
+ "nano",
21
+ "nmtui",
22
+ "psql",
23
+ "python",
24
+ "python3",
25
+ "redis-cli",
26
+ "sftp",
27
+ "ssh",
28
+ "sqlite3",
29
+ "telnet",
30
+ "tig",
31
+ "tmux",
32
+ "top",
33
+ "vi",
34
+ "view",
35
+ "vim",
36
+ "watch",
37
+ }
38
+
39
+ PREFIX_WRAPPERS = {
40
+ "builtin",
41
+ "chronic",
42
+ "command",
43
+ "env",
44
+ "exec",
45
+ "nohup",
46
+ "stdbuf",
47
+ "time",
48
+ }
49
+
50
+ ANSI_ESCAPE_RE = re.compile(
51
+ r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x1b\x07]*(?:\x07|\x1b\\))"
52
+ )
53
+
54
+
55
+ @dataclass(slots=True)
56
+ class ActiveCommand:
57
+ started_at: str
58
+ cmd: str
59
+ output_buffer: bytearray
60
+ truncated: bool = False
61
+
62
+
63
+ class EventAssembler:
64
+ def __init__(self, config: AppConfig, session_id: str) -> None:
65
+ self._config = config
66
+ self._session_id = session_id
67
+ self._seq = 0
68
+ self._current: ActiveCommand | None = None
69
+
70
+ def start_command(self, started_at: str, cmd: str) -> None:
71
+ self._current = ActiveCommand(
72
+ started_at=started_at,
73
+ cmd=cmd.strip(),
74
+ output_buffer=bytearray(),
75
+ )
76
+
77
+ def append_output(self, chunk: bytes) -> None:
78
+ if self._current is None or not chunk:
79
+ return
80
+ remaining = self._config.max_output_bytes - len(self._current.output_buffer)
81
+ if remaining <= 0:
82
+ self._current.truncated = True
83
+ return
84
+ self._current.output_buffer.extend(chunk[:remaining])
85
+ if len(chunk) > remaining:
86
+ self._current.truncated = True
87
+
88
+ def finish_command(
89
+ self,
90
+ *,
91
+ finished_at: str,
92
+ exit_code: int,
93
+ cwd: str,
94
+ ) -> ShellEvent | None:
95
+ current = self._current
96
+ self._current = None
97
+ if current is None or not current.cmd:
98
+ return None
99
+
100
+ self._seq += 1
101
+ return ShellEvent(
102
+ session_id=self._session_id,
103
+ hostname=self._config.hostname,
104
+ shell="bash",
105
+ seq=self._seq,
106
+ cwd=cwd,
107
+ cmd=current.cmd,
108
+ exit_code=exit_code,
109
+ output=_sanitize_output(
110
+ current.output_buffer.decode("utf-8", errors="replace")
111
+ ),
112
+ output_truncated=current.truncated,
113
+ started_at=current.started_at,
114
+ finished_at=finished_at,
115
+ is_interactive=is_interactive_command(current.cmd),
116
+ metadata=dict(self._config.metadata),
117
+ )
118
+
119
+
120
+ def is_interactive_command(command: str) -> bool:
121
+ executable = _extract_command_name(command)
122
+ if executable is None:
123
+ return False
124
+ return executable in INTERACTIVE_COMMANDS
125
+
126
+
127
+ def _extract_command_name(command: str) -> str | None:
128
+ try:
129
+ tokens = shlex.split(command, posix=True)
130
+ except ValueError:
131
+ return None
132
+
133
+ index = 0
134
+ while index < len(tokens):
135
+ token = tokens[index]
136
+ if _looks_like_env_assignment(token):
137
+ index += 1
138
+ continue
139
+ if token == "sudo":
140
+ index += 1
141
+ while index < len(tokens):
142
+ sudo_token = tokens[index]
143
+ if sudo_token == "--":
144
+ index += 1
145
+ break
146
+ if not sudo_token.startswith("-"):
147
+ break
148
+ index += 1
149
+ if sudo_token in {"-g", "-h", "-p", "-u"} and index < len(tokens):
150
+ index += 1
151
+ continue
152
+ if token == "env":
153
+ index += 1
154
+ while index < len(tokens) and _looks_like_env_assignment(tokens[index]):
155
+ index += 1
156
+ continue
157
+ if token in PREFIX_WRAPPERS:
158
+ index += 1
159
+ continue
160
+ return token
161
+ return None
162
+
163
+
164
+ def _looks_like_env_assignment(token: str) -> bool:
165
+ if "=" not in token or token.startswith("="):
166
+ return False
167
+ name, _ = token.split("=", 1)
168
+ return name.replace("_", "A").isalnum()
169
+
170
+
171
+ def _sanitize_output(value: str) -> str:
172
+ cleaned = ANSI_ESCAPE_RE.sub("", value)
173
+ cleaned = cleaned.replace("\r\n", "\n").replace("\r", "\n")
174
+ return cleaned