troxy-cli 1.29.4 → 1.29.5
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/package.json +1 -1
- package/src/interceptor.js +17 -0
- package/src/tests/interceptor.test.js +37 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "troxy-cli",
|
|
3
|
-
"version": "1.29.
|
|
3
|
+
"version": "1.29.5",
|
|
4
4
|
"description": "A secure control layer for AI agents: policies across payments, messages, logins, destructive actions, model usage, and secrets, all enforceable from the CLI",
|
|
5
5
|
"homepage": "https://troxy.io",
|
|
6
6
|
"bugs": {
|
package/src/interceptor.js
CHANGED
|
@@ -73,6 +73,21 @@ export function parseConnectTarget(url) {
|
|
|
73
73
|
// meaningless (or wrong) to forward on either outbound leg.
|
|
74
74
|
const _HOP_BY_HOP_HEADERS = new Set(['host', 'connection', 'content-length', 'transfer-encoding']);
|
|
75
75
|
|
|
76
|
+
/** 'desktop' | 'terminal' | null. Verified live 2026-09-11 against two real
|
|
77
|
+
* captured user-agents from this same machine: terminal sends
|
|
78
|
+
* "claude-cli/2.1.236 (external, cli)", desktop sends "claude-cli/2.1.260
|
|
79
|
+
* (external, claude-desktop, agent-sdk/0.3.260)" - the literal substring
|
|
80
|
+
* "claude-desktop" is the one stable, explicit signal found; everything
|
|
81
|
+
* else in the string (version numbers) will drift across releases. null
|
|
82
|
+
* (never a guess) when the client sends no recognizable claude-cli
|
|
83
|
+
* user-agent at all - some other tool on this port, or a future format
|
|
84
|
+
* change this hasn't been updated for. */
|
|
85
|
+
export function detectSurface(incomingHeaders) {
|
|
86
|
+
const ua = incomingHeaders['user-agent'] || '';
|
|
87
|
+
if (!/^claude-cli\//.test(ua)) return null;
|
|
88
|
+
return ua.includes('claude-desktop') ? 'desktop' : 'terminal';
|
|
89
|
+
}
|
|
90
|
+
|
|
76
91
|
/** Headers for the leg going to Troxy: the client's own Authorization (an
|
|
77
92
|
* Anthropic API key or OAuth token) is never sent to Troxy as-is - it
|
|
78
93
|
* rides the side channel app.py already knows how to consume
|
|
@@ -87,6 +102,8 @@ export function buildTroxyForwardHeaders(incomingHeaders, troxyKey) {
|
|
|
87
102
|
if (incomingHeaders.authorization) {
|
|
88
103
|
headers['x-troxy-upstream-authorization'] = incomingHeaders.authorization;
|
|
89
104
|
}
|
|
105
|
+
const surface = detectSurface(incomingHeaders);
|
|
106
|
+
if (surface) headers['x-troxy-surface'] = surface;
|
|
90
107
|
headers.authorization = `Bearer ${troxyKey}`;
|
|
91
108
|
return headers;
|
|
92
109
|
}
|
|
@@ -21,6 +21,7 @@ import { format } from 'node:util';
|
|
|
21
21
|
|
|
22
22
|
import {
|
|
23
23
|
parseConnectTarget,
|
|
24
|
+
detectSurface,
|
|
24
25
|
buildTroxyForwardHeaders,
|
|
25
26
|
buildRelayHeaders,
|
|
26
27
|
createInterceptor,
|
|
@@ -41,7 +42,43 @@ describe('parseConnectTarget', () => {
|
|
|
41
42
|
});
|
|
42
43
|
});
|
|
43
44
|
|
|
45
|
+
describe('detectSurface', () => {
|
|
46
|
+
// Real user-agents captured live 2026-09-11 from this same machine's own
|
|
47
|
+
// terminal and desktop-app traffic through this interceptor.
|
|
48
|
+
it('recognizes a real desktop-app user-agent', () => {
|
|
49
|
+
assert.equal(
|
|
50
|
+
detectSurface({ 'user-agent': 'claude-cli/2.1.260 (external, claude-desktop, agent-sdk/0.3.260)' }),
|
|
51
|
+
'desktop',
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('recognizes a real terminal user-agent', () => {
|
|
56
|
+
assert.equal(detectSurface({ 'user-agent': 'claude-cli/2.1.236 (external, cli)' }), 'terminal');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('returns null when there is no user-agent at all', () => {
|
|
60
|
+
assert.equal(detectSurface({}), null);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('returns null for a user-agent that is not claude-cli - never a guess', () => {
|
|
64
|
+
assert.equal(detectSurface({ 'user-agent': 'curl/8.4.0' }), null);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
44
68
|
describe('buildTroxyForwardHeaders', () => {
|
|
69
|
+
it('sets x-troxy-surface from a recognized user-agent', () => {
|
|
70
|
+
const headers = buildTroxyForwardHeaders(
|
|
71
|
+
{ 'user-agent': 'claude-cli/2.1.260 (external, claude-desktop, agent-sdk/0.3.260)' },
|
|
72
|
+
'txy-key',
|
|
73
|
+
);
|
|
74
|
+
assert.equal(headers['x-troxy-surface'], 'desktop');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('omits x-troxy-surface entirely when the surface cannot be determined', () => {
|
|
78
|
+
const headers = buildTroxyForwardHeaders({ 'user-agent': 'curl/8.4.0' }, 'txy-key');
|
|
79
|
+
assert.equal(headers['x-troxy-surface'], undefined);
|
|
80
|
+
});
|
|
81
|
+
|
|
45
82
|
it('translates the client\'s own Authorization into X-Troxy-Upstream-Authorization, ' +
|
|
46
83
|
'and sets Authorization to the Troxy key instead', () => {
|
|
47
84
|
const headers = buildTroxyForwardHeaders(
|