tewip-ai 0.18.1 → 0.18.3
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.
|
@@ -116,6 +116,9 @@ function junitCases(root, workspace) {
|
|
|
116
116
|
}
|
|
117
117
|
out.push({ testId, file: file || (testcase.attrs.classname ?? '').trim(), duration: Number.isFinite(seconds) ? Math.round(seconds * 1000) : 0, attempts });
|
|
118
118
|
};
|
|
119
|
+
// <testrun> is the root Eclipse's JUnit runner and Ant write; everything inside it is ordinary
|
|
120
|
+
// JUnit XML. A real report in that shape was rejected outright as "no test report Tewip can
|
|
121
|
+
// read", which is a poor answer to a file holding 36 perfectly readable results.
|
|
119
122
|
walk(root, root.name === 'testsuite' ? root : undefined);
|
|
120
123
|
// Attempts whose error the runner did not record borrow the final one's, when there is one.
|
|
121
124
|
for (const c of out) {
|
|
@@ -249,7 +252,7 @@ export function importJunit(path, meta) {
|
|
|
249
252
|
if (start && (!startedAt || start < startedAt))
|
|
250
253
|
startedAt = start;
|
|
251
254
|
}
|
|
252
|
-
else if (root.name === 'testsuites' || root.name === 'testsuite') {
|
|
255
|
+
else if (root.name === 'testsuites' || root.name === 'testsuite' || root.name === 'testrun') {
|
|
253
256
|
cases = junitCases(root, meta.workspace);
|
|
254
257
|
const stamp = root.attrs.timestamp ?? root.children.find((c) => c.name === 'testsuite')?.attrs.timestamp;
|
|
255
258
|
if (stamp && (!startedAt || stamp < startedAt))
|
|
@@ -317,7 +320,7 @@ export function importJunit(path, meta) {
|
|
|
317
320
|
if (unread.length > 0) {
|
|
318
321
|
const list = unread.slice(0, 5).join(', ');
|
|
319
322
|
if (parsedFiles === 0) {
|
|
320
|
-
throw new Error(`no test report Tewip can read in ${path}: ${list}. It reads JUnit XML (<testsuites>/<testsuite>), NUnit 3 (<test-run>), .NET TRX (<TestRun>), ` +
|
|
323
|
+
throw new Error(`no test report Tewip can read in ${path}: ${list}. It reads JUnit XML (<testsuites>/<testsuite>/<testrun>), NUnit 3 (<test-run>), .NET TRX (<TestRun>), ` +
|
|
321
324
|
'Robot Framework output.xml (<robot>), JMeter .jtl (<testResults>) and a k6 summary (--summary-export). ' +
|
|
322
325
|
'Most runners can write JUnit XML; for TestNG use the Surefire report, and for NUnit use --result=x.xml.');
|
|
323
326
|
}
|
|
@@ -54,18 +54,26 @@ export function entropyOf(value) {
|
|
|
54
54
|
const PLACEHOLDER_WORDS = /example|placeholder|dummy|sample|changeme|your[-_ ]?(?:key|token|secret|api)|xxxx|redacted|fake|notreal|test[-_]?key|<[^>]+>|\$\{|\{\{/i;
|
|
55
55
|
/** Files whose whole purpose is to show the shape of a secret, not to hold one. */
|
|
56
56
|
const DOC_FILE = /(?:^|[\\/])(?:\.env\.example|\.env\.sample|\.env\.template|[^\\/]*\.(?:md|rst|txt|adoc)|[^\\/]*example[^\\/]*|[^\\/]*sample[^\\/]*)$/i;
|
|
57
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Why this match is probably not a live credential, or null when nothing says so.
|
|
59
|
+
*
|
|
60
|
+
* The file's name is deliberately not enough on its own any more. A realistic AWS key placed in
|
|
61
|
+
* `src/config-sample.js` was dismissed as noise - "nothing: this is not a credential" - while the
|
|
62
|
+
* identical key in `src/settings.js` was reported as critical. A filename is the weakest evidence
|
|
63
|
+
* there is, and a key pasted into an example file by accident is one of the commonest ways
|
|
64
|
+
* credentials leak. That case is now shown as a template finding instead of being dismissed.
|
|
65
|
+
*/
|
|
58
66
|
export function looksPlanted(value, line, file) {
|
|
59
67
|
if (PLACEHOLDER_WORDS.test(value))
|
|
60
68
|
return 'the value itself says it is an example';
|
|
61
69
|
if (PLACEHOLDER_WORDS.test(line))
|
|
62
70
|
return 'the line around it says it is an example';
|
|
63
|
-
if (DOC_FILE.test(file))
|
|
64
|
-
return 'it is in a documentation or example file';
|
|
65
71
|
if (/(.)\1{5,}/.test(value))
|
|
66
72
|
return 'the value repeats one character, so it is a stand-in';
|
|
67
73
|
return null;
|
|
68
74
|
}
|
|
75
|
+
/** True when only the file's name suggests a template, whatever the value itself looks like. */
|
|
76
|
+
export const inATemplate = (file) => DOC_FILE.test(file);
|
|
69
77
|
const SKIP_DIR = new Set(['node_modules', '.git', 'dist', 'build', 'out', 'coverage', 'target', 'vendor', '__pycache__', '.venv', 'venv', '.tewip', '.next', '.cache']);
|
|
70
78
|
const BINARY = /\.(?:png|jpe?g|gif|webp|ico|svg|pdf|zip|gz|tar|exe|dll|so|dylib|woff2?|ttf|eot|mp4|mp3|wav|class|jar|pyc|lock)$/i;
|
|
71
79
|
const MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
@@ -150,6 +158,7 @@ export function scanSecrets(root, files = sourceFiles(root)) {
|
|
|
150
158
|
evidence: redact(lines[line - 1] ?? '', value),
|
|
151
159
|
entropy: Number(entropy.toFixed(2)),
|
|
152
160
|
placeholder: looksPlanted(value, lines[line - 1] ?? '', rel),
|
|
161
|
+
template: inATemplate(rel),
|
|
153
162
|
});
|
|
154
163
|
}
|
|
155
164
|
}
|
|
@@ -60,6 +60,18 @@ export function triageSecret(finding, root, accepted) {
|
|
|
60
60
|
if (finding.placeholder) {
|
|
61
61
|
return { ...base, verdict: 'noise', severity: 'low', why: `${finding.placeholder}: ${finding.evidence}`, next: 'nothing: this is not a credential' };
|
|
62
62
|
}
|
|
63
|
+
if (finding.template) {
|
|
64
|
+
// Only the file's name says this is a template; the value itself looks like a real credential.
|
|
65
|
+
// Dismissing it would hide the commonest accident there is - a working key pasted into an
|
|
66
|
+
// example file - so it is shown, below the ones that need acting on now.
|
|
67
|
+
return {
|
|
68
|
+
...base,
|
|
69
|
+
verdict: 'not-reachable',
|
|
70
|
+
severity: 'medium',
|
|
71
|
+
why: `${finding.evidence} - the file is a template or documentation, but the value itself does not say it is an example`,
|
|
72
|
+
next: 'check whether this was ever a working credential; if it was, rotate it and replace it here with an obvious placeholder',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
63
75
|
const tracked = isTracked(root, finding.file);
|
|
64
76
|
if (!tracked) {
|
|
65
77
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tewip-ai",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.3",
|
|
4
4
|
"description": "Tewip finds why each failing test failed - real bug, flaky test, environment, test data or changed selector - for any stack, from the command line, CI, or your editor.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"test-failures",
|