ira-review 2.0.1 → 2.0.2

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/README.md CHANGED
@@ -15,55 +15,19 @@ No install required. Drop `--dry-run` to post comments directly on the PR. For B
15
15
  ## What You Get
16
16
 
17
17
  ```
18
- 🔍 IRA Scanning PR before your reviewers do
19
-
20
- ✓ Config loaded — AI-only mode, openai, PR #42
21
- Diff loaded 4 files changed
22
- Review complete 3 issues found
23
-
24
- ────────────────────────────────────────────────────────────
25
- 📄 src/routes/auth.ts:31
26
- Rule: IRA/security (CRITICAL)
27
- Message: User input passed directly to SQL query
28
- Explain: The username parameter is concatenated into a SQL string,
29
- creating a SQL injection vector.
30
- Impact: Attacker could execute arbitrary SQL and gain database control.
31
- Fix: BEFORE: `db.query(`SELECT * FROM users WHERE name = ${username}`)`
32
- → AFTER: `db.query('SELECT * FROM users WHERE name = $1', [username])`
33
-
34
- ────────────────────────────────────────────────────────────
35
- 📄 src/middleware/cors.ts:8
36
- Rule: IRA/error-handling (MAJOR)
37
- Message: Empty catch block swallows CORS validation errors
38
- Explain: fetch() failure in CORS preflight is caught and ignored,
39
- leaving the request in an undefined state.
40
- Impact: Silent CORS failures in production with no logging.
41
- Fix: BEFORE: `} catch {}`
42
- → AFTER: `} catch (err) { logger.error('CORS preflight failed', err); throw err; }`
43
-
44
- # 🔍 IRA Review Summary
45
-
46
- ## 🟡 Risk: MEDIUM (38/100)
47
-
48
- | Metric | Value |
49
- |---------------|----------|
50
- | Review mode | AI-only |
51
- | Total issues | 3 |
52
- | Reviewed (AI) | 3 |
53
- | Framework | react |
54
-
55
- ## ✅ Requirements: AUTH-234 — 83% Complete (5/6)
56
-
57
- ✅ OAuth2 login flow implemented with Google provider
58
- ✅ JWT tokens generated on successful authentication
59
- ✅ Refresh token rotation with 7-day expiry
60
- ❌ Input validation on login endpoint — no email format check
61
- ✅ Logout endpoint clears session and revokes token
62
- ✅ Rate limiting on login attempts
63
-
64
- ⚠️ Edge Cases Not Covered
65
- - What happens when Google OAuth is unreachable?
66
- - Token refresh during concurrent requests?
18
+ IRA: Found 3 issues (Risk: MEDIUM - 47/100)
19
+
20
+ src/routes/todos.ts
21
+ [BLOCKER] SQL injection risk - user input passed directly to query
22
+ [MAJOR] Missing database index on frequently queried column
23
+
24
+ src/middleware/auth.ts
25
+ [CRITICAL] JWT secret hardcoded - move to environment variable
26
+
27
+ JIRA AC Validation (PROJ-1234):
28
+ AC 1: User can create a todo item COVERED
29
+ AC 2: Input is validated before save NOT COVERED
30
+ AC 3: Error returns 422 with details COVERED
67
31
  ```
68
32
 
69
33
  Each issue is posted as an inline comment on the exact PR line with explanation, impact, and a minimal BEFORE → AFTER fix.
package/README.npm.md CHANGED
@@ -15,55 +15,19 @@ No install required. Drop `--dry-run` to post comments directly on the PR. For B
15
15
  ## What You Get
16
16
 
