@tityra/cli 1.1.3 → 1.1.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.4
4
+
5
+ - Stop cancelled OAuth sign-in during discovery and token exchange without saving credentials.
6
+ - Reject malformed UTF-8 JSON from files and standard input before sending account changes.
7
+ - Update the development dependency override for the minimatch security fix.
8
+
3
9
  ## 1.1.3
4
10
 
5
11
  - Reject monitoring enablement before transport and direct customers to recurring credit approval in Tityra.
package/README.md CHANGED
@@ -6,7 +6,7 @@ Tityra's website content audits review content delivery, structure and available
6
6
 
7
7
  ## Project status
8
8
 
9
- Version `1.1.3` rejects unsupported monitoring enablement before sending a request and directs customers to recurring credit approval in Tityra. Funding errors no longer imply that monitoring needs a paid subscription. Credit purchases and spending approval take place in Tityra. Stable releases use npm's `latest` tag; check the [npm package page](https://www.npmjs.com/package/@tityra/cli) for the currently published version. CI targets Node.js 22 and 24 on Linux, macOS and Windows; local regression tests use isolated fixtures rather than live customer accounts.
9
+ Version `1.1.4` stops cancelled OAuth sign-in from continuing authorisation or saving credentials, and rejects malformed UTF-8 JSON before sending account changes. Credit purchases and spending approval take place in Tityra. Stable releases use npm's `latest` tag; check the [npm package page](https://www.npmjs.com/package/@tityra/cli) for the currently published version. CI targets Node.js 22 and 24 on Linux, macOS and Windows; local regression tests use isolated fixtures rather than live customer accounts.
10
10
 
11
11
  ## Basic functionality
12
12
 
@@ -26,6 +26,10 @@ const opaque = (value) => typeof value === 'string' &&
26
26
  value.length <= 8192 &&
27
27
  /^[A-Za-z0-9._~+/-]+=*$/.test(value);
28
28
  const loopback = (hostname) => ['127.0.0.1', 'localhost', '[::1]'].includes(hostname);
