@tracelens/shared 0.1.0
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/dist/__tests__/validation.test.d.ts +2 -0
- package/dist/__tests__/validation.test.d.ts.map +1 -0
- package/dist/__tests__/validation.test.js +16 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/schemas/dependency.schema.d.ts +177 -0
- package/dist/schemas/dependency.schema.d.ts.map +1 -0
- package/dist/schemas/dependency.schema.js +187 -0
- package/dist/schemas/error.schema.d.ts +170 -0
- package/dist/schemas/error.schema.d.ts.map +1 -0
- package/dist/schemas/error.schema.js +176 -0
- package/dist/schemas/index.d.ts +5 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +5 -0
- package/dist/schemas/performance.schema.d.ts +333 -0
- package/dist/schemas/performance.schema.d.ts.map +1 -0
- package/dist/schemas/performance.schema.js +200 -0
- package/dist/schemas/trace.schema.d.ts +325 -0
- package/dist/schemas/trace.schema.d.ts.map +1 -0
- package/dist/schemas/trace.schema.js +145 -0
- package/dist/types/dependency.types.d.ts +109 -0
- package/dist/types/dependency.types.d.ts.map +1 -0
- package/dist/types/dependency.types.js +8 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/performance.types.d.ts +95 -0
- package/dist/types/performance.types.d.ts.map +1 -0
- package/dist/types/performance.types.js +1 -0
- package/dist/types/security.types.d.ts +154 -0
- package/dist/types/security.types.d.ts.map +1 -0
- package/dist/types/security.types.js +8 -0
- package/dist/types/trace.types.d.ts +57 -0
- package/dist/types/trace.types.d.ts.map +1 -0
- package/dist/types/trace.types.js +20 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/validation.d.ts +21 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +157 -0
- package/jest.config.js +14 -0
- package/package.json +22 -0
- package/src/__tests__/validation.test.ts +19 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.ts +4 -0
- package/src/schemas/dependency.schema.ts +187 -0
- package/src/schemas/error.schema.ts +176 -0
- package/src/schemas/index.ts +5 -0
- package/src/schemas/performance.schema.ts +200 -0
- package/src/schemas/trace.schema.ts +146 -0
- package/src/types/dependency.types.ts +120 -0
- package/src/types/index.d.ts.map +1 -0
- package/src/types/index.ts +11 -0
- package/src/types/performance.types.ts +108 -0
- package/src/types/security.types.ts +172 -0
- package/src/types/trace.types.ts +62 -0
- package/src/utils/index.d.ts.map +1 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/validation.ts +209 -0
- package/tsconfig.json +9 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// CVE and vulnerability types for TraceLens
|
|
2
|
+
export interface CVERecord {
|
|
3
|
+
id: string; // CVE-YYYY-NNNN format
|
|
4
|
+
published: string; // ISO date string
|
|
5
|
+
lastModified: string; // ISO date string
|
|
6
|
+
vulnStatus: VulnerabilityStatus;
|
|
7
|
+
descriptions: CVEDescription[];
|
|
8
|
+
metrics: CVEMetrics;
|
|
9
|
+
weaknesses: CVEWeakness[];
|
|
10
|
+
configurations: CVEConfiguration[];
|
|
11
|
+
references: CVEReference[];
|
|
12
|
+
vendorComments?: CVEVendorComment[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export enum VulnerabilityStatus {
|
|
16
|
+
ANALYZED = 'Analyzed',
|
|
17
|
+
MODIFIED = 'Modified',
|
|
18
|
+
REJECTED = 'Rejected',
|
|
19
|
+
AWAITING_ANALYSIS = 'Awaiting Analysis',
|
|
20
|
+
RECEIVED = 'Received'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CVEDescription {
|
|
24
|
+
lang: string;
|
|
25
|
+
value: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface CVEMetrics {
|
|
29
|
+
cvssMetricV31?: CVSSv31[];
|
|
30
|
+
cvssMetricV30?: CVSSv30[];
|
|
31
|
+
cvssMetricV2?: CVSSv2[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface CVSSv31 {
|
|
35
|
+
source: string;
|
|
36
|
+
type: 'Primary' | 'Secondary';
|
|
37
|
+
cvssData: {
|
|
38
|
+
version: '3.1';
|
|
39
|
+
vectorString: string;
|
|
40
|
+
attackVector: 'NETWORK' | 'ADJACENT_NETWORK' | 'LOCAL' | 'PHYSICAL';
|
|
41
|
+
attackComplexity: 'LOW' | 'HIGH';
|
|
42
|
+
privilegesRequired: 'NONE' | 'LOW' | 'HIGH';
|
|
43
|
+
userInteraction: 'NONE' | 'REQUIRED';
|
|
44
|
+
scope: 'UNCHANGED' | 'CHANGED';
|
|
45
|
+
confidentialityImpact: 'NONE' | 'LOW' | 'HIGH';
|
|
46
|
+
integrityImpact: 'NONE' | 'LOW' | 'HIGH';
|
|
47
|
+
availabilityImpact: 'NONE' | 'LOW' | 'HIGH';
|
|
48
|
+
baseScore: number;
|
|
49
|
+
baseSeverity: 'NONE' | 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
|
50
|
+
};
|
|
51
|
+
exploitabilityScore: number;
|
|
52
|
+
impactScore: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface CVSSv30 {
|
|
56
|
+
source: string;
|
|
57
|
+
type: 'Primary' | 'Secondary';
|
|
58
|
+
cvssData: {
|
|
59
|
+
version: '3.0';
|
|
60
|
+
vectorString: string;
|
|
61
|
+
baseScore: number;
|
|
62
|
+
baseSeverity: 'NONE' | 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface CVSSv2 {
|
|
67
|
+
source: string;
|
|
68
|
+
type: 'Primary' | 'Secondary';
|
|
69
|
+
cvssData: {
|
|
70
|
+
version: '2.0';
|
|
71
|
+
vectorString: string;
|
|
72
|
+
baseScore: number;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface CVEWeakness {
|
|
77
|
+
source: string;
|
|
78
|
+
type: 'Primary' | 'Secondary';
|
|
79
|
+
description: CVEDescription[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface CVEConfiguration {
|
|
83
|
+
nodes: CVENode[];
|
|
84
|
+
operator?: 'AND' | 'OR';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface CVENode {
|
|
88
|
+
operator: 'AND' | 'OR';
|
|
89
|
+
negate?: boolean;
|
|
90
|
+
cpeMatch: CPEMatch[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface CPEMatch {
|
|
94
|
+
vulnerable: boolean;
|
|
95
|
+
criteria: string; // CPE 2.3 format
|
|
96
|
+
matchCriteriaId: string;
|
|
97
|
+
versionStartExcluding?: string;
|
|
98
|
+
versionStartIncluding?: string;
|
|
99
|
+
versionEndExcluding?: string;
|
|
100
|
+
versionEndIncluding?: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface CVEReference {
|
|
104
|
+
url: string;
|
|
105
|
+
source: string;
|
|
106
|
+
tags?: string[];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface CVEVendorComment {
|
|
110
|
+
organization: string;
|
|
111
|
+
comment: string;
|
|
112
|
+
lastModified: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface VulnerabilityAssessment {
|
|
116
|
+
cveId: string;
|
|
117
|
+
packageName: string;
|
|
118
|
+
packageVersion: string;
|
|
119
|
+
severity: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW' | 'NONE';
|
|
120
|
+
score: number;
|
|
121
|
+
vector: string;
|
|
122
|
+
runtimeRelevant: boolean;
|
|
123
|
+
executionPaths: string[];
|
|
124
|
+
exploitability: ExploitabilityAssessment;
|
|
125
|
+
mitigation: MitigationRecommendation;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface ExploitabilityAssessment {
|
|
129
|
+
isExploitable: boolean;
|
|
130
|
+
exploitComplexity: 'LOW' | 'MEDIUM' | 'HIGH';
|
|
131
|
+
requiresUserInteraction: boolean;
|
|
132
|
+
requiresPrivileges: boolean;
|
|
133
|
+
networkAccessible: boolean;
|
|
134
|
+
publicExploitAvailable: boolean;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface MitigationRecommendation {
|
|
138
|
+
action: 'UPDATE' | 'PATCH' | 'WORKAROUND' | 'MONITOR';
|
|
139
|
+
targetVersion?: string;
|
|
140
|
+
patchUrl?: string;
|
|
141
|
+
workaroundDescription?: string;
|
|
142
|
+
priority: 'IMMEDIATE' | 'HIGH' | 'MEDIUM' | 'LOW';
|
|
143
|
+
estimatedEffort: 'MINUTES' | 'HOURS' | 'DAYS' | 'WEEKS';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface SecurityScan {
|
|
147
|
+
id: string;
|
|
148
|
+
projectId: string;
|
|
149
|
+
timestamp: number;
|
|
150
|
+
scanType: 'FULL' | 'INCREMENTAL' | 'TARGETED';
|
|
151
|
+
vulnerabilities: VulnerabilityAssessment[];
|
|
152
|
+
summary: SecuritySummary;
|
|
153
|
+
runtimeAnalysis: RuntimeSecurityAnalysis;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface SecuritySummary {
|
|
157
|
+
totalVulnerabilities: number;
|
|
158
|
+
criticalCount: number;
|
|
159
|
+
highCount: number;
|
|
160
|
+
mediumCount: number;
|
|
161
|
+
lowCount: number;
|
|
162
|
+
runtimeRelevantCount: number;
|
|
163
|
+
exploitableCount: number;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface RuntimeSecurityAnalysis {
|
|
167
|
+
executedPackages: string[];
|
|
168
|
+
vulnerableExecutedPackages: string[];
|
|
169
|
+
criticalPathVulnerabilities: VulnerabilityAssessment[];
|
|
170
|
+
riskScore: number;
|
|
171
|
+
recommendations: MitigationRecommendation[];
|
|
172
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// OpenTelemetry trace definitions for TraceLens
|
|
2
|
+
export interface TraceSpan {
|
|
3
|
+
traceId: string;
|
|
4
|
+
spanId: string;
|
|
5
|
+
parentSpanId?: string;
|
|
6
|
+
operationName: string;
|
|
7
|
+
startTime: number; // microseconds since epoch
|
|
8
|
+
endTime?: number;
|
|
9
|
+
duration?: number; // microseconds
|
|
10
|
+
tags: Record<string, string | number | boolean>;
|
|
11
|
+
logs?: TraceLog[];
|
|
12
|
+
status: SpanStatus;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface TraceLog {
|
|
16
|
+
timestamp: number;
|
|
17
|
+
fields: Record<string, string | number | boolean>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export enum SpanStatus {
|
|
21
|
+
OK = 'OK',
|
|
22
|
+
CANCELLED = 'CANCELLED',
|
|
23
|
+
UNKNOWN = 'UNKNOWN',
|
|
24
|
+
INVALID_ARGUMENT = 'INVALID_ARGUMENT',
|
|
25
|
+
DEADLINE_EXCEEDED = 'DEADLINE_EXCEEDED',
|
|
26
|
+
NOT_FOUND = 'NOT_FOUND',
|
|
27
|
+
ALREADY_EXISTS = 'ALREADY_EXISTS',
|
|
28
|
+
PERMISSION_DENIED = 'PERMISSION_DENIED',
|
|
29
|
+
RESOURCE_EXHAUSTED = 'RESOURCE_EXHAUSTED',
|
|
30
|
+
FAILED_PRECONDITION = 'FAILED_PRECONDITION',
|
|
31
|
+
ABORTED = 'ABORTED',
|
|
32
|
+
OUT_OF_RANGE = 'OUT_OF_RANGE',
|
|
33
|
+
UNIMPLEMENTED = 'UNIMPLEMENTED',
|
|
34
|
+
INTERNAL = 'INTERNAL',
|
|
35
|
+
UNAVAILABLE = 'UNAVAILABLE',
|
|
36
|
+
DATA_LOSS = 'DATA_LOSS',
|
|
37
|
+
UNAUTHENTICATED = 'UNAUTHENTICATED'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface Trace {
|
|
41
|
+
traceId: string;
|
|
42
|
+
spans: TraceSpan[];
|
|
43
|
+
startTime: number;
|
|
44
|
+
endTime?: number;
|
|
45
|
+
duration?: number;
|
|
46
|
+
rootSpan?: TraceSpan;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface TraceContext {
|
|
50
|
+
traceId: string;
|
|
51
|
+
spanId: string;
|
|
52
|
+
traceFlags: number;
|
|
53
|
+
traceState?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface TracingConfig {
|
|
57
|
+
serviceName: string;
|
|
58
|
+
serviceVersion: string;
|
|
59
|
+
environment: string;
|
|
60
|
+
samplingRate: number;
|
|
61
|
+
endpoint?: string;
|
|
62
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AACA,wBAAgB,UAAU,IAAI,MAAM,CAEnC"}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// Data validation utilities for TraceLens
|
|
2
|
+
import { TraceSpan, Trace } from '../types/trace.types';
|
|
3
|
+
import { WebVitalsMetrics, PerformanceEvent } from '../types/performance.types';
|
|
4
|
+
import { DependencySnapshot, PackageDependency } from '../types/dependency.types';
|
|
5
|
+
import { CVERecord, VulnerabilityAssessment } from '../types/security.types';
|
|
6
|
+
|
|
7
|
+
export class ValidationError extends Error {
|
|
8
|
+
constructor(
|
|
9
|
+
message: string,
|
|
10
|
+
public field: string,
|
|
11
|
+
public value: unknown
|
|
12
|
+
) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'ValidationError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function validateTraceSpan(span: unknown): TraceSpan {
|
|
19
|
+
if (!span || typeof span !== 'object') {
|
|
20
|
+
throw new ValidationError('Span must be an object', 'span', span);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const s = span as Record<string, unknown>;
|
|
24
|
+
|
|
25
|
+
if (!s.traceId || typeof s.traceId !== 'string') {
|
|
26
|
+
throw new ValidationError('traceId must be a string', 'traceId', s.traceId);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!s.spanId || typeof s.spanId !== 'string') {
|
|
30
|
+
throw new ValidationError('spanId must be a string', 'spanId', s.spanId);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!s.operationName || typeof s.operationName !== 'string') {
|
|
34
|
+
throw new ValidationError('operationName must be a string', 'operationName', s.operationName);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!s.startTime || typeof s.startTime !== 'number') {
|
|
38
|
+
throw new ValidationError('startTime must be a number', 'startTime', s.startTime);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (s.endTime !== undefined && typeof s.endTime !== 'number') {
|
|
42
|
+
throw new ValidationError('endTime must be a number', 'endTime', s.endTime);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!s.tags || typeof s.tags !== 'object') {
|
|
46
|
+
throw new ValidationError('tags must be an object', 'tags', s.tags);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return s as unknown as TraceSpan;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function validateTrace(trace: unknown): Trace {
|
|
53
|
+
if (!trace || typeof trace !== 'object') {
|
|
54
|
+
throw new ValidationError('Trace must be an object', 'trace', trace);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const t = trace as Record<string, unknown>;
|
|
58
|
+
|
|
59
|
+
if (!t.traceId || typeof t.traceId !== 'string') {
|
|
60
|
+
throw new ValidationError('traceId must be a string', 'traceId', t.traceId);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!Array.isArray(t.spans)) {
|
|
64
|
+
throw new ValidationError('spans must be an array', 'spans', t.spans);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Validate each span
|
|
68
|
+
t.spans.forEach((span, index) => {
|
|
69
|
+
try {
|
|
70
|
+
validateTraceSpan(span);
|
|
71
|
+
} catch (error: unknown) {
|
|
72
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
73
|
+
throw new ValidationError(
|
|
74
|
+
`Invalid span at index ${index}: ${errorMessage}`,
|
|
75
|
+
`spans[${index}]`,
|
|
76
|
+
span
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return t as unknown as Trace;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function validatePerformanceEvent(event: unknown): PerformanceEvent {
|
|
85
|
+
if (!event || typeof event !== 'object') {
|
|
86
|
+
throw new ValidationError('Event must be an object', 'event', event);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const e = event as Record<string, unknown>;
|
|
90
|
+
|
|
91
|
+
if (!e.id || typeof e.id !== 'string') {
|
|
92
|
+
throw new ValidationError('id must be a string', 'id', e.id);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!e.timestamp || typeof e.timestamp !== 'number') {
|
|
96
|
+
throw new ValidationError('timestamp must be a number', 'timestamp', e.timestamp);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const validTypes = ['web-vitals', 'resource-timing', 'navigation-timing', 'long-task'];
|
|
100
|
+
if (!e.type || !validTypes.includes(e.type as string)) {
|
|
101
|
+
throw new ValidationError(
|
|
102
|
+
`type must be one of: ${validTypes.join(', ')}`,
|
|
103
|
+
'type',
|
|
104
|
+
e.type
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!e.url || typeof e.url !== 'string') {
|
|
109
|
+
throw new ValidationError('url must be a string', 'url', e.url);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return e as unknown as PerformanceEvent;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function validateDependencySnapshot(snapshot: unknown): DependencySnapshot {
|
|
116
|
+
if (!snapshot || typeof snapshot !== 'object') {
|
|
117
|
+
throw new ValidationError('Snapshot must be an object', 'snapshot', snapshot);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const s = snapshot as Record<string, unknown>;
|
|
121
|
+
|
|
122
|
+
if (!s.id || typeof s.id !== 'string') {
|
|
123
|
+
throw new ValidationError('id must be a string', 'id', s.id);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!s.projectId || typeof s.projectId !== 'string') {
|
|
127
|
+
throw new ValidationError('projectId must be a string', 'projectId', s.projectId);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!s.timestamp || typeof s.timestamp !== 'number') {
|
|
131
|
+
throw new ValidationError('timestamp must be a number', 'timestamp', s.timestamp);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!Array.isArray(s.dependencies)) {
|
|
135
|
+
throw new ValidationError('dependencies must be an array', 'dependencies', s.dependencies);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return s as unknown as DependencySnapshot;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function validateCVERecord(cve: unknown): CVERecord {
|
|
142
|
+
if (!cve || typeof cve !== 'object') {
|
|
143
|
+
throw new ValidationError('CVE must be an object', 'cve', cve);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const c = cve as Record<string, unknown>;
|
|
147
|
+
|
|
148
|
+
if (!c.id || typeof c.id !== 'string') {
|
|
149
|
+
throw new ValidationError('id must be a string', 'id', c.id);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Validate CVE ID format
|
|
153
|
+
const cveIdPattern = /^CVE-\d{4}-\d{4,}$/;
|
|
154
|
+
if (!cveIdPattern.test(c.id as string)) {
|
|
155
|
+
throw new ValidationError('id must be in CVE-YYYY-NNNN format', 'id', c.id);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!c.published || typeof c.published !== 'string') {
|
|
159
|
+
throw new ValidationError('published must be a string', 'published', c.published);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return c as unknown as CVERecord;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function sanitizeInput(input: string): string {
|
|
166
|
+
// Remove potentially dangerous characters
|
|
167
|
+
return input
|
|
168
|
+
.replace(/[<>]/g, '') // Remove angle brackets
|
|
169
|
+
.replace(/javascript:/gi, '') // Remove javascript: protocol
|
|
170
|
+
.replace(/on\w+=/gi, '') // Remove event handlers
|
|
171
|
+
.trim();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function validateProjectId(projectId: string): boolean {
|
|
175
|
+
// Project ID should be alphanumeric with hyphens and underscores
|
|
176
|
+
const projectIdPattern = /^[a-zA-Z0-9_-]+$/;
|
|
177
|
+
return projectIdPattern.test(projectId) && projectId.length >= 3 && projectId.length <= 50;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function validateTimestamp(timestamp: number): boolean {
|
|
181
|
+
// Timestamp should be a reasonable Unix timestamp (after 2020, before 2050)
|
|
182
|
+
const minTimestamp = 1577836800000; // 2020-01-01
|
|
183
|
+
const maxTimestamp = 2524608000000; // 2050-01-01
|
|
184
|
+
return timestamp >= minTimestamp && timestamp <= maxTimestamp;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function validateUrl(url: string): boolean {
|
|
188
|
+
try {
|
|
189
|
+
new URL(url);
|
|
190
|
+
return true;
|
|
191
|
+
} catch {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function validateVersion(version: string): boolean {
|
|
197
|
+
// Validate semantic version format
|
|
198
|
+
const semverPattern = /^\d+\.\d+\.\d+(?:-[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*)?(?:\+[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*)?$/;
|
|
199
|
+
return semverPattern.test(version);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function isValidJSON(str: string): boolean {
|
|
203
|
+
try {
|
|
204
|
+
JSON.parse(str);
|
|
205
|
+
return true;
|
|
206
|
+
} catch {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|