apply security audit
This commit is contained in:
@@ -16,6 +16,47 @@ export interface VikunjaProject {
|
||||
title: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a URL is safe to use as an outbound service endpoint.
|
||||
* Rejects non-http(s) protocols and private/loopback IP ranges to prevent SSRF.
|
||||
*/
|
||||
function isValidServiceUrl(raw: string): boolean {
|
||||
let parsed: URL;
|
||||
try {
|
||||
parsed = new URL(raw);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hostname = parsed.hostname.toLowerCase();
|
||||
|
||||
// Reject plain loopback / localhost names
|
||||
if (hostname === 'localhost' || hostname === '::1') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reject numeric IPv4 private / loopback / link-local ranges
|
||||
const ipv4Parts = hostname.split('.');
|
||||
if (ipv4Parts.length === 4) {
|
||||
const [a, b] = ipv4Parts.map(Number);
|
||||
if (
|
||||
a === 127 || // 127.0.0.0/8 loopback
|
||||
a === 10 || // 10.0.0.0/8 private
|
||||
(a === 172 && b >= 16 && b <= 31) || // 172.16.0.0/12 private
|
||||
(a === 192 && b === 168) || // 192.168.0.0/16 private
|
||||
(a === 169 && b === 254) // 169.254.0.0/16 link-local
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function buildHeaders(): Record<string, string> {
|
||||
return {
|
||||
'Authorization': `Bearer ${environment.vikunja.apiToken}`,
|
||||
@@ -25,8 +66,8 @@ function buildHeaders(): Record<string, string> {
|
||||
|
||||
async function getMyTasks(): Promise<VikunjaTask[]> {
|
||||
const { vikunja } = environment;
|
||||
if (!vikunja.url) {
|
||||
throw new Error('VIKUNJA_URL is not configured');
|
||||
if (!vikunja.url || !isValidServiceUrl(vikunja.url)) {
|
||||
throw new Error('VIKUNJA_URL is not configured or is not a valid service URL');
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -58,8 +99,8 @@ async function getOverdueTasks(): Promise<VikunjaTask[]> {
|
||||
|
||||
async function getProjects(): Promise<VikunjaProject[]> {
|
||||
const { vikunja } = environment;
|
||||
if (!vikunja.url) {
|
||||
throw new Error('VIKUNJA_URL is not configured');
|
||||
if (!vikunja.url || !isValidServiceUrl(vikunja.url)) {
|
||||
throw new Error('VIKUNJA_URL is not configured or is not a valid service URL');
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -82,8 +123,8 @@ async function getProjects(): Promise<VikunjaProject[]> {
|
||||
|
||||
async function createTask(projectId: number, title: string, dueDate?: string): Promise<VikunjaTask> {
|
||||
const { vikunja } = environment;
|
||||
if (!vikunja.url) {
|
||||
throw new Error('VIKUNJA_URL is not configured');
|
||||
if (!vikunja.url || !isValidServiceUrl(vikunja.url)) {
|
||||
throw new Error('VIKUNJA_URL is not configured or is not a valid service URL');
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user