@sikkuumiii/defuse 0.0.0-stage → 0.5.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/LICENSE +235 -0
- package/README.md +561 -2
- package/dist/src/cli.d.ts +20 -0
- package/dist/src/cli.js +619 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/core/analyze.d.ts +178 -0
- package/dist/src/core/analyze.js +834 -0
- package/dist/src/core/analyze.js.map +1 -0
- package/dist/src/core/coverage.d.ts +87 -0
- package/dist/src/core/coverage.js +181 -0
- package/dist/src/core/coverage.js.map +1 -0
- package/dist/src/core/finding.d.ts +196 -0
- package/dist/src/core/finding.js +178 -0
- package/dist/src/core/finding.js.map +1 -0
- package/dist/src/core/licence.d.ts +50 -0
- package/dist/src/core/licence.js +56 -0
- package/dist/src/core/licence.js.map +1 -0
- package/dist/src/core/paths.d.ts +21 -0
- package/dist/src/core/paths.js +58 -0
- package/dist/src/core/paths.js.map +1 -0
- package/dist/src/core/score.d.ts +137 -0
- package/dist/src/core/score.js +232 -0
- package/dist/src/core/score.js.map +1 -0
- package/dist/src/dast/targets.d.ts +30 -0
- package/dist/src/dast/targets.js +79 -0
- package/dist/src/dast/targets.js.map +1 -0
- package/dist/src/dast/zap.d.ts +42 -0
- package/dist/src/dast/zap.js +184 -0
- package/dist/src/dast/zap.js.map +1 -0
- package/dist/src/engine/scan.d.ts +80 -0
- package/dist/src/engine/scan.js +129 -0
- package/dist/src/engine/scan.js.map +1 -0
- package/dist/src/engine/shapes.d.ts +47 -0
- package/dist/src/engine/shapes.js +328 -0
- package/dist/src/engine/shapes.js.map +1 -0
- package/dist/src/engine/walk.d.ts +60 -0
- package/dist/src/engine/walk.js +156 -0
- package/dist/src/engine/walk.js.map +1 -0
- package/dist/src/parse/astDump.d.ts +31 -0
- package/dist/src/parse/astDump.js +66 -0
- package/dist/src/parse/astDump.js.map +1 -0
- package/dist/src/parse/languages.d.ts +70 -0
- package/dist/src/parse/languages.js +174 -0
- package/dist/src/parse/languages.js.map +1 -0
- package/dist/src/parse/location.d.ts +17 -0
- package/dist/src/parse/location.js +38 -0
- package/dist/src/parse/location.js.map +1 -0
- package/dist/src/parse/node-grammars.d.ts +12 -0
- package/dist/src/parse/node-grammars.js +35 -0
- package/dist/src/parse/node-grammars.js.map +1 -0
- package/dist/src/parse/parser.d.ts +126 -0
- package/dist/src/parse/parser.js +202 -0
- package/dist/src/parse/parser.js.map +1 -0
- package/dist/src/parse/tree-store.d.ts +139 -0
- package/dist/src/parse/tree-store.js +236 -0
- package/dist/src/parse/tree-store.js.map +1 -0
- package/dist/src/platform/dependency-rule.d.ts +27 -0
- package/dist/src/platform/dependency-rule.js +21 -0
- package/dist/src/platform/dependency-rule.js.map +1 -0
- package/dist/src/platform/deps.d.ts +86 -0
- package/dist/src/platform/deps.js +304 -0
- package/dist/src/platform/deps.js.map +1 -0
- package/dist/src/platform/php-facts.d.ts +49 -0
- package/dist/src/platform/php-facts.js +67 -0
- package/dist/src/platform/php-facts.js.map +1 -0
- package/dist/src/platform/runtime-version.d.ts +78 -0
- package/dist/src/platform/runtime-version.js +573 -0
- package/dist/src/platform/runtime-version.js.map +1 -0
- package/dist/src/report/colors.d.ts +50 -0
- package/dist/src/report/colors.js +97 -0
- package/dist/src/report/colors.js.map +1 -0
- package/dist/src/report/coverage-table.d.ts +90 -0
- package/dist/src/report/coverage-table.js +291 -0
- package/dist/src/report/coverage-table.js.map +1 -0
- package/dist/src/report/html-report.d.ts +66 -0
- package/dist/src/report/html-report.js +489 -0
- package/dist/src/report/html-report.js.map +1 -0
- package/dist/src/report/human.d.ts +44 -0
- package/dist/src/report/human.js +575 -0
- package/dist/src/report/human.js.map +1 -0
- package/dist/src/report/json.d.ts +24 -0
- package/dist/src/report/json.js +178 -0
- package/dist/src/report/json.js.map +1 -0
- package/dist/src/report/mark.d.ts +17 -0
- package/dist/src/report/mark.js +39 -0
- package/dist/src/report/mark.js.map +1 -0
- package/dist/src/report/pricing.d.ts +33 -0
- package/dist/src/report/pricing.js +343 -0
- package/dist/src/report/pricing.js.map +1 -0
- package/dist/src/report/sarif.d.ts +39 -0
- package/dist/src/report/sarif.js +219 -0
- package/dist/src/report/sarif.js.map +1 -0
- package/dist/src/report/site.d.ts +87 -0
- package/dist/src/report/site.js +742 -0
- package/dist/src/report/site.js.map +1 -0
- package/dist/src/rules/code-injection.d.ts +47 -0
- package/dist/src/rules/code-injection.js +326 -0
- package/dist/src/rules/code-injection.js.map +1 -0
- package/dist/src/rules/command-injection.d.ts +30 -0
- package/dist/src/rules/command-injection.js +286 -0
- package/dist/src/rules/command-injection.js.map +1 -0
- package/dist/src/rules/contract.d.ts +198 -0
- package/dist/src/rules/contract.js +39 -0
- package/dist/src/rules/contract.js.map +1 -0
- package/dist/src/rules/format-string.d.ts +46 -0
- package/dist/src/rules/format-string.js +146 -0
- package/dist/src/rules/format-string.js.map +1 -0
- package/dist/src/rules/hardcoded-secret.d.ts +38 -0
- package/dist/src/rules/hardcoded-secret.js +440 -0
- package/dist/src/rules/hardcoded-secret.js.map +1 -0
- package/dist/src/rules/lib/guards.d.ts +168 -0
- package/dist/src/rules/lib/guards.js +1179 -0
- package/dist/src/rules/lib/guards.js.map +1 -0
- package/dist/src/rules/lib/html-safety.d.ts +75 -0
- package/dist/src/rules/lib/html-safety.js +755 -0
- package/dist/src/rules/lib/html-safety.js.map +1 -0
- package/dist/src/rules/lib/local-build.d.ts +59 -0
- package/dist/src/rules/lib/local-build.js +320 -0
- package/dist/src/rules/lib/local-build.js.map +1 -0
- package/dist/src/rules/lib/strings.d.ts +208 -0
- package/dist/src/rules/lib/strings.js +1970 -0
- package/dist/src/rules/lib/strings.js.map +1 -0
- package/dist/src/rules/path-traversal.d.ts +33 -0
- package/dist/src/rules/path-traversal.js +96 -0
- package/dist/src/rules/path-traversal.js.map +1 -0
- package/dist/src/rules/registry.d.ts +19 -0
- package/dist/src/rules/registry.js +77 -0
- package/dist/src/rules/registry.js.map +1 -0
- package/dist/src/rules/sql-injection.d.ts +36 -0
- package/dist/src/rules/sql-injection.js +299 -0
- package/dist/src/rules/sql-injection.js.map +1 -0
- package/dist/src/rules/ssrf.d.ts +39 -0
- package/dist/src/rules/ssrf.js +111 -0
- package/dist/src/rules/ssrf.js.map +1 -0
- package/dist/src/rules/unbounded-copy.d.ts +37 -0
- package/dist/src/rules/unbounded-copy.js +147 -0
- package/dist/src/rules/unbounded-copy.js.map +1 -0
- package/dist/src/rules/unsafe-deserialization.d.ts +43 -0
- package/dist/src/rules/unsafe-deserialization.js +276 -0
- package/dist/src/rules/unsafe-deserialization.js.map +1 -0
- package/dist/src/rules/weak-hash.d.ts +37 -0
- package/dist/src/rules/weak-hash.js +197 -0
- package/dist/src/rules/weak-hash.js.map +1 -0
- package/dist/src/rules/xss.d.ts +38 -0
- package/dist/src/rules/xss.js +527 -0
- package/dist/src/rules/xss.js.map +1 -0
- package/dist/src/taint/content-type.d.ts +19 -0
- package/dist/src/taint/content-type.js +20 -0
- package/dist/src/taint/content-type.js.map +1 -0
- package/dist/src/taint/dictionaries.d.ts +23 -0
- package/dist/src/taint/dictionaries.js +1778 -0
- package/dist/src/taint/dictionaries.js.map +1 -0
- package/dist/src/taint/fold.d.ts +59 -0
- package/dist/src/taint/fold.js +1177 -0
- package/dist/src/taint/fold.js.map +1 -0
- package/dist/src/taint/java-scope.d.ts +61 -0
- package/dist/src/taint/java-scope.js +212 -0
- package/dist/src/taint/java-scope.js.map +1 -0
- package/dist/src/taint/local-map.d.ts +79 -0
- package/dist/src/taint/local-map.js +603 -0
- package/dist/src/taint/local-map.js.map +1 -0
- package/dist/src/taint/project.d.ts +172 -0
- package/dist/src/taint/project.js +617 -0
- package/dist/src/taint/project.js.map +1 -0
- package/dist/src/taint/tracer.d.ts +265 -0
- package/dist/src/taint/tracer.js +3817 -0
- package/dist/src/taint/tracer.js.map +1 -0
- package/dist/src/taint/types.d.ts +484 -0
- package/dist/src/taint/types.js +47 -0
- package/dist/src/taint/types.js.map +1 -0
- package/dist/src/taint/visibility.d.ts +25 -0
- package/dist/src/taint/visibility.js +96 -0
- package/dist/src/taint/visibility.js.map +1 -0
- package/docs/examples/defuse.json +20 -0
- package/docs/examples/dvwa-urls.json +14 -0
- package/docs/examples/github-code-scanning.yml +54 -0
- package/docs/examples/tiny.js +4 -0
- package/package.json +73 -4
- package/src/cli.ts +747 -0
- package/src/core/analyze.ts +1072 -0
- package/src/core/coverage.ts +269 -0
- package/src/core/finding.ts +311 -0
- package/src/core/licence.ts +61 -0
- package/src/core/paths.ts +59 -0
- package/src/core/score.ts +353 -0
- package/src/dast/targets.ts +121 -0
- package/src/dast/zap.ts +246 -0
- package/src/engine/scan.ts +200 -0
- package/src/engine/shapes.ts +348 -0
- package/src/engine/walk.ts +196 -0
- package/src/parse/astDump.ts +85 -0
- package/src/parse/languages.ts +232 -0
- package/src/parse/location.ts +44 -0
- package/src/parse/node-grammars.ts +41 -0
- package/src/parse/parser.ts +262 -0
- package/src/parse/tree-store.ts +301 -0
- package/src/platform/dependency-rule.ts +33 -0
- package/src/platform/deps.ts +374 -0
- package/src/platform/php-facts.ts +85 -0
- package/src/platform/runtime-version.ts +710 -0
- package/src/report/colors.ts +107 -0
- package/src/report/coverage-table.ts +359 -0
- package/src/report/html-report.ts +601 -0
- package/src/report/human.ts +811 -0
- package/src/report/json.ts +189 -0
- package/src/report/mark.ts +47 -0
- package/src/report/pricing.ts +374 -0
- package/src/report/sarif.ts +235 -0
- package/src/report/site.ts +760 -0
- package/src/rules/code-injection.ts +346 -0
- package/src/rules/command-injection.ts +296 -0
- package/src/rules/contract.ts +220 -0
- package/src/rules/format-string.ts +158 -0
- package/src/rules/hardcoded-secret.ts +485 -0
- package/src/rules/lib/guards.ts +1235 -0
- package/src/rules/lib/html-safety.ts +793 -0
- package/src/rules/lib/local-build.ts +329 -0
- package/src/rules/lib/strings.ts +2034 -0
- package/src/rules/path-traversal.ts +101 -0
- package/src/rules/registry.ts +90 -0
- package/src/rules/sql-injection.ts +311 -0
- package/src/rules/ssrf.ts +118 -0
- package/src/rules/unbounded-copy.ts +162 -0
- package/src/rules/unsafe-deserialization.ts +291 -0
- package/src/rules/weak-hash.ts +226 -0
- package/src/rules/xss.ts +572 -0
- package/src/taint/content-type.ts +20 -0
- package/src/taint/dictionaries.ts +1855 -0
- package/src/taint/fold.ts +1206 -0
- package/src/taint/java-scope.ts +200 -0
- package/src/taint/local-map.ts +594 -0
- package/src/taint/project.ts +774 -0
- package/src/taint/tracer.ts +4298 -0
- package/src/taint/types.ts +511 -0
- package/src/taint/visibility.ts +100 -0
- package/tsconfig.json +61 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { ALL_RULES } from '../rules/registry.js';
|
|
2
|
+
import { LANGUAGES } from '../parse/languages.js';
|
|
3
|
+
import { BASE_CSS, FOOTER_CSS, NAV_CSS, esc, siteFooter, siteNav } from './site.js';
|
|
4
|
+
/**
|
|
5
|
+
* THE PRICING PAGE
|
|
6
|
+
* ================
|
|
7
|
+
*
|
|
8
|
+
* Generated like the landing page, and held to the same rule: it may not say
|
|
9
|
+
* more than is true today.
|
|
10
|
+
*
|
|
11
|
+
* - Every feature listed under a plan EXISTS. Nothing planned is listed as
|
|
12
|
+
* if it were built; a plan that is not built yet is shown as "coming soon"
|
|
13
|
+
* with no price and no feature list.
|
|
14
|
+
* - Numbers come from the registry and the measurement files, not from the
|
|
15
|
+
* prose, so `npm test` fails if the page and the engine disagree.
|
|
16
|
+
* - No "most popular" badge. There is nothing to be popular among yet, and a
|
|
17
|
+
* badge that says otherwise is the kind of claim this project exists to
|
|
18
|
+
* refuse. The cards do carry a badge, because the design has a place for
|
|
19
|
+
* one - and it holds a FACT about the plan ("Open source", "5+ developers"),
|
|
20
|
+
* never a ranking.
|
|
21
|
+
* - How buying works is said plainly: there is no checkout. A plan is set up
|
|
22
|
+
* by hand after someone asks.
|
|
23
|
+
*
|
|
24
|
+
* The paid tools are Defuse Team, a separate private program that reads the
|
|
25
|
+
* scanner's JSON. They organise findings; they never re-decide one. That is
|
|
26
|
+
* stated on the page, because it is the promise a buyer most needs.
|
|
27
|
+
*/
|
|
28
|
+
/** Where a plan enquiry goes: a public GitHub issue form, .github/ISSUE_TEMPLATE/plan-enquiry.yml. */
|
|
29
|
+
const ENQUIRY_URL = 'https://github.com/Sikkuumi/Defuse/issues/new?template=plan-enquiry.yml';
|
|
30
|
+
const enquiry = (plan) => `${ENQUIRY_URL}&title=${encodeURIComponent(`Plan enquiry: ${plan}`)}`;
|
|
31
|
+
/** Team is priced per developer with a floor of this many. */
|
|
32
|
+
export const TEAM_MIN_SEATS = 5;
|
|
33
|
+
const TEAM_PRICE = 49;
|
|
34
|
+
/** Rule/language pairs the engine declares it does not implement - counted, never typed in. */
|
|
35
|
+
function countNotImplemented() {
|
|
36
|
+
let n = 0;
|
|
37
|
+
for (const rule of ALL_RULES) {
|
|
38
|
+
for (const language of LANGUAGES) {
|
|
39
|
+
if (rule.support[language.id]?.status === 'not-implemented')
|
|
40
|
+
n++;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return n;
|
|
44
|
+
}
|
|
45
|
+
export function plans() {
|
|
46
|
+
const notImplemented = countNotImplemented();
|
|
47
|
+
return [
|
|
48
|
+
{
|
|
49
|
+
name: 'Community',
|
|
50
|
+
status: 'available',
|
|
51
|
+
price: { amount: 'Free', unit: 'for everyone, for good', note: 'Open source, AGPL-3.0' },
|
|
52
|
+
forWhom: 'Anyone scanning their own code, on their own machine or in their own CI.',
|
|
53
|
+
badge: 'Open source',
|
|
54
|
+
builds: 'Included:',
|
|
55
|
+
features: [
|
|
56
|
+
{
|
|
57
|
+
name: 'The scanner',
|
|
58
|
+
detail: `${LANGUAGES.length} languages and ${ALL_RULES.length} rules. Data flow is traced in every language, into other functions, and into other files in every language but PHP.`,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'Two labels on every finding',
|
|
62
|
+
detail: 'Proven, with the path from source to sink printed; or to review, a matched pattern that was not traced. Never blended into one score.',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'What it did not check',
|
|
66
|
+
detail: `Every run lists the rule and language pairs it does not cover - ${notImplemented} today - each with its reason.`,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'Terminal, browser and CI',
|
|
70
|
+
detail: 'A terminal command, a browser page that scans on your own machine and uploads nothing, JSON output, and SARIF for GitHub code scanning.',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'Client report',
|
|
74
|
+
detail: 'One HTML file for a client or a manager, with the proven problems, what to review and what was not checked. Prints to PDF.',
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
cta: { label: 'Run it now', href: 'https://github.com/Sikkuumi/Defuse#readme' },
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'Pro',
|
|
81
|
+
status: 'available',
|
|
82
|
+
price: { amount: '$19', unit: 'per developer / month' },
|
|
83
|
+
forWhom: 'One developer working through findings over time, on their own machine.',
|
|
84
|
+
badge: 'One developer',
|
|
85
|
+
builds: 'Everything in Community, and:',
|
|
86
|
+
features: [
|
|
87
|
+
{
|
|
88
|
+
name: 'New problems only',
|
|
89
|
+
detail: 'Compare two scans and see only what a change added. A line that moved is matched, not called new; what disappeared is "no longer reported", never "fixed".',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'Trend dashboard',
|
|
93
|
+
detail: 'Proven and to-review counts across your scans, in one file that opens offline. Where the scanner itself changed, the point is marked with the reason.',
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
cta: { label: 'Ask for Pro', href: enquiry('Pro') },
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'Team',
|
|
100
|
+
status: 'available',
|
|
101
|
+
price: {
|
|
102
|
+
amount: `$${TEAM_PRICE}`,
|
|
103
|
+
unit: 'per developer / month',
|
|
104
|
+
note: `From ${TEAM_MIN_SEATS} developers ($${TEAM_PRICE * TEAM_MIN_SEATS} a month).`,
|
|
105
|
+
},
|
|
106
|
+
forWhom: 'A team that wants every pull request checked, and one shared record of what was decided.',
|
|
107
|
+
badge: `${TEAM_MIN_SEATS}+ developers`,
|
|
108
|
+
builds: 'Everything in Pro, and:',
|
|
109
|
+
features: [
|
|
110
|
+
{
|
|
111
|
+
name: 'Pull-request check',
|
|
112
|
+
detail: 'Each pull request is scanned against its base. One comment, updated on every push, lists only what it adds, and the check fails only on new problems at the level you set.',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'Shared triage',
|
|
116
|
+
detail: 'False alarms and accepted risks are marked in the repository with a reason and a name. A mark never hides a finding, ends when the code changes, and a pull request cannot approve its own.',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'Automatic history',
|
|
120
|
+
detail: 'One scan a day kept on its own branch, and the trend dashboard rebuilt on every push to main. On private repositories, unless you choose otherwise.',
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
cta: { label: 'Ask for Team', href: enquiry('Team'), primary: true },
|
|
124
|
+
},
|
|
125
|
+
/*
|
|
126
|
+
* Business and Enterprise were listed here as "coming soon", each with a
|
|
127
|
+
* "Tell us you need it" button. Removed: a button for a plan that does not
|
|
128
|
+
* exist only brings requests nobody can fill. A plan is added back when it
|
|
129
|
+
* is built - and if one is listed before then, it is drawn as "coming
|
|
130
|
+
* soon", with no price and no features (planCard).
|
|
131
|
+
*/
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
/** Lucide's "check" icon (ISC licence), inline so the page still calls nobody. */
|
|
135
|
+
const CHECK_ICON = '<svg class="tick" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.25" ' +
|
|
136
|
+
'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>';
|
|
137
|
+
function planCard(p) {
|
|
138
|
+
if (p.status === 'coming-soon') {
|
|
139
|
+
return ` <div class="plan soon">
|
|
140
|
+
<h3 class="pname">${esc(p.name)}</h3>
|
|
141
|
+
<div class="tag">Coming soon</div>
|
|
142
|
+
<p class="who">${esc(p.forWhom)}</p>
|
|
143
|
+
<p class="who">Not available yet. If you need it, say so - it will be shaped around real requests rather than guessed at.</p>
|
|
144
|
+
<a class="pbtn" href="${esc(p.cta.href)}">${esc(p.cta.label)}</a>
|
|
145
|
+
</div>`;
|
|
146
|
+
}
|
|
147
|
+
const price = p.price;
|
|
148
|
+
// One card: a header (name, who it is for, a fact in the corner), the price
|
|
149
|
+
// on one line, the features as a checked list, and one full-width button.
|
|
150
|
+
// The plan whose button is the primary one gets a brand-coloured ring.
|
|
151
|
+
return ` <div class="plan glass${p.cta.primary ? ' featured' : ''}">
|
|
152
|
+
<div class="phead">
|
|
153
|
+
<h3 class="pname">${esc(p.name)}</h3>
|
|
154
|
+
${p.badge ? `<span class="badge">${esc(p.badge)}</span>` : ''}
|
|
155
|
+
<p class="who">${esc(p.forWhom)}</p>
|
|
156
|
+
</div>
|
|
157
|
+
<div class="pricebox">
|
|
158
|
+
<div class="priceline"><span class="amount">${esc(price.amount)}</span><span class="unit">${esc(price.unit)}</span></div>
|
|
159
|
+
${price.note ? `<div class="pnote">${esc(price.note)}</div>` : ''}
|
|
160
|
+
</div>
|
|
161
|
+
${p.builds ? `<div class="builds">${esc(p.builds)}</div>` : ''}
|
|
162
|
+
<ul class="feats">
|
|
163
|
+
${p.features.map((f) => ` <li>${CHECK_ICON}<div><b>${esc(f.name)}</b><span>${esc(f.detail)}</span></div></li>`).join('\n')}
|
|
164
|
+
</ul>
|
|
165
|
+
<a class="pbtn${p.cta.primary ? ' primary' : ''}" href="${esc(p.cta.href)}">${esc(p.cta.label)}</a>
|
|
166
|
+
</div>`;
|
|
167
|
+
}
|
|
168
|
+
export function renderPricing(benchmark) {
|
|
169
|
+
// The precision figures stay on the main page, beside the footnote that says what they do and do not
|
|
170
|
+
// promise. Quoted alone on a page that sells, a 100% would read as a promise about your code.
|
|
171
|
+
const all = plans();
|
|
172
|
+
const available = all.filter((p) => p.status === 'available');
|
|
173
|
+
const soon = all.filter((p) => p.status === 'coming-soon');
|
|
174
|
+
const notImplemented = countNotImplemented();
|
|
175
|
+
return `<!doctype html>
|
|
176
|
+
<html lang="en">
|
|
177
|
+
<head>
|
|
178
|
+
<meta charset="utf-8">
|
|
179
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
180
|
+
<title>Defuse — plans and pricing</title>
|
|
181
|
+
<meta name="description" content="Defuse is free and open source. Paid plans add tools for working through findings over time and across a team - and never change what the scanner finds.">
|
|
182
|
+
<meta property="og:title" content="Defuse — plans and pricing">
|
|
183
|
+
<meta property="og:description" content="The scanner is free. Paid plans organise findings; they never re-decide one.">
|
|
184
|
+
<meta property="og:type" content="website">
|
|
185
|
+
<meta name="theme-color" content="#262626">
|
|
186
|
+
<link rel="icon" href="defuse-mark.png">
|
|
187
|
+
<style>
|
|
188
|
+
${BASE_CSS}
|
|
189
|
+
header{position:relative;padding:0 0 clamp(40px,6vw,72px)}
|
|
190
|
+
${NAV_CSS}
|
|
191
|
+
h1{font-size:clamp(38px,6.4vw,72px);letter-spacing:-2px;margin:22px 0 0;max-width:17ch}
|
|
192
|
+
.lede{margin-top:22px;font-size:clamp(16px,1.9vw,19px);font-weight:300;max-width:62ch}
|
|
193
|
+
|
|
194
|
+
/* ---------- the plans ----------
|
|
195
|
+
Card layout after shadcn's pricing card: a header with a fact badge in the
|
|
196
|
+
corner, the price on one line, a checked feature list, one full-width
|
|
197
|
+
button, and a ring on the plan whose button is the primary one. Drawn
|
|
198
|
+
with this site's own colours, fonts and glass surface - no framework. */
|
|
199
|
+
.plans{display:grid;grid-template-columns:1fr;gap:20px;margin-top:8px;max-width:420px;margin-left:auto;margin-right:auto}
|
|
200
|
+
@media(min-width:960px){.plans{grid-template-columns:repeat(3,minmax(0,1fr));gap:22px;max-width:none}}
|
|
201
|
+
.plan{position:relative;display:flex;flex-direction:column;border-radius:12px;box-shadow:0 1px 2px rgba(0,0,0,.25)}
|
|
202
|
+
.plan.featured{border-color:var(--brand);box-shadow:0 0 0 1px var(--brand),0 18px 50px -24px rgba(121,44,247,.55)}
|
|
203
|
+
.phead{position:relative;padding:24px 24px 0}
|
|
204
|
+
.pname{font-size:24px;font-weight:600;letter-spacing:-.4px;line-height:1.1;color:var(--white);padding-right:120px}
|
|
205
|
+
.badge{position:absolute;top:24px;right:24px;font-family:"IBM Plex Mono",monospace;font-size:11.5px;font-weight:500;letter-spacing:.3px;
|
|
206
|
+
color:var(--brand-soft);background:rgba(121,44,247,.14);border:1px solid rgba(121,44,247,.35);border-radius:999px;padding:3px 10px;white-space:nowrap}
|
|
207
|
+
.plan.featured .badge{color:var(--white);background:var(--brand);border-color:var(--brand)}
|
|
208
|
+
.who{margin-top:8px;font-size:14px;line-height:1.55;color:var(--mute)}
|
|
209
|
+
.pricebox{padding:22px 24px 0}
|
|
210
|
+
.priceline{display:flex;align-items:baseline;flex-wrap:wrap;gap:4px 8px}
|
|
211
|
+
.amount{font-weight:600;font-size:clamp(32px,3.2vw,38px);letter-spacing:-1.2px;line-height:1;color:var(--white);font-variant-numeric:tabular-nums}
|
|
212
|
+
.unit{font-size:14px;color:var(--mute)}
|
|
213
|
+
.pnote{margin-top:8px;font-size:13px;color:var(--faint)}
|
|
214
|
+
/* Side by side, the headers and price blocks are given the same height, so
|
|
215
|
+
the feature lists start on one line across the three cards. */
|
|
216
|
+
@media(min-width:960px){.plan .phead{min-height:130px}.plan .pricebox{min-height:92px}}
|
|
217
|
+
.builds{margin:20px 24px 0;padding-top:16px;border-top:1px solid var(--hair);font-family:"IBM Plex Mono",monospace;font-size:12px;letter-spacing:1px;color:var(--faint)}
|
|
218
|
+
.feats{list-style:none;margin:12px 24px 24px;flex-grow:1;display:flex;flex-direction:column;gap:14px}
|
|
219
|
+
.feats li{display:flex;gap:10px;align-items:flex-start}
|
|
220
|
+
.feats .tick{flex-shrink:0;margin-top:3px;color:var(--brand-soft)}
|
|
221
|
+
.feats b{display:block;font-weight:500;font-size:14.5px;color:var(--white)}
|
|
222
|
+
.feats span{display:block;margin-top:3px;font-size:13.5px;line-height:1.55;color:var(--mute)}
|
|
223
|
+
.pbtn{display:flex;align-items:center;justify-content:center;height:42px;margin:0 24px 24px;font-size:14px;font-weight:500;color:var(--white);
|
|
224
|
+
background:var(--glass);border:1px solid var(--line);border-radius:8px;padding:0 16px;transition:background 200ms ease,border-color 200ms ease}
|
|
225
|
+
.pbtn:hover{background:rgba(245,245,245,.07);border-color:var(--brand-lift);color:var(--white)}
|
|
226
|
+
.pbtn.primary{background:var(--brand);border-color:var(--brand)}
|
|
227
|
+
.pbtn.primary:hover{background:var(--brand-lift);border-color:var(--brand-lift)}
|
|
228
|
+
.pbtn:focus-visible{outline:2px solid var(--brand-soft);outline-offset:3px}
|
|
229
|
+
|
|
230
|
+
/* Not built yet: drawn as an outline with no price, so it cannot be mistaken
|
|
231
|
+
for something on sale. None are listed today - see plans(). */
|
|
232
|
+
.soonrow{display:grid;grid-template-columns:1fr;gap:18px;margin-top:18px}
|
|
233
|
+
@media(min-width:760px){.soonrow{grid-template-columns:1fr 1fr;gap:20px}}
|
|
234
|
+
.plan.soon{border:1px solid var(--hair);border-radius:12px;background:transparent;padding:24px;box-shadow:none}
|
|
235
|
+
.plan.soon .pname{color:var(--mute);font-size:20px;padding-right:0}
|
|
236
|
+
.plan.soon .who{margin-top:10px}
|
|
237
|
+
.plan.soon .pbtn{margin:20px 0 0;align-self:flex-start;color:var(--mute)}
|
|
238
|
+
.tag{margin-top:12px;align-self:flex-start;font-family:"IBM Plex Mono",monospace;font-size:11.5px;letter-spacing:1.4px;text-transform:uppercase;color:var(--mute);border:1px solid var(--line);border-radius:6px;padding:4px 9px}
|
|
239
|
+
|
|
240
|
+
/* ---------- promises, effort, buying ---------- */
|
|
241
|
+
.three{display:grid;grid-template-columns:1fr;gap:18px;margin-top:40px}
|
|
242
|
+
@media(min-width:900px){.three{grid-template-columns:repeat(3,1fr);gap:20px}}
|
|
243
|
+
.three .glass{padding:26px 24px}
|
|
244
|
+
.three h3{font-size:19px;font-weight:500;letter-spacing:-.3px;color:var(--white)}
|
|
245
|
+
.three p{margin-top:10px;font-size:14.5px}
|
|
246
|
+
.stats{display:grid;grid-template-columns:repeat(2,1fr);gap:14px;margin-top:40px}
|
|
247
|
+
@media(min-width:760px){.stats{grid-template-columns:repeat(4,1fr);gap:18px}}
|
|
248
|
+
.stat{padding:24px 22px;border-radius:14px}
|
|
249
|
+
.stat b{display:block;font-weight:700;font-size:clamp(26px,3.2vw,34px);letter-spacing:-1px}
|
|
250
|
+
.stat span{display:block;margin-top:5px;font-family:"IBM Plex Mono",monospace;font-size:11.5px;letter-spacing:1.4px;text-transform:uppercase;color:var(--faint)}
|
|
251
|
+
.steps{margin:26px 0 0 20px;color:var(--mute);max-width:70ch}
|
|
252
|
+
.steps li{margin:10px 0}
|
|
253
|
+
.steps strong{color:var(--white);font-weight:500}
|
|
254
|
+
${FOOTER_CSS}
|
|
255
|
+
</style>
|
|
256
|
+
</head>
|
|
257
|
+
<body>
|
|
258
|
+
|
|
259
|
+
<header>
|
|
260
|
+
<div class="wrap">
|
|
261
|
+
${siteNav('pricing')}
|
|
262
|
+
<div class="kicker" style="margin-top:clamp(30px,5vw,64px)">Plans and pricing</div>
|
|
263
|
+
<h1>The scanner is free. Teams pay for the tools around it.</h1>
|
|
264
|
+
<p class="lede">Community is the whole scanner, open source, with nothing held back. The paid plans add
|
|
265
|
+
tools for working through findings over time and across a team. They read what the scanner found;
|
|
266
|
+
they never change what it found or how it is labelled.</p>
|
|
267
|
+
</div>
|
|
268
|
+
</header>
|
|
269
|
+
|
|
270
|
+
<section id="plans" style="padding-top:clamp(36px,5vw,56px)">
|
|
271
|
+
<div class="wrap">
|
|
272
|
+
<div class="plans">
|
|
273
|
+
${available.map(planCard).join('\n')}
|
|
274
|
+
</div>
|
|
275
|
+
${soon.length > 0 ? ` <div class="soonrow">\n${soon.map(planCard).join('\n')}\n </div>\n` : ''} <p class="mono" style="margin-top:22px;font-size:12.5px;color:var(--faint);max-width:none">Prices in US dollars.
|
|
276
|
+
There is no checkout yet - see how buying works below.</p>
|
|
277
|
+
</div>
|
|
278
|
+
</section>
|
|
279
|
+
|
|
280
|
+
<section id="promise">
|
|
281
|
+
<div class="wrap">
|
|
282
|
+
<div class="kicker">The same in every plan</div>
|
|
283
|
+
<h2>Paying never makes<br>a guess look like a proof.</h2>
|
|
284
|
+
<div class="three">
|
|
285
|
+
<div class="glass">
|
|
286
|
+
<h3>The scanner decides</h3>
|
|
287
|
+
<p>The paid tools read the scanner's own output. They organise findings - compare them, mark them,
|
|
288
|
+
chart them - and never re-decide or relabel one.</p>
|
|
289
|
+
</div>
|
|
290
|
+
<div class="glass">
|
|
291
|
+
<h3>Nothing is hidden</h3>
|
|
292
|
+
<p>A finding your team marks is still listed, with the reason beside it. A comparison never calls a
|
|
293
|
+
finding fixed; it says "no longer reported", because that is all it can know.</p>
|
|
294
|
+
</div>
|
|
295
|
+
<div class="glass">
|
|
296
|
+
<h3>Your code stays with you</h3>
|
|
297
|
+
<p>Everything runs on your machine or in your own CI. There is no account to make and nothing is
|
|
298
|
+
uploaded anywhere.</p>
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
</section>
|
|
303
|
+
|
|
304
|
+
<section id="work">
|
|
305
|
+
<div class="wrap">
|
|
306
|
+
<div class="kicker">The work behind it</div>
|
|
307
|
+
<h2>Measured, and<br>published whole.</h2>
|
|
308
|
+
<p style="margin-top:20px">The engine is scored against OWASP BenchmarkJava, with the two labels scored
|
|
309
|
+
separately. Safety rules are switched off one at a time to prove a test notices. And the scanner
|
|
310
|
+
is run on large real projects - Django, WordPress and VS Code among them - to find what synthetic tests
|
|
311
|
+
miss. <a href="index.html#numbers">The measurements, and what they do not promise, are on the main page.</a></p>
|
|
312
|
+
<div class="stats">
|
|
313
|
+
<div class="stat glass"><b>${benchmark.scored.toLocaleString('en-US')}</b><span>benchmark cases scored</span></div>
|
|
314
|
+
<div class="stat glass"><b>${ALL_RULES.length}</b><span>rules</span></div>
|
|
315
|
+
<div class="stat glass"><b>${LANGUAGES.length}</b><span>languages traced</span></div>
|
|
316
|
+
<div class="stat glass"><b>${notImplemented}</b><span>gaps listed by name</span></div>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
</section>
|
|
320
|
+
|
|
321
|
+
<section id="buying">
|
|
322
|
+
<div class="wrap">
|
|
323
|
+
<div class="kicker">How buying works, today</div>
|
|
324
|
+
<h2>Set up by hand,<br>for now.</h2>
|
|
325
|
+
<ol class="steps">
|
|
326
|
+
<li><strong>Ask</strong> using the button on the plan you want. It opens a short request form on GitHub.</li>
|
|
327
|
+
<li><strong>Hear back</strong> privately, with the licence terms and an invoice.</li>
|
|
328
|
+
<li><strong>Get the package</strong> - a file your machine or your CI installs next to the scanner.</li>
|
|
329
|
+
</ol>
|
|
330
|
+
<p style="margin-top:20px">The request form is public. Write only the plan, the number of developers and
|
|
331
|
+
how to reach you privately - never code, keys or company details.</p>
|
|
332
|
+
</div>
|
|
333
|
+
</section>
|
|
334
|
+
|
|
335
|
+
<div class="wrap">
|
|
336
|
+
${siteFooter()}
|
|
337
|
+
</div>
|
|
338
|
+
|
|
339
|
+
</body>
|
|
340
|
+
</html>
|
|
341
|
+
`;
|
|
342
|
+
}
|
|
343
|
+
//# sourceMappingURL=pricing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pricing.js","sourceRoot":"","sources":["../../../src/report/pricing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SARIF 2.1.0 EXPORT
|
|
3
|
+
*
|
|
4
|
+
* SARIF is the OASIS format GitHub, GitLab and Azure DevOps read. Emitting it
|
|
5
|
+
* is what turns this from "a thing you run" into "a thing a pull request runs",
|
|
6
|
+
* and it is the single cheapest step on the roadmap.
|
|
7
|
+
*
|
|
8
|
+
* THE PROBLEM SARIF CREATES FOR THIS PROJECT, and how it is solved here.
|
|
9
|
+
*
|
|
10
|
+
* Every finding this tool emits is labelled signature-based (pattern matched,
|
|
11
|
+
* data flow NOT traced) or flow-verified (a source-to-sink path was followed
|
|
12
|
+
* and is printed). That distinction is the whole product. SARIF has no field
|
|
13
|
+
* for it: `level` carries severity, and there is nowhere that means "we did not
|
|
14
|
+
* check this one".
|
|
15
|
+
*
|
|
16
|
+
* So a naive export loses the differentiator at exactly the moment it matters -
|
|
17
|
+
* in CI, where nobody reads a terminal and a red annotation looks equally
|
|
18
|
+
* certain either way. Three things carry it across instead, deliberately
|
|
19
|
+
* redundant, because a consumer that ignores one should still meet the others:
|
|
20
|
+
*
|
|
21
|
+
* 1. `message.text` OPENS with the label. It is the one string every SARIF
|
|
22
|
+
* consumer displays, so the label cannot be dropped by a viewer that
|
|
23
|
+
* ignores everything else.
|
|
24
|
+
* 2. `properties.confidence` holds the machine-readable value, alongside
|
|
25
|
+
* `properties.limitations` - the rule's own statement of what it did not
|
|
26
|
+
* check.
|
|
27
|
+
* 3. `codeFlows` is populated for flow-verified findings ONLY. SARIF has a
|
|
28
|
+
* first-class representation for a traced path, and GitHub renders it as a
|
|
29
|
+
* clickable sequence of steps. A finding with a codeFlow is one we proved;
|
|
30
|
+
* a finding without one is one we guessed. That asymmetry is visible in
|
|
31
|
+
* the UI without reading any of our text.
|
|
32
|
+
*
|
|
33
|
+
* WHAT IS DELIBERATELY NOT CLAIMED: `securitySeverity` is emitted because
|
|
34
|
+
* GitHub sorts by it, but it is derived from our severity rather than from CVSS,
|
|
35
|
+
* and the property block says so. Inventing a CVSS vector we never computed
|
|
36
|
+
* would be the same class of lie the confidence labels exist to prevent.
|
|
37
|
+
*/
|
|
38
|
+
import type { ScanResult } from '../engine/scan.js';
|
|
39
|
+
export declare function renderSarif(result: ScanResult, pretty?: boolean): string;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SARIF 2.1.0 EXPORT
|
|
3
|
+
*
|
|
4
|
+
* SARIF is the OASIS format GitHub, GitLab and Azure DevOps read. Emitting it
|
|
5
|
+
* is what turns this from "a thing you run" into "a thing a pull request runs",
|
|
6
|
+
* and it is the single cheapest step on the roadmap.
|
|
7
|
+
*
|
|
8
|
+
* THE PROBLEM SARIF CREATES FOR THIS PROJECT, and how it is solved here.
|
|
9
|
+
*
|
|
10
|
+
* Every finding this tool emits is labelled signature-based (pattern matched,
|
|
11
|
+
* data flow NOT traced) or flow-verified (a source-to-sink path was followed
|
|
12
|
+
* and is printed). That distinction is the whole product. SARIF has no field
|
|
13
|
+
* for it: `level` carries severity, and there is nowhere that means "we did not
|
|
14
|
+
* check this one".
|
|
15
|
+
*
|
|
16
|
+
* So a naive export loses the differentiator at exactly the moment it matters -
|
|
17
|
+
* in CI, where nobody reads a terminal and a red annotation looks equally
|
|
18
|
+
* certain either way. Three things carry it across instead, deliberately
|
|
19
|
+
* redundant, because a consumer that ignores one should still meet the others:
|
|
20
|
+
*
|
|
21
|
+
* 1. `message.text` OPENS with the label. It is the one string every SARIF
|
|
22
|
+
* consumer displays, so the label cannot be dropped by a viewer that
|
|
23
|
+
* ignores everything else.
|
|
24
|
+
* 2. `properties.confidence` holds the machine-readable value, alongside
|
|
25
|
+
* `properties.limitations` - the rule's own statement of what it did not
|
|
26
|
+
* check.
|
|
27
|
+
* 3. `codeFlows` is populated for flow-verified findings ONLY. SARIF has a
|
|
28
|
+
* first-class representation for a traced path, and GitHub renders it as a
|
|
29
|
+
* clickable sequence of steps. A finding with a codeFlow is one we proved;
|
|
30
|
+
* a finding without one is one we guessed. That asymmetry is visible in
|
|
31
|
+
* the UI without reading any of our text.
|
|
32
|
+
*
|
|
33
|
+
* WHAT IS DELIBERATELY NOT CLAIMED: `securitySeverity` is emitted because
|
|
34
|
+
* GitHub sorts by it, but it is derived from our severity rather than from CVSS,
|
|
35
|
+
* and the property block says so. Inventing a CVSS vector we never computed
|
|
36
|
+
* would be the same class of lie the confidence labels exist to prevent.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* SARIF has four levels; we have five severities.
|
|
40
|
+
*
|
|
41
|
+
* `low` maps to note rather than warning on purpose: a note does not fail a
|
|
42
|
+
* default GitHub check, and after the test-file ranking work most `low`
|
|
43
|
+
* findings are exactly the "unescaped, but no source traced" class that should
|
|
44
|
+
* be visible without blocking anyone's merge.
|
|
45
|
+
*/
|
|
46
|
+
const SARIF_LEVEL = {
|
|
47
|
+
critical: 'error',
|
|
48
|
+
high: 'error',
|
|
49
|
+
medium: 'warning',
|
|
50
|
+
low: 'note',
|
|
51
|
+
info: 'none',
|
|
52
|
+
};
|
|
53
|
+
/** GitHub buckets these: >=9 critical, >=7 high, >=4 medium, >0 low. */
|
|
54
|
+
const SECURITY_SEVERITY = {
|
|
55
|
+
critical: '9.3',
|
|
56
|
+
high: '7.5',
|
|
57
|
+
medium: '5.0',
|
|
58
|
+
low: '3.0',
|
|
59
|
+
info: '0.0',
|
|
60
|
+
};
|
|
61
|
+
/** SARIF wants a URI, and a Windows path is not one. */
|
|
62
|
+
function toUri(filePath) {
|
|
63
|
+
return filePath.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
64
|
+
}
|
|
65
|
+
function regionOf(location) {
|
|
66
|
+
return {
|
|
67
|
+
startLine: Math.max(1, location.startLine),
|
|
68
|
+
startColumn: Math.max(1, location.startColumn + 1), // SARIF columns are 1-based
|
|
69
|
+
endLine: Math.max(1, location.endLine),
|
|
70
|
+
endColumn: Math.max(1, location.endColumn + 1),
|
|
71
|
+
...(location.snippet ? { snippet: { text: location.snippet } } : {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function physicalLocation(location) {
|
|
75
|
+
return {
|
|
76
|
+
physicalLocation: {
|
|
77
|
+
artifactLocation: { uri: toUri(location.file) },
|
|
78
|
+
region: regionOf(location),
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export function renderSarif(result, pretty = true) {
|
|
83
|
+
// One rule descriptor per rule that actually fired. Emitting the whole
|
|
84
|
+
// catalogue would list rules this run never applied, which reads as coverage.
|
|
85
|
+
const seen = new Map();
|
|
86
|
+
for (const finding of result.findings) {
|
|
87
|
+
if (!seen.has(finding.ruleId))
|
|
88
|
+
seen.set(finding.ruleId, finding);
|
|
89
|
+
}
|
|
90
|
+
const rules = [...seen.values()].map((finding) => ({
|
|
91
|
+
id: finding.ruleId,
|
|
92
|
+
name: finding.ruleName,
|
|
93
|
+
shortDescription: { text: finding.ruleName },
|
|
94
|
+
fullDescription: {
|
|
95
|
+
// The limitations text travels with the rule, so a consumer reading the
|
|
96
|
+
// rule metadata meets the caveats without opening our JSON report.
|
|
97
|
+
text: finding.limitations,
|
|
98
|
+
},
|
|
99
|
+
help: {
|
|
100
|
+
text: `${finding.ruleName}\n\n${finding.limitations}`,
|
|
101
|
+
markdown: `**${finding.ruleName}**\n\n${finding.limitations}`,
|
|
102
|
+
},
|
|
103
|
+
properties: {
|
|
104
|
+
tags: ['security', finding.cwe, finding.owasp].filter(Boolean),
|
|
105
|
+
'security-severity': SECURITY_SEVERITY[finding.severity],
|
|
106
|
+
precision: 'medium',
|
|
107
|
+
},
|
|
108
|
+
}));
|
|
109
|
+
const results = result.findings.map((finding) => {
|
|
110
|
+
const label = finding.verified ? 'flow-verified' : 'signature-based, UNVERIFIED';
|
|
111
|
+
const base = {
|
|
112
|
+
ruleId: finding.ruleId,
|
|
113
|
+
level: SARIF_LEVEL[finding.severity],
|
|
114
|
+
message: { text: `[${label}] ${finding.message}\n\n${finding.reasoning}` },
|
|
115
|
+
locations: [physicalLocation(finding.location)],
|
|
116
|
+
properties: {
|
|
117
|
+
confidence: finding.confidence,
|
|
118
|
+
severity: finding.severity,
|
|
119
|
+
cwe: finding.cwe,
|
|
120
|
+
owasp: finding.owasp,
|
|
121
|
+
limitations: finding.limitations,
|
|
122
|
+
'security-severity': SECURITY_SEVERITY[finding.severity],
|
|
123
|
+
securitySeverityIsDerived: 'Derived from our own severity, not from a CVSS vector. No CVSS score was computed.',
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
// A traced path becomes a real SARIF codeFlow. Signature findings get none,
|
|
127
|
+
// which is the honest shape: there is no flow to show.
|
|
128
|
+
if (!finding.verified || !finding.flowPath || finding.flowPath.length === 0)
|
|
129
|
+
return base;
|
|
130
|
+
return {
|
|
131
|
+
...base,
|
|
132
|
+
codeFlows: [
|
|
133
|
+
{
|
|
134
|
+
message: { text: `Traced ${finding.flowPath.length} steps from source to sink.` },
|
|
135
|
+
threadFlows: [
|
|
136
|
+
{
|
|
137
|
+
locations: finding.flowPath.map((step) => ({
|
|
138
|
+
location: {
|
|
139
|
+
...physicalLocation(step.location),
|
|
140
|
+
message: { text: `${step.kind}: ${step.description}` },
|
|
141
|
+
},
|
|
142
|
+
// SARIF's own vocabulary for taint. A consumer that understands
|
|
143
|
+
// it can render source and sink differently without parsing our
|
|
144
|
+
// prose.
|
|
145
|
+
kinds: step.kind === 'source'
|
|
146
|
+
? ['taint', 'source']
|
|
147
|
+
: step.kind === 'sink'
|
|
148
|
+
? ['taint', 'sink']
|
|
149
|
+
: ['taint'],
|
|
150
|
+
})),
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
/*
|
|
158
|
+
* TYPED AS `Sarif.Log`, WHICH IS THE POINT.
|
|
159
|
+
*
|
|
160
|
+
* The JSON report's own comment says emitting invalid SARIF and calling it
|
|
161
|
+
* SARIF would be its own small lie. A hand-rolled object literal that merely
|
|
162
|
+
* LOOKS like SARIF is exactly that lie waiting to happen, so the shape is
|
|
163
|
+
* checked by the compiler against the official 2.1.0 type definitions
|
|
164
|
+
* (@types/sarif, a devDependency). A misspelled property or a wrong nesting
|
|
165
|
+
* level fails `npm run build` rather than failing silently inside somebody's
|
|
166
|
+
* CI three months from now.
|
|
167
|
+
*
|
|
168
|
+
* This validates STRUCTURE, not semantics: the compiler confirms this is a
|
|
169
|
+
* well-formed SARIF log, not that GitHub will render it the way intended.
|
|
170
|
+
*/
|
|
171
|
+
const document = {
|
|
172
|
+
$schema: 'https://json.schemastore.org/sarif-2.1.0.json',
|
|
173
|
+
version: '2.1.0',
|
|
174
|
+
runs: [
|
|
175
|
+
{
|
|
176
|
+
tool: {
|
|
177
|
+
driver: {
|
|
178
|
+
name: 'Defuse',
|
|
179
|
+
version: result.coverage.engine.version,
|
|
180
|
+
informationUri: 'https://github.com/',
|
|
181
|
+
rules,
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
results,
|
|
185
|
+
invocations: [
|
|
186
|
+
{
|
|
187
|
+
executionSuccessful: true,
|
|
188
|
+
// Files we could not parse are stated, not swallowed - a green CI
|
|
189
|
+
// run over a file nobody read is the quietest kind of false clean.
|
|
190
|
+
...(result.parseProblems.length > 0
|
|
191
|
+
? {
|
|
192
|
+
toolExecutionNotifications: result.parseProblems.map((problem) => ({
|
|
193
|
+
level: 'warning',
|
|
194
|
+
message: {
|
|
195
|
+
text: `${problem.file} did not parse cleanly (${problem.issues.length} issue(s)). ` +
|
|
196
|
+
`Findings in this file may be incomplete.`,
|
|
197
|
+
},
|
|
198
|
+
})),
|
|
199
|
+
}
|
|
200
|
+
: {}),
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
properties: {
|
|
204
|
+
// The same honesty block the JSON report leads with. A CI consumer
|
|
205
|
+
// that only ever reads SARIF still gets the engine's own account of
|
|
206
|
+
// what it does not do.
|
|
207
|
+
signatureBasedFindings: result.findings.filter((f) => !f.verified).length,
|
|
208
|
+
flowVerifiedFindings: result.findings.filter((f) => f.verified).length,
|
|
209
|
+
notImplemented: result.coverage.engine.notImplemented,
|
|
210
|
+
meaningOfConfidence: result.coverage.engine.meaningOfConfidence,
|
|
211
|
+
filesParsed: result.stats.filesParsed,
|
|
212
|
+
filesSkipped: result.stats.filesSkipped,
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
};
|
|
217
|
+
return pretty ? JSON.stringify(document, null, 2) : JSON.stringify(document);
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=sarif.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sarif.js","sourceRoot":"","sources":["../../../src/report/sarif.ts"],"names":[],"mappings":"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"}
|