29
+ function assertAuthorizationActive(signal) {
30
+ if (signal?.aborted)
31
+ throw new AuthError('Authorisation was cancelled.', 'authorization_cancelled');
32
+ }
29
33
  export function normalizeBaseUrl(value = DEFAULT_BASE_URL, allowLocalhost = false) {
30
34
  let url;
31
35
  try {
@@ -60,18 +64,23 @@ function endpoint(value, issuer) {
60
64
  return url.href;
61
65
  }
62
66
  async function jsonRequest(url, init = {}, operation = 'authorisation') {
67
+ assertAuthorizationActive(init.signal);
63
68
  let response;
64
69
  try {
65
70
  response = await fetch(url, {
66
71
  ...init,
67
72
  redirect: 'error',
68
73
  credentials: 'omit',
69
- signal: init.signal || AbortSignal.timeout(15000),
74
+ signal: init.signal
75
+ ? AbortSignal.any([init.signal, AbortSignal.timeout(15000)])
76
+ : AbortSignal.timeout(15000),
70
77
  });
71
78
  }
72
79
  catch {
80
+ assertAuthorizationActive(init.signal);
73
81
  throw new AuthError(`The ${operation} request could not be completed. No automatic retry was made.`, 'network_error');
74
82
  }
83
+ assertAuthorizationActive(init.signal);
75
84
  if (!response.ok) {
76
85
  await response.body?.cancel();
77
86
  throw new AuthError(`The Tityra server rejected the ${operation} request (HTTP ${response.status}).`, 'oauth_request_failed', response.status);
@@ -101,11 +110,13 @@ async function jsonRequest(url, init = {}, operation = 'authorisation') {
101
110
  catch (error) {
102
111
  if (error instanceof AuthError)
103
112
  throw error;
113
+ assertAuthorizationActive(init.signal);
104
114
  throw new AuthError('The OAuth response could not be read. No automatic retry was made.', 'network_error');
105
115
  }
106
116
  finally {
107
117
  reader.releaseLock();
108
118
  }
119
+ assertAuthorizationActive(init.signal);
109
120
  const bytes = new Uint8Array(size);
110
121
  let offset = 0;
111
122
  for (const chunk of chunks) {
@@ -122,9 +133,9 @@ async function jsonRequest(url, init = {}, operation = 'authorisation') {
122
133
  }
123
134
  throw new AuthError('The Tityra server returned an invalid OAuth response.', 'invalid_response');
124
135
  }
125
- export async function discoverAuthorizationServer(baseUrl = DEFAULT_BASE_URL, allowLocalhost = false) {
136
+ export async function discoverAuthorizationServer(baseUrl = DEFAULT_BASE_URL, allowLocalhost = false, signal) {
126
137
  const issuer = normalizeBaseUrl(baseUrl, allowLocalhost);
127
- const metadata = await jsonRequest(issuer + '/.well-known/oauth-authorization-server', {}, 'discovery');
138
+ const metadata = await jsonRequest(issuer + '/.well-known/oauth-authorization-server', { signal }, 'discovery');
128
139
  if (metadata.issuer !== issuer ||
129
140
  !Array.isArray(metadata.code_challenge_methods_supported) ||
130
141
  !metadata.code_challenge_methods_supported.includes('S256') ||
@@ -281,7 +292,8 @@ const postForm = (body) => ({
281
292
  body,
282
293
  });
283
294
  export async function login(options = {}) {
284
- const metadata = await discoverAuthorizationServer(options.baseUrl, options.allowLocalhost);
295
+ assertAuthorizationActive(options.signal);
296
+ const metadata = await discoverAuthorizationServer(options.baseUrl, options.allowLocalhost, options.signal);
285
297
  const scopes = [...new Set(options.scopes || DEFAULT_SCOPES)];
286
298
  if (!scopes.length || scopes.some((scope) => !metadata.scopes_supported.includes(scope)))
287
299
  throw new AuthError('Request one or more supported OAuth permissions.', 'invalid_scope');
@@ -296,6 +308,7 @@ export async function login(options = {}) {
296
308
  try {
297
309
  const client = await jsonRequest(metadata.registration_endpoint, {
298
310
  method: 'POST',
311
+ signal: options.signal,
299
312
  headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
300
313
  body: JSON.stringify({
301
314
  client_name: 'Tityra CLI',
@@ -325,21 +338,29 @@ export async function login(options = {}) {
325
338
  state,
326
339
  }))
327
340
  url.searchParams.set(key, value);
341
+ assertAuthorizationActive(options.signal);
328
342
  if (options.onAuthorize)
329
343
  await options.onAuthorize(url.href);
330
344
  else
331
345
  process.stderr.write(`Open this URL in your browser to authorise Tityra CLI:\n${url.href}\n`);
332
346
  const code = await callback.code;
333
- const tokens = await jsonRequest(metadata.token_endpoint, postForm(new URLSearchParams({
334
- grant_type: 'authorization_code',
335
- client_id: client.client_id,
336
- code,
337
- redirect_uri: callback.redirectUri,
338
- code_verifier: verifier,
339
- resource: metadata.issuer + '/mcp',
340
- })), 'token exchange');
347
+ assertAuthorizationActive(options.signal);
348
+ const tokens = await jsonRequest(metadata.token_endpoint, {
349
+ ...postForm(new URLSearchParams({
350
+ grant_type: 'authorization_code',
351
+ client_id: client.client_id,
352
+ code,
353
+ redirect_uri: callback.redirectUri,
354
+ code_verifier: verifier,
355
+ resource: metadata.issuer + '/mcp',
356
+ })),
357
+ signal: options.signal,
358
+ }, 'token exchange');
341
359
  const credentials = tokenCredentials(tokens, metadata.issuer, client.client_id, scopes);
342
- await (options.store || new CredentialStore()).write(credentials);
360
+ await (options.store || new CredentialStore()).withLock(metadata.issuer, async (locked) => {
361
+ assertAuthorizationActive(options.signal);
362
+ await locked.write(credentials);
363
+ });
343
364
  return {
344
365
  issuer: credentials.issuer,
345
366
  scopes: credentials.scopes,
@@ -58,7 +58,7 @@ export async function writeInput(kind, options) {
58
58
  usage('Input must not exceed 64 KiB.');
59
59
  let input;
60
60
  try {
61
- input = JSON.parse(bytes.toString('utf8'));
61
+ input = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
62
62
  }
63
63
  catch {
64
64
  usage('The input is not valid JSON.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tityra/cli",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "Official Tityra CLI for projects, audits, reports and findings. Built for developers, AI agents and automated workflows.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -73,6 +73,9 @@
73
73
  "@types/prompts": "2.4.9"
74
74
  },
75
75
  "overrides": {
76
+ "@typescript-eslint/typescript-estree": {
77
+ "minimatch": "11.0.4"
78
+ },
76
79
  "markdownlint": {
77
80
  "re2js": "2.9.1"
78
81
  },