@produtype/core 0.46.0 → 0.47.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.
|
@@ -9,8 +9,28 @@ export interface ComplianceReference {
|
|
|
9
9
|
export interface ComplianceObligation extends ComplianceReference {
|
|
10
10
|
/** Findings that leave this obligation unmet. */
|
|
11
11
|
findingIds: string[];
|
|
12
|
+
/**
|
|
13
|
+
* `unknown` where every check behind this obligation came back `unknown`.
|
|
14
|
+
*
|
|
15
|
+
* This was a boolean, and `met` was the default: an obligation whose only supporting
|
|
16
|
+
* check could not reach a verdict was reported as satisfied. Measured across
|
|
17
|
+
* seventy-eight repositories, 84 of 163 obligations marked met rested on not one
|
|
18
|
+
* passing check — most of them OWASP A01, broken access control, declared met
|
|
19
|
+
* because a single check said it did not know.
|
|
20
|
+
*
|
|
21
|
+
* The comment on the builder already stated the principle and the code applied it
|
|
22
|
+
* only halfway: it omitted obligations that nothing mapped to, and marked met the
|
|
23
|
+
* ones that mapped to silence.
|
|
24
|
+
*/
|
|
25
|
+
status: 'met' | 'unmet' | 'unknown';
|
|
26
|
+
/** @deprecated Read `status`. True only where `status` is `met`. */
|
|
12
27
|
met: boolean;
|
|
13
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Exported so a test can ask the same question the builder asks, rather than keeping
|
|
31
|
+
* a second copy of the prefix table that would drift from this one.
|
|
32
|
+
*/
|
|
33
|
+
export declare function referencesFor(finding: Finding): ComplianceReference[];
|
|
14
34
|
/**
|
|
15
35
|
* Returns every obligation touched by the analysis, marking as unmet those with at
|
|
16
36
|
* least one actionable finding attached. An obligation nothing mapped to is omitted
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.referencesFor = referencesFor;
|
|
3
4
|
exports.buildComplianceMapping = buildComplianceMapping;
|
|
4
5
|
/**
|
|
5
6
|
* Maps findings onto the obligations a reader has to answer to.
|
|
@@ -82,6 +83,10 @@ const RULES = [
|
|
|
82
83
|
references: [{ framework: 'owasp-top-10', reference: 'A08', title: 'Software and data integrity failures' }],
|
|
83
84
|
},
|
|
84
85
|
];
|
|
86
|
+
/**
|
|
87
|
+
* Exported so a test can ask the same question the builder asks, rather than keeping
|
|
88
|
+
* a second copy of the prefix table that would drift from this one.
|
|
89
|
+
*/
|
|
85
90
|
function referencesFor(finding) {
|
|
86
91
|
for (const rule of RULES) {
|
|
87
92
|
if (finding.id.startsWith(rule.prefix))
|
|
@@ -108,15 +113,39 @@ function buildComplianceMapping(findings) {
|
|
|
108
113
|
obligations.set(key, {
|
|
109
114
|
...reference,
|
|
110
115
|
findingIds: actionable ? [finding.id] : [],
|
|
111
|
-
|
|
116
|
+
status: 'unknown',
|
|
117
|
+
met: false,
|
|
118
|
+
verified: finding.status === 'passed',
|
|
112
119
|
});
|
|
113
120
|
continue;
|
|
114
121
|
}
|
|
115
|
-
if (actionable)
|
|
122
|
+
if (actionable)
|
|
116
123
|
existing.findingIds.push(finding.id);
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
if (finding.status === 'passed')
|
|
125
|
+
existing.verified = true;
|
|
119
126
|
}
|
|
120
127
|
}
|
|
121
|
-
|
|
128
|
+
/**
|
|
129
|
+
* An obligation is met when something was checked and found right.
|
|
130
|
+
*
|
|
131
|
+
* Three outcomes, because the reader needs to tell them apart before quoting any of
|
|
132
|
+
* this to an auditor: something is wrong here, something was verified here, and
|
|
133
|
+
* nothing here reached a verdict.
|
|
134
|
+
*/
|
|
135
|
+
for (const obligation of obligations.values()) {
|
|
136
|
+
obligation.status = obligation.findingIds.length > 0
|
|
137
|
+
? 'unmet'
|
|
138
|
+
: obligation.verified ? 'met' : 'unknown';
|
|
139
|
+
obligation.met = obligation.status === 'met';
|
|
140
|
+
}
|
|
141
|
+
return [...obligations.values()]
|
|
142
|
+
.map((obligation) => ({
|
|
143
|
+
framework: obligation.framework,
|
|
144
|
+
reference: obligation.reference,
|
|
145
|
+
title: obligation.title,
|
|
146
|
+
findingIds: obligation.findingIds,
|
|
147
|
+
status: obligation.status,
|
|
148
|
+
met: obligation.met,
|
|
149
|
+
}))
|
|
150
|
+
.sort((left, right) => left.framework.localeCompare(right.framework) || left.reference.localeCompare(right.reference));
|
|
122
151
|
}
|
|
@@ -143,18 +143,40 @@ function categoryScoreSection(report) {
|
|
|
143
143
|
function complianceSection(report) {
|
|
144
144
|
if (report.compliance.length === 0)
|
|
145
145
|
return [];
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
/**
|
|
147
|
+
* Exposure and silence, listed apart.
|
|
148
|
+
*
|
|
149
|
+
* This filtered on `!met`, which was two different things wearing one label: an
|
|
150
|
+
* obligation with findings against it, and an obligation whose every check came
|
|
151
|
+
* back unknown. The second was printed under "Compliance Exposure" with a findings
|
|
152
|
+
* count of zero — a reader would quote that to an auditor as a gap when it is the
|
|
153
|
+
* absence of an answer.
|
|
154
|
+
*/
|
|
155
|
+
const unmet = report.compliance.filter((obligation) => obligation.status === 'unmet');
|
|
156
|
+
const unassessed = report.compliance.filter((obligation) => obligation.status === 'unknown');
|
|
157
|
+
if (unmet.length === 0 && unassessed.length === 0)
|
|
148
158
|
return [];
|
|
149
159
|
return [
|
|
150
160
|
'## Compliance Exposure',
|
|
151
161
|
'',
|
|
152
162
|
'_Advisory mapping, not a compliance certification. Obligations nothing mapped to are omitted rather than reported as met._',
|
|
153
163
|
'',
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
164
|
+
...(unmet.length > 0
|
|
165
|
+
? [
|
|
166
|
+
'| Framework | Reference | Obligation | Findings |',
|
|
167
|
+
'| --- | --- | --- | --- |',
|
|
168
|
+
...unmet.map((obligation) => `| ${obligation.framework} | ${obligation.reference} | ${obligation.title} | ${obligation.findingIds.length} |`),
|
|
169
|
+
'',
|
|
170
|
+
]
|
|
171
|
+
: ['No obligation in this mapping has a finding against it.', '']),
|
|
172
|
+
...(unassessed.length > 0
|
|
173
|
+
? [
|
|
174
|
+
`Not assessed — every check behind these came back unknown, so this report says nothing either way about ${unassessed.length === 1 ? 'it' : 'them'}:`,
|
|
175
|
+
'',
|
|
176
|
+
...unassessed.map((obligation) => `- ${obligation.framework} ${obligation.reference}: ${obligation.title}`),
|
|
177
|
+
'',
|
|
178
|
+
]
|
|
179
|
+
: []),
|
|
158
180
|
];
|
|
159
181
|
}
|
|
160
182
|
function renderMarkdown(report) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@produtype/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.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": {
|