@produtype/core 0.43.0 → 0.45.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.
- package/dist/analyzer/detectAuth.js +1 -1
- package/dist/analyzer/detectBilling.js +5 -5
- package/dist/analyzer/detectDocker.js +2 -1
- package/dist/analyzer/detectJobs.js +2 -1
- package/dist/analyzer/detectMobile.js +3 -3
- package/dist/analyzer/detectPackaging.js +1 -1
- package/dist/cli.js +11 -4
- package/dist/mcp/server.js +1 -1
- package/dist/planner/types.d.ts +2 -1
- package/dist/report/buildReport.js +21 -7
- package/dist/report/executiveSummary.d.ts +1 -1
- package/dist/report/executiveSummary.js +6 -3
- package/dist/report/markdownReport.js +3 -3
- package/dist/report/types.d.ts +16 -2
- package/dist/rules/rules.js +22 -3
- package/package.json +1 -1
|
@@ -334,7 +334,7 @@ async function detectAuth(ctx) {
|
|
|
334
334
|
{
|
|
335
335
|
key: 'auth.2fa',
|
|
336
336
|
present: twoFaDeps.length > 0 || twoFaSignals.length > 0,
|
|
337
|
-
evidence: [...depEvidence(twoFaDeps), ...snippetEvidence(twoFaSignals)],
|
|
337
|
+
evidence: (0, absenceEvidence_1.evidenceOrSearch)([...depEvidence(twoFaDeps), ...snippetEvidence(twoFaSignals)], 'a second factor', ['otplib', 'speakeasy', 'notp', 'pyotp', 'django-otp', 'totp', 'authenticator app', 'webauthn', '@simplewebauthn']),
|
|
338
338
|
},
|
|
339
339
|
{
|
|
340
340
|
key: 'auth.apiKeys',
|
|
@@ -129,25 +129,25 @@ async function detectBilling(ctx) {
|
|
|
129
129
|
{
|
|
130
130
|
key: 'billing.webhook.route',
|
|
131
131
|
present: webhookRouteHits.length > 0 || webhookPathHits.length > 0,
|
|
132
|
-
evidence: [
|
|
132
|
+
evidence: (0, absenceEvidence_1.evidenceOrSearch)([
|
|
133
133
|
...toEvidence(webhookRouteHits),
|
|
134
134
|
...webhookPathHits.map((file) => ({ type: 'file', value: file, file })),
|
|
135
|
-
],
|
|
135
|
+
], 'a route the payment provider calls back', ['/webhook', '/webhooks/stripe', 'a file under a webhook directory']),
|
|
136
136
|
},
|
|
137
137
|
{
|
|
138
138
|
key: 'billing.webhook.rawBody',
|
|
139
139
|
present: rawBodyHits.length > 0,
|
|
140
|
-
evidence: toEvidence(rawBodyHits),
|
|
140
|
+
evidence: (0, absenceEvidence_1.evidenceOrSearch)(toEvidence(rawBodyHits), 'the unparsed body a signature is computed over', ['express.raw', 'bodyParser.raw', 'request.text()', 'rawBody', 'await req.arrayBuffer()']),
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
key: 'billing.webhook.secret',
|
|
144
144
|
present: secretHits.length > 0,
|
|
145
|
-
evidence: toEvidence(secretHits),
|
|
145
|
+
evidence: (0, absenceEvidence_1.evidenceOrSearch)(toEvidence(secretHits), 'the signing secret, read from configuration', ['STRIPE_WEBHOOK_SECRET', 'WEBHOOK_SIGNING_SECRET', 'whsec_']),
|
|
146
146
|
},
|
|
147
147
|
{
|
|
148
148
|
key: 'billing.webhook.signatureValidation',
|
|
149
149
|
present: signatureHits.length > 0,
|
|
150
|
-
evidence: toEvidence(signatureHits),
|
|
150
|
+
evidence: (0, absenceEvidence_1.evidenceOrSearch)(toEvidence(signatureHits), 'the signature being checked', ['constructEvent(', 'verifyHeader(', 'Webhook.constructEvent', 'stripe-signature']),
|
|
151
151
|
},
|
|
152
152
|
];
|
|
153
153
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.detectDocker = detectDocker;
|
|
4
4
|
const readTextFileSafe_1 = require("../utils/readTextFileSafe");
|
|
5
|
+
const absenceEvidence_1 = require("./absenceEvidence");
|
|
5
6
|
async function detectDocker(ctx) {
|
|
6
7
|
const evidence = [];
|
|
7
8
|
const dockerfile = ctx.files.all.find((f) => /(^|\/)Dockerfile$/.test(f));
|
|
@@ -37,7 +38,7 @@ async function detectDocker(ctx) {
|
|
|
37
38
|
key: 'infra.docker',
|
|
38
39
|
present: Boolean(dockerfile || composeFile),
|
|
39
40
|
complete: hasHealthcheck,
|
|
40
|
-
evidence,
|
|
41
|
+
evidence: (0, absenceEvidence_1.evidenceOrSearch)(evidence, 'a container definition', ['Dockerfile', 'Containerfile', 'docker-compose.yml', 'compose.yaml']),
|
|
41
42
|
details: { dockerfile: Boolean(dockerfile), compose: Boolean(composeFile), hasHealthcheck, hasExpose, services },
|
|
42
43
|
};
|
|
43
44
|
}
|
|
@@ -4,6 +4,7 @@ exports.detectJobs = detectJobs;
|
|
|
4
4
|
const detectContext_1 = require("./detectContext");
|
|
5
5
|
const textSearch_1 = require("../utils/textSearch");
|
|
6
6
|
const readTextFileSafe_1 = require("../utils/readTextFileSafe");
|
|
7
|
+
const absenceEvidence_1 = require("./absenceEvidence");
|
|
7
8
|
/**
|
|
8
9
|
* Work that happens outside a request.
|
|
9
10
|
*
|
|
@@ -124,7 +125,7 @@ async function detectJobs(ctx) {
|
|
|
124
125
|
// A file named `workers/` is only taken as proof alongside something declared in
|
|
125
126
|
// the code: the name is a convention, and conventions are borrowed.
|
|
126
127
|
present: declared,
|
|
127
|
-
evidence,
|
|
128
|
+
evidence: (0, absenceEvidence_1.evidenceOrSearch)(evidence, 'work that happens outside a request', ['bullmq', 'bull', 'agenda', 'bee-queue', 'celery', 'rq', 'sidekiq', 'resque', 'graphile-worker', 'a cron schedule', 'a worker entrypoint']),
|
|
128
129
|
details: {
|
|
129
130
|
nodeQueue: nodeDeps.length > 0,
|
|
130
131
|
pythonQueue: pyDeps.length > 0,
|
|
@@ -367,17 +367,17 @@ async function detectMobile(ctx) {
|
|
|
367
367
|
{
|
|
368
368
|
key: 'mobile.forcedUpdate',
|
|
369
369
|
present: forcedUpdate.length > 0,
|
|
370
|
-
evidence: forcedUpdate.map((match) => ({
|
|
370
|
+
evidence: (0, absenceEvidence_1.evidenceOrSearch)(forcedUpdate.map((match) => ({
|
|
371
371
|
type: 'snippet',
|
|
372
372
|
value: match.snippet,
|
|
373
373
|
file: match.file,
|
|
374
374
|
line: match.line,
|
|
375
|
-
})),
|
|
375
|
+
})), 'a check that the installed version is still supported', ['minimumVersion', 'minSupportedVersion', 'forceUpdate', 'upgrade_required', 'in_app_update', 'AppUpdateManager']),
|
|
376
376
|
},
|
|
377
377
|
{
|
|
378
378
|
key: 'mobile.privacyDeclaration',
|
|
379
379
|
present: privacyFiles.length > 0,
|
|
380
|
-
evidence: privacyFiles.map((file) => ({ type: 'file', value: file, file })),
|
|
380
|
+
evidence: (0, absenceEvidence_1.evidenceOrSearch)(privacyFiles.map((file) => ({ type: 'file', value: file, file })), 'the privacy declaration the stores require', ['PrivacyInfo.xcprivacy', 'a data-safety declaration', 'privacy_policy', 'PRIVACY.md']),
|
|
381
381
|
},
|
|
382
382
|
];
|
|
383
383
|
}
|
|
@@ -253,7 +253,7 @@ async function detectPackaging(ctx) {
|
|
|
253
253
|
key: 'quality.ci',
|
|
254
254
|
present: ciFiles.length > 0,
|
|
255
255
|
complete: ciFiles.length > 0 && testFiles.length > 0,
|
|
256
|
-
evidence: ciFiles.map((file) => ({ type: 'file', value: file, file })),
|
|
256
|
+
evidence: (0, absenceEvidence_1.evidenceOrSearch)(ciFiles.map((file) => ({ type: 'file', value: file, file })), 'anything that runs the tests without being asked', ['.github/workflows/', '.gitlab-ci.yml', '.circleci/config.yml', 'azure-pipelines.yml', 'Jenkinsfile', '.travis.yml']),
|
|
257
257
|
details: { files: ciFiles.length },
|
|
258
258
|
},
|
|
259
259
|
];
|
package/dist/cli.js
CHANGED
|
@@ -98,7 +98,12 @@ function parseMinMaturity(value) {
|
|
|
98
98
|
return value;
|
|
99
99
|
}
|
|
100
100
|
function enforceThresholds(report, failUnder, minMaturity) {
|
|
101
|
-
if (failUnder !== undefined && report.overallScore
|
|
101
|
+
if (failUnder !== undefined && report.overallScore === null) {
|
|
102
|
+
// A gate asked for a number and there is none. Passing would say the project
|
|
103
|
+
// cleared a bar nobody measured it against.
|
|
104
|
+
throw new Error(`Score gate failed: this report has no score. ${report.inconclusiveReasons?.[0] ?? 'The reading was inconclusive.'}`);
|
|
105
|
+
}
|
|
106
|
+
if (failUnder !== undefined && report.overallScore !== null && report.overallScore < failUnder) {
|
|
102
107
|
throw new Error(`Score gate failed: overall score ${report.overallScore} is below --fail-under ${failUnder}.`);
|
|
103
108
|
}
|
|
104
109
|
if (minMaturity !== undefined && maturityOrder.indexOf(report.maturityLevel) < maturityOrder.indexOf(minMaturity)) {
|
|
@@ -137,7 +142,7 @@ function summarize(report) {
|
|
|
137
142
|
`Package manager: ${report.detectedStack.packageManager} (${report.detectedStack.packageManagerConfidence})`,
|
|
138
143
|
report.detectedStack.warnings.length > 0 ? `Warnings: ${report.detectedStack.warnings.join('; ')}` : 'Warnings: none',
|
|
139
144
|
`Workspaces: ${workspaceSummary}`,
|
|
140
|
-
`Score: ${report.overallScore}/100`,
|
|
145
|
+
report.overallScore === null ? 'Score: not scored' : `Score: ${report.overallScore}/100`,
|
|
141
146
|
/**
|
|
142
147
|
* The reasons, not a verdict about the project.
|
|
143
148
|
*
|
|
@@ -145,8 +150,10 @@ function summarize(report) {
|
|
|
145
150
|
* "Detected frontend: flutter" — the same summary naming the stack it had just
|
|
146
151
|
* failed to recognise. The report has carried the reasons since the flag existed.
|
|
147
152
|
*/
|
|
148
|
-
`Maturity: ${report.maturityLevel}
|
|
149
|
-
...(report.inconclusive
|
|
153
|
+
`Maturity: ${report.maturityLevel}`,
|
|
154
|
+
...(report.inconclusive
|
|
155
|
+
? ['INCONCLUSIVE — this report has no score, for these reasons:', ...report.inconclusiveReasons.map((reason) => ` - ${reason}`)]
|
|
156
|
+
: []),
|
|
150
157
|
// Printed next to the score it is not allowed to change, so a reader who knows what
|
|
151
158
|
// their project is can act on it in one step.
|
|
152
159
|
...(report.productProfile?.profileSuggestion
|
package/dist/mcp/server.js
CHANGED
|
@@ -201,7 +201,7 @@ async function createProdkitMcpServer() {
|
|
|
201
201
|
requiredTotal: report.productProfile?.gap.requiredTotal ?? 0,
|
|
202
202
|
verdict: report.executiveSummary.verdict,
|
|
203
203
|
};
|
|
204
|
-
}).sort((left, right) => right.overallScore - left.overallScore);
|
|
204
|
+
}).sort((left, right) => (right.overallScore ?? -1) - (left.overallScore ?? -1));
|
|
205
205
|
return textResult({
|
|
206
206
|
observedOnlyScore: observed.observedScore,
|
|
207
207
|
detectedStack: observed.detectedStack,
|
package/dist/planner/types.d.ts
CHANGED
|
@@ -29,7 +29,8 @@ export interface RemediationPhase {
|
|
|
29
29
|
export interface RemediationPlan {
|
|
30
30
|
projectPath: string;
|
|
31
31
|
generatedAt: string;
|
|
32
|
-
|
|
32
|
+
/** `null` where the report could not form a reading. */
|
|
33
|
+
score: number | null;
|
|
33
34
|
maturityLevel: string;
|
|
34
35
|
summary: string;
|
|
35
36
|
phases: RemediationPhase[];
|
|
@@ -246,9 +246,19 @@ function buildReport(analysis, options) {
|
|
|
246
246
|
if (tooLittleAssessed) {
|
|
247
247
|
inconclusiveReasons.push(`Only ${assessedChecks} check${assessedChecks === 1 ? '' : 's'} reached a verdict, which is too few to characterise this project.`);
|
|
248
248
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
249
|
+
/**
|
|
250
|
+
* An inconclusive reading has no score.
|
|
251
|
+
*
|
|
252
|
+
* This was a cap at 39, so that an unrecognised project could not be rewarded for
|
|
253
|
+
* the absence of findings. The intent was right and the output was not: eleven
|
|
254
|
+
* repositories in the corpus came out at exactly 39, and one of them — a Python
|
|
255
|
+
* transcription script — had no findings at all. A reader saw "39/100, prototype"
|
|
256
|
+
* about a repository whose own report said it could not be read.
|
|
257
|
+
*
|
|
258
|
+
* `null` is the rule this product applies everywhere else. The reasons already
|
|
259
|
+
* collected above are the answer in its place.
|
|
260
|
+
*/
|
|
261
|
+
const scoreAfterInconclusive = combinedScore;
|
|
252
262
|
/**
|
|
253
263
|
* The number obeys the same rule as the label.
|
|
254
264
|
*
|
|
@@ -269,10 +279,14 @@ function buildReport(analysis, options) {
|
|
|
269
279
|
* band can be refused and both must reach the number, not only the label.
|
|
270
280
|
*/
|
|
271
281
|
const openCriticals = findings.filter((f) => f.severity === 'critical' && f.status !== 'passed' && f.status !== 'unknown').length;
|
|
272
|
-
const overallScore =
|
|
273
|
-
?
|
|
274
|
-
:
|
|
275
|
-
|
|
282
|
+
const overallScore = inconclusive
|
|
283
|
+
? null
|
|
284
|
+
: (0, score_1.mayClaimTopBand)(coverage, openCriticals)
|
|
285
|
+
? scoreAfterInconclusive
|
|
286
|
+
: Math.min(scoreAfterInconclusive, score_1.TOP_BAND_CEILING);
|
|
287
|
+
const maturityLevel = overallScore === null
|
|
288
|
+
? 'inconclusive'
|
|
289
|
+
: (0, score_1.computeMaturity)(overallScore, coverage, openCriticals);
|
|
276
290
|
const findingsByCategory = Object.fromEntries(types_1.CATEGORIES.map((c) => [c, []]));
|
|
277
291
|
for (const f of findings)
|
|
278
292
|
findingsByCategory[f.category].push(f);
|
|
@@ -21,7 +21,7 @@ export declare function buildExecutiveSummary(args: {
|
|
|
21
21
|
profile: ProductExpectationResult | undefined;
|
|
22
22
|
maturity: MaturityLevel;
|
|
23
23
|
observedScore: number;
|
|
24
|
-
overallScore: number;
|
|
24
|
+
overallScore: number | null;
|
|
25
25
|
inconclusive: boolean;
|
|
26
26
|
/** Why, when the report could not form a reading. Named in the verdict. */
|
|
27
27
|
inconclusiveReasons?: string[];
|
|
@@ -30,6 +30,7 @@ const CATEGORY_LABEL = {
|
|
|
30
30
|
docs: 'saying what this is',
|
|
31
31
|
};
|
|
32
32
|
const MATURITY_LABEL = {
|
|
33
|
+
inconclusive: 'not characterised, because too little of it could be read',
|
|
33
34
|
prototype: 'a prototype',
|
|
34
35
|
early: 'an early build',
|
|
35
36
|
partial: 'partly ready',
|
|
@@ -192,9 +193,11 @@ function buildExecutiveSummary(args) {
|
|
|
192
193
|
}
|
|
193
194
|
return `${label}: ${entry.findingCount} ${entry.findingCount === 1 ? 'issue' : 'issues'} to address`;
|
|
194
195
|
});
|
|
195
|
-
const scoreExplanation = args.
|
|
196
|
-
?
|
|
197
|
-
:
|
|
196
|
+
const scoreExplanation = args.overallScore === null
|
|
197
|
+
? `Not scored. ${args.inconclusiveReasons?.[0] ?? 'Too little of this repository could be read to characterise it.'}`
|
|
198
|
+
: args.profile
|
|
199
|
+
? `${args.observedScore} of 100 on what the code does today, ${args.overallScore} of 100 once measured against what ${args.profile.profileTitle} normally requires.`
|
|
200
|
+
: `${args.observedScore} of 100 on what the code does today, with no product expectations applied.`;
|
|
198
201
|
return {
|
|
199
202
|
verdict,
|
|
200
203
|
launchReady,
|
|
@@ -42,7 +42,7 @@ function profileSummary(report) {
|
|
|
42
42
|
`- Description: ${profile.profileDescription}`,
|
|
43
43
|
`- Observed score: ${report.observedScore}/100`,
|
|
44
44
|
`- Expected capability score: ${report.expectedCapabilityScore}/100`,
|
|
45
|
-
`- Final score: ${report.overallScore}/100`,
|
|
45
|
+
report.overallScore === null ? '- Final score: not scored' : `- Final score: ${report.overallScore}/100`,
|
|
46
46
|
'- Capability summary:',
|
|
47
47
|
`- Required: ${requiredPresent} present / ${requiredMissing} missing / ${requiredPartial} partial`,
|
|
48
48
|
`- Recommended: ${recommendedPresent} present / ${recommendedMissing} missing / ${recommendedPartial} partial`,
|
|
@@ -221,7 +221,7 @@ function renderMarkdown(report) {
|
|
|
221
221
|
'',
|
|
222
222
|
'## Score',
|
|
223
223
|
'',
|
|
224
|
-
`- Overall score: ${report.overallScore}/100`,
|
|
224
|
+
report.overallScore === null ? '- Overall score: not scored' : `- Overall score: ${report.overallScore}/100`,
|
|
225
225
|
`- Observed score: ${report.observedScore}/100`,
|
|
226
226
|
...(report.expectedCapabilityScore !== undefined ? [`- Expected capability score: ${report.expectedCapabilityScore}/100`] : []),
|
|
227
227
|
`- Maturity level: ${report.maturityLevel}${report.inconclusive ? ' (inconclusive)' : ''}`,
|
|
@@ -233,7 +233,7 @@ function renderMarkdown(report) {
|
|
|
233
233
|
* one of them rested on two verified checks while the other rested on twelve.
|
|
234
234
|
*/
|
|
235
235
|
`- Verified: ${report.diagnostics.verifiedChecks} of ${report.diagnostics.assessedChecks} checks that reached a verdict`,
|
|
236
|
-
...(report.overallScore > 84 && report.maturityLevel !== 'production_ready'
|
|
236
|
+
...(report.overallScore !== null && report.overallScore > 84 && report.maturityLevel !== 'production_ready'
|
|
237
237
|
? ['- Note: the score is high because little was found, not because much was verified. Too few checks apply to this repository to call it production ready.']
|
|
238
238
|
: []),
|
|
239
239
|
...(report.inconclusive
|
package/dist/report/types.d.ts
CHANGED
|
@@ -45,7 +45,16 @@ export interface Finding {
|
|
|
45
45
|
*/
|
|
46
46
|
businessImpact?: string;
|
|
47
47
|
}
|
|
48
|
-
|
|
48
|
+
/**
|
|
49
|
+
* `inconclusive` is a band of its own, not the bottom of the scale.
|
|
50
|
+
*
|
|
51
|
+
* A repository this analyzer could not read is not a prototype. It used to be called
|
|
52
|
+
* one, with a score of 39 beside it — the cap applied so that absence of findings
|
|
53
|
+
* could not be rewarded — and eleven repositories in the corpus came out at exactly
|
|
54
|
+
* 39: a wake-word engine, a database manager, an Advent of Code repository. One of
|
|
55
|
+
* them had no findings at all. The number was not measuring them; it was the cap.
|
|
56
|
+
*/
|
|
57
|
+
export type MaturityLevel = 'inconclusive' | 'prototype' | 'early' | 'partial' | 'production_ready';
|
|
49
58
|
export type ExpectationMode = 'observed-only' | 'explicit-profile' | 'auto-applied' | 'auto-inconclusive';
|
|
50
59
|
import type { LanguageReading } from '../analyzer/readingDepth';
|
|
51
60
|
export interface ReportDiagnostics {
|
|
@@ -81,7 +90,12 @@ export interface ProductionReadinessReport {
|
|
|
81
90
|
generatedAt: string;
|
|
82
91
|
observedScore: number;
|
|
83
92
|
expectedCapabilityScore?: number;
|
|
84
|
-
|
|
93
|
+
/**
|
|
94
|
+
* `null` where the reading is inconclusive, which is the same rule this product
|
|
95
|
+
* applies everywhere else: a number that was not measured is not reported. The
|
|
96
|
+
* reasons in `inconclusiveReasons` are the answer in its place.
|
|
97
|
+
*/
|
|
98
|
+
overallScore: number | null;
|
|
85
99
|
maturityLevel: MaturityLevel;
|
|
86
100
|
inconclusive: boolean;
|
|
87
101
|
inconclusiveReasons: string[];
|
package/dist/rules/rules.js
CHANGED
|
@@ -263,7 +263,18 @@ exports.rules = [
|
|
|
263
263
|
? `Hardcoded fallback secrets detected (${weakTypes || 'unknown'} key context).`
|
|
264
264
|
: 'No weak fallback secret patterns detected.',
|
|
265
265
|
recommendation: 'Require strong secrets through environment variables with strict startup validation.',
|
|
266
|
-
|
|
266
|
+
/**
|
|
267
|
+
* A check that passes because it found nothing has to say what it looked for.
|
|
268
|
+
*
|
|
269
|
+
* This was the most repeated line in the product: 175 reports said "no weak
|
|
270
|
+
* fallback secret patterns detected" with nothing underneath, which tells the
|
|
271
|
+
* reader neither how hard it looked nor whether their own placeholder would
|
|
272
|
+
* have been caught. A negative result is only worth something when the search
|
|
273
|
+
* behind it is visible.
|
|
274
|
+
*/
|
|
275
|
+
evidence: weakEvidence.length > 0
|
|
276
|
+
? weakEvidence
|
|
277
|
+
: (0, absenceEvidence_1.searchedFor)('hardcoded fallback secrets', ['changeme', 'your-secret', 'dev-secret', 'local-secret', 'change-in-production', 'test123', 'a secret assigned a literal beside jwt_secret, secret_key or session_secret']),
|
|
267
278
|
});
|
|
268
279
|
},
|
|
269
280
|
},
|
|
@@ -438,7 +449,11 @@ exports.rules = [
|
|
|
438
449
|
? 'Django settings contain DEBUG=True.'
|
|
439
450
|
: 'No DEBUG=True signal found.',
|
|
440
451
|
recommendation: 'Set DEBUG=False for non-local environments and enforce via environment variables.',
|
|
441
|
-
evidence: !isDjango
|
|
452
|
+
evidence: !isDjango
|
|
453
|
+
? [{ type: 'note', value: 'Django stack not detected' }]
|
|
454
|
+
: evidenceForClaim(sec?.evidence, 'django-debug').length > 0
|
|
455
|
+
? evidenceForClaim(sec?.evidence, 'django-debug')
|
|
456
|
+
: (0, absenceEvidence_1.searchedFor)('DEBUG left on', ['a line reading DEBUG = True in the settings Django actually loads, outside any if settings.DEBUG block']),
|
|
442
457
|
});
|
|
443
458
|
},
|
|
444
459
|
},
|
|
@@ -464,7 +479,11 @@ exports.rules = [
|
|
|
464
479
|
? 'Secure cookie settings appear configured.'
|
|
465
480
|
: 'SESSION/CSRF secure cookie flags are weak.',
|
|
466
481
|
recommendation: 'Enable SESSION_COOKIE_SECURE and CSRF_COOKIE_SECURE in production.',
|
|
467
|
-
evidence: !isDjango
|
|
482
|
+
evidence: !isDjango
|
|
483
|
+
? [{ type: 'note', value: 'Django stack not detected' }]
|
|
484
|
+
: evidenceForClaim(sec?.evidence, 'django-cookies').length > 0
|
|
485
|
+
? evidenceForClaim(sec?.evidence, 'django-cookies')
|
|
486
|
+
: (0, absenceEvidence_1.searchedFor)('cookies left insecure', ['SESSION_COOKIE_SECURE = False', 'CSRF_COOKIE_SECURE = False']),
|
|
468
487
|
});
|
|
469
488
|
},
|
|
470
489
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@produtype/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.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": {
|