@lanonasis/cli 3.7.5 → 3.7.7

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.
@@ -91,7 +91,7 @@ async function handleAuthenticationFailure(error, config, authMethod = 'jwt') {
91
91
  await config.clearInvalidCredentials();
92
92
  break;
93
93
  default:
94
- console.log(chalk.red(`Unexpected error: ${error.message || 'Unknown error'}`));
94
+ console.log(chalk.red(`Unexpected error: ${sanitizeErrorMessage(error.message || 'Unknown error')}`));
95
95
  console.log(chalk.gray('• Please try again'));
96
96
  console.log(chalk.gray('• If the problem persists, contact support'));
97
97
  }
@@ -183,12 +183,38 @@ function generatePKCE() {
183
183
  .digest('base64url');
184
184
  return { verifier, challenge };
185
185
  }
186
+ /**
187
+ * Sanitize error messages to prevent command injection
188
+ */
189
+ function sanitizeErrorMessage(message) {
190
+ if (typeof message !== 'string')
191
+ return 'Unknown error';
192
+ // Remove potential command injection characters
193
+ return message
194
+ .replace(/[;&|`$()]/g, '') // Remove shell metacharacters
195
+ .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') // Remove script tags
196
+ .replace(/javascript:/gi, '') // Remove javascript: URLs
197
+ .trim();
198
+ }
186
199
  /**
187
200
  * Start local HTTP server to catch OAuth2 callback
188
201
  */
189
202
  function createCallbackServer(port = 8888) {
190
203
  return new Promise((resolve, reject) => {
204
+ // Sanitize HTML to prevent XSS
205
+ function sanitizeHtml(str) {
206
+ return str
207
+ .replace(/&/g, '&amp;')
208
+ .replace(/</g, '&lt;')
209
+ .replace(/>/g, '&gt;')
210
+ .replace(/"/g, '&quot;')
211
+ .replace(/'/g, '&#x27;');
212
+ }
191
213
  const server = http.createServer((req, res) => {
214
+ // Set security headers
215
+ res.setHeader('X-Content-Type-Options', 'nosniff');
216
+ res.setHeader('X-Frame-Options', 'DENY');
217
+ res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
192
218
  const parsedUrl = url.parse(req.url, true);
193
219
  if (parsedUrl.pathname === '/callback') {
194
220
  const { code, state, error, error_description } = parsedUrl.query;
@@ -200,7 +226,7 @@ function createCallbackServer(port = 8888) {
200
226
  <head><title>Authentication Failed</title></head>
201
227
  <body style="font-family: sans-serif; text-align: center; padding: 50px;">
202
228
  <h1>❌ Authentication Failed</h1>
203
- <p>${error_description || error}</p>
229
+ <p>${sanitizeHtml(String(error_description || error))}</p>
204
230
  <p style="color: gray;">You can close this window.</p>
205
231
  </body>
206
232
  </html>
@@ -280,7 +306,9 @@ async function exchangeCodeForTokens(code, verifier, authBase, redirectUri) {
280
306
  }
281
307
  /**
282
308
  * Refresh OAuth2 access token using refresh token
309
+ * @internal Used for token refresh flows
283
310
  */
311
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
284
312
  async function refreshOAuth2Token(config) {
285
313
  const refreshToken = config.get('refresh_token');
286
314
  if (!refreshToken) {
@@ -300,45 +328,11 @@ async function refreshOAuth2Token(config) {
300
328
  await config.set('token_expires_at', Date.now() + (response.expires_in * 1000));
301
329
  return true;
302
330
  }
303
- catch (error) {
331
+ catch {
304
332
  console.error(chalk.yellow('⚠️ Token refresh failed, please re-authenticate'));
305
333
  return false;
306
334
  }
307
335
  }
308
- /**
309
- * Exchange Supabase JWT token for auth-gateway API key
310
- * This enables CLI to work with MCP WebSocket and all services seamlessly
311
- */
312
- async function exchangeSupabaseTokenForApiKey(supabaseToken, config) {
313
- try {
314
- const discoveredServices = config.get('discoveredServices');
315
- const authBase = discoveredServices?.auth_base || 'https://auth.lanonasis.com';
316
- if (process.env.CLI_VERBOSE === 'true') {
317
- console.log(chalk.dim(` Exchanging token at: ${authBase}/v1/auth/token/exchange`));
318
- }
319
- const response = await axios.post(`${authBase}/v1/auth/token/exchange`, {
320
- project_scope: 'lanonasis-maas',
321
- platform: 'cli'
322
- }, {
323
- headers: {
324
- 'Authorization': `Bearer ${supabaseToken}`,
325
- 'Content-Type': 'application/json',
326
- 'X-Project-Scope': 'lanonasis-maas'
327
- }
328
- });
329
- return {
330
- access_token: response.data.access_token,
331
- user: response.data.user
332
- };
333
- }
334
- catch (error) {
335
- console.error(chalk.yellow('⚠️ Token exchange failed:', error.message));
336
- if (process.env.CLI_VERBOSE === 'true' && error.response) {
337
- console.error(chalk.dim(' Response:', JSON.stringify(error.response.data, null, 2)));
338
- }
339
- return null;
340
- }
341
- }
342
336
  export async function diagnoseCommand() {
343
337
  const config = new CLIConfig();
344
338
  await config.init();
@@ -708,33 +702,23 @@ async function handleOAuthFlow(config) {
708
702
  }
709
703
  const tokens = await exchangeCodeForTokens(code, pkce.verifier, authBase, redirectUri);
710
704
  spinner.succeed('Access tokens received');
711
- // Store OAuth tokens
705
+ // Store OAuth tokens - these are already valid auth-gateway tokens from /oauth/token
706
+ // No need for additional exchange since /oauth/token returns auth-gateway's own tokens
712
707
  await config.setToken(tokens.access_token);
713
708
  await config.set('refresh_token', tokens.refresh_token);
714
709
  await config.set('token_expires_at', Date.now() + (tokens.expires_in * 1000));
715
- // Exchange for unified API key
710
+ await config.set('authMethod', 'oauth2');
711
+ // The OAuth access token from auth-gateway works as the API token for all services
712
+ // Store it as the vendor key equivalent for MCP and API access
716
713
  spinner.text = 'Configuring unified access...';
717
714
  spinner.start();
718
- const exchangeResult = await exchangeSupabaseTokenForApiKey(tokens.access_token, config);
719
- if (exchangeResult) {
720
- // Store the auth-gateway API key for MCP and other services
721
- await config.setVendorKey(exchangeResult.access_token);
722
- await config.set('authMethod', 'oauth2');
723
- spinner.succeed('Unified authentication configured');
724
- console.log();
725
- console.log(chalk.green('✓ OAuth2 authentication successful'));
726
- console.log(colors.info('You can now use all Lanonasis services'));
727
- console.log(chalk.gray('✓ MCP, API, and CLI access configured'));
728
- }
729
- else {
730
- // Fallback
731
- await config.set('authMethod', 'oauth2');
732
- spinner.warn('Token exchange failed, OAuth token stored');
733
- console.log();
734
- console.log(chalk.green('✓ OAuth2 authentication successful'));
735
- console.log(colors.info('You can now use Lanonasis services'));
736
- console.log(chalk.yellow('⚠️ Some services may require re-authentication'));
737
- }
715
+ // Use the OAuth access token directly - it's already an auth-gateway token
716
+ await config.setVendorKey(tokens.access_token);
717
+ spinner.succeed('Unified authentication configured');
718
+ console.log();
719
+ console.log(chalk.green(' OAuth2 authentication successful'));
720
+ console.log(colors.info('You can now use all Lanonasis services'));
721
+ console.log(chalk.gray('✓ MCP, API, and CLI access configured'));
738
722
  process.exit(0);
739
723
  }
740
724
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lanonasis/cli",
3
- "version": "3.7.5",
3
+ "version": "3.7.7",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -22,7 +22,7 @@
22
22
  "LICENSE"
23
23
  ],
24
24
  "dependencies": {
25
- "@lanonasis/oauth-client": "^1.2.1",
25
+ "@lanonasis/oauth-client": "1.2.5",
26
26
  "@lanonasis/security-sdk": "^1.0.1",
27
27
  "@modelcontextprotocol/sdk": "^1.1.1",
28
28
  "axios": "^1.7.7",