tibet-nc 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,193 @@
1
+ # Secrets & env
2
+ .env
3
+ *.env
4
+ *.secret
5
+
6
+ # Keys & certs
7
+ *.key
8
+ *.pem
9
+ certs/
10
+ secrets/
11
+
12
+ # Databases & dumps
13
+ *.db
14
+ *.sqlite
15
+ *.sql
16
+ dump_*/
17
+
18
+ # EXCEPT: Allow database schemas (needed for server rebuild)
19
+ !database-schemas/*.sql
20
+
21
+ # Logs & runtime data
22
+ logs/
23
+ *.log
24
+ __pycache__/
25
+ *.pyc
26
+ venv/
27
+ .venv/
28
+ **/venv/
29
+ **/.venv/
30
+
31
+ # ─── Brain API runtime state (mirrored to OHM1, NOT to GitHub) ──────────
32
+ # Registry + sessions + caches contain hardware hashes, public keys,
33
+ # session tokens, conversation state. Privacy-sensitive. Restore from
34
+ # OHM1 mirror on server rebuild, not from git.
35
+ brain_api/data/
36
+ brain_api/.conversation_cache/
37
+ brain_api/ains_registry.json
38
+ brain_api/ipoll_registry.json
39
+ brain_api/high_five_log.json
40
+ brain_api/agent_keys/
41
+ brain_api/founder_counter.json
42
+
43
+ # Pending claims, phantom sessions, consent store — never to GitHub
44
+ brain_api/**/pending_claims.json
45
+ brain_api/**/phantom_sessions.json
46
+ brain_api/**/consent_store.json
47
+ brain_api/**/ainternet_sessions.json
48
+ brain_api/**/ainternet_challenges.json
49
+ brain_api/**/byoa_agents.json
50
+ brain_api/**/canvas_data.json
51
+ brain_api/**/ai_response_log.json
52
+ brain_api/**/ai_team_context.json
53
+ brain_api/**/ai_teams_sessions.json
54
+ brain_api/**/evolution_timeline.json
55
+
56
+ # Static downloads (binaries served via nginx, not source)
57
+ brain_api/static/downloads/
58
+
59
+ # ─── Signing keys / keystores — NEVER on GitHub ─────────────────────────
60
+ # These live on DL360 + OHM1 mirror + USB stick + encrypted off-site backup.
61
+ # Loss = no more Play Store updates for org.ainternet.kit forever.
62
+ *.keystore
63
+ *.jks
64
+ *.keystore.gpg
65
+ *.jks.gpg
66
+ keystore.properties
67
+ keystores/
68
+
69
+ # Configs met secrets (we gebruiken straks templates)
70
+ config/
71
+ brain_api/provisioning.local.json
72
+ brain_api/provisioning.json
73
+
74
+ # Landing pages (privé - niet open source)
75
+ landing-pages/
76
+ humotica.com/
77
+ jtel.nl/
78
+
79
+ # Social media posts (strategie - niet open source)
80
+ SOCIAL-MEDIA-POSTS.md
81
+ HN-POST-UNDER-4000.md
82
+ STRATO-DEPLOY-HUMOTICA.md
83
+
84
+ # Endorsement outreach (privaat contact)
85
+ ARXIV-ENDORSEMENT-OUTREACH.md
86
+
87
+ # Deployment secrets
88
+ DEPLOYMENT-GUIDE.md
89
+
90
+ # R Project files (Dirty Data Challenge)
91
+ .Rproj.user
92
+ .Rhistory
93
+ .RData
94
+ .Ruserdata
95
+ *.zip
96
+ .mural_tokens.json
97
+ auth.json
98
+ gen-lang-client*.json
99
+ *.credentials.json
100
+
101
+ # Rust build artifacts
102
+ **/target/
103
+ *.whl
104
+
105
+ # Compiled binaries (build locally)
106
+ jis-router/jis-router
107
+ sentinel-rs/sentinel-rs
108
+
109
+ # Build distribution
110
+ sandbox/ai/codex/dist/
111
+ sandbox_backup/
112
+ did-jis-core
113
+
114
+ # =============================================================================
115
+ # Eigen repos — hebben hun eigen git remotes, niet dubbel opslaan
116
+ # =============================================================================
117
+
118
+ # Packages (elk een eigen repo)
119
+ packages/jis-iam-bridge/
120
+ packages/rapid-rag/
121
+ packages/reflux/
122
+ packages/sema-protocol/
123
+ packages/tibet-anticheat/
124
+ packages/tibet-ci/
125
+ packages/tibet-claw/
126
+ packages/tibet-context/
127
+ packages/tibet-core/
128
+ packages/tibet-db/
129
+ packages/tibet-edge/
130
+ packages/tibet-forge/
131
+ packages/tibet-iot/
132
+ packages/tibet-jawbreaker/
133
+ packages/tibet-ledger/
134
+ packages/tibet-marketplace/
135
+ packages/tibet-mesh/
136
+ packages/tibet-mirror/
137
+ packages/tibet-nis2/
138
+ packages/tibet-overlay/
139
+ packages/tibet-phantom/
140
+ packages/tibet-phantom-mcp/
141
+ packages/tibet-ping/
142
+ packages/tibet-pol/
143
+ packages/tibet-pqc/
144
+ packages/tibet-sbom/
145
+ packages/tibet-snap/
146
+ packages/tibet-soc/
147
+ packages/tibet-spiffe/
148
+ packages/tibet-tools/
149
+ packages/tibet-trail/
150
+ packages/tibet-triage/
151
+ packages/tibet-triage-mcp/
152
+ packages/tibet-twin/
153
+ packages/tibet-workload/
154
+ packages/tibet-y2k38/
155
+ packages/tlex-edge/
156
+ packages/tibet-tail/
157
+ packages/tibet-nc/
158
+
159
+ # Sub-projects met eigen repos
160
+ bunq7/
161
+ humotica-core/
162
+ jis-core/
163
+ JTm-dev/
164
+ kit-package/
165
+ symbAIon/
166
+ tibet-audit/
167
+ tibet-audit-npm/
168
+ tibet-core/
169
+ tibetclaw/
170
+ snaft/
171
+
172
+ # MCP servers (eigen repos)
173
+ mcp-servers/aidrac/
174
+ mcp-servers/ainternet/
175
+ mcp-servers/mcp-server-jis/
176
+ mcp-servers/sensory/
177
+ mcp-servers/tibet/
178
+
179
+ # Hackathon sub-repos
180
+ hackaway2026/clawmetry/
181
+
182
+ # Private memory (eigen repo)
183
+ .root_ai_memory/
184
+ .root_ai_thoughts/
185
+ brain_api/static/*.apk
186
+
187
+ # SWARM-003 refactor backups (local rollback only)
188
+ *.pre-secrets-refactor.bak
189
+ .env.bak-*
190
+
191
+ # Forensic audit trail — pentest logs (groot, niet versioned)
192
+ brain_api/pentest_logs/*.jsonl
193
+ brain_api/pentest_logs/*.log
@@ -0,0 +1,16 @@
1
+ {
2
+ "package": "tibet-nc",
3
+ "registries": {
4
+ "pypi": {
5
+ "url": "https://pypi.org/project/tibet-nc/",
6
+ "version": "0.1.0a1",
7
+ "deprecated": false
8
+ }
9
+ },
10
+ "publish": {
11
+ "pypi": {
12
+ "command": "cd /srv/jtel-stack/packages/tibet-nc && python -m build && twine upload dist/*",
13
+ "auth": "~/.pypirc [pypi] token"
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,41 @@
1
+ # Enterprise Support
2
+
3
+ This package is part of the [TIBET ecosystem](https://pypi.org/project/tibet/) by [Humotica](https://humotica.com) — provenance, identity, and trust infrastructure for AI systems.
4
+
5
+ ## Contact
6
+
7
+ | Channel | Address | Use case |
8
+ |---------|---------|----------|
9
+ | **Enterprise** | enterprise@humotica.com | Private hub, SLA, dedicated support, integration partnerships |
10
+ | **Support** | support@humotica.com | Technical support, bug reports, feature requests |
11
+ | **Security** | security@humotica.com | Vulnerability disclosure (coordinated disclosure preferred) |
12
+
13
+ ## What we offer
14
+
15
+ - **Private Hub** — self-hosted .aint domain registry and I-Poll messaging for your organization
16
+ - **Dedicated Support** — direct engineering support with SLA
17
+ - **Custom Integration** — help integrating TIBET provenance into your existing CI/CD, compliance, or audit workflows
18
+ - **Compliance Mapping** — guidance on EU AI Act, NIS2, and DORA alignment using TIBET tokens
19
+
20
+ ## Compliance
21
+
22
+ The TIBET ecosystem provides building blocks for compliance with:
23
+
24
+ - **EU AI Act** (Art. 12) — logging and traceability via TIBET tokens
25
+ - **NIS2** (Art. 21) — supply chain security via cryptographic provenance
26
+ - **DORA** — ICT risk management via audit trails and sealed snapshots
27
+
28
+ > TIBET is compliance-enabling, not compliance-certifying. It provides the technical controls; your legal team maps them to your obligations.
29
+
30
+ ## Stats
31
+
32
+ - 109,000+ PyPI downloads across 112 countries
33
+ - 91+ packages in the ecosystem
34
+ - Used in enterprise mirrors, CI/CD pipelines, and production workflows
35
+
36
+ ## Links
37
+
38
+ - [AInternet](https://ainternet.org) — the AI network
39
+ - [Documentation](https://ainternet.org/docs/)
40
+ - [PyPI ecosystem](https://pypi.org/project/tibet/)
41
+ - [GitHub](https://github.com/Humotica)
@@ -0,0 +1,132 @@
1
+ Metadata-Version: 2.4
2
+ Name: tibet-nc
3
+ Version: 0.1.0
4
+ Summary: Secure remote shell via Matrix E2EE — no ports, no TCP surface, TIBET L4 Airlock verification.
5
+ Project-URL: Homepage, https://github.com/humotica/tibet-nc
6
+ Project-URL: Repository, https://github.com/humotica/tibet-nc
7
+ Project-URL: Documentation, https://humotica.com/docs/tibet-nc
8
+ Author-email: "J. van de Meent" <jasper@humotica.com>, "R. AI" <root_idd@humotica.nl>
9
+ Maintainer-email: Humotica AI Lab <ai@humotica.nl>
10
+ License: MIT
11
+ Keywords: airlock,e2ee,matrix,no-port,provenance,remote-shell,ssh-alternative,tibet,zero-trust
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Communications :: Chat
23
+ Classifier: Topic :: Security
24
+ Classifier: Topic :: System :: Systems Administration
25
+ Requires-Python: >=3.10
26
+ Requires-Dist: matrix-nio>=0.21
27
+ Requires-Dist: python-dotenv>=1.0
28
+ Requires-Dist: requests>=2.28
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
31
+ Requires-Dist: pytest>=7.0; extra == 'dev'
32
+ Provides-Extra: e2ee
33
+ Requires-Dist: matrix-nio[e2e]>=0.21; extra == 'e2ee'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # tibet-nc
37
+
38
+ > **PRE-ALPHA (v0.1.0a1) — NOT PRODUCTION READY**
39
+ >
40
+ > API, protocol, and security model are subject to change.
41
+ > Do not deploy in production environments.
42
+
43
+ Secure remote shell via Matrix E2EE — SSH without the attack surface.
44
+
45
+ ## What is tibet-nc?
46
+
47
+ tibet-nc replaces SSH/telnet with a remote shell that has **no open ports, no TCP listener, and no discoverable attack surface**. Commands are sent via Matrix (end-to-end encrypted) and every execution is a verified TIBET token.
48
+
49
+ Think of it as a speakeasy: there's no visible door. You need to know the Matrix room and have a verified TIBET identity to get in.
50
+
51
+ ## How it works
52
+
53
+ ```
54
+ [Matrix Client] → E2EE message → [Matrix Server] → [tibet-nc daemon]
55
+
56
+ L4 Airlock
57
+ ├── Identity check
58
+ ├── Timebox check
59
+ ├── Command safety
60
+ └── Hash chain
61
+
62
+ Restricted PTY
63
+
64
+ Output + TIBET token
65
+
66
+ [Matrix Client] ← E2EE
67
+ ```
68
+
69
+ ### L4 Airlock Verification
70
+
71
+ Every command passes 4 layers before execution:
72
+
73
+ 1. **Identity** — Matrix user must be in the allowed list
74
+ 2. **Timebox** — Command must arrive within latency window for its DID type
75
+ 3. **Command safety** — Blocked patterns (`rm -rf /`, `dd if=`, etc.) are rejected
76
+ 4. **Hash chain** — SHA256 chain links every command to the previous one
77
+
78
+ ### What makes it different from SSH?
79
+
80
+ | | SSH | tibet-nc |
81
+ |---|---|---|
82
+ | Open port | 22 (scannable) | None |
83
+ | Protocol | TCP | Matrix E2EE |
84
+ | Auth | Keys/password | TIBET identity |
85
+ | Audit trail | auth.log | Full TIBET provenance per command |
86
+ | Command safety | None | L4 Airlock (blocked patterns) |
87
+ | Hash chain | None | SHA256 per session |
88
+
89
+ ## Current status
90
+
91
+ - [x] Matrix E2EE transport
92
+ - [x] L4 Airlock verification
93
+ - [x] Restricted PTY execution
94
+ - [x] TIBET token per command
95
+ - [x] Hash chain integrity
96
+ - [x] Blocked dangerous commands
97
+ - [x] Systemd service (DL360)
98
+ - [ ] Multi-device session management
99
+ - [ ] File transfer via Matrix
100
+ - [ ] Interactive mode (vim, top)
101
+ - [ ] PyPI release
102
+
103
+ ## Running (development)
104
+
105
+ The daemon currently runs from `/srv/jtel-stack/tibet-nc/` as a systemd service.
106
+ See the deployed instance for reference — package structure is being formalized.
107
+
108
+ ## License
109
+
110
+ MIT — Humotica AI Lab
111
+
112
+
113
+ ## Credits
114
+
115
+ Designed by [Jasper van de Meent](https://github.com/jaspertvdm). Built by Jasper and [Root AI](https://humotica.com) as part of [HumoticaOS](https://humotica.com).
116
+
117
+ ---
118
+
119
+ **Stack-positie:** Groep `experimental` · Bootstrap = OSAPI-handshake naar [`tibet`](https://pypi.org/project/tibet-core/) + [`jis`](https://pypi.org/project/jis-core/) (fail → snaft-rule + tibet-pol-rapport) · ← [`tibet-airlock`](https://pypi.org/project/tibet-airlock/) · See `STACK.md` · See `demo/golden-path/` for the spine end-to-end.
120
+ ---
121
+
122
+ ## Enterprise
123
+
124
+ For private hub hosting, SLA support, custom integrations, or compliance guidance:
125
+
126
+ | | |
127
+ |---|---|
128
+ | **Enterprise** | enterprise@humotica.com |
129
+ | **Support** | support@humotica.com |
130
+ | **Security** | security@humotica.com |
131
+
132
+ See [ENTERPRISE.md](ENTERPRISE.md) for details.
@@ -0,0 +1,97 @@
1
+ # tibet-nc
2
+
3
+ > **PRE-ALPHA (v0.1.0a1) — NOT PRODUCTION READY**
4
+ >
5
+ > API, protocol, and security model are subject to change.
6
+ > Do not deploy in production environments.
7
+
8
+ Secure remote shell via Matrix E2EE — SSH without the attack surface.
9
+
10
+ ## What is tibet-nc?
11
+
12
+ tibet-nc replaces SSH/telnet with a remote shell that has **no open ports, no TCP listener, and no discoverable attack surface**. Commands are sent via Matrix (end-to-end encrypted) and every execution is a verified TIBET token.
13
+
14
+ Think of it as a speakeasy: there's no visible door. You need to know the Matrix room and have a verified TIBET identity to get in.
15
+
16
+ ## How it works
17
+
18
+ ```
19
+ [Matrix Client] → E2EE message → [Matrix Server] → [tibet-nc daemon]
20
+
21
+ L4 Airlock
22
+ ├── Identity check
23
+ ├── Timebox check
24
+ ├── Command safety
25
+ └── Hash chain
26
+
27
+ Restricted PTY
28
+
29
+ Output + TIBET token
30
+
31
+ [Matrix Client] ← E2EE
32
+ ```
33
+
34
+ ### L4 Airlock Verification
35
+
36
+ Every command passes 4 layers before execution:
37
+
38
+ 1. **Identity** — Matrix user must be in the allowed list
39
+ 2. **Timebox** — Command must arrive within latency window for its DID type
40
+ 3. **Command safety** — Blocked patterns (`rm -rf /`, `dd if=`, etc.) are rejected
41
+ 4. **Hash chain** — SHA256 chain links every command to the previous one
42
+
43
+ ### What makes it different from SSH?
44
+
45
+ | | SSH | tibet-nc |
46
+ |---|---|---|
47
+ | Open port | 22 (scannable) | None |
48
+ | Protocol | TCP | Matrix E2EE |
49
+ | Auth | Keys/password | TIBET identity |
50
+ | Audit trail | auth.log | Full TIBET provenance per command |
51
+ | Command safety | None | L4 Airlock (blocked patterns) |
52
+ | Hash chain | None | SHA256 per session |
53
+
54
+ ## Current status
55
+
56
+ - [x] Matrix E2EE transport
57
+ - [x] L4 Airlock verification
58
+ - [x] Restricted PTY execution
59
+ - [x] TIBET token per command
60
+ - [x] Hash chain integrity
61
+ - [x] Blocked dangerous commands
62
+ - [x] Systemd service (DL360)
63
+ - [ ] Multi-device session management
64
+ - [ ] File transfer via Matrix
65
+ - [ ] Interactive mode (vim, top)
66
+ - [ ] PyPI release
67
+
68
+ ## Running (development)
69
+
70
+ The daemon currently runs from `/srv/jtel-stack/tibet-nc/` as a systemd service.
71
+ See the deployed instance for reference — package structure is being formalized.
72
+
73
+ ## License
74
+
75
+ MIT — Humotica AI Lab
76
+
77
+
78
+ ## Credits
79
+
80
+ Designed by [Jasper van de Meent](https://github.com/jaspertvdm). Built by Jasper and [Root AI](https://humotica.com) as part of [HumoticaOS](https://humotica.com).
81
+
82
+ ---
83
+
84
+ **Stack-positie:** Groep `experimental` · Bootstrap = OSAPI-handshake naar [`tibet`](https://pypi.org/project/tibet-core/) + [`jis`](https://pypi.org/project/jis-core/) (fail → snaft-rule + tibet-pol-rapport) · ← [`tibet-airlock`](https://pypi.org/project/tibet-airlock/) · See `STACK.md` · See `demo/golden-path/` for the spine end-to-end.
85
+ ---
86
+
87
+ ## Enterprise
88
+
89
+ For private hub hosting, SLA support, custom integrations, or compliance guidance:
90
+
91
+ | | |
92
+ |---|---|
93
+ | **Enterprise** | enterprise@humotica.com |
94
+ | **Support** | support@humotica.com |
95
+ | **Security** | security@humotica.com |
96
+
97
+ See [ENTERPRISE.md](ENTERPRISE.md) for details.
@@ -0,0 +1,60 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "tibet-nc"
7
+ version = "0.1.0"
8
+ description = "Secure remote shell via Matrix E2EE — no ports, no TCP surface, TIBET L4 Airlock verification."
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ {name = "J. van de Meent", email = "jasper@humotica.com"},
14
+ {name = "R. AI", email = "root_idd@humotica.nl"},
15
+ ]
16
+ maintainers = [
17
+ {name = "Humotica AI Lab", email = "ai@humotica.nl"},
18
+ ]
19
+ keywords = [
20
+ "tibet", "matrix", "e2ee", "remote-shell", "ssh-alternative",
21
+ "airlock", "provenance", "zero-trust", "no-port",
22
+ ]
23
+ classifiers = [
24
+ "Development Status :: 4 - Beta",
25
+ "Intended Audience :: System Administrators",
26
+ "Intended Audience :: Developers",
27
+ "License :: OSI Approved :: MIT License",
28
+ "Operating System :: POSIX :: Linux",
29
+ "Programming Language :: Python :: 3",
30
+ "Programming Language :: Python :: 3.10",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ "Programming Language :: Python :: 3.13",
34
+ "Topic :: Security",
35
+ "Topic :: System :: Systems Administration",
36
+ "Topic :: Communications :: Chat",
37
+ ]
38
+
39
+ dependencies = [
40
+ "matrix-nio>=0.21",
41
+ "python-dotenv>=1.0",
42
+ "requests>=2.28",
43
+ ]
44
+
45
+ [project.optional-dependencies]
46
+ e2ee = [
47
+ "matrix-nio[e2e]>=0.21",
48
+ ]
49
+ dev = [
50
+ "pytest>=7.0",
51
+ "pytest-asyncio>=0.21",
52
+ ]
53
+
54
+ [project.scripts]
55
+ tibet-nc = "tibet_nc.cli:main"
56
+
57
+ [project.urls]
58
+ Homepage = "https://github.com/humotica/tibet-nc"
59
+ Repository = "https://github.com/humotica/tibet-nc"
60
+ Documentation = "https://humotica.com/docs/tibet-nc"
@@ -0,0 +1,21 @@
1
+ """
2
+ tibet-nc — Secure remote shell via Matrix E2EE.
3
+
4
+ *** PRE-ALPHA — NOT PRODUCTION READY ***
5
+
6
+ No ports. No TCP surface. No SSH daemon.
7
+ Every command is a TIBET token with L4 Airlock verification.
8
+
9
+ Transport: Matrix E2EE (end-to-end encrypted)
10
+ Auth: TIBET identity + Matrix user verification
11
+ Exec: Restricted PTY with blocked command patterns
12
+ Audit: Full TIBET provenance chain per command
13
+
14
+ Status: Pre-alpha (v0.1.0a1) — API and protocol WILL change.
15
+ """
16
+
17
+ __version__ = "0.1.0a1"
18
+ __status__ = "Pre-Alpha"
19
+
20
+ # Will be populated as modules stabilize
21
+ __all__: list[str] = []
@@ -0,0 +1,602 @@
1
+ """
2
+ TIBET-NC Daemon — Port 22 is dood. Leve de Matrix.
3
+
4
+ Remote shell via Matrix E2EE transport + TIBET L4 Airlock.
5
+ Elke keystroke is een TIBET token. Geen poort. Geen aanvalsoppervlak.
6
+
7
+ Architectuur:
8
+ JTM App / Element / CLI
9
+ ↓ Matrix E2EE room
10
+ ↓ TIBET-signed command payload
11
+ tibet_nc_daemon
12
+ → Timebox check
13
+ → Sequence chain (L4 hash)
14
+ → Command hash integrity
15
+ → Identity (ed25519 / Matrix verified)
16
+ → PTY execute (bash --restricted)
17
+ → Output terug via Matrix
18
+ → TIBET token issued
19
+
20
+ Door: Jasper van de Meent (concept) + Root AI (implementatie)
21
+ HumoticaOS — Hackaway 2026
22
+ """
23
+
24
+ import os
25
+ import pty
26
+ import select
27
+ import json
28
+ import time
29
+ import hashlib
30
+ import signal
31
+ import asyncio
32
+ import threading
33
+ from datetime import datetime, timezone
34
+ from typing import Optional, Dict, Any
35
+
36
+ import requests
37
+ from nio import AsyncClient, RoomMessageText, InviteEvent
38
+ from dotenv import load_dotenv
39
+
40
+ # Optioneel: ed25519 cryptografie
41
+ try:
42
+ from nacl.signing import SigningKey, VerifyKey
43
+ from nacl.exceptions import BadSignatureError
44
+ NACL_AVAILABLE = True
45
+ except ImportError:
46
+ NACL_AVAILABLE = False
47
+
48
+ load_dotenv()
49
+
50
+ # ---------------------------------------------------------
51
+ # CONFIGURATIE
52
+ # ---------------------------------------------------------
53
+ MATRIX_HOMESERVER = os.getenv("MATRIX_HOMESERVER", "https://chat.jaspervandemeent.nl")
54
+ MATRIX_USER_ID = os.getenv("TIBET_NC_USER_ID", "@tibet-nc:chat.jaspervandemeent.nl")
55
+ MATRIX_ACCESS_TOKEN = os.getenv("TIBET_NC_ACCESS_TOKEN", "")
56
+ BRAIN_API_BASE = os.getenv("BRAIN_API_BASE", "http://localhost:8000")
57
+
58
+ # Room waar TIBET-NC in luistert — dedicated terminal room
59
+ NC_ROOM_ID = os.getenv("TIBET_NC_ROOM", "")
60
+
61
+ # Command prefix — berichten die hiermee beginnen worden als commands behandeld
62
+ CMD_PREFIX = "$ "
63
+
64
+ # Wie mag commands sturen (Matrix user IDs)
65
+ ALLOWED_USERS = os.getenv("TIBET_NC_ALLOWED_USERS", "@jasper:chat.jaspervandemeent.nl").split(",")
66
+
67
+ # Trust requirement
68
+ MIN_TRUST_SCORE = float(os.getenv("TIBET_NC_MIN_TRUST", "0.80"))
69
+
70
+ # Hostname voor audit
71
+ HOSTNAME = os.getenv("TIBET_NC_HOSTNAME", os.uname().nodename)
72
+
73
+ # ---------------------------------------------------------
74
+ # TIMEBOX PER DID TYPE
75
+ # ---------------------------------------------------------
76
+ TIMEBOX_PER_DID = {
77
+ "jis:pixel": 5.0, # Snelle 5G/WiFi apparaten
78
+ "jis:satellite": 30.0, # Hoge latency verbindingen
79
+ "jis:iot": 2.0, # Sensoren/Beacons
80
+ "matrix": 10.0, # Matrix berichten (netwerk latency)
81
+ "default": 10.0
82
+ }
83
+
84
+ # ---------------------------------------------------------
85
+ # BLOCKED COMMANDS — Nooit uitvoeren
86
+ # ---------------------------------------------------------
87
+ BLOCKED_COMMANDS = {
88
+ "rm -rf /", "rm -rf /*", ":(){ :|:& };:", # Fork bomb
89
+ "dd if=/dev/zero", "mkfs", "fdisk",
90
+ "shutdown", "reboot", "halt", "poweroff",
91
+ "passwd", "useradd", "userdel", "usermod",
92
+ "> /dev/sda", "chmod -R 777 /",
93
+ }
94
+
95
+ BLOCKED_PATTERNS = [
96
+ "rm -rf /",
97
+ "> /dev/sd",
98
+ "mkfs.",
99
+ "dd if=/dev",
100
+ ":(){ :",
101
+ "chmod -R 777 /",
102
+ "curl|sh", "curl | sh", "wget|sh", "wget | sh", # Pipe to shell
103
+ ]
104
+
105
+
106
+ def log(msg: str) -> None:
107
+ now = datetime.now(timezone.utc).isoformat()
108
+ print(f"[{now}] {msg}", flush=True)
109
+
110
+
111
+ # ---------------------------------------------------------
112
+ # TIBET TOKEN CREATION
113
+ # ---------------------------------------------------------
114
+ def create_tibet_token(action_type: str, actor: str, command_hash: str,
115
+ l4_state: str, metadata: dict = None) -> Optional[str]:
116
+ """Issue a TIBET token for this command execution via protocol/send (matrix)"""
117
+ try:
118
+ resp = requests.post(
119
+ f"{BRAIN_API_BASE}/api/protocol/send",
120
+ json={
121
+ "protocol": "matrix",
122
+ "sender_id": actor,
123
+ "recipient_id": f"tibet-nc@{HOSTNAME}",
124
+ "intent": f"nc.{action_type}",
125
+ "content": f"[TIBET-NC] {action_type} | hash:{command_hash[:16]} | l4:{l4_state[:16]}",
126
+ "metadata": {
127
+ "hostname": HOSTNAME,
128
+ "l4_state": l4_state,
129
+ "action": action_type,
130
+ "transport": "matrix-e2ee",
131
+ "service": "tibet-nc",
132
+ **(metadata or {})
133
+ }
134
+ },
135
+ timeout=5
136
+ )
137
+ if resp.status_code == 200:
138
+ data = resp.json()
139
+ token_id = data.get("token_id") or data.get("id")
140
+ if token_id:
141
+ log(f"✓ TIBET token: {token_id} [{action_type}]")
142
+ return token_id
143
+ else:
144
+ log(f"✓ TIBET logged (no token_id in response)")
145
+ return "logged"
146
+ else:
147
+ log(f"✗ TIBET token HTTP {resp.status_code}: {resp.text[:100]}")
148
+ except Exception as e:
149
+ log(f"✗ TIBET token error: {e}")
150
+ return None
151
+
152
+
153
+ # ---------------------------------------------------------
154
+ # L4 AIRLOCK — De onbreekbare check
155
+ # ---------------------------------------------------------
156
+ class TibetSession:
157
+ def __init__(self, session_id: str):
158
+ self.session_id = session_id
159
+ self.l4_hash = hashlib.sha256(b"genesis_tibet_nc_" + session_id.encode()).hexdigest()
160
+ self.lock = threading.Lock()
161
+ self.command_count = 0
162
+ self.created_at = time.time()
163
+ self.last_command_at = 0.0
164
+
165
+ # PTY state
166
+ self.fd: Optional[int] = None
167
+ self.pid: Optional[int] = None
168
+ self.active = False
169
+
170
+ def verify_airlock(self, sender: str, command: str, timestamp: float) -> tuple[bool, str]:
171
+ """
172
+ L4 Airlock Check — 4 verification layers
173
+
174
+ Returns: (passed, reason)
175
+ """
176
+ with self.lock:
177
+ # 1. IDENTITY CHECK — is de afzender toegestaan?
178
+ if sender not in ALLOWED_USERS:
179
+ return False, f"Unauthorized: {sender} niet in allowed users"
180
+
181
+ # 2. TIMEBOX CHECK — is het bericht recent genoeg?
182
+ max_latency = TIMEBOX_PER_DID.get("matrix", TIMEBOX_PER_DID["default"])
183
+ age = abs(time.time() - timestamp)
184
+ if age > max_latency:
185
+ return False, f"Expired: {age:.1f}s > {max_latency}s timebox"
186
+
187
+ # 3. COMMAND SAFETY CHECK — geblokkeerde commands
188
+ cmd_lower = command.lower().strip()
189
+ if cmd_lower in BLOCKED_COMMANDS:
190
+ return False, f"Blocked: dangerous command"
191
+ for pattern in BLOCKED_PATTERNS:
192
+ if pattern in cmd_lower:
193
+ return False, f"Blocked: matches pattern '{pattern}'"
194
+
195
+ # 4. COMMAND HASH + CHAIN ADVANCE
196
+ command_hash = hashlib.sha256(command.encode('utf-8')).hexdigest()
197
+
198
+ # Advance the L4 chain
199
+ new_l4_data = f"{self.l4_hash}:{command_hash}:{timestamp}:{sender}".encode()
200
+ self.l4_hash = hashlib.sha256(new_l4_data).hexdigest()
201
+ self.command_count += 1
202
+ self.last_command_at = time.time()
203
+
204
+ return True, command_hash
205
+
206
+ def start_pty(self) -> bool:
207
+ """Fork een restricted bash shell"""
208
+ if self.active:
209
+ return True
210
+
211
+ try:
212
+ self.pid, self.fd = pty.fork()
213
+
214
+ if self.pid == 0:
215
+ # CHILD: Restricted shell
216
+ # --restricted: geen cd, geen command redirects, geen PATH wijzigingen
217
+ # --norc: geen .bashrc laden (voorkomt alias exploits)
218
+ env = {
219
+ "HOME": "/tmp/tibet-nc",
220
+ "PATH": "/usr/bin:/bin",
221
+ "TERM": "xterm",
222
+ "PS1": f"tibet-nc@{HOSTNAME}$ ",
223
+ "TIBET_SESSION": self.session_id,
224
+ }
225
+ os.makedirs("/tmp/tibet-nc", exist_ok=True)
226
+ for k, v in env.items():
227
+ os.environ[k] = v
228
+ os.execv('/bin/bash', ['bash', '--restricted', '--norc', '--noprofile'])
229
+ else:
230
+ # PARENT: Cleanup handler voor orphan processes
231
+ signal.signal(signal.SIGCHLD, lambda s, f: self._reap_child())
232
+ self.active = True
233
+ log(f"PTY gestart: pid={self.pid}, fd={self.fd}")
234
+
235
+ # Wacht even op shell init
236
+ time.sleep(0.3)
237
+ return True
238
+
239
+ except Exception as e:
240
+ log(f"PTY fork error: {e}")
241
+ return False
242
+
243
+ def _reap_child(self):
244
+ """Cleanup zombie processes"""
245
+ try:
246
+ if self.pid:
247
+ os.waitpid(self.pid, os.WNOHANG)
248
+ except ChildProcessError:
249
+ pass
250
+
251
+ def execute_command(self, command: str) -> str:
252
+ """Voer command uit in PTY en return output"""
253
+ if not self.active or self.fd is None:
254
+ return "[!] Geen actieve PTY sessie"
255
+
256
+ try:
257
+ # Stuur command naar de shell
258
+ os.write(self.fd, (command + '\n').encode('utf-8'))
259
+
260
+ # Lees output met timeout
261
+ output_parts = []
262
+ deadline = time.time() + 5.0 # Max 5 seconden wachten op output
263
+
264
+ while time.time() < deadline:
265
+ r, _, _ = select.select([self.fd], [], [], 0.2)
266
+ if self.fd in r:
267
+ try:
268
+ chunk = os.read(self.fd, 8192).decode('utf-8', errors='replace')
269
+ if chunk:
270
+ output_parts.append(chunk)
271
+ deadline = time.time() + 0.5 # Reset kort na output
272
+ except OSError:
273
+ break
274
+ elif output_parts:
275
+ # Geen nieuwe data en we hebben al output — klaar
276
+ break
277
+
278
+ output = ''.join(output_parts)
279
+
280
+ # Strip ANSI escape codes en terminal control sequences
281
+ import re
282
+ output = re.sub(r'\x1b\[[?]?\d*[a-zA-Z]', '', output) # ANSI escapes
283
+ output = re.sub(r'\x1b\[\d*;\d*[a-zA-Z]', '', output) # Color codes
284
+ output = re.sub(r'\r', '', output) # Carriage returns
285
+
286
+ # Strip het commando zelf uit de output (echo)
287
+ lines = output.split('\n')
288
+ filtered = []
289
+ prompt_pattern = f"tibet-nc@{HOSTNAME}$"
290
+ for line in lines:
291
+ stripped = line.strip()
292
+ if not stripped:
293
+ continue
294
+ # Skip de echo van het command
295
+ if stripped == command.strip():
296
+ continue
297
+ # Skip prompt lines
298
+ if stripped.endswith('$ ' + command.strip()):
299
+ continue
300
+ if stripped == prompt_pattern or stripped.endswith('$ '):
301
+ continue
302
+ filtered.append(line)
303
+
304
+ return '\n'.join(filtered).strip() or "(geen output)"
305
+
306
+ except Exception as e:
307
+ return f"[!] Execute error: {e}"
308
+
309
+ def close(self):
310
+ """Sluit de PTY sessie"""
311
+ self.active = False
312
+ if self.fd is not None:
313
+ try:
314
+ os.close(self.fd)
315
+ except OSError:
316
+ pass
317
+ if self.pid is not None:
318
+ try:
319
+ os.kill(self.pid, signal.SIGTERM)
320
+ except ProcessLookupError:
321
+ pass
322
+ log(f"Sessie {self.session_id} gesloten. Commands: {self.command_count}")
323
+
324
+
325
+ # ---------------------------------------------------------
326
+ # MATRIX DAEMON
327
+ # ---------------------------------------------------------
328
+ class TibetNCDaemon:
329
+ def __init__(self):
330
+ self.sessions: Dict[str, TibetSession] = {}
331
+ self.client: Optional[AsyncClient] = None
332
+ self.boot_time_ms = int(time.time() * 1000)
333
+
334
+ def get_or_create_session(self, room_id: str) -> TibetSession:
335
+ """Eén sessie per room"""
336
+ if room_id not in self.sessions:
337
+ session_id = hashlib.sha256(
338
+ f"tibet-nc:{room_id}:{time.time()}".encode()
339
+ ).hexdigest()[:16]
340
+ session = TibetSession(session_id)
341
+ if session.start_pty():
342
+ self.sessions[room_id] = session
343
+ log(f"Nieuwe sessie: {session_id} voor room {room_id}")
344
+ else:
345
+ log(f"[!] Kon PTY niet starten voor {room_id}")
346
+ return None
347
+ return self.sessions.get(room_id)
348
+
349
+ async def handle_message(self, room, event: RoomMessageText):
350
+ """Verwerk inkomend Matrix bericht als TIBET-NC command"""
351
+ body = (event.body or "").strip()
352
+ sender = event.sender
353
+ room_id = room.room_id
354
+
355
+ # Skip oude berichten (breinbot patroon)
356
+ if event.server_timestamp < self.boot_time_ms:
357
+ return
358
+
359
+ # Skip eigen berichten
360
+ if sender == MATRIX_USER_ID:
361
+ return
362
+
363
+ # Alleen berichten met command prefix of in de NC room
364
+ is_nc_room = (NC_ROOM_ID and room_id == NC_ROOM_ID)
365
+ has_prefix = body.startswith(CMD_PREFIX)
366
+
367
+ if not is_nc_room and not has_prefix:
368
+ return
369
+
370
+ # Strip prefix als aanwezig
371
+ command = body[len(CMD_PREFIX):].strip() if has_prefix else body.strip()
372
+ if not command:
373
+ return
374
+
375
+ # Special commands
376
+ if command.lower() in ("exit", "quit", "bye"):
377
+ session = self.sessions.get(room_id)
378
+ if session:
379
+ session.close()
380
+ del self.sessions[room_id]
381
+ await self._send_response(room_id, "🔒 TIBET-NC sessie gesloten.")
382
+ create_tibet_token("session.close", sender,
383
+ hashlib.sha256(b"session.close").hexdigest(),
384
+ "closed")
385
+ return
386
+
387
+ if command.lower() == "status":
388
+ session = self.sessions.get(room_id)
389
+ status = {
390
+ "daemon": f"tibet-nc@{HOSTNAME}",
391
+ "session": session.session_id if session else "none",
392
+ "l4_hash": session.l4_hash[:16] + "..." if session else "n/a",
393
+ "commands": session.command_count if session else 0,
394
+ "uptime": f"{time.time() - session.created_at:.0f}s" if session else "n/a",
395
+ "nacl": NACL_AVAILABLE,
396
+ "allowed_users": len(ALLOWED_USERS),
397
+ }
398
+ await self._send_response(room_id,
399
+ f"📊 TIBET-NC Status\n```json\n{json.dumps(status, indent=2)}\n```")
400
+ return
401
+
402
+ if command.lower() == "chain":
403
+ session = self.sessions.get(room_id)
404
+ if session:
405
+ await self._send_response(room_id,
406
+ f"🔗 L4 Chain State\n"
407
+ f"Session: `{session.session_id}`\n"
408
+ f"L4 Hash: `{session.l4_hash}`\n"
409
+ f"Commands: {session.command_count}\n"
410
+ f"Last: {session.last_command_at:.0f}")
411
+ else:
412
+ await self._send_response(room_id, "Geen actieve sessie.")
413
+ return
414
+
415
+ if command.lower() == "help":
416
+ help_text = (
417
+ "🖥️ **TIBET-NC** — Secure Shell via Matrix\n\n"
418
+ f"Prefix: `{CMD_PREFIX}` (of typ in de NC room)\n\n"
419
+ "**Commands:**\n"
420
+ f"• `{CMD_PREFIX}ls -la` — Voer shell command uit\n"
421
+ f"• `{CMD_PREFIX}status` — Sessie status + L4 hash\n"
422
+ f"• `{CMD_PREFIX}chain` — Toon L4 chain state\n"
423
+ f"• `{CMD_PREFIX}exit` — Sluit sessie\n"
424
+ f"• `{CMD_PREFIX}help` — Dit bericht\n\n"
425
+ "**Beveiliging:** Elk command doorloopt L4 Airlock:\n"
426
+ "1. Identity (Matrix verified sender)\n"
427
+ "2. Timebox (max latency per device type)\n"
428
+ "3. Command safety (blocked patterns)\n"
429
+ "4. Hash chain (L4 continuity)\n\n"
430
+ f"Daemon: `tibet-nc@{HOSTNAME}`\n"
431
+ f"Transport: Matrix E2EE — Port 22 is dood 🔒"
432
+ )
433
+ await self._send_response(room_id, help_text)
434
+ return
435
+
436
+ # --- EXECUTE COMMAND ---
437
+ timestamp = time.time()
438
+
439
+ # L4 Airlock Check
440
+ session = self.get_or_create_session(room_id)
441
+ if not session:
442
+ await self._send_response(room_id, "❌ Kon geen PTY sessie starten.")
443
+ return
444
+
445
+ passed, result = session.verify_airlock(sender, command, timestamp)
446
+
447
+ if not passed:
448
+ # Airlock DENIED
449
+ log(f"[AIRLOCK DENY] {sender}: {result}")
450
+ await self._send_response(room_id,
451
+ f"🛑 **Airlock Denied**\n`{result}`")
452
+
453
+ create_tibet_token("airlock.deny", sender,
454
+ hashlib.sha256(command.encode()).hexdigest(),
455
+ session.l4_hash,
456
+ {"reason": result})
457
+ return
458
+
459
+ # Airlock PASSED — command_hash is in result
460
+ command_hash = result
461
+
462
+ # Audit log (hash only, never plaintext!)
463
+ audit = {
464
+ "type": "nc.command.execute",
465
+ "did": sender,
466
+ "command_hash": command_hash[:16],
467
+ "timestamp": timestamp,
468
+ "l4_state": session.l4_hash[:16],
469
+ "session": session.session_id,
470
+ "seq": session.command_count
471
+ }
472
+ log(f"[AUDIT] {json.dumps(audit)}")
473
+
474
+ # Execute in PTY
475
+ output = session.execute_command(command)
476
+
477
+ # Truncate output voor Matrix (max 4000 chars)
478
+ if len(output) > 4000:
479
+ output = output[:3900] + f"\n... (truncated, {len(output)} chars total)"
480
+
481
+ # TIBET token voor de executie
482
+ token_id = create_tibet_token(
483
+ "command.execute", sender, command_hash,
484
+ session.l4_hash,
485
+ {
486
+ "session_id": session.session_id,
487
+ "seq": session.command_count,
488
+ "output_hash": hashlib.sha256(output.encode()).hexdigest()[:16],
489
+ "output_len": len(output)
490
+ }
491
+ )
492
+
493
+ # Response met TIBET provenance
494
+ token_ref = f"TIBET: `{token_id[:12]}...`" if token_id else "TIBET: (offline)"
495
+ chain_ref = f"L4: `{session.l4_hash[:12]}...` | #{session.command_count}"
496
+
497
+ response = (
498
+ f"```\n{output}\n```\n"
499
+ f"_{token_ref} | {chain_ref}_"
500
+ )
501
+
502
+ await self._send_response(room_id, response)
503
+
504
+ async def _send_response(self, room_id: str, message: str):
505
+ """Stuur response naar Matrix room"""
506
+ if not self.client:
507
+ return
508
+ try:
509
+ await self.client.room_send(
510
+ room_id=room_id,
511
+ message_type="m.room.message",
512
+ content={
513
+ "msgtype": "m.text",
514
+ "body": message,
515
+ "format": "org.matrix.custom.html",
516
+ "formatted_body": message,
517
+ },
518
+ )
519
+ except Exception as e:
520
+ log(f"Send error: {e}")
521
+
522
+ async def on_invite(self, room, event: InviteEvent):
523
+ """Auto-join rooms bij invite"""
524
+ log(f"Invite voor room {room.room_id}")
525
+ try:
526
+ await self.client.join(room.room_id)
527
+ log(f"Gejoined: {room.room_id}")
528
+ except Exception as e:
529
+ log(f"Join error: {e}")
530
+
531
+ async def run(self):
532
+ """Start de TIBET-NC daemon"""
533
+ if not MATRIX_ACCESS_TOKEN:
534
+ log("FATAL: TIBET_NC_ACCESS_TOKEN niet gezet!")
535
+ return
536
+
537
+ log(f"TIBET-NC Daemon v1.0 — {HOSTNAME}")
538
+ log(f"Transport: Matrix E2EE via {MATRIX_HOMESERVER}")
539
+ log(f"Allowed users: {ALLOWED_USERS}")
540
+ log(f"Command prefix: '{CMD_PREFIX}'")
541
+ log(f"NaCl crypto: {'✓' if NACL_AVAILABLE else '✗ (demo mode)'}")
542
+ log(f"Port 22: DOOD 🔒")
543
+ log("")
544
+
545
+ self.client = AsyncClient(
546
+ homeserver=MATRIX_HOMESERVER,
547
+ user=MATRIX_USER_ID,
548
+ device_id="TIBET_NC_DAEMON",
549
+ )
550
+ self.client.access_token = MATRIX_ACCESS_TOKEN
551
+
552
+ # Callbacks
553
+ self.client.add_event_callback(
554
+ lambda room, event: self.on_invite(room, event),
555
+ InviteEvent,
556
+ )
557
+ self.client.add_event_callback(
558
+ lambda room, event: self.handle_message(room, event),
559
+ RoomMessageText,
560
+ )
561
+
562
+ # Initial sync
563
+ log("Eerste sync...")
564
+ try:
565
+ await self.client.sync(timeout=30000, full_state=True)
566
+ except Exception as e:
567
+ log(f"Sync error: {e}")
568
+ await self.client.close()
569
+ return
570
+
571
+ log("TIBET-NC draait. Wacht op commands via Matrix...")
572
+ log("=" * 60)
573
+
574
+ # Sync loop
575
+ while True:
576
+ try:
577
+ await self.client.sync(timeout=30000)
578
+ except Exception as e:
579
+ log(f"Sync error: {e}, herverbinden...")
580
+ await asyncio.sleep(5)
581
+
582
+ def shutdown(self):
583
+ """Cleanup alle sessies"""
584
+ log("Shutdown: sessies sluiten...")
585
+ for room_id, session in self.sessions.items():
586
+ session.close()
587
+ self.sessions.clear()
588
+ log("TIBET-NC daemon gestopt.")
589
+
590
+
591
+ # ---------------------------------------------------------
592
+ # MAIN
593
+ # ---------------------------------------------------------
594
+ if __name__ == "__main__":
595
+ daemon = TibetNCDaemon()
596
+ try:
597
+ asyncio.run(daemon.run())
598
+ except KeyboardInterrupt:
599
+ daemon.shutdown()
600
+ except Exception as e:
601
+ log(f"FATAL: {e}")
602
+ daemon.shutdown()
File without changes
@@ -0,0 +1,61 @@
1
+ """
2
+ Tests for tibet-nc L4 Airlock verification.
3
+
4
+ PRE-ALPHA — These tests validate the core security model.
5
+ """
6
+
7
+ import pytest
8
+
9
+
10
+ class TestBlockedCommands:
11
+ """Test that dangerous commands are blocked by the airlock."""
12
+
13
+ BLOCKED = [
14
+ "rm -rf /",
15
+ "rm -rf /*",
16
+ "dd if=/dev/zero of=/dev/sda",
17
+ "mkfs.ext4 /dev/sda",
18
+ ":(){ :|:& };:",
19
+ "> /dev/sda",
20
+ "chmod -R 777 /",
21
+ "shutdown -h now",
22
+ "reboot",
23
+ "init 0",
24
+ ]
25
+
26
+ ALLOWED = [
27
+ "ls -la",
28
+ "whoami",
29
+ "hostname",
30
+ "date",
31
+ "echo hello",
32
+ "cat /etc/hostname",
33
+ "uname -a",
34
+ "df -h",
35
+ "ps aux",
36
+ ]
37
+
38
+ def test_blocked_patterns_exist(self):
39
+ """Verify that the daemon defines blocked patterns."""
40
+ from tibet_nc.daemon import BLOCKED_COMMANDS, BLOCKED_PATTERNS
41
+ assert len(BLOCKED_COMMANDS) > 0
42
+ assert len(BLOCKED_PATTERNS) > 0
43
+
44
+ @pytest.mark.parametrize("cmd", BLOCKED)
45
+ def test_dangerous_commands_blocked(self, cmd):
46
+ """Dangerous commands must be caught by airlock."""
47
+ from tibet_nc.daemon import BLOCKED_COMMANDS, BLOCKED_PATTERNS
48
+ import re
49
+
50
+ is_blocked = False
51
+ cmd_base = cmd.split()[0] if cmd.split() else cmd
52
+
53
+ if cmd_base in BLOCKED_COMMANDS:
54
+ is_blocked = True
55
+ else:
56
+ for pattern in BLOCKED_PATTERNS:
57
+ if re.search(pattern, cmd):
58
+ is_blocked = True
59
+ break
60
+
61
+ assert is_blocked, f"Command should be blocked: {cmd}"