@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
- met: !actionable,
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
- existing.met = false;
118
- }
124
+ if (finding.status === 'passed')
125
+ existing.verified = true;
119
126
  }
120
127
  }
121
- return [...obligations.values()].sort((left, right) => left.framework.localeCompare(right.framework) || left.reference.localeCompare(right.reference));
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
- const unmet = report.compliance.filter((obligation) => !obligation.met);
147
- if (unmet.length === 0)
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
- '| Framework | Reference | Obligation | Findings |',
155
- '| --- | --- | --- | --- |',
156
- ...unmet.map((obligation) => `| ${obligation.framework} | ${obligation.reference} | ${obligation.title} | ${obligation.findingIds.length} |`),
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.46.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": {