tewip-ai 0.18.2 → 0.18.4
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.
|
@@ -34,6 +34,8 @@ export function npmAudit(root) {
|
|
|
34
34
|
id: String(via?.url?.split('/').pop() ?? via?.source ?? ''),
|
|
35
35
|
title: String(via?.title ?? `${name} has a known vulnerability`),
|
|
36
36
|
fixedIn: entry.fixAvailable && typeof entry.fixAvailable === 'object' ? String(entry.fixAvailable.version ?? '') : '',
|
|
37
|
+
fixExists: entry.fixAvailable === true || (entry.fixAvailable && typeof entry.fixAvailable === 'object'),
|
|
38
|
+
direct: entry.isDirect === true,
|
|
37
39
|
source: 'npm audit',
|
|
38
40
|
dev: Boolean(entry.isDirect === false ? false : entry.dev ?? false) || Boolean(entry.effects?.length === 0 && entry.dev),
|
|
39
41
|
});
|
|
@@ -82,6 +84,10 @@ export function osvScan(root) {
|
|
|
82
84
|
id: String(v.id ?? ''),
|
|
83
85
|
title: String(v.summary ?? '').slice(0, 200),
|
|
84
86
|
fixedIn: '',
|
|
87
|
+
// osv-scanner's output says neither which version fixes it nor whether the project
|
|
88
|
+
// declares the package, so neither is claimed.
|
|
89
|
+
fixExists: false,
|
|
90
|
+
direct: false,
|
|
85
91
|
source: 'osv-scanner',
|
|
86
92
|
dev: false,
|
|
87
93
|
});
|
|
@@ -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 {
|
|
@@ -104,7 +116,15 @@ export function triageDependency(finding, root, sources, accepted) {
|
|
|
104
116
|
where,
|
|
105
117
|
source: finding.source,
|
|
106
118
|
};
|
|
107
|
-
|
|
119
|
+
// npm answers `fixAvailable: true` when `npm audit fix` can resolve something without naming a
|
|
120
|
+
// version. Reading only the version form made Tewip say "no fixed version is published yet,
|
|
121
|
+
// consider replacing the package" about three packages npm could have fixed in one command -
|
|
122
|
+
// found by running this against real advisories for lodash, minimist and marked.
|
|
123
|
+
const upgrade = finding.fixedIn
|
|
124
|
+
? `upgrade ${finding.package} to ${finding.fixedIn}`
|
|
125
|
+
: finding.fixExists
|
|
126
|
+
? `a fix is available: run \`npm audit fix\` (the scanner did not name a version)`
|
|
127
|
+
: `no fixed version is published yet: watch ${finding.id || 'the advisory'} and consider replacing the package`;
|
|
108
128
|
if (accepted.has(`dependency ${finding.package}`) || accepted.has(`dependency ${where}`)) {
|
|
109
129
|
return { ...base, verdict: 'accepted', why: 'a person marked this one accepted in .tewip/security-accepted.txt', next: 'nothing: it was reviewed' };
|
|
110
130
|
}
|
|
@@ -122,8 +142,12 @@ export function triageDependency(finding, root, sources, accepted) {
|
|
|
122
142
|
...base,
|
|
123
143
|
verdict: 'not-reachable',
|
|
124
144
|
severity: base.severity === 'critical' ? 'medium' : 'low',
|
|
125
|
-
why:
|
|
126
|
-
|
|
145
|
+
why: finding.direct
|
|
146
|
+
? `this project declares ${finding.package} but no file imports it, so the vulnerable code may never run here`
|
|
147
|
+
: `nothing in this project imports ${finding.package}: it is pulled in by another package, so the vulnerable code may never run here`,
|
|
148
|
+
next: finding.direct
|
|
149
|
+
? `${upgrade}, or remove ${finding.package} from the project if nothing uses it any more`
|
|
150
|
+
: `${upgrade}, or check which dependency requires it (npm why ${finding.package}) before spending time on it`,
|
|
127
151
|
};
|
|
128
152
|
}
|
|
129
153
|
return { ...base, verdict: 'real-risk', why: `this project imports ${finding.package} directly, and the advisory applies to the installed version`, next: upgrade };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tewip-ai",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.4",
|
|
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",
|