@produtype/core 1.7.1 → 1.8.1

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.
@@ -104,7 +104,20 @@ exports.JVM_DATABASES = [
104
104
  ];
105
105
  /** Ruby frameworks, read from the Gemfile. */
106
106
  exports.RUBY_BACKEND_FRAMEWORKS = [
107
- ['rails', ['rails']],
107
+ /**
108
+ * Rails is often declared by its parts rather than by its name.
109
+ *
110
+ * Discourse's Gemfile has no `gem "rails"` in it at all: it pins `actionpack`,
111
+ * `actionview`, `activerecord` and `railties` separately, which is what a large
112
+ * application does when it wants to control the pieces. The report said
113
+ * `backend: ruby` — the fallback for a Gemfile with no web framework in it — about
114
+ * the best-known Rails application there is.
115
+ *
116
+ * `railties` is the framework itself and nothing but Rails and its engines depends
117
+ * on it; `actionpack` is the half that handles requests. Both are names the Rails
118
+ * team chose, which is the whole argument.
119
+ */
120
+ ['rails', ['rails', 'railties', 'actionpack']],
108
121
  ['sinatra', ['sinatra']],
109
122
  ['hanami', ['hanami']],
110
123
  ['roda', ['roda']],
@@ -12,7 +12,24 @@ async function detectBilling(ctx) {
12
12
  const hasStripeDep = (0, detectContext_1.hasDep)(ctx, 'stripe');
13
13
  if (hasStripeDep)
14
14
  evidence.push({ type: 'dependency', value: 'stripe' });
15
- const stripeContextHits = await (0, textSearch_1.searchInFiles)(ctx.root, ctx.files.source, [
15
+ /**
16
+ * Laravel's table of service credentials, which is a list of slots.
17
+ *
18
+ * `config/services.php` ships with the framework and holds a block per service the
19
+ * skeleton knows about — mailgun, postmark, ses, and for years a `stripe` one
20
+ * reading `env('STRIPE_KEY')`. Firefly III still carries it, beside sparkpost,
21
+ * mandrill and pushover, and takes no payments at all: no processor package in its
22
+ * composer.json, no charge anywhere. It was reported at `medium` for an unhardened
23
+ * billing webhook, and the rest of that finding was its own webhooks feature —
24
+ * notifications a *user* registers to hear about their transactions, which point
25
+ * the other way entirely.
26
+ *
27
+ * An entry there is a slot the deployment may fill, not an integration this project
28
+ * has. A project that charges people declares `stripe/stripe-php` or
29
+ * `laravel/cashier`, and that is read from the manifest a few lines down.
30
+ */
31
+ const LARAVEL_SERVICE_SLOTS = /(^|\/)config\/services\.php$/;
32
+ const stripeContextHits = (await (0, textSearch_1.searchInFiles)(ctx.root, ctx.files.source, [
16
33
  /**
17
34
  * The bare word is not here, on purpose.
18
35
  *
@@ -38,7 +55,7 @@ async function detectBilling(ctx) {
38
55
  /stripeSubscriptionId/i,
39
56
  /\/webhooks?\/stripe/i,
40
57
  /\/stripe\/webhooks?/i,
41
- ], 30);
58
+ ], 30)).filter((hit) => !LARAVEL_SERVICE_SLOTS.test(hit.file));
42
59
  /**
43
60
  * The processor, declared wherever this project declares its dependencies.
44
61
  *
@@ -97,7 +97,7 @@ function resolveFromOtherManifests(ctx) {
97
97
  const best = pool.reduce((winner, candidate) => candidate.depth < winner.depth || (candidate.depth === winner.depth && candidate.order < winner.order)
98
98
  ? candidate
99
99
  : winner);
100
- return { manager: best.manager, confidence: 'manifest', warnings: [], evidence: [best.file] };
100
+ return { manager: best.manager, confidence: 'manifest', warnings: [], evidence: [best.file], share: best.share };
101
101
  }
102
102
  function resolveWorkspaceManager(workspace) {
103
103
  const warnings = [];
@@ -156,7 +156,24 @@ async function detectPackageManager(ctx) {
156
156
  * prefer: a repository whose only manifest is a package.json is an npm repository
157
157
  * however little JavaScript it has.
158
158
  */
159
- const nodeOrPythonIsTheProduct = (0, languageShare_1.languageShare)(ctx, /\.(ts|tsx|js|jsx|mjs|cjs|py)$/) >= languageShare_1.MINIMUM_LANGUAGE_SHARE;
159
+ const nodeOrPythonShare = (0, languageShare_1.languageShare)(ctx, /\.(ts|tsx|js|jsx|mjs|cjs|py)$/);
160
+ /**
161
+ * And clearing the line is not the same as winning.
162
+ *
163
+ * The test above asks whether Node is a real part of the repository, and the
164
+ * question is which manifest builds the *product*. Firefly III is 1748 PHP files
165
+ * and 221 of JavaScript — eleven per cent, comfortably over the line — and it is a
166
+ * Laravel application whose package.json runs Vite over its frontend assets. The
167
+ * report said "Package manager: npm (lockfile)" about a project installed with
168
+ * Composer.
169
+ *
170
+ * Where both manifests clear the line, the larger language decides, which is the
171
+ * comparison the other manifests already make among themselves. Below the line
172
+ * nothing changes, and a repository whose only manifest is a package.json is an npm
173
+ * repository however little JavaScript it has.
174
+ */
175
+ const nodeOrPythonIsTheProduct = nodeOrPythonShare >= languageShare_1.MINIMUM_LANGUAGE_SHARE
176
+ && nodeOrPythonShare >= (otherManifest?.share ?? 0);
160
177
  const selected = fromWorkspaces
161
178
  && fromWorkspaces.manager !== 'unknown'
162
179
  && (nodeOrPythonIsTheProduct || !otherManifest)
@@ -11,6 +11,23 @@ const readingDepth_1 = require("./readingDepth");
11
11
  const developmentOnly_1 = require("./developmentOnly");
12
12
  /** Lines that decide which origins may call this server. */
13
13
  const ORIGIN_HANDLING = [/Access-Control-Allow-Origin/i, /ALLOWED_ORIGINS/, /allowedOrigins/i];
14
+ /** The cloud SDKs' names for a bucket's own cross-origin rules. */
15
+ const CLOUD_STORAGE_CORS = /\bStorageCorsRule\b|\bCorsRules\b|\bCORSRule\b|\bCORSConfiguration\b|\bsetCorsConfiguration\b/;
16
+ /**
17
+ * Whether this line sits inside one of those rules.
18
+ *
19
+ * The type is named where the object is opened and the origins are listed a few lines
20
+ * further in — bitwarden's `CorsRules.Add(new StorageCorsRule` is two lines above its
21
+ * `AllowedOrigins`. A short window rather than the whole file, because the names are
22
+ * specific enough to be decisive and a file-wide test would excuse a real policy that
23
+ * happens to share a file with a bucket's.
24
+ */
25
+ const CLOUD_STORAGE_CORS_WINDOW = 5;
26
+ function namesACloudStorageRule(text, line) {
27
+ const lines = text.split(/\r?\n/);
28
+ const from = Math.max(0, line - 1 - CLOUD_STORAGE_CORS_WINDOW);
29
+ return lines.slice(from, line).some((candidate) => CLOUD_STORAGE_CORS.test(candidate));
30
+ }
14
31
  /**
15
32
  * The same line, allowing everyone.
16
33
  *
@@ -454,6 +471,22 @@ async function detectSecurity(ctx) {
454
471
  // allowlist made a wildcard read as restricted; and it cited line 1, which in
455
472
  // every repository that triggered it was an import.
456
473
  for (const m of (0, textSearch_1.matchLines)(text, ORIGIN_HANDLING, file)) {
474
+ /**
475
+ * A bucket's CORS is not the application's CORS.
476
+ *
477
+ * bitwarden's Aspire host configures the local Azurite storage emulator with
478
+ * `AllowedOrigins = [new BicepValue<string>("*")]` inside a `StorageCorsRule`,
479
+ * and that one line made a `high` finding out of an API whose actual policy is
480
+ * `SetIsOriginAllowed(o => CoreHelpers.IsCorsOriginAllowed(o, globalSettings))`
481
+ * — a function deciding, cited two lines below it in the same report.
482
+ *
483
+ * The subject is different, not the severity: a storage account, a bucket or a
484
+ * CDN distribution answers for the objects it serves, and this check is about
485
+ * the requests this application answers. `StorageCorsRule`, `CorsRules` and S3's
486
+ * `CORSRule` are type names from the cloud SDKs, so they are the anchor.
487
+ */
488
+ if (namesACloudStorageRule(text, m.line))
489
+ continue;
457
490
  if (WILDCARD_ORIGIN.test(m.snippet))
458
491
  corsLoose.push(m);
459
492
  else if (allowsAChosenOrigin(m.snippet, text))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@produtype/core",
3
- "version": "1.7.1",
3
+ "version": "1.8.1",
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": {