@cyclonedx/cdxgen 12.2.0 → 12.3.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/README.md +242 -90
- package/bin/audit.js +191 -0
- package/bin/cdxgen.js +532 -168
- package/bin/convert.js +99 -0
- package/bin/evinse.js +23 -0
- package/bin/repl.js +339 -8
- package/bin/sign.js +8 -0
- package/bin/validate.js +8 -0
- package/bin/verify.js +8 -0
- package/data/container-knowledge-index.json +125 -0
- package/data/gtfobins-index.json +6296 -0
- package/data/lolbas-index.json +150 -0
- package/data/queries-darwin.json +63 -3
- package/data/queries-win.json +45 -3
- package/data/queries.json +74 -2
- package/data/rules/chrome-extensions.yaml +240 -0
- package/data/rules/ci-permissions.yaml +478 -18
- package/data/rules/container-risk.yaml +270 -0
- package/data/rules/obom-runtime.yaml +891 -0
- package/data/rules/package-integrity.yaml +49 -0
- package/data/spdx-export.schema.json +6794 -0
- package/data/spdx-model-v3.0.1.jsonld +15999 -0
- package/lib/audit/index.js +1924 -0
- package/lib/audit/index.poku.js +1488 -0
- package/lib/audit/progress.js +137 -0
- package/lib/audit/progress.poku.js +188 -0
- package/lib/audit/reporters.js +618 -0
- package/lib/audit/scoring.js +310 -0
- package/lib/audit/scoring.poku.js +341 -0
- package/lib/audit/targets.js +260 -0
- package/lib/audit/targets.poku.js +331 -0
- package/lib/cli/index.js +276 -68
- package/lib/cli/index.poku.js +368 -0
- package/lib/helpers/analyzer.js +1052 -5
- package/lib/helpers/analyzer.poku.js +301 -0
- package/lib/helpers/annotationFormatter.js +49 -0
- package/lib/helpers/annotationFormatter.poku.js +44 -0
- package/lib/helpers/bomUtils.js +36 -0
- package/lib/helpers/bomUtils.poku.js +51 -0
- package/lib/helpers/caxa.js +2 -2
- package/lib/helpers/chromextutils.js +1153 -0
- package/lib/helpers/chromextutils.poku.js +493 -0
- package/lib/helpers/ciParsers/githubActions.js +1632 -45
- package/lib/helpers/ciParsers/githubActions.poku.js +853 -1
- package/lib/helpers/containerRisk.js +186 -0
- package/lib/helpers/containerRisk.poku.js +52 -0
- package/lib/helpers/depsUtils.js +16 -0
- package/lib/helpers/depsUtils.poku.js +58 -1
- package/lib/helpers/display.js +245 -61
- package/lib/helpers/display.poku.js +162 -2
- package/lib/helpers/exportUtils.js +123 -0
- package/lib/helpers/exportUtils.poku.js +60 -0
- package/lib/helpers/formulationParsers.js +69 -0
- package/lib/helpers/formulationParsers.poku.js +44 -0
- package/lib/helpers/gtfobins.js +189 -0
- package/lib/helpers/gtfobins.poku.js +49 -0
- package/lib/helpers/lolbas.js +267 -0
- package/lib/helpers/lolbas.poku.js +39 -0
- package/lib/helpers/osqueryTransform.js +84 -0
- package/lib/helpers/osqueryTransform.poku.js +49 -0
- package/lib/helpers/provenanceUtils.js +193 -0
- package/lib/helpers/provenanceUtils.poku.js +145 -0
- package/lib/helpers/pylockutils.js +281 -0
- package/lib/helpers/pylockutils.poku.js +48 -0
- package/lib/helpers/registryProvenance.js +793 -0
- package/lib/helpers/registryProvenance.poku.js +452 -0
- package/lib/helpers/remote/dependency-track.js +84 -0
- package/lib/helpers/remote/dependency-track.poku.js +119 -0
- package/lib/helpers/source.js +1267 -0
- package/lib/helpers/source.poku.js +771 -0
- package/lib/helpers/spdxUtils.js +97 -0
- package/lib/helpers/spdxUtils.poku.js +70 -0
- package/lib/helpers/table.js +384 -0
- package/lib/helpers/table.poku.js +186 -0
- package/lib/helpers/unicodeScan.js +147 -0
- package/lib/helpers/unicodeScan.poku.js +45 -0
- package/lib/helpers/utils.js +882 -136
- package/lib/helpers/utils.poku.js +995 -91
- package/lib/managers/binary.js +29 -5
- package/lib/managers/docker.js +179 -52
- package/lib/managers/docker.poku.js +327 -28
- package/lib/managers/oci.js +107 -23
- package/lib/managers/oci.poku.js +132 -0
- package/lib/server/openapi.yaml +50 -0
- package/lib/server/server.js +228 -331
- package/lib/server/server.poku.js +220 -5
- package/lib/stages/postgen/annotator.js +7 -0
- package/lib/stages/postgen/annotator.poku.js +40 -0
- package/lib/stages/postgen/auditBom.js +20 -5
- package/lib/stages/postgen/auditBom.poku.js +1729 -67
- package/lib/stages/postgen/postgen.js +40 -0
- package/lib/stages/postgen/postgen.poku.js +47 -0
- package/lib/stages/postgen/ruleEngine.js +80 -2
- package/lib/stages/postgen/spdxConverter.js +796 -0
- package/lib/stages/postgen/spdxConverter.poku.js +341 -0
- package/lib/validator/bomValidator.js +232 -0
- package/lib/validator/bomValidator.poku.js +70 -0
- package/lib/validator/complianceRules.js +70 -7
- package/lib/validator/complianceRules.poku.js +30 -0
- package/lib/validator/reporters/annotations.js +2 -2
- package/lib/validator/reporters/console.js +13 -2
- package/lib/validator/reporters.poku.js +13 -0
- package/package.json +10 -8
- package/types/bin/audit.d.ts +3 -0
- package/types/bin/audit.d.ts.map +1 -0
- package/types/bin/convert.d.ts +3 -0
- package/types/bin/convert.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +115 -0
- package/types/lib/audit/index.d.ts.map +1 -0
- package/types/lib/audit/progress.d.ts +27 -0
- package/types/lib/audit/progress.d.ts.map +1 -0
- package/types/lib/audit/reporters.d.ts +35 -0
- package/types/lib/audit/reporters.d.ts.map +1 -0
- package/types/lib/audit/scoring.d.ts +35 -0
- package/types/lib/audit/scoring.d.ts.map +1 -0
- package/types/lib/audit/targets.d.ts +63 -0
- package/types/lib/audit/targets.d.ts.map +1 -0
- package/types/lib/cli/index.d.ts +8 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts +13 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/annotationFormatter.d.ts +23 -0
- package/types/lib/helpers/annotationFormatter.d.ts.map +1 -0
- package/types/lib/helpers/bomUtils.d.ts +5 -0
- package/types/lib/helpers/bomUtils.d.ts.map +1 -0
- package/types/lib/helpers/chromextutils.d.ts +97 -0
- package/types/lib/helpers/chromextutils.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts +3 -8
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
- package/types/lib/helpers/containerRisk.d.ts +17 -0
- package/types/lib/helpers/containerRisk.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts +4 -1
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/exportUtils.d.ts +40 -0
- package/types/lib/helpers/exportUtils.d.ts.map +1 -0
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
- package/types/lib/helpers/gtfobins.d.ts +17 -0
- package/types/lib/helpers/gtfobins.d.ts.map +1 -0
- package/types/lib/helpers/lolbas.d.ts +16 -0
- package/types/lib/helpers/lolbas.d.ts.map +1 -0
- package/types/lib/helpers/osqueryTransform.d.ts +7 -0
- package/types/lib/helpers/osqueryTransform.d.ts.map +1 -0
- package/types/lib/helpers/provenanceUtils.d.ts +90 -0
- package/types/lib/helpers/provenanceUtils.d.ts.map +1 -0
- package/types/lib/helpers/pylockutils.d.ts +51 -0
- package/types/lib/helpers/pylockutils.d.ts.map +1 -0
- package/types/lib/helpers/registryProvenance.d.ts +17 -0
- package/types/lib/helpers/registryProvenance.d.ts.map +1 -0
- package/types/lib/helpers/remote/dependency-track.d.ts +16 -0
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
- package/types/lib/helpers/source.d.ts +141 -0
- package/types/lib/helpers/source.d.ts.map +1 -0
- package/types/lib/helpers/spdxUtils.d.ts +2 -0
- package/types/lib/helpers/spdxUtils.d.ts.map +1 -0
- package/types/lib/helpers/table.d.ts +6 -0
- package/types/lib/helpers/table.d.ts.map +1 -0
- package/types/lib/helpers/unicodeScan.d.ts +46 -0
- package/types/lib/helpers/unicodeScan.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +30 -11
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +0 -35
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/postgen/spdxConverter.d.ts +11 -0
- package/types/lib/stages/postgen/spdxConverter.d.ts.map +1 -0
- package/types/lib/validator/bomValidator.d.ts +1 -0
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/complianceRules.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/bin/dependencies.d.ts +0 -3
- package/types/bin/dependencies.d.ts.map +0 -1
- package/types/bin/licenses.d.ts +0 -3
- package/types/bin/licenses.d.ts.map +0 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
|
|
3
|
+
import esmock from "esmock";
|
|
4
|
+
import { assert, describe, it } from "poku";
|
|
5
|
+
import sinon from "sinon";
|
|
6
|
+
|
|
7
|
+
import { createStream, table } from "./table.js";
|
|
8
|
+
|
|
9
|
+
const withStdoutTTY = (ttyValue, action) => {
|
|
10
|
+
const descriptor = Object.getOwnPropertyDescriptor(process.stdout, "isTTY");
|
|
11
|
+
Object.defineProperty(process.stdout, "isTTY", {
|
|
12
|
+
configurable: true,
|
|
13
|
+
enumerable: true,
|
|
14
|
+
value: ttyValue,
|
|
15
|
+
writable: true,
|
|
16
|
+
});
|
|
17
|
+
try {
|
|
18
|
+
action();
|
|
19
|
+
} finally {
|
|
20
|
+
if (descriptor) {
|
|
21
|
+
Object.defineProperty(process.stdout, "isTTY", descriptor);
|
|
22
|
+
} else {
|
|
23
|
+
delete process.stdout.isTTY;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
describe("table()", () => {
|
|
29
|
+
it("renders headers, rows, and borders", () => {
|
|
30
|
+
const output = table(
|
|
31
|
+
[
|
|
32
|
+
["Name", "Score"],
|
|
33
|
+
["alpha", "100"],
|
|
34
|
+
],
|
|
35
|
+
{
|
|
36
|
+
borderStyle: "ascii",
|
|
37
|
+
columns: [{ width: 8 }, { width: 5, alignment: "right" }],
|
|
38
|
+
header: { alignment: "center", content: "Report" },
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
assert.ok(output.includes("Report"));
|
|
43
|
+
assert.ok(output.includes("alpha"));
|
|
44
|
+
assert.ok(output.includes(" 100"));
|
|
45
|
+
assert.ok(output.includes("+----------+-------+"));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("wraps long words when wrapWord is enabled", () => {
|
|
49
|
+
const output = table([["A", "supercalifragilistic"]], {
|
|
50
|
+
borderStyle: "ascii",
|
|
51
|
+
columns: [{ width: 2 }, { width: 6, wrapWord: true }],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
assert.ok(output.includes("superc"));
|
|
55
|
+
assert.ok(output.includes("alifra"));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("preserves ANSI escape sequences while wrapping by characters", () => {
|
|
59
|
+
const output = table([["\x1b[1;35mabcdef\x1b[0m"]], {
|
|
60
|
+
borderStyle: "ascii",
|
|
61
|
+
columns: [{ width: 4, wrapWord: true }],
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// biome-ignore lint/complexity/useRegexLiterals: avoid control-character regex literal warnings for ANSI pattern.
|
|
65
|
+
const ansiRegex = new RegExp("\\u001B\\[[0-?]*[ -/]*[@-~]", "g");
|
|
66
|
+
const ansiMatches = output.match(ansiRegex) || [];
|
|
67
|
+
assert.strictEqual(ansiMatches.length, 2);
|
|
68
|
+
assert.ok(output.includes("abcd"));
|
|
69
|
+
assert.ok(output.includes("ef"));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("keeps falsy values like 0 and false in cells", () => {
|
|
73
|
+
const output = table([[0, false]], {
|
|
74
|
+
borderStyle: "ascii",
|
|
75
|
+
columns: [{ width: 3 }, { width: 5 }],
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
assert.ok(output.includes(" 0 "));
|
|
79
|
+
assert.ok(output.includes("false"));
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("uses unicode borders in auto mode on tty when not in CI", () => {
|
|
83
|
+
const originalCI = process.env.CI;
|
|
84
|
+
delete process.env.CI;
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
withStdoutTTY(true, () => {
|
|
88
|
+
const output = table([["x"]], {
|
|
89
|
+
borderStyle: "auto",
|
|
90
|
+
columns: [{ width: 3 }],
|
|
91
|
+
});
|
|
92
|
+
assert.ok(output.includes("┌"));
|
|
93
|
+
assert.ok(output.includes("│"));
|
|
94
|
+
});
|
|
95
|
+
} finally {
|
|
96
|
+
if (originalCI === undefined) {
|
|
97
|
+
delete process.env.CI;
|
|
98
|
+
} else {
|
|
99
|
+
process.env.CI = originalCI;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("uses ascii borders in auto mode when CI=true", () => {
|
|
105
|
+
const originalCI = process.env.CI;
|
|
106
|
+
process.env.CI = "true";
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
withStdoutTTY(true, () => {
|
|
110
|
+
const output = table([["x"]], {
|
|
111
|
+
borderStyle: "auto",
|
|
112
|
+
columns: [{ width: 3 }],
|
|
113
|
+
});
|
|
114
|
+
assert.ok(output.includes("+"));
|
|
115
|
+
assert.ok(output.includes("|"));
|
|
116
|
+
assert.ok(!output.includes("┌"));
|
|
117
|
+
});
|
|
118
|
+
} finally {
|
|
119
|
+
if (originalCI === undefined) {
|
|
120
|
+
delete process.env.CI;
|
|
121
|
+
} else {
|
|
122
|
+
process.env.CI = originalCI;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("uses TABLE_BORDER_STYLE=unicode from utils even when not tty", async () => {
|
|
128
|
+
const { table: tableWithUnicode } = await esmock("./table.js", {
|
|
129
|
+
"./utils.js": { TABLE_BORDER_STYLE: "unicode" },
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
withStdoutTTY(false, () => {
|
|
133
|
+
const output = tableWithUnicode([["x"]], {
|
|
134
|
+
columns: [{ width: 3 }],
|
|
135
|
+
});
|
|
136
|
+
assert.ok(output.includes("┌"));
|
|
137
|
+
assert.ok(output.includes("│"));
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("falls back to auto-detect when TABLE_BORDER_STYLE is auto", async () => {
|
|
142
|
+
const { table: tableWithAuto } = await esmock("./table.js", {
|
|
143
|
+
"./utils.js": { TABLE_BORDER_STYLE: "auto" },
|
|
144
|
+
});
|
|
145
|
+
const originalCI = process.env.CI;
|
|
146
|
+
process.env.CI = "true";
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
withStdoutTTY(true, () => {
|
|
150
|
+
const output = tableWithAuto([["x"]], {
|
|
151
|
+
columns: [{ width: 3 }],
|
|
152
|
+
});
|
|
153
|
+
assert.ok(output.includes("+"));
|
|
154
|
+
assert.ok(output.includes("|"));
|
|
155
|
+
});
|
|
156
|
+
} finally {
|
|
157
|
+
if (originalCI === undefined) {
|
|
158
|
+
delete process.env.CI;
|
|
159
|
+
} else {
|
|
160
|
+
process.env.CI = originalCI;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe("createStream()", () => {
|
|
167
|
+
it("writes rows incrementally to stdout and closes with a bottom border", () => {
|
|
168
|
+
const writeStub = sinon.stub(process.stdout, "write");
|
|
169
|
+
const stream = createStream({
|
|
170
|
+
borderStyle: "unicode",
|
|
171
|
+
columns: [{ width: 5 }, { width: 5 }],
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
stream.write(["h1", "h2"]);
|
|
175
|
+
stream.write(["v1", "v2"]);
|
|
176
|
+
stream.end();
|
|
177
|
+
|
|
178
|
+
assert.ok(writeStub.callCount >= 3);
|
|
179
|
+
assert.ok(writeStub.calledWithMatch("h1"));
|
|
180
|
+
assert.ok(writeStub.calledWithMatch("v2"));
|
|
181
|
+
const output = writeStub.args.map((args) => args[0]).join("");
|
|
182
|
+
assert.ok(output.includes("├"));
|
|
183
|
+
assert.ok(output.trimEnd().endsWith("┘"));
|
|
184
|
+
writeStub.restore();
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
const BIDI_CHARS = /[\u202A-\u202E\u2066-\u2069]/gu;
|
|
2
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: Hidden Unicode scanning must detect raw control ranges.
|
|
3
|
+
const CONTROL_CHARS = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/gu;
|
|
4
|
+
const ZERO_WIDTH_CHARS = /[\u200B-\u200D\uFEFF]/gu;
|
|
5
|
+
|
|
6
|
+
function formatCodePoint(char) {
|
|
7
|
+
return `U+${char.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function findMatchesByPattern(text, pattern, kind) {
|
|
11
|
+
const matches = [];
|
|
12
|
+
for (const match of text.matchAll(pattern)) {
|
|
13
|
+
matches.push({
|
|
14
|
+
char: match[0],
|
|
15
|
+
codePoint: formatCodePoint(match[0]),
|
|
16
|
+
index: match.index,
|
|
17
|
+
kind,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return matches;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function lineNumberForIndex(text, index) {
|
|
24
|
+
return text.slice(0, index).split(/\r?\n/u).length;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function commentLineNumbers(text, syntax) {
|
|
28
|
+
const commentLines = new Set();
|
|
29
|
+
if (!text) {
|
|
30
|
+
return commentLines;
|
|
31
|
+
}
|
|
32
|
+
const lines = text.split(/\r?\n/u);
|
|
33
|
+
if (syntax === "yaml") {
|
|
34
|
+
lines.forEach((line, lineIndex) => {
|
|
35
|
+
if (line.trim().startsWith("#")) {
|
|
36
|
+
commentLines.add(lineIndex + 1);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return commentLines;
|
|
40
|
+
}
|
|
41
|
+
if (syntax === "markdown") {
|
|
42
|
+
let inHtmlComment = false;
|
|
43
|
+
lines.forEach((line, lineIndex) => {
|
|
44
|
+
const hasCommentStart = line.includes("<!--");
|
|
45
|
+
const hasCommentEnd = line.includes("-->");
|
|
46
|
+
if (inHtmlComment || hasCommentStart) {
|
|
47
|
+
commentLines.add(lineIndex + 1);
|
|
48
|
+
}
|
|
49
|
+
if (hasCommentStart && !hasCommentEnd) {
|
|
50
|
+
inHtmlComment = true;
|
|
51
|
+
} else if (hasCommentEnd) {
|
|
52
|
+
inHtmlComment = false;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return commentLines;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Find dangerous Unicode characters and return their details.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} text string to inspect
|
|
63
|
+
* @returns {{ char: string, codePoint: string, index: number, kind: string }[]} matches
|
|
64
|
+
*/
|
|
65
|
+
export function findDangerousUnicodeMatches(text) {
|
|
66
|
+
if (!text || typeof text !== "string") {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
const matches = [
|
|
70
|
+
...findMatchesByPattern(text, BIDI_CHARS, "bidirectional-control"),
|
|
71
|
+
...findMatchesByPattern(text, ZERO_WIDTH_CHARS, "zero-width"),
|
|
72
|
+
...findMatchesByPattern(text, CONTROL_CHARS, "control"),
|
|
73
|
+
];
|
|
74
|
+
matches.sort((left, right) => left.index - right.index);
|
|
75
|
+
return matches;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Scan a text blob for dangerous Unicode characters and summarize where they appear.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} text text to inspect
|
|
82
|
+
* @param {{ syntax?: "markdown" | "text" | "yaml" }} [options] scan options
|
|
83
|
+
* @returns {{
|
|
84
|
+
* codePoints: string[],
|
|
85
|
+
* commentCodePoints: string[],
|
|
86
|
+
* contexts: string[],
|
|
87
|
+
* hasHiddenUnicode: boolean,
|
|
88
|
+
* inComments: boolean,
|
|
89
|
+
* lineNumbers: number[],
|
|
90
|
+
* matches: { char: string, codePoint: string, index: number, kind: string, lineNumber: number, inComment: boolean }[],
|
|
91
|
+
* }} scan result
|
|
92
|
+
*/
|
|
93
|
+
export function scanTextForHiddenUnicode(text, options = {}) {
|
|
94
|
+
const matches = findDangerousUnicodeMatches(text);
|
|
95
|
+
if (!matches.length) {
|
|
96
|
+
return {
|
|
97
|
+
codePoints: [],
|
|
98
|
+
commentCodePoints: [],
|
|
99
|
+
contexts: [],
|
|
100
|
+
hasHiddenUnicode: false,
|
|
101
|
+
inComments: false,
|
|
102
|
+
lineNumbers: [],
|
|
103
|
+
matches: [],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const commentLines = commentLineNumbers(text, options.syntax || "text");
|
|
107
|
+
const enrichedMatches = matches.map((match) => {
|
|
108
|
+
const lineNumber = lineNumberForIndex(text, match.index);
|
|
109
|
+
return {
|
|
110
|
+
...match,
|
|
111
|
+
inComment: commentLines.has(lineNumber),
|
|
112
|
+
lineNumber,
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
const commentCodePoints = [
|
|
116
|
+
...new Set(
|
|
117
|
+
enrichedMatches
|
|
118
|
+
.filter((match) => match.inComment)
|
|
119
|
+
.map((match) => match.codePoint),
|
|
120
|
+
),
|
|
121
|
+
];
|
|
122
|
+
const contentCodePoints = [
|
|
123
|
+
...new Set(
|
|
124
|
+
enrichedMatches
|
|
125
|
+
.filter((match) => !match.inComment)
|
|
126
|
+
.map((match) => match.codePoint),
|
|
127
|
+
),
|
|
128
|
+
];
|
|
129
|
+
const contexts = [];
|
|
130
|
+
if (commentCodePoints.length) {
|
|
131
|
+
contexts.push("comment");
|
|
132
|
+
}
|
|
133
|
+
if (contentCodePoints.length) {
|
|
134
|
+
contexts.push("content");
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
codePoints: [...new Set(enrichedMatches.map((match) => match.codePoint))],
|
|
138
|
+
commentCodePoints,
|
|
139
|
+
contexts,
|
|
140
|
+
hasHiddenUnicode: true,
|
|
141
|
+
inComments: commentCodePoints.length > 0,
|
|
142
|
+
lineNumbers: [
|
|
143
|
+
...new Set(enrichedMatches.map((match) => match.lineNumber)),
|
|
144
|
+
].sort((left, right) => left - right),
|
|
145
|
+
matches: enrichedMatches,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { assert, describe, it } from "poku";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
findDangerousUnicodeMatches,
|
|
5
|
+
scanTextForHiddenUnicode,
|
|
6
|
+
} from "./unicodeScan.js";
|
|
7
|
+
|
|
8
|
+
describe("findDangerousUnicodeMatches()", () => {
|
|
9
|
+
it("finds bidirectional and zero-width characters with code points", () => {
|
|
10
|
+
const matches = findDangerousUnicodeMatches("safe\u202Evalue\u200Bhidden");
|
|
11
|
+
|
|
12
|
+
assert.strictEqual(matches.length, 2);
|
|
13
|
+
assert.deepStrictEqual(
|
|
14
|
+
matches.map((match) => match.codePoint),
|
|
15
|
+
["U+202E", "U+200B"],
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("scanTextForHiddenUnicode()", () => {
|
|
21
|
+
it("tracks markdown comment context for hidden Unicode", () => {
|
|
22
|
+
const scan = scanTextForHiddenUnicode(
|
|
23
|
+
"Visible line\n<!-- sneaky \u200B marker -->\nTrailing line",
|
|
24
|
+
{ syntax: "markdown" },
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
assert.strictEqual(scan.hasHiddenUnicode, true);
|
|
28
|
+
assert.strictEqual(scan.inComments, true);
|
|
29
|
+
assert.deepStrictEqual(scan.commentCodePoints, ["U+200B"]);
|
|
30
|
+
assert.deepStrictEqual(scan.lineNumbers, [2]);
|
|
31
|
+
assert.deepStrictEqual(scan.contexts, ["comment"]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("tracks yaml comment context for hidden Unicode", () => {
|
|
35
|
+
const scan = scanTextForHiddenUnicode(
|
|
36
|
+
"name: build\n# hidden \u202E comment\njobs:\n test:\n runs-on: ubuntu-latest",
|
|
37
|
+
{ syntax: "yaml" },
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
assert.strictEqual(scan.hasHiddenUnicode, true);
|
|
41
|
+
assert.strictEqual(scan.inComments, true);
|
|
42
|
+
assert.deepStrictEqual(scan.commentCodePoints, ["U+202E"]);
|
|
43
|
+
assert.deepStrictEqual(scan.lineNumbers, [2]);
|
|
44
|
+
});
|
|
45
|
+
});
|