@produtype/core 0.90.0 → 0.92.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.
|
@@ -157,6 +157,23 @@ function parsePyprojectSections(text, runtimeOnly) {
|
|
|
157
157
|
}
|
|
158
158
|
return deps;
|
|
159
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Everything after `src/main/java` is a package name, not a project layout.
|
|
162
|
+
*
|
|
163
|
+
* spring-petclinic lives in `org.springframework.samples.petclinic`, so every one of
|
|
164
|
+
* its thirty Java files sits under a path segment called `samples` — and the rule that
|
|
165
|
+
* removes sample code removed the whole application. The report saw a project with no
|
|
166
|
+
* Java in it at all: twelve files, an HTML front end, no backend, and a profile of
|
|
167
|
+
* `client-app` at high confidence for a Spring Boot server.
|
|
168
|
+
*
|
|
169
|
+
* Maven and Gradle both fix that prefix, so the segments after it are the author's
|
|
170
|
+
* package and mean nothing about the layout. A real sample directory sits beside
|
|
171
|
+
* `src`, not inside its source root — ktor-documentation keeps its under
|
|
172
|
+
* `codeSnippets/snippets/`, which this still removes.
|
|
173
|
+
*/
|
|
174
|
+
function isAPackageName(file) {
|
|
175
|
+
return /(^|\/)src\/(main|test)\/(java|kotlin|scala|groovy)\//.test(file);
|
|
176
|
+
}
|
|
160
177
|
function isTestOrExamplePath(file) {
|
|
161
178
|
// `__mocks__` was missing, and a mock is the most misleading file in a repository:
|
|
162
179
|
// `application_fee_percent: null` inside a Stripe fixture made an open-source CRM read
|
|
@@ -219,7 +236,7 @@ function isTestOrExamplePath(file) {
|
|
|
219
236
|
* spell it. A repository whose every source file is a sample now has no source
|
|
220
237
|
* files, and says so — which is the honest answer for one.
|
|
221
238
|
*/
|
|
222
|
-
|| /(^|\/)(samples?|snippets?|codesnippets)(\/|$)/i.test(file)
|
|
239
|
+
|| (/(^|\/)(samples?|snippets?|codesnippets)(\/|$)/i.test(file) && !isAPackageName(file))
|
|
223
240
|
/**
|
|
224
241
|
* Somebody else's code, vendored in.
|
|
225
242
|
*
|
|
@@ -60,7 +60,25 @@ async function detectObservability(ctx) {
|
|
|
60
60
|
const healthFiles = ctx.files.all.filter((file) => /(^|\/)(health|healthz|readyz|liveness|readiness)(\/route|\.[a-z]+)?$|(^|\/)(health|healthz|readyz)\//i.test(file));
|
|
61
61
|
for (const file of healthFiles)
|
|
62
62
|
evidence.push({ type: 'file', value: file, claim: 'health' });
|
|
63
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Spring Boot Actuator, which is the endpoint rather than a route to it.
|
|
65
|
+
*
|
|
66
|
+
* Adding `spring-boot-starter-actuator` publishes `/actuator/health` — exposed by
|
|
67
|
+
* default, with no route written anywhere — so a project that has it cannot be
|
|
68
|
+
* found by searching for a path. spring-petclinic declares it in both its pom and
|
|
69
|
+
* its build.gradle and was reported as having no health endpoint.
|
|
70
|
+
*
|
|
71
|
+
* A deliberate dependency, unlike two things this file and its neighbour decline to
|
|
72
|
+
* count: Dropwizard's admin `/healthcheck`, which every Dropwizard application has
|
|
73
|
+
* for being Dropwizard, and Django's `SecurityMiddleware`, which `startproject`
|
|
74
|
+
* writes into every new project. Actuator is in no Spring application by default.
|
|
75
|
+
*/
|
|
76
|
+
const actuator = (0, detectContext_1.hasAnyGradleDep)(ctx, ['spring-boot-starter-actuator']);
|
|
77
|
+
for (const dep of actuator)
|
|
78
|
+
evidence.push({ type: 'dependency', value: dep, claim: 'health' });
|
|
79
|
+
const hasHealth = healthFiles.length > 0
|
|
80
|
+
|| actuator.length > 0
|
|
81
|
+
|| hits.some((h) => /\/(health|healthz|readyz|livez|alive)\b/i.test(h.snippet));
|
|
64
82
|
const hasReqId = hits.some((h) => /x-request-id|correlation-id/i.test(h.snippet));
|
|
65
83
|
// Structured logging without a logging library is still structured logging. What
|
|
66
84
|
// matters is that entries are machine-readable and correlated, not which package
|
|
@@ -196,7 +196,45 @@ async function detectSecurity(ctx) {
|
|
|
196
196
|
/^\s*SECURE_CROSS_ORIGIN_OPENER_POLICY\s*=/m,
|
|
197
197
|
/^\s*CSP_DEFAULT_SRC\s*=/m,
|
|
198
198
|
], 20);
|
|
199
|
-
|
|
199
|
+
/**
|
|
200
|
+
* Spring Security, which writes the headers without being asked.
|
|
201
|
+
*
|
|
202
|
+
* Adding `spring-boot-starter-security` and configuring an `HttpSecurity` chain
|
|
203
|
+
* gives every response `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`
|
|
204
|
+
* and a no-store `Cache-Control` — the framework's defaults, applied whether or not
|
|
205
|
+
* anybody writes a line about headers. shopizer does exactly that and was told it
|
|
206
|
+
* has none.
|
|
207
|
+
*
|
|
208
|
+
* Unlike Django's `SecurityMiddleware`, which is deliberately not counted a few
|
|
209
|
+
* lines down: `django-admin startproject` writes that into every new project, so it
|
|
210
|
+
* distinguishes nothing, and its HSTS and nosniff behaviour still waits on
|
|
211
|
+
* `SECURE_*` settings. This starter is not in every Spring project — petclinic has
|
|
212
|
+
* no security at all — and its defaults need no settings.
|
|
213
|
+
*
|
|
214
|
+
* The chain is what is required. A project can pull the starter for method-level
|
|
215
|
+
* authorization in something that serves no requests, and then there is no filter
|
|
216
|
+
* chain and no headers — which is what the second fixture holds.
|
|
217
|
+
*
|
|
218
|
+
* The dependency check in front of it is a pre-filter and nothing more: it keeps
|
|
219
|
+
* this search off every file of every non-Spring repository. Removing it fails no
|
|
220
|
+
* test, and the comment says so rather than implying a safety it does not provide.
|
|
221
|
+
*/
|
|
222
|
+
const springSecurity = (0, detectContext_1.hasAnyGradleDep)(ctx, ['spring-boot-starter-security', 'spring-security-config']);
|
|
223
|
+
const springFilterChain = springSecurity.length > 0
|
|
224
|
+
? await (0, textSearch_1.searchInFiles)(ctx.root, source, [/HttpSecurity\s+\w+|\bhttp\s*\n?\s*\.\s*(?:authorizeHttpRequests|authorizeRequests|securityMatcher|antMatcher)/], 2)
|
|
225
|
+
: [];
|
|
226
|
+
if (springFilterChain.length > 0) {
|
|
227
|
+
for (const dep of springSecurity)
|
|
228
|
+
evidence.push({ type: 'dependency', value: dep, claim: 'headers' });
|
|
229
|
+
evidence.push({
|
|
230
|
+
type: 'snippet',
|
|
231
|
+
value: springFilterChain[0].snippet,
|
|
232
|
+
file: springFilterChain[0].file,
|
|
233
|
+
line: springFilterChain[0].line,
|
|
234
|
+
claim: 'headers',
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
const helmet = helmetDep || headerSignals.length > 0 || springFilterChain.length > 0;
|
|
200
238
|
/**
|
|
201
239
|
* The packages the ecosystem names, as distinct from the variables authors do.
|
|
202
240
|
* Shared between the dependency check below and the binding walk further down.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@produtype/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.92.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": {
|