@dmsdc-ai/aigentry-telepty 0.1.54 → 0.1.56
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.
- package/BUS_EVENT_SCHEMA.md +7 -0
- package/daemon.js +31 -5
- package/package.json +1 -1
package/BUS_EVENT_SCHEMA.md
CHANGED
|
@@ -53,6 +53,13 @@ Every session is uniquely identified by a locator triple:
|
|
|
53
53
|
- Override: `TELEPTY_MACHINE_ID` env var
|
|
54
54
|
- Exposed in: `GET /api/meta` (`machine_id` field), session `locator` object, bus event `source_host`
|
|
55
55
|
|
|
56
|
+
### Global Session ID Uniqueness (P4)
|
|
57
|
+
- Convention: `{project}-{NNN}` (e.g. `aigentry-devkit-001`)
|
|
58
|
+
- Cross-machine uniqueness: guaranteed by `locator.machine_id` prefix in bus events
|
|
59
|
+
- Collision resolution: `resolveSessionAlias` returns local session only; remote sessions discovered via `source_host` field
|
|
60
|
+
- If two machines have `aigentry-devkit-001`, inject uses `target@host` to disambiguate
|
|
61
|
+
- Short form `aigentry-devkit-001` resolves to LOCAL session; remote requires explicit `@host`
|
|
62
|
+
|
|
56
63
|
### Peer Auth
|
|
57
64
|
- Localhost: always trusted
|
|
58
65
|
- Tailscale (100.x.y.z): trusted by default
|
package/daemon.js
CHANGED
|
@@ -69,6 +69,24 @@ function relayToPeers(msg) {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
// JWT auth: set TELEPTY_JWT_SECRET to enable. Tokens in Authorization: Bearer <token>
|
|
73
|
+
const JWT_SECRET = process.env.TELEPTY_JWT_SECRET || null;
|
|
74
|
+
|
|
75
|
+
function verifyJwt(token) {
|
|
76
|
+
if (!JWT_SECRET || !token) return false;
|
|
77
|
+
try {
|
|
78
|
+
// Simple HS256 JWT verification (no external deps)
|
|
79
|
+
const [headerB64, payloadB64, sigB64] = token.split('.');
|
|
80
|
+
if (!headerB64 || !payloadB64 || !sigB64) return false;
|
|
81
|
+
const expected = crypto.createHmac('sha256', JWT_SECRET)
|
|
82
|
+
.update(`${headerB64}.${payloadB64}`).digest('base64url');
|
|
83
|
+
if (sigB64 !== expected) return false;
|
|
84
|
+
const payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString());
|
|
85
|
+
if (payload.exp && Date.now() / 1000 > payload.exp) return false;
|
|
86
|
+
return payload;
|
|
87
|
+
} catch { return false; }
|
|
88
|
+
}
|
|
89
|
+
|
|
72
90
|
function isAllowedPeer(ip) {
|
|
73
91
|
if (!ip) return false;
|
|
74
92
|
const cleanIp = ip.replace('::ffff:', '');
|
|
@@ -95,6 +113,12 @@ app.use((req, res, next) => {
|
|
|
95
113
|
return next();
|
|
96
114
|
}
|
|
97
115
|
|
|
116
|
+
// JWT Bearer token
|
|
117
|
+
const authHeader = req.headers['authorization'] || '';
|
|
118
|
+
if (authHeader.startsWith('Bearer ') && verifyJwt(authHeader.slice(7))) {
|
|
119
|
+
return next();
|
|
120
|
+
}
|
|
121
|
+
|
|
98
122
|
console.warn(`[AUTH] Rejected unauthorized request from ${clientIp}`);
|
|
99
123
|
res.status(401).json({ error: 'Unauthorized: Invalid or missing token.' });
|
|
100
124
|
});
|
|
@@ -565,7 +589,7 @@ function sendViaKitty(sessionId, text) {
|
|
|
565
589
|
}
|
|
566
590
|
|
|
567
591
|
try {
|
|
568
|
-
// Split text and CR — send-text for
|
|
592
|
+
// Split text and CR — send-text for both (send-key corrupts keyboard protocol)
|
|
569
593
|
const hasCr = text.endsWith('\r') || text.endsWith('\n');
|
|
570
594
|
const textOnly = hasCr ? text.slice(0, -1) : text;
|
|
571
595
|
if (textOnly.length > 0) {
|
|
@@ -577,7 +601,7 @@ function sendViaKitty(sessionId, text) {
|
|
|
577
601
|
if (hasCr) {
|
|
578
602
|
// Delay before sending Return — CLI needs time to process text input
|
|
579
603
|
execSync('sleep 0.5', { timeout: 2000 });
|
|
580
|
-
execSync(`kitty @ --to unix:${socket} send-
|
|
604
|
+
execSync(`kitty @ --to unix:${socket} send-text --match id:${windowId} $'\\r'`, {
|
|
581
605
|
timeout: 3000, stdio: ['pipe', 'pipe', 'pipe']
|
|
582
606
|
});
|
|
583
607
|
}
|
|
@@ -773,7 +797,7 @@ app.post('/api/sessions/:id/inject', (req, res) => {
|
|
|
773
797
|
setTimeout(() => {
|
|
774
798
|
if (wid && sock) {
|
|
775
799
|
try {
|
|
776
|
-
require('child_process').execSync(`kitty @ --to unix:${sock} send-
|
|
800
|
+
require('child_process').execSync(`kitty @ --to unix:${sock} send-text --match id:${wid} $'\\r'`, {
|
|
777
801
|
timeout: 3000, stdio: ['pipe', 'pipe', 'pipe']
|
|
778
802
|
});
|
|
779
803
|
require('child_process').execSync(`kitty @ --to unix:${sock} set-tab-title --match id:${wid} '⚡ telepty :: ${id}'`, {
|
|
@@ -946,7 +970,7 @@ function busAutoRoute(msg) {
|
|
|
946
970
|
});
|
|
947
971
|
setTimeout(() => {
|
|
948
972
|
try {
|
|
949
|
-
require('child_process').execSync(`kitty @ --to unix:${sock} send-
|
|
973
|
+
require('child_process').execSync(`kitty @ --to unix:${sock} send-text --match id:${wid} $'\\r'`, {
|
|
950
974
|
timeout: 3000, stdio: ['pipe', 'pipe', 'pipe']
|
|
951
975
|
});
|
|
952
976
|
} catch {}
|
|
@@ -1435,7 +1459,9 @@ server.on('upgrade', (req, socket, head) => {
|
|
|
1435
1459
|
const url = new URL(req.url, 'http://' + req.headers.host);
|
|
1436
1460
|
const token = url.searchParams.get('token');
|
|
1437
1461
|
|
|
1438
|
-
|
|
1462
|
+
const wsAuthHeader = req.headers['authorization'] || '';
|
|
1463
|
+
const wsJwtValid = wsAuthHeader.startsWith('Bearer ') && verifyJwt(wsAuthHeader.slice(7));
|
|
1464
|
+
if (!isAllowedPeer(req.socket.remoteAddress) && token !== EXPECTED_TOKEN && !wsJwtValid) {
|
|
1439
1465
|
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
|
1440
1466
|
socket.destroy();
|
|
1441
1467
|
return;
|