@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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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": {
|