Harden code-scanning findings

pull/1163/head
voc0der 2 months ago
parent ee19b9514f
commit 21747fa929

@ -162,7 +162,15 @@ app.use(bodyParser.json());
// use passport
app.use(auth_api.passport.initialize());
app.use(session({ secret: uuid(), resave: true, saveUninitialized: true }))
app.use(session({
secret: uuid(),
resave: true,
saveUninitialized: true,
cookie: {
httpOnly: true,
sameSite: 'strict'
}
}))
app.use(auth_api.passport.session());
// reverse proxy whitelist

@ -1,5 +1,6 @@
const fs = require('fs-extra');
const path = require('path');
const { URL } = require('url');
const ffmpeg = require('fluent-ffmpeg');
const archiver = require('archiver');
const fetch = require('node-fetch');
@ -11,6 +12,57 @@ const logger = require('./logger');
const CONSTS = require('./consts');
const is_windows = process.platform === 'win32';
const TRUSTED_DOWNLOAD_HOSTS = new Set([
'api.github.com',
'codeload.github.com',
'github.com',
'github-releases.githubusercontent.com',
'objects.githubusercontent.com',
'raw.githubusercontent.com',
'release-assets.githubusercontent.com',
]);
function validateTrustedDownloadUrl(rawUrl) {
let parsedUrl = null;
try {
parsedUrl = new URL(rawUrl);
} catch {
throw new Error(`Invalid download URL: ${rawUrl}`);
}
if (parsedUrl.protocol !== 'https:') {
throw new Error(`Refusing non-HTTPS download URL: ${parsedUrl.protocol}`);
}
const hostname = parsedUrl.hostname.toLowerCase();
const isGithubusercontentHost = hostname === 'githubusercontent.com' || hostname.endsWith('.githubusercontent.com');
if (!TRUSTED_DOWNLOAD_HOSTS.has(hostname) && !isGithubusercontentHost) {
throw new Error(`Refusing download from untrusted host: ${hostname}`);
}
return parsedUrl.toString();
}
async function fetchTrustedDownload(url, redirects = 0) {
const validatedUrl = validateTrustedDownloadUrl(url);
const res = await fetch(validatedUrl, { redirect: 'manual' });
if (res.status >= 300 && res.status < 400) {
if (redirects >= 5) {
throw new Error(`Too many redirects while downloading from ${validatedUrl}`);
}
const location = res.headers.get('location');
if (!location) {
throw new Error(`Redirect response missing location for ${validatedUrl}`);
}
const nextUrl = new URL(location, validatedUrl).toString();
return fetchTrustedDownload(nextUrl, redirects + 1);
}
return res;
}
// replaces .webm with appropriate extension
exports.getTrueFileName = (unfixed_path, type, force_ext = null) => {
@ -361,7 +413,10 @@ exports.checkExistsWithTimeout = async (filePath, timeout) => {
// helper function to download file using fetch
exports.fetchFile = async (url, path, file_label) => {
var len = null;
const res = await fetch(url);
const res = await fetchTrustedDownload(url);
if (!res.ok) {
throw new Error(`Failed to download ${file_label}: HTTP ${res.status}`);
}
len = parseInt(res.headers.get("Content-Length"), 10);
@ -591,4 +646,3 @@ function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, p
this.favorite = false;
}
exports.File = File;

@ -56,7 +56,7 @@ export class MainComponent implements OnInit {
customDownloadingAgent = null;
// cache
cachedAvailableFormats = {};
cachedAvailableFormats: { [key: string]: any } = Object.create(null);
cachedFileManagerEnabled = localStorage.getItem('cached_filemanager_enabled') === 'true';
// youtube api
@ -556,7 +556,7 @@ export class MainComponent implements OnInit {
getURLInfo(url: string): void {
if (!this.cachedAvailableFormats[url]) {
this.cachedAvailableFormats[url] = {};
this.cachedAvailableFormats[url] = Object.create(null);
}
// If URL is a YouTube playlist page (not a single watch?v=...), skip format probing
if (this.isYouTubePlaylistUrl(url)) {

Loading…
Cancel
Save