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
- /** Why this match is probably not a live credential, or null when nothing says so. */
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
- const upgrade = finding.fixedIn ? `upgrade ${finding.package} to ${finding.fixedIn}` : `no fixed version is published yet: watch ${finding.id || 'the advisory'} and consider replacing the package`;
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: `nothing in this project imports ${finding.package}: it is pulled in by another package, so the vulnerable code may never run here`,
126
- next: `${upgrade}, or check which dependency requires it (npm why ${finding.package}) before spending time on it`,
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.2",
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",