tibet-nc 0.1.0a2__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.
- tibet_nc-0.1.0a2/.gitignore +147 -0
- tibet_nc-0.1.0a2/.publish.json +16 -0
- tibet_nc-0.1.0a2/ENTERPRISE.md +41 -0
- tibet_nc-0.1.0a2/PKG-INFO +125 -0
- tibet_nc-0.1.0a2/README.md +90 -0
- tibet_nc-0.1.0a2/pyproject.toml +60 -0
- tibet_nc-0.1.0a2/src/tibet_nc/__init__.py +21 -0
- tibet_nc-0.1.0a2/src/tibet_nc/daemon.py +602 -0
- tibet_nc-0.1.0a2/tests/__init__.py +0 -0
- tibet_nc-0.1.0a2/tests/test_airlock.py +61 -0
|
@@ -0,0 +1,147 @@
|
|
|
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
|
+
# Configs met secrets (we gebruiken straks templates)
|
|
32
|
+
config/
|
|
33
|
+
brain_api/provisioning.local.json
|
|
34
|
+
brain_api/provisioning.json
|
|
35
|
+
|
|
36
|
+
# Landing pages (privé - niet open source)
|
|
37
|
+
landing-pages/
|
|
38
|
+
humotica.com/
|
|
39
|
+
jtel.nl/
|
|
40
|
+
|
|
41
|
+
# Social media posts (strategie - niet open source)
|
|
42
|
+
SOCIAL-MEDIA-POSTS.md
|
|
43
|
+
HN-POST-UNDER-4000.md
|
|
44
|
+
STRATO-DEPLOY-HUMOTICA.md
|
|
45
|
+
|
|
46
|
+
# Endorsement outreach (privaat contact)
|
|
47
|
+
ARXIV-ENDORSEMENT-OUTREACH.md
|
|
48
|
+
|
|
49
|
+
# Deployment secrets
|
|
50
|
+
DEPLOYMENT-GUIDE.md
|
|
51
|
+
|
|
52
|
+
# R Project files (Dirty Data Challenge)
|
|
53
|
+
.Rproj.user
|
|
54
|
+
.Rhistory
|
|
55
|
+
.RData
|
|
56
|
+
.Ruserdata
|
|
57
|
+
*.zip
|
|
58
|
+
.mural_tokens.json
|
|
59
|
+
auth.json
|
|
60
|
+
gen-lang-client*.json
|
|
61
|
+
*.credentials.json
|
|
62
|
+
|
|
63
|
+
# Rust build artifacts
|
|
64
|
+
**/target/
|
|
65
|
+
*.whl
|
|
66
|
+
|
|
67
|
+
# Compiled binaries (build locally)
|
|
68
|
+
jis-router/jis-router
|
|
69
|
+
sentinel-rs/sentinel-rs
|
|
70
|
+
|
|
71
|
+
# Build distribution
|
|
72
|
+
sandbox/ai/codex/dist/
|
|
73
|
+
sandbox_backup/
|
|
74
|
+
did-jis-core
|
|
75
|
+
|
|
76
|
+
# =============================================================================
|
|
77
|
+
# Eigen repos — hebben hun eigen git remotes, niet dubbel opslaan
|
|
78
|
+
# =============================================================================
|
|
79
|
+
|
|
80
|
+
# Packages (elk een eigen repo)
|
|
81
|
+
packages/jis-iam-bridge/
|
|
82
|
+
packages/rapid-rag/
|
|
83
|
+
packages/reflux/
|
|
84
|
+
packages/sema-protocol/
|
|
85
|
+
packages/tibet-anticheat/
|
|
86
|
+
packages/tibet-ci/
|
|
87
|
+
packages/tibet-claw/
|
|
88
|
+
packages/tibet-context/
|
|
89
|
+
packages/tibet-core/
|
|
90
|
+
packages/tibet-db/
|
|
91
|
+
packages/tibet-edge/
|
|
92
|
+
packages/tibet-forge/
|
|
93
|
+
packages/tibet-iot/
|
|
94
|
+
packages/tibet-jawbreaker/
|
|
95
|
+
packages/tibet-ledger/
|
|
96
|
+
packages/tibet-marketplace/
|
|
97
|
+
packages/tibet-mesh/
|
|
98
|
+
packages/tibet-mirror/
|
|
99
|
+
packages/tibet-nis2/
|
|
100
|
+
packages/tibet-overlay/
|
|
101
|
+
packages/tibet-phantom/
|
|
102
|
+
packages/tibet-phantom-mcp/
|
|
103
|
+
packages/tibet-ping/
|
|
104
|
+
packages/tibet-pol/
|
|
105
|
+
packages/tibet-pqc/
|
|
106
|
+
packages/tibet-sbom/
|
|
107
|
+
packages/tibet-snap/
|
|
108
|
+
packages/tibet-soc/
|
|
109
|
+
packages/tibet-spiffe/
|
|
110
|
+
packages/tibet-tools/
|
|
111
|
+
packages/tibet-trail/
|
|
112
|
+
packages/tibet-triage/
|
|
113
|
+
packages/tibet-triage-mcp/
|
|
114
|
+
packages/tibet-twin/
|
|
115
|
+
packages/tibet-workload/
|
|
116
|
+
packages/tibet-y2k38/
|
|
117
|
+
packages/tlex-edge/
|
|
118
|
+
packages/tibet-tail/
|
|
119
|
+
packages/tibet-nc/
|
|
120
|
+
|
|
121
|
+
# Sub-projects met eigen repos
|
|
122
|
+
bunq7/
|
|
123
|
+
humotica-core/
|
|
124
|
+
jis-core/
|
|
125
|
+
JTm-dev/
|
|
126
|
+
kit-package/
|
|
127
|
+
symbAIon/
|
|
128
|
+
tibet-audit/
|
|
129
|
+
tibet-audit-npm/
|
|
130
|
+
tibet-core/
|
|
131
|
+
tibetclaw/
|
|
132
|
+
snaft/
|
|
133
|
+
|
|
134
|
+
# MCP servers (eigen repos)
|
|
135
|
+
mcp-servers/aidrac/
|
|
136
|
+
mcp-servers/ainternet/
|
|
137
|
+
mcp-servers/mcp-server-jis/
|
|
138
|
+
mcp-servers/sensory/
|
|
139
|
+
mcp-servers/tibet/
|
|
140
|
+
|
|
141
|
+
# Hackathon sub-repos
|
|
142
|
+
hackaway2026/clawmetry/
|
|
143
|
+
|
|
144
|
+
# Private memory (eigen repo)
|
|
145
|
+
.root_ai_memory/
|
|
146
|
+
.root_ai_thoughts/
|
|
147
|
+
brain_api/static/*.apk
|
|
@@ -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,125 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tibet-nc
|
|
3
|
+
Version: 0.1.0a2
|
|
4
|
+
Summary: [DEPRECATED] 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 :: 7 - Inactive
|
|
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
|
+
---
|
|
114
|
+
|
|
115
|
+
## Enterprise
|
|
116
|
+
|
|
117
|
+
For private hub hosting, SLA support, custom integrations, or compliance guidance:
|
|
118
|
+
|
|
119
|
+
| | |
|
|
120
|
+
|---|---|
|
|
121
|
+
| **Enterprise** | enterprise@humotica.com |
|
|
122
|
+
| **Support** | support@humotica.com |
|
|
123
|
+
| **Security** | security@humotica.com |
|
|
124
|
+
|
|
125
|
+
See [ENTERPRISE.md](ENTERPRISE.md) for details.
|
|
@@ -0,0 +1,90 @@
|
|
|
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
|
+
---
|
|
79
|
+
|
|
80
|
+
## Enterprise
|
|
81
|
+
|
|
82
|
+
For private hub hosting, SLA support, custom integrations, or compliance guidance:
|
|
83
|
+
|
|
84
|
+
| | |
|
|
85
|
+
|---|---|
|
|
86
|
+
| **Enterprise** | enterprise@humotica.com |
|
|
87
|
+
| **Support** | support@humotica.com |
|
|
88
|
+
| **Security** | security@humotica.com |
|
|
89
|
+
|
|
90
|
+
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.0a2"
|
|
8
|
+
description = "[DEPRECATED] 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 :: 7 - Inactive",
|
|
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}"
|