17
17
  ```
18
- 🔍 IRA Scanning PR before your reviewers do
19
-
20
- ✓ Config loaded — AI-only mode, openai, PR #42
21
- Diff loaded 4 files changed
22
- Review complete 3 issues found
23
-
24
- ────────────────────────────────────────────────────────────
25
- 📄 src/routes/auth.ts:31
26
- Rule: IRA/security (CRITICAL)
27
- Message: User input passed directly to SQL query
28
- Explain: The username parameter is concatenated into a SQL string,
29
- creating a SQL injection vector.
30
- Impact: Attacker could execute arbitrary SQL and gain database control.
31
- Fix: BEFORE: `db.query(`SELECT * FROM users WHERE name = ${username}`)`
32
- → AFTER: `db.query('SELECT * FROM users WHERE name = $1', [username])`
33
-
34
- ────────────────────────────────────────────────────────────
35
- 📄 src/middleware/cors.ts:8
36
- Rule: IRA/error-handling (MAJOR)
37
- Message: Empty catch block swallows CORS validation errors
38
- Explain: fetch() failure in CORS preflight is caught and ignored,
39
- leaving the request in an undefined state.
40
- Impact: Silent CORS failures in production with no logging.
41
- Fix: BEFORE: `} catch {}`
42
- → AFTER: `} catch (err) { logger.error('CORS preflight failed', err); throw err; }`
43
-
44
- # 🔍 IRA Review Summary
45
-
46
- ## 🟡 Risk: MEDIUM (38/100)
47
-
48
- | Metric | Value |
49
- |---------------|----------|
50
- | Review mode | AI-only |
51
- | Total issues | 3 |
52
- | Reviewed (AI) | 3 |
53
- | Framework | react |
54
-
55
- ## ✅ Requirements: AUTH-234 — 83% Complete (5/6)
56
-
57
- ✅ OAuth2 login flow implemented with Google provider
58
- ✅ JWT tokens generated on successful authentication
59
- ✅ Refresh token rotation with 7-day expiry
60
- ❌ Input validation on login endpoint — no email format check
61
- ✅ Logout endpoint clears session and revokes token
62
- ✅ Rate limiting on login attempts
63
-
64
- ⚠️ Edge Cases Not Covered
65
- - What happens when Google OAuth is unreachable?
66
- - Token refresh during concurrent requests?
18
+ IRA: Found 3 issues (Risk: MEDIUM - 47/100)
19
+
20
+ src/routes/todos.ts
21
+ [BLOCKER] SQL injection risk - user input passed directly to query
22
+ [MAJOR] Missing database index on frequently queried column
23
+
24
+ src/middleware/auth.ts
25
+ [CRITICAL] JWT secret hardcoded - move to environment variable
26
+
27
+ JIRA AC Validation (PROJ-1234):
28
+ AC 1: User can create a todo item COVERED
29
+ AC 2: Input is validated before save NOT COVERED
30
+ AC 3: Error returns 422 with details COVERED
67
31
  ```
68
32
 
69
33
  Each issue is posted as an inline comment on the exact PR line with explanation, impact, and a minimal BEFORE → AFTER fix.
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  BitbucketClient
4
- } from "./chunk-XOR6EFZE.js";
5
- import "./chunk-AHCFDKK4.js";
4
+ } from "./chunk-RAJNISC2.js";
5
+ import "./chunk-AFLVYFZ2.js";
6
6
  export {
7
7
  BitbucketClient
8
8
  };
@@ -2,8 +2,9 @@
2
2
  import {
3
3
  RetryableError,
4
4
  fetchWithTimeout,
5
+ parseApiError,
5
6
  withRetry
6
- } from "./chunk-AHCFDKK4.js";
7
+ } from "./chunk-AFLVYFZ2.js";
7
8
 
8
9
  // src/scm/github.ts
