Files
Webklar-Kundenbereich/runner/src/validate.js
T

74 lines
4.1 KiB
JavaScript

// Strikte Eingabeprüfung für Runner-Jobs.
import { RE_SITE_KEY, RE_COLOR } from './analytics-inject.js';
const RE_SUB = /^[a-z0-9-]{1,63}$/;
const RE_REPO = /^[\w.-]+\/[\w.-]+$/;
const RE_SHA = /^[0-9a-f]{7,40}$/;
const RE_BRANCH = /^[\w./-]{1,100}$/;
const RE_ENV = /^[A-Z_][A-Z0-9_]*$/;
const MODES = ['auto', 'build', 'dev'];
export const isValidRunId = (s) => typeof s === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s);
export const isValidSub = (s) => typeof s === 'string' && RE_SUB.test(s);
export function validateJob(body) {
if (!body || typeof body !== 'object' || Array.isArray(body)) return { ok: false, error: 'Ungültige Anfrage' };
const { sub, repo, sha, branch } = body;
if (!isValidSub(sub)) return { ok: false, error: 'Ungültige Subdomain' };
if (typeof repo !== 'string' || !RE_REPO.test(repo) || repo.split('/').some((x) => x === '.' || x === '..' || x.startsWith('-'))) return { ok: false, error: 'Ungültiges Repository' };
if (typeof sha !== 'string' || !RE_SHA.test(sha)) return { ok: false, error: 'Ungültiger Commit' };
if (typeof branch !== 'string' || !RE_BRANCH.test(branch) || branch.startsWith('-') || branch.includes('..') || branch.endsWith('/') || branch.endsWith('.lock')) return { ok: false, error: 'Ungültiger Branch' };
const mode = body.mode === undefined || body.mode === '' ? 'auto' : body.mode;
if (!MODES.includes(mode)) return { ok: false, error: 'Ungültiger Modus' };
let index;
if (body.index !== undefined && body.index !== '') {
const i = body.index;
if (typeof i !== 'string' || i.length > 200 || i.startsWith('/') || i.split(/[\\/]/).includes('..') || i.includes('\0')) {
return { ok: false, error: 'Ungültiger Startseiten-Pfad' };
}
index = i;
}
let displayName;
if (body.displayName !== undefined && body.displayName !== '') {
const d = body.displayName;
// eslint-disable-next-line no-control-regex
if (typeof d !== 'string' || d.length > 100 || /[\x00-\x1f\x7f]/.test(d)) return { ok: false, error: 'Ungültiger Anzeigename' };
displayName = d;
}
const env = {};
if (body.env !== undefined) {
if (!body.env || typeof body.env !== 'object' || Array.isArray(body.env)) return { ok: false, error: 'Ungültige Variablen' };
const names = Object.keys(body.env);
if (names.length > 30) return { ok: false, error: 'Zu viele Variablen (max. 30)' };
for (const n of names) {
const v = body.env[n];
if (!RE_ENV.test(n)) return { ok: false, error: `Ungültiger Variablenname: ${n}` };
if (typeof v !== 'string' || Buffer.byteLength(v) > 4096) return { ok: false, error: `Ungültiger Wert für ${n}` };
env[n] = v;
}
}
let analytics;
if (body.analytics !== undefined && body.analytics !== null) {
const a = body.analytics;
if (!a || typeof a !== 'object' || Array.isArray(a)) return { ok: false, error: 'Ungültige Analyse-Angaben' };
if (typeof a.siteKey !== 'string' || !RE_SITE_KEY.test(a.siteKey)) return { ok: false, error: 'Ungültiger Analyse-Schlüssel' };
if (typeof a.color !== 'string' || !RE_COLOR.test(a.color)) return { ok: false, error: 'Ungültige Analyse-Farbe' };
const pr = a.privacy;
if (!pr || typeof pr !== 'object' || Array.isArray(pr)) return { ok: false, error: 'Ungültige Analyse-Datenschutzangaben' };
const privacy = {};
for (const [k, max] of [['company', 200], ['address', 300], ['email', 200]]) {
const v = pr[k];
if (k === 'address' && (v === undefined || (typeof v === 'string' && !v.trim()))) { privacy[k] = ''; continue; }
// eslint-disable-next-line no-control-regex
if (typeof v !== 'string' || !v.trim() || v.length > max || /[\x00-\x1f\x7f]/.test(v)) return { ok: false, error: `Ungültige Analyse-Datenschutzangabe: ${k}` };
privacy[k] = v.trim();
}
analytics = { siteKey: a.siteKey, color: a.color, privacy };
}
// Whitelist: keine ungeprüften Client-Felder im Job
const job = { sub, repo, sha, branch, mode, env };
if (index !== undefined) job.index = index;
if (displayName !== undefined) job.displayName = displayName;
if (analytics !== undefined) job.analytics = analytics;
return { ok: true, job };
}