@produtype/core 0.59.0 → 0.60.0

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.
@@ -135,6 +135,29 @@ async function detectSecurity(ctx) {
135
135
  (0, detectContext_1.hasDep)(ctx, 'slowapi');
136
136
  const rateLimitSignals = await (0, textSearch_1.searchInFiles)(ctx.root, source, [/rateLimit\s*\(/, /rate_?limit/i, /Retry-After/i, /\b429\b/, /TooManyRequests/i], 20);
137
137
  const rateLimit = rateLimitDep || rateLimitSignals.length > 0;
138
+ /**
139
+ * Rate limiting where the brute force happens.
140
+ *
141
+ * The rule is titled "Rate limit on auth surfaces" and its own passing sentence says
142
+ * "detected on the authentication surface", and the flag behind both was rate
143
+ * limiting *anywhere*: a limiter on a public feed cleared the check for a sign-in
144
+ * page that has none. Sign-in is the endpoint the limit exists for.
145
+ *
146
+ * The same file, which is as far as this reaches honestly. Where a project splits
147
+ * the limiter from the login the answer becomes "found, not shown to cover sign-in",
148
+ * which is true and which the reader can dismiss in two seconds if they know better.
149
+ *
150
+ * A prefix rule was written for this and removed. TranscribeAI protects its login
151
+ * with `app.use('/api/', limiter)` above `app.use('/api/auth', authRoutes)`, and
152
+ * matching the limiter's mount path against the auth router's looked like the right
153
+ * generalisation — but the mount line says `limiter`, not `rateLimit`, so no rate
154
+ * limit signal is ever on it and the rule never fired on the one case it was written
155
+ * for. Following the variable would work and is a third layer of guessing on top of
156
+ * two; TranscribeAI stays `partial`, which is what this can show.
157
+ */
158
+ const authSurfaceFiles = new Set((await (0, textSearch_1.searchInFiles)(ctx.root, source, [/['"`]\/(login|signin|sign-in|auth|session)/i, /passport\./, /signIn\s*\(/, /authenticate\s*\(/], 40))
159
+ .map((match) => match.file));
160
+ const rateLimitNearAuth = rateLimitSignals.some((match) => authSurfaceFiles.has(match.file));
138
161
  if (helmetDep)
139
162
  evidence.push({ type: 'dependency', value: 'helmet', claim: 'headers' });
140
163
  if (rateLimitDep)
@@ -253,6 +276,7 @@ async function detectSecurity(ctx) {
253
276
  details: {
254
277
  helmet,
255
278
  rateLimit,
279
+ rateLimitNearAuth,
256
280
  corsLoose: corsLoose.length > 0,
257
281
  corsStrict: corsStrict.length > 0,
258
282
  webhookSignature: webhookSig.length > 0,
@@ -358,7 +358,18 @@ exports.rules = [
358
358
  const sec = analysis.detectors['security.core'];
359
359
  const hasAuth = Boolean(auth?.present);
360
360
  const hasRate = Boolean(sec?.details?.rateLimit);
361
- const status = !isExpress || !hasAuth ? 'unknown' : hasRate ? 'passed' : 'missing';
361
+ /**
362
+ * Near the login, not merely somewhere.
363
+ *
364
+ * This passed on `rateLimit` alone, which is throttling anywhere in the
365
+ * repository — a limiter on a public feed cleared the check for a sign-in page
366
+ * that has none. The rule's own passing sentence says "on the authentication
367
+ * surface", and now it only says that when something shows it.
368
+ */
369
+ const nearAuth = Boolean(sec?.details?.rateLimitNearAuth);
370
+ const status = !isExpress || !hasAuth
371
+ ? 'unknown'
372
+ : nearAuth ? 'passed' : hasRate ? 'partial' : 'missing';
362
373
  return mkFinding({
363
374
  id: 'security.rate-limit-auth',
364
375
  title: 'Authentication rate limiting',
@@ -373,11 +384,13 @@ exports.rules = [
373
384
  */
374
385
  description: status === 'passed'
375
386
  ? 'Rate limiting signals detected on the authentication surface.'
376
- : status === 'missing'
377
- ? 'No auth-focused rate limiting detected.'
378
- : !hasAuth
379
- ? 'Nothing here authenticates anybody, so there is no login surface to throttle.'
380
- : 'This check reads Express middleware, and this project does not use it — any throttling it has is somewhere this cannot see.',
387
+ : status === 'partial'
388
+ ? 'Rate limiting is in place somewhere, but nothing here shows it covering sign-in.'
389
+ : status === 'missing'
390
+ ? 'No auth-focused rate limiting detected.'
391
+ : !hasAuth
392
+ ? 'Nothing here authenticates anybody, so there is no login surface to throttle.'
393
+ : 'This check reads Express middleware, and this project does not use it — any throttling it has is somewhere this cannot see.',
381
394
  recommendation: 'Apply express-rate-limit (or equivalent) to login/register/password reset endpoints.',
382
395
  /**
383
396
  * The claim is about rate limiting, so the evidence is about rate limiting.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@produtype/core",
3
- "version": "0.59.0",
3
+ "version": "0.60.0",
4
4
  "description": "Deterministic CLI and library that analyzes a web application repository and reports how far it is from production-ready for the kind of product it is meant to be.",
5
5
  "license": "MIT",
6
6
  "bin": {