9
10
  var GitHubClient = class {
@@ -48,7 +49,7 @@ var GitHubClient = class {
48
49
  if (!response.ok) {
49
50
  const text = await response.text();
50
51
  throw new RetryableError(
51
- `GitHub API error (${response.status}): ${text}`,
52
+ parseApiError(response.status, text, "GitHub"),
52
53
  response.status
53
54
  );
54
55
  }
@@ -81,7 +82,7 @@ var GitHubClient = class {
81
82
  if (!response.ok) {
82
83
  const text = await response.text();
83
84
  throw new RetryableError(
84
- `GitHub API error (${response.status}): ${text}`,
85
+ parseApiError(response.status, text, "GitHub"),
85
86
  response.status
86
87
  );
87
88
  }
@@ -97,7 +98,7 @@ var GitHubClient = class {
97
98
  const response = await fetchWithTimeout(url, { headers: this.headers });
98
99
  if (!response.ok) {
99
100
  const text = await response.text();
100
- throw new RetryableError(`GitHub API error (${response.status}): ${text}`, response.status);
101
+ throw new RetryableError(parseApiError(response.status, text, "GitHub"), response.status);
101
102
  }
102
103
  return response.json();
103
104
  });
@@ -125,7 +126,7 @@ ${file.patch}`;
125
126
  if (!response.ok) {
126
127
  const text = await response.text();
127
128
  throw new RetryableError(
128
- `GitHub API error (${response.status}): ${text}`,
129
+ parseApiError(response.status, text, "GitHub"),
129
130
  response.status
130
131
  );
131
132
  }
@@ -154,7 +155,7 @@ ${file.patch}`;
154
155
  if (!response.ok) {
155
156
  const text = await response.text();
156
157
  throw new RetryableError(
157
- `GitHub API error (${response.status}): ${text}`,
158
+ parseApiError(response.status, text, "GitHub"),
158
159
  response.status
159
160
  );
160
161
  }
@@ -171,7 +172,7 @@ ${file.patch}`;
171
172
  if (!response.ok) {
172
173
  const text = await response.text();
173
174
  throw new RetryableError(
174
- `GitHub API error (${response.status}): ${text}`,
175
+ parseApiError(response.status, text, "GitHub"),
175
176
  response.status
176
177
  );
177
178
  }
@@ -188,7 +189,7 @@ ${file.patch}`;
188
189
  if (!response.ok) {
189
190
  const text = await response.text();
190
191
  throw new RetryableError(
191
- `GitHub API error (${response.status}): ${text}`,
192
+ parseApiError(response.status, text, "GitHub"),
192
193
  response.status
193
194
  );
194
195
  }
@@ -221,7 +222,7 @@ ${file.patch}`;
221
222
  if (!response.ok && response.status !== 422) {
222
223
  const text = await response.text();
223
224
  throw new RetryableError(
224
- `GitHub API error (${response.status}): ${text}`,
225
+ parseApiError(response.status, text, "GitHub"),
225
226
  response.status
226
227
  );
227
228
  }
@@ -258,7 +259,7 @@ ${file.patch}`;
258
259
  if (!response.ok) {
259
260
  const text = await response.text();
260
261
  throw new RetryableError(
261
- `GitHub API error (${response.status}): ${text}`,
262
+ parseApiError(response.status, text, "GitHub"),
262
263
  response.status
263
264
  );
264
265
  }
@@ -88,6 +88,41 @@ async function fetchWithTimeout(url, init = {}, timeoutMs = 3e4) {
88
88
  clearTimeout(timer);
89
89
  }
90
90
  }
91
+ function parseApiError(status, body, provider) {
92
+ const statusMessages = {
93
+ 400: "Bad request",
94
+ 401: "Authentication failed \u2014 check your token or credentials",
95
+ 403: "Access denied \u2014 you may not have permission for this resource",
96
+ 404: "Not found \u2014 check the URL, project key, or PR number",
97
+ 408: "Request timed out \u2014 try again in a moment",
98
+ 409: "Conflict \u2014 the resource may have been modified",
99
+ 422: "Invalid request \u2014 the server couldn't process it",
100
+ 429: "Rate limited \u2014 too many requests, try again shortly",
101
+ 500: "Server error \u2014 the service is having issues",
102
+ 502: "Bad gateway \u2014 the service is temporarily unavailable",
103
+ 503: "Service unavailable \u2014 try again in a moment",
104
+ 504: "Gateway timeout \u2014 the service took too long to respond"
105
+ };
106
+ const friendlyStatus = statusMessages[status] ?? `HTTP ${status}`;
107
+ const trimmed = body.trim();
108
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
109
+ try {
110
+ const json = JSON.parse(trimmed);
111
+ const msg = json.message ?? json.error?.message ?? json.error ?? json.errors?.[0]?.message ?? json.detail;
112
+ if (typeof msg === "string" && msg.length > 0 && msg.length < 200) {
113
+ return `${provider} (${status}): ${msg}`;
114
+ }
115
+ } catch {
116
+ }
117
+ }
118
+ if (trimmed.startsWith("<!") || trimmed.startsWith("<html") || trimmed.includes("<body")) {
119
+ return `${provider} (${status}): ${friendlyStatus}`;
120
+ }
121
+ if (trimmed.length > 0 && trimmed.length < 150) {
122
+ return `${provider} (${status}): ${trimmed}`;
123
+ }
124
+ return `${provider} (${status}): ${friendlyStatus}`;
125
+ }
91
126
  function sleep(ms) {
92
127
  return new Promise((resolve) => setTimeout(resolve, ms));
93
128
  }
