content-grade 1.0.7 → 1.0.9
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/bin/content-grade.js +37 -2
- package/package.json +1 -1
package/bin/content-grade.js
CHANGED
|
@@ -90,6 +90,17 @@ const MG = '\x1b[35m';
|
|
|
90
90
|
const BL = '\x1b[34m';
|
|
91
91
|
const WH = '\x1b[97m';
|
|
92
92
|
|
|
93
|
+
// ── Input sanitization ────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
// Reject null bytes (path traversal vector) and trim surrounding whitespace.
|
|
96
|
+
// Returns sanitized string, or null if the input is invalid.
|
|
97
|
+
function sanitizeFilePath(p) {
|
|
98
|
+
if (typeof p !== 'string') return null;
|
|
99
|
+
if (p.includes('\0')) return null;
|
|
100
|
+
const trimmed = p.trim();
|
|
101
|
+
return trimmed || null;
|
|
102
|
+
}
|
|
103
|
+
|
|
93
104
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
94
105
|
|
|
95
106
|
function ok(msg) { console.log(` ${GN}✓${R} ${msg}`); }
|
|
@@ -257,6 +268,15 @@ async function cmdAnalyze(filePath) {
|
|
|
257
268
|
process.exit(2);
|
|
258
269
|
}
|
|
259
270
|
|
|
271
|
+
const safeFilePath = sanitizeFilePath(filePath);
|
|
272
|
+
if (!safeFilePath) {
|
|
273
|
+
blank();
|
|
274
|
+
fail(`Invalid file path.`);
|
|
275
|
+
blank();
|
|
276
|
+
process.exit(2);
|
|
277
|
+
}
|
|
278
|
+
filePath = safeFilePath;
|
|
279
|
+
|
|
260
280
|
const absPath = resolve(process.cwd(), filePath);
|
|
261
281
|
if (!existsSync(absPath)) {
|
|
262
282
|
blank();
|
|
@@ -856,6 +876,15 @@ async function cmdBatch(dirPath) {
|
|
|
856
876
|
process.exit(2);
|
|
857
877
|
}
|
|
858
878
|
|
|
879
|
+
const safeDirPath = sanitizeFilePath(dirPath);
|
|
880
|
+
if (!safeDirPath) {
|
|
881
|
+
blank();
|
|
882
|
+
fail(`Invalid directory path.`);
|
|
883
|
+
blank();
|
|
884
|
+
process.exit(2);
|
|
885
|
+
}
|
|
886
|
+
dirPath = safeDirPath;
|
|
887
|
+
|
|
859
888
|
const absDir = resolve(process.cwd(), dirPath);
|
|
860
889
|
if (!existsSync(absDir)) {
|
|
861
890
|
blank();
|
|
@@ -1439,9 +1468,15 @@ function fetchUrl(url) {
|
|
|
1439
1468
|
return new Promise((resolve, reject) => {
|
|
1440
1469
|
const get = url.startsWith('https') ? httpsGet : httpGet;
|
|
1441
1470
|
const req = get(url, { headers: { 'User-Agent': 'ContentGrade/1.0 (+https://github.com/content-grade/Content-Grade)' }, timeout: 15000 }, (res) => {
|
|
1442
|
-
// Follow one redirect
|
|
1471
|
+
// Follow one redirect — validate location is http/https before following
|
|
1443
1472
|
if ((res.statusCode === 301 || res.statusCode === 302) && res.headers.location) {
|
|
1444
|
-
|
|
1473
|
+
const loc = res.headers.location;
|
|
1474
|
+
if (!/^https?:\/\//i.test(loc)) {
|
|
1475
|
+
reject(new Error(`Redirect to non-HTTP location rejected: ${loc.slice(0, 80)}`));
|
|
1476
|
+
res.resume();
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
fetchUrl(loc).then(resolve).catch(reject);
|
|
1445
1480
|
res.resume();
|
|
1446
1481
|
return;
|
|
1447
1482
|
}
|
package/package.json
CHANGED