tighten validation, replace negative type checks with allowed list

This commit is contained in:
Alex committed 2026-05-11 10:27:41 -07:00
1 parent 08043a5ea0
commit 766d847561
5 files changed
+37 -15

No files matched your search

+3 -2
View File
@@ -124,9 +124,10 @@ const websocketSchema = baseSchema.extend({
});
// Hostname (FQDN) — labels of 1-63 alphanumerics/hyphens separated by dots,
// no scheme, port, path, or whitespace. Total length ≤ 253.
// optionally prefixed with `_` for service labels (e.g. _dmarc, _imaps._tcp).
// No scheme, port, path, or whitespace. Total length ≤ 253.
const hostnameRegex =
/^(?=.{1,253}$)([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$/;
/^(?=.{1,253}$)([a-zA-Z0-9_](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$/;
// DNS monitor schema
const dnsSchema = baseSchema.extend({
+6 -11
View File
@@ -9,7 +9,8 @@ import type {
GamesMap,
GroupedGeoCheckResult,
} from "@/types/monitor.js";
import { supportsGeoCheck } from "@/types/monitor.js";
import { supportsGeoCheck, supportsUptimeDetails } from "@/types/monitor.js";
import type { UptimeChecksResult, HardwareChecksResult, PageSpeedChecksResult } from "@/types/check.js";
import type { GeoContinent } from "@/types/geoCheck.js";
import type {
IChecksRepository,
@@ -28,6 +29,9 @@ import { ILogger } from "@/utils/logger.js";
const SERVICE_NAME = "MonitorService";
type DateRangeKey = "recent" | "day" | "week" | "month" | "all";
const isUptimeChecksResult = (result: UptimeChecksResult | HardwareChecksResult | PageSpeedChecksResult): result is UptimeChecksResult =>
supportsUptimeDetails(result.monitorType);
export interface IMonitorService {
readonly serviceName: string;
@@ -216,16 +220,7 @@ export class MonitorService implements IMonitorService {
});
const monitorStats = await this.monitorStatsRepository.findByMonitorId(monitor.id);
if (
checksData.monitorType !== "http" &&
checksData.monitorType !== "ping" &&
checksData.monitorType !== "docker" &&
checksData.monitorType !== "port" &&
checksData.monitorType !== "game" &&
checksData.monitorType !== "grpc" &&
checksData.monitorType !== "websocket" &&
checksData.monitorType !== "dns"
) {
if (!isUptimeChecksResult(checksData)) {
throw new AppError({ message: `${monitor.type} monitors are not supported for uptime details`, status: 400 });
}
+13
View File
@@ -9,6 +9,19 @@ export type MonitorType = (typeof MonitorTypes)[number];
export const GeoCheckSupportedTypes: readonly MonitorType[] = ["http", "ping"] as const;
export const supportsGeoCheck = (type: MonitorType): boolean => GeoCheckSupportedTypes.includes(type);
export const UptimeDetailsSupportedTypes = [
"http",
"ping",
"docker",
"port",
"game",
"grpc",
"websocket",
"dns",
] as const satisfies readonly MonitorType[];
export type UptimeDetailsSupportedType = (typeof UptimeDetailsSupportedTypes)[number];
export const supportsUptimeDetails = (type: MonitorType): type is UptimeDetailsSupportedType => UptimeDetailsSupportedTypes.some((t) => t === type);
export const MonitorStatuses = ["up", "down", "paused", "initializing", "maintenance", "breached"] as const;
export type MonitorStatus = (typeof MonitorStatuses)[number];
+3 -2
View File
@@ -21,9 +21,10 @@ export const dnsServerValidation = z
.refine((v) => z.ipv4().safeParse(v).success || z.ipv6().safeParse(v).success, "Enter a valid IPv4 or IPv6 address (e.g. 8.8.8.8)");
// Hostname (FQDN) — labels of 1-63 alphanumerics/hyphens separated by dots,
// no scheme, port, path, or whitespace. Total length ≤ 253. Kept in sync with
// optionally prefixed with `_` for service labels (e.g. _dmarc, _imaps._tcp).
// No scheme, port, path, or whitespace. Total length ≤ 253. Kept in sync with
// the client-side regex in client/src/Validation/monitor.ts.
export const dnsHostnameRegex = /^(?=.{1,253}$)([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$/;
export const dnsHostnameRegex = /^(?=.{1,253}$)([a-zA-Z0-9_](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$/;
export const booleanCoercion = z.preprocess((val) => {
if (val === "true" || val === true) return true;
@@ -75,6 +75,18 @@ describe("monitorValidation — DNS fields", () => {
).toThrow();
});
it("accepts service labels with leading underscore (DMARC, SRV, ACME)", () => {
for (const url of ["_dmarc.example.com", "_imaps._tcp.example.com", "_acme-challenge.example.com"]) {
const parsed = createMonitorBodyValidation.parse({
...baseDnsBody,
url,
dnsServer: "8.8.8.8",
dnsRecordType: "TXT",
});
expect(parsed.url).toBe(url);
}
});
it("does not enforce the hostname rule for non-DNS monitors", () => {
const parsed = createMonitorBodyValidation.parse({
name: "HTTP check",