@@ -95,5 +130,6 @@ function sleep(ms) {
95
130
  export {
96
131
  RetryableError,
97
132
  withRetry,
98
- fetchWithTimeout
133
+ fetchWithTimeout,
134
+ parseApiError
99
135
  };
@@ -2,8 +2,9 @@
2
2
  import {
3
3
  RetryableError,
4
4
  fetchWithTimeout,
5
+ parseApiError,
5
6
  withRetry
6
- } from "./chunk-AHCFDKK4.js";
7
+ } from "./chunk-AFLVYFZ2.js";
7
8
 
8
9
  // src/scm/bitbucket.ts
9
10
  var BitbucketClient = class {
@@ -55,14 +56,14 @@ var BitbucketClient = class {
55
56
  if (!retryResponse.ok) {
56
57
  const retryText = await retryResponse.text();
57
58
  throw new RetryableError(
58
- `Bitbucket API error (${retryResponse.status}): ${retryText}`,
59
+ parseApiError(retryResponse.status, retryText, "Bitbucket"),
59
60
  retryResponse.status
60
61
  );
61
62
  }
62
63
  return;
63
64
  }
64
65
  throw new RetryableError(
65
- `Bitbucket API error (${response.status}): ${text}`,
66
+ parseApiError(response.status, text, "Bitbucket"),
66
67
  response.status
67
68
  );
68
69
  }
@@ -82,7 +83,7 @@ var BitbucketClient = class {
82
83
  if (!response.ok) {
83
84
  const text = await response.text();
84
85
  throw new RetryableError(
85
- `Bitbucket API error (${response.status}): ${text}`,
86
+ parseApiError(response.status, text, "Bitbucket"),
86
87
  response.status
87
88
  );
88
89
  }
@@ -99,7 +100,7 @@ var BitbucketClient = class {
99
100
  if (!response.ok) {
100
101
  const text = await response.text();
101
102
  throw new RetryableError(
102
- `Bitbucket API error (${response.status}): ${text}`,
103
+ parseApiError(response.status, text, "Bitbucket"),
103
104
  response.status
104
105
  );
105
106
  }
@@ -131,7 +132,7 @@ var BitbucketClient = class {
131
132
  if (!response.ok) {
132
133
  const text = await response.text();
133
134
  throw new RetryableError(
134
- `Bitbucket API error (${response.status}): ${text}`,
135
+ parseApiError(response.status, text, "Bitbucket"),
135
136
  response.status
136
137
  );
137
138
  }
@@ -147,7 +148,7 @@ var BitbucketClient = class {
147
148
  const response = await fetchWithTimeout(nextUrl, { headers: this.headers });
148
149
  if (!response.ok) {
149
150
  const text = await response.text();
150
- throw new RetryableError(`Bitbucket API error (${response.status}): ${text}`, response.status);
151
+ throw new RetryableError(parseApiError(response.status, text, "Bitbucket"), response.status);
151
152
  }
152
153
  return response.json();
153
154
  });
@@ -166,7 +167,7 @@ var BitbucketClient = class {
166
167
  const response = await fetchWithTimeout(url, { headers: this.headers }, 15e3);
167
168
  if (!response.ok) {
168
169
  const text = await response.text();
169
- throw new RetryableError(`Bitbucket API error (${response.status}): ${text}`, response.status);
170
+ throw new RetryableError(parseApiError(response.status, text, "Bitbucket"), response.status);
170
171
  }
171
172
  return response.text();
172
173
  });
@@ -189,7 +190,7 @@ var BitbucketClient = class {
189
190
  if (!response.ok) {
190
191
  const text = await response.text();
191
192
  throw new RetryableError(
192
- `Bitbucket API error (${response.status}): ${text}`,
193
+ parseApiError(response.status, text, "Bitbucket"),
193
194
  response.status
194
195
  );
195
196
  }
@@ -218,7 +219,7 @@ var BitbucketClient = class {
218
219
  if (!response.ok) {
219
220
  const text = await response.text();
220
221
  throw new RetryableError(
221
- `Bitbucket API error (${response.status}): ${text}`,
222
+ parseApiError(response.status, text, "Bitbucket"),
222
223
  response.status
223
224
  );
224
225
  }