feat(#285): Add input sanitization for XSS prevention
Security improvements: - Create sanitization utility using sanitize-html library - Add @Sanitize() and @SanitizeObject() decorators for DTOs - Apply sanitization to vulnerable fields: - Connection rejection/disconnection reasons - Connection metadata - Identity linking metadata - Command payloads - Remove script tags, event handlers, javascript: URLs - Prevent data exfiltration, CSS-based XSS, SVG-based XSS Changes: - Add sanitize.util.ts with recursive sanitization functions - Add sanitize.decorator.ts for class-transformer integration - Update connection.dto.ts with sanitization decorators - Update identity-linking.dto.ts with sanitization decorators - Update command.dto.ts with sanitization decorators - Add comprehensive test coverage including attack vectors Part of M7.1 Remediation Sprint P1 security fixes. Fixes #285 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
52
apps/api/src/common/decorators/sanitize.decorator.ts
Normal file
52
apps/api/src/common/decorators/sanitize.decorator.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* Sanitize Decorator
|
||||||
|
*
|
||||||
|
* Custom class-validator decorator to sanitize string input and prevent XSS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Transform } from "class-transformer";
|
||||||
|
import { sanitizeString, sanitizeObject } from "../utils/sanitize.util";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize decorator for DTO properties
|
||||||
|
* Automatically sanitizes string values to prevent XSS attacks
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* ```typescript
|
||||||
|
* class MyDto {
|
||||||
|
* @Sanitize()
|
||||||
|
* @IsString()
|
||||||
|
* userInput!: string;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function Sanitize(): PropertyDecorator {
|
||||||
|
return Transform(({ value }: { value: unknown }) => {
|
||||||
|
if (typeof value === "string") {
|
||||||
|
return sanitizeString(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SanitizeObject decorator for nested objects
|
||||||
|
* Recursively sanitizes all string values in an object
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* ```typescript
|
||||||
|
* class MyDto {
|
||||||
|
* @SanitizeObject()
|
||||||
|
* @IsObject()
|
||||||
|
* metadata?: Record<string, unknown>;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function SanitizeObject(): PropertyDecorator {
|
||||||
|
return Transform(({ value }: { value: unknown }) => {
|
||||||
|
if (typeof value === "object" && value !== null) {
|
||||||
|
return sanitizeObject(value as Record<string, unknown>);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
}
|
||||||
171
apps/api/src/common/utils/sanitize.util.spec.ts
Normal file
171
apps/api/src/common/utils/sanitize.util.spec.ts
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
/**
|
||||||
|
* Sanitization Utility Tests
|
||||||
|
*
|
||||||
|
* Tests for HTML sanitization and XSS prevention.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { sanitizeString, sanitizeObject, sanitizeArray } from "./sanitize.util";
|
||||||
|
|
||||||
|
describe("Sanitization Utilities", () => {
|
||||||
|
describe("sanitizeString", () => {
|
||||||
|
it("should remove script tags", () => {
|
||||||
|
const dirty = '<script>alert("XSS")</script>Hello';
|
||||||
|
const clean = sanitizeString(dirty);
|
||||||
|
|
||||||
|
expect(clean).not.toContain("<script>");
|
||||||
|
expect(clean).not.toContain("alert");
|
||||||
|
expect(clean).toContain("Hello");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove javascript: URLs", () => {
|
||||||
|
const dirty = '<a href="javascript:alert(1)">Click</a>';
|
||||||
|
const clean = sanitizeString(dirty);
|
||||||
|
|
||||||
|
expect(clean).not.toContain("javascript:");
|
||||||
|
expect(clean).toContain("Click");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove on* event handlers", () => {
|
||||||
|
const dirty = '<div onclick="alert(1)">Click me</div>';
|
||||||
|
const clean = sanitizeString(dirty);
|
||||||
|
|
||||||
|
expect(clean).not.toContain("onclick");
|
||||||
|
expect(clean).toContain("Click me");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should allow safe HTML tags", () => {
|
||||||
|
const input = "<p>Hello <b>world</b>!</p>";
|
||||||
|
const clean = sanitizeString(input);
|
||||||
|
|
||||||
|
expect(clean).toContain("<p>");
|
||||||
|
expect(clean).toContain("<b>");
|
||||||
|
expect(clean).toContain("Hello");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle null and undefined", () => {
|
||||||
|
expect(sanitizeString(null)).toBe("");
|
||||||
|
expect(sanitizeString(undefined)).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle non-string values", () => {
|
||||||
|
expect(sanitizeString(123 as any)).toBe("123");
|
||||||
|
expect(sanitizeString(true as any)).toBe("true");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should prevent data exfiltration via img tags", () => {
|
||||||
|
const dirty = '<img src="x" onerror="fetch(\'/steal?data=\'+document.cookie)">';
|
||||||
|
const clean = sanitizeString(dirty);
|
||||||
|
|
||||||
|
expect(clean).not.toContain("onerror");
|
||||||
|
expect(clean).not.toContain("fetch");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove style tags with malicious CSS", () => {
|
||||||
|
const dirty = '<style>body { background: url("javascript:alert(1)") }</style>Hello';
|
||||||
|
const clean = sanitizeString(dirty);
|
||||||
|
|
||||||
|
expect(clean).not.toContain("<style>");
|
||||||
|
expect(clean).not.toContain("javascript:");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("sanitizeObject", () => {
|
||||||
|
it("should sanitize all string values in an object", () => {
|
||||||
|
const dirty = {
|
||||||
|
name: '<script>alert("XSS")</script>John',
|
||||||
|
description: "Safe text",
|
||||||
|
nested: {
|
||||||
|
value: '<img src=x onerror="alert(1)">',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const clean = sanitizeObject(dirty);
|
||||||
|
|
||||||
|
expect(clean.name).not.toContain("<script>");
|
||||||
|
expect(clean.name).toContain("John");
|
||||||
|
expect(clean.description).toBe("Safe text");
|
||||||
|
expect(clean.nested.value).not.toContain("onerror");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should preserve non-string values", () => {
|
||||||
|
const input = {
|
||||||
|
string: "test",
|
||||||
|
number: 123,
|
||||||
|
boolean: true,
|
||||||
|
nullValue: null,
|
||||||
|
undefinedValue: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const clean = sanitizeObject(input);
|
||||||
|
|
||||||
|
expect(clean.number).toBe(123);
|
||||||
|
expect(clean.boolean).toBe(true);
|
||||||
|
expect(clean.nullValue).toBeNull();
|
||||||
|
expect(clean.undefinedValue).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle arrays within objects", () => {
|
||||||
|
const input = {
|
||||||
|
tags: ["<script>alert(1)</script>safe", "another<script>"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const clean = sanitizeObject(input);
|
||||||
|
|
||||||
|
expect(clean.tags[0]).not.toContain("<script>");
|
||||||
|
expect(clean.tags[0]).toContain("safe");
|
||||||
|
expect(clean.tags[1]).not.toContain("<script>");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle deeply nested objects", () => {
|
||||||
|
const input = {
|
||||||
|
level1: {
|
||||||
|
level2: {
|
||||||
|
level3: {
|
||||||
|
xss: "<script>alert(1)</script>",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const clean = sanitizeObject(input);
|
||||||
|
|
||||||
|
expect(clean.level1.level2.level3.xss).not.toContain("<script>");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("sanitizeArray", () => {
|
||||||
|
it("should sanitize all strings in an array", () => {
|
||||||
|
const dirty = ["<script>alert(1)</script>safe", "clean", '<img src=x onerror="alert(2)">'];
|
||||||
|
|
||||||
|
const clean = sanitizeArray(dirty);
|
||||||
|
|
||||||
|
expect(clean[0]).not.toContain("<script>");
|
||||||
|
expect(clean[0]).toContain("safe");
|
||||||
|
expect(clean[1]).toBe("clean");
|
||||||
|
expect(clean[2]).not.toContain("onerror");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle arrays with mixed types", () => {
|
||||||
|
const input = ["<script>test</script>", 123, true, null, { key: "<b>value</b>" }];
|
||||||
|
|
||||||
|
const clean = sanitizeArray(input);
|
||||||
|
|
||||||
|
expect(clean[0]).not.toContain("<script>");
|
||||||
|
expect(clean[1]).toBe(123);
|
||||||
|
expect(clean[2]).toBe(true);
|
||||||
|
expect(clean[3]).toBeNull();
|
||||||
|
expect((clean[4] as any).key).toContain("<b>");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle nested arrays", () => {
|
||||||
|
const input = [["<script>alert(1)</script>", "safe"], ['<img src=x onerror="alert(2)">']];
|
||||||
|
|
||||||
|
const clean = sanitizeArray(input);
|
||||||
|
|
||||||
|
expect(clean[0][0]).not.toContain("<script>");
|
||||||
|
expect(clean[0][1]).toBe("safe");
|
||||||
|
expect(clean[1][0]).not.toContain("onerror");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
123
apps/api/src/common/utils/sanitize.util.ts
Normal file
123
apps/api/src/common/utils/sanitize.util.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
/**
|
||||||
|
* Sanitization Utilities
|
||||||
|
*
|
||||||
|
* Provides HTML/XSS sanitization for user-controlled input.
|
||||||
|
* Uses sanitize-html to prevent XSS attacks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import sanitizeHtml from "sanitize-html";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize options for strict mode (default)
|
||||||
|
* Allows only safe tags and attributes, removes all scripts and dangerous content
|
||||||
|
*/
|
||||||
|
const STRICT_OPTIONS: sanitizeHtml.IOptions = {
|
||||||
|
allowedTags: ["p", "b", "i", "em", "strong", "a", "br", "ul", "ol", "li"],
|
||||||
|
allowedAttributes: {
|
||||||
|
a: ["href"],
|
||||||
|
},
|
||||||
|
allowedSchemes: ["http", "https", "mailto"],
|
||||||
|
disallowedTagsMode: "discard",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize a string value to prevent XSS attacks
|
||||||
|
* Removes dangerous HTML tags, scripts, and event handlers
|
||||||
|
*
|
||||||
|
* @param value - String to sanitize
|
||||||
|
* @param options - Optional sanitize-html options (defaults to strict)
|
||||||
|
* @returns Sanitized string
|
||||||
|
*/
|
||||||
|
export function sanitizeString(
|
||||||
|
value: string | null | undefined,
|
||||||
|
options: sanitizeHtml.IOptions = STRICT_OPTIONS
|
||||||
|
): string {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert non-strings to strings
|
||||||
|
const stringValue = typeof value === "string" ? value : String(value);
|
||||||
|
|
||||||
|
return sanitizeHtml(stringValue, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize all string values in an object recursively
|
||||||
|
* Preserves object structure and non-string values
|
||||||
|
*
|
||||||
|
* @param obj - Object to sanitize
|
||||||
|
* @param options - Optional sanitize-html options
|
||||||
|
* @returns Sanitized object
|
||||||
|
*/
|
||||||
|
export function sanitizeObject<T extends Record<string, unknown> | null | undefined>(
|
||||||
|
obj: T,
|
||||||
|
options: sanitizeHtml.IOptions = STRICT_OPTIONS
|
||||||
|
): T {
|
||||||
|
// Handle null/undefined
|
||||||
|
if (obj == null) {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle arrays
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
return obj.map((item: unknown) => {
|
||||||
|
if (typeof item === "string") {
|
||||||
|
return sanitizeString(item, options);
|
||||||
|
}
|
||||||
|
if (typeof item === "object" && item !== null) {
|
||||||
|
return sanitizeObject(item as Record<string, unknown>, options);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}) as unknown as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle objects
|
||||||
|
const sanitized: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
|
if (typeof value === "string") {
|
||||||
|
sanitized[key] = sanitizeString(value, options);
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
sanitized[key] = sanitizeArray(value, options);
|
||||||
|
} else if (typeof value === "object" && value !== null) {
|
||||||
|
sanitized[key] = sanitizeObject(value as Record<string, unknown>, options);
|
||||||
|
} else {
|
||||||
|
sanitized[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sanitized as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize all string values in an array recursively
|
||||||
|
* Preserves array structure and non-string values
|
||||||
|
*
|
||||||
|
* @param arr - Array to sanitize
|
||||||
|
* @param options - Optional sanitize-html options
|
||||||
|
* @returns Sanitized array
|
||||||
|
*/
|
||||||
|
export function sanitizeArray<T extends unknown[] = unknown[]>(
|
||||||
|
arr: T,
|
||||||
|
options: sanitizeHtml.IOptions = STRICT_OPTIONS
|
||||||
|
): T {
|
||||||
|
if (!Array.isArray(arr)) {
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = arr.map((item: unknown) => {
|
||||||
|
if (typeof item === "string") {
|
||||||
|
return sanitizeString(item, options);
|
||||||
|
}
|
||||||
|
if (Array.isArray(item)) {
|
||||||
|
return sanitizeArray(item as unknown[], options);
|
||||||
|
}
|
||||||
|
if (typeof item === "object" && item !== null) {
|
||||||
|
return sanitizeObject(item as Record<string, unknown>, options);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
|
return result as T;
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import { IsString, IsObject, IsNotEmpty, IsNumber } from "class-validator";
|
import { IsString, IsObject, IsNotEmpty, IsNumber } from "class-validator";
|
||||||
import type { CommandMessage } from "../types/message.types";
|
import type { CommandMessage } from "../types/message.types";
|
||||||
|
import { SanitizeObject } from "../../common/decorators/sanitize.decorator";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DTO for sending a command to a remote instance
|
* DTO for sending a command to a remote instance
|
||||||
@@ -21,6 +22,7 @@ export class SendCommandDto {
|
|||||||
|
|
||||||
@IsObject()
|
@IsObject()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
|
@SanitizeObject()
|
||||||
payload!: Record<string, unknown>;
|
payload!: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +44,7 @@ export class IncomingCommandDto implements CommandMessage {
|
|||||||
|
|
||||||
@IsObject()
|
@IsObject()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
|
@SanitizeObject()
|
||||||
payload!: Record<string, unknown>;
|
payload!: Record<string, unknown>;
|
||||||
|
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { IsString, IsUrl, IsOptional, IsObject, IsNumber } from "class-validator";
|
import { IsString, IsUrl, IsOptional, IsObject, IsNumber } from "class-validator";
|
||||||
|
import { Sanitize, SanitizeObject } from "../../common/decorators/sanitize.decorator";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DTO for initiating a connection
|
* DTO for initiating a connection
|
||||||
@@ -20,6 +21,7 @@ export class InitiateConnectionDto {
|
|||||||
export class AcceptConnectionDto {
|
export class AcceptConnectionDto {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsObject()
|
@IsObject()
|
||||||
|
@SanitizeObject()
|
||||||
metadata?: Record<string, unknown>;
|
metadata?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +30,7 @@ export class AcceptConnectionDto {
|
|||||||
*/
|
*/
|
||||||
export class RejectConnectionDto {
|
export class RejectConnectionDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@Sanitize()
|
||||||
reason!: string;
|
reason!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +40,7 @@ export class RejectConnectionDto {
|
|||||||
export class DisconnectConnectionDto {
|
export class DisconnectConnectionDto {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@Sanitize()
|
||||||
reason?: string;
|
reason?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { IsString, IsEmail, IsOptional, IsObject, IsArray, IsNumber } from "class-validator";
|
import { IsString, IsEmail, IsOptional, IsObject, IsArray, IsNumber } from "class-validator";
|
||||||
|
import { SanitizeObject } from "../../common/decorators/sanitize.decorator";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DTO for verifying identity from remote instance
|
* DTO for verifying identity from remote instance
|
||||||
@@ -81,6 +82,7 @@ export class CreateIdentityMappingDto {
|
|||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsObject()
|
@IsObject()
|
||||||
|
@SanitizeObject()
|
||||||
metadata?: Record<string, unknown>;
|
metadata?: Record<string, unknown>;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@@ -94,5 +96,6 @@ export class CreateIdentityMappingDto {
|
|||||||
export class UpdateIdentityMappingDto {
|
export class UpdateIdentityMappingDto {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsObject()
|
@IsObject()
|
||||||
|
@SanitizeObject()
|
||||||
metadata?: Record<string, unknown>;
|
metadata?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|||||||
225
apps/api/src/federation/dto/sanitization.integration.spec.ts
Normal file
225
apps/api/src/federation/dto/sanitization.integration.spec.ts
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
/**
|
||||||
|
* DTO Sanitization Integration Tests
|
||||||
|
*
|
||||||
|
* Tests that DTOs properly sanitize XSS attempts.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { plainToInstance } from "class-transformer";
|
||||||
|
import { validate } from "class-validator";
|
||||||
|
import {
|
||||||
|
RejectConnectionDto,
|
||||||
|
DisconnectConnectionDto,
|
||||||
|
AcceptConnectionDto,
|
||||||
|
} from "./connection.dto";
|
||||||
|
import { CreateIdentityMappingDto, UpdateIdentityMappingDto } from "./identity-linking.dto";
|
||||||
|
import { SendCommandDto, IncomingCommandDto } from "./command.dto";
|
||||||
|
|
||||||
|
describe("DTO Sanitization Integration", () => {
|
||||||
|
describe("Connection DTOs", () => {
|
||||||
|
it("should sanitize rejection reason", async () => {
|
||||||
|
const dirty = {
|
||||||
|
reason: '<script>alert("XSS")</script>Connection rejected',
|
||||||
|
};
|
||||||
|
|
||||||
|
const dto = plainToInstance(RejectConnectionDto, dirty);
|
||||||
|
const errors = await validate(dto);
|
||||||
|
|
||||||
|
expect(errors).toHaveLength(0);
|
||||||
|
expect(dto.reason).not.toContain("<script>");
|
||||||
|
expect(dto.reason).toContain("Connection rejected");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should sanitize disconnection reason", async () => {
|
||||||
|
const dirty = {
|
||||||
|
reason: '<img src=x onerror="alert(1)">Goodbye',
|
||||||
|
};
|
||||||
|
|
||||||
|
const dto = plainToInstance(DisconnectConnectionDto, dirty);
|
||||||
|
const errors = await validate(dto);
|
||||||
|
|
||||||
|
expect(errors).toHaveLength(0);
|
||||||
|
expect(dto.reason).not.toContain("onerror");
|
||||||
|
expect(dto.reason).toContain("Goodbye");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should sanitize acceptance metadata", async () => {
|
||||||
|
const dirty = {
|
||||||
|
metadata: {
|
||||||
|
note: "<script>alert(1)</script>Important",
|
||||||
|
nested: {
|
||||||
|
value: "<img src=x onerror=\"fetch('/steal')\">",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const dto = plainToInstance(AcceptConnectionDto, dirty);
|
||||||
|
const errors = await validate(dto);
|
||||||
|
|
||||||
|
expect(errors).toHaveLength(0);
|
||||||
|
expect(dto.metadata!.note).not.toContain("<script>");
|
||||||
|
expect(dto.metadata!.note).toContain("Important");
|
||||||
|
expect((dto.metadata!.nested as any).value).not.toContain("onerror");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Identity Linking DTOs", () => {
|
||||||
|
it("should sanitize identity mapping metadata", async () => {
|
||||||
|
const dirty = {
|
||||||
|
remoteInstanceId: "instance-123",
|
||||||
|
remoteUserId: "user-456",
|
||||||
|
oidcSubject: "sub-789",
|
||||||
|
email: "test@example.com",
|
||||||
|
metadata: {
|
||||||
|
displayName: '<script>alert("XSS")</script>John Doe',
|
||||||
|
bio: '<img src=x onerror="alert(1)">Developer',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const dto = plainToInstance(CreateIdentityMappingDto, dirty);
|
||||||
|
const errors = await validate(dto);
|
||||||
|
|
||||||
|
expect(errors).toHaveLength(0);
|
||||||
|
expect(dto.metadata!.displayName).not.toContain("<script>");
|
||||||
|
expect(dto.metadata!.displayName).toContain("John Doe");
|
||||||
|
expect(dto.metadata!.bio).not.toContain("onerror");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should sanitize update metadata", async () => {
|
||||||
|
const dirty = {
|
||||||
|
metadata: {
|
||||||
|
tags: ["<script>tag1</script>", "<b>tag2</b>"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const dto = plainToInstance(UpdateIdentityMappingDto, dirty);
|
||||||
|
const errors = await validate(dto);
|
||||||
|
|
||||||
|
expect(errors).toHaveLength(0);
|
||||||
|
expect((dto.metadata!.tags as any)[0]).not.toContain("<script>");
|
||||||
|
expect((dto.metadata!.tags as any)[1]).toContain("<b>");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Command DTOs", () => {
|
||||||
|
it("should sanitize command payload", async () => {
|
||||||
|
const dirty = {
|
||||||
|
connectionId: "conn-123",
|
||||||
|
commandType: "execute",
|
||||||
|
payload: {
|
||||||
|
script: '<script>alert("XSS")</script>console.log("hello")',
|
||||||
|
params: {
|
||||||
|
arg1: '<img src=x onerror="alert(1)">',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const dto = plainToInstance(SendCommandDto, dirty);
|
||||||
|
const errors = await validate(dto);
|
||||||
|
|
||||||
|
expect(errors).toHaveLength(0);
|
||||||
|
expect(dto.payload.script).not.toContain("<script>");
|
||||||
|
expect((dto.payload.params as any).arg1).not.toContain("onerror");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should sanitize incoming command payload", async () => {
|
||||||
|
const dirty = {
|
||||||
|
messageId: "msg-123",
|
||||||
|
instanceId: "instance-456",
|
||||||
|
commandType: "update",
|
||||||
|
payload: {
|
||||||
|
data: '<style>body{background:url("javascript:alert(1)")}</style>Data',
|
||||||
|
metadata: {
|
||||||
|
author: "<script>alert(1)</script>Admin",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
timestamp: Date.now(),
|
||||||
|
signature: "sig-789",
|
||||||
|
};
|
||||||
|
|
||||||
|
const dto = plainToInstance(IncomingCommandDto, dirty);
|
||||||
|
const errors = await validate(dto);
|
||||||
|
|
||||||
|
expect(errors).toHaveLength(0);
|
||||||
|
expect(dto.payload.data).not.toContain("<style>");
|
||||||
|
expect(dto.payload.data).not.toContain("javascript:");
|
||||||
|
expect((dto.payload.metadata as any).author).not.toContain("<script>");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle deeply nested XSS attempts", async () => {
|
||||||
|
const dirty = {
|
||||||
|
connectionId: "conn-123",
|
||||||
|
commandType: "complex",
|
||||||
|
payload: {
|
||||||
|
level1: {
|
||||||
|
level2: {
|
||||||
|
level3: {
|
||||||
|
xss: "<script>alert(1)</script>",
|
||||||
|
array: ['<img src=x onerror="alert(2)">', "safe"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const dto = plainToInstance(SendCommandDto, dirty);
|
||||||
|
const errors = await validate(dto);
|
||||||
|
|
||||||
|
expect(errors).toHaveLength(0);
|
||||||
|
const level3 = (dto.payload.level1 as any).level2.level3;
|
||||||
|
expect(level3.xss).not.toContain("<script>");
|
||||||
|
expect(level3.array[0]).not.toContain("onerror");
|
||||||
|
expect(level3.array[1]).toBe("safe");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("XSS Attack Vectors", () => {
|
||||||
|
it("should prevent javascript: URL injection", async () => {
|
||||||
|
const dirty = {
|
||||||
|
metadata: {
|
||||||
|
link: '<a href="javascript:alert(1)">Click</a>',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const dto = plainToInstance(AcceptConnectionDto, dirty);
|
||||||
|
|
||||||
|
expect(dto.metadata!.link).not.toContain("javascript:");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should prevent data exfiltration attempts", async () => {
|
||||||
|
const dirty = {
|
||||||
|
reason: "<img src=x onerror=\"fetch('/api/steal?cookie='+document.cookie)\">",
|
||||||
|
};
|
||||||
|
|
||||||
|
const dto = plainToInstance(RejectConnectionDto, dirty);
|
||||||
|
|
||||||
|
expect(dto.reason).not.toContain("onerror");
|
||||||
|
expect(dto.reason).not.toContain("fetch");
|
||||||
|
expect(dto.reason).not.toContain("document.cookie");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should prevent CSS-based XSS", async () => {
|
||||||
|
const dirty = {
|
||||||
|
metadata: {
|
||||||
|
style: '<style>@import "http://evil.com/steal.css";</style>',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const dto = plainToInstance(AcceptConnectionDto, dirty);
|
||||||
|
|
||||||
|
expect(dto.metadata!.style).not.toContain("<style>");
|
||||||
|
expect(dto.metadata!.style).not.toContain("@import");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should prevent SVG-based XSS", async () => {
|
||||||
|
const dirty = {
|
||||||
|
reason: '<svg onload="alert(1)">Test</svg>',
|
||||||
|
};
|
||||||
|
|
||||||
|
const dto = plainToInstance(RejectConnectionDto, dirty);
|
||||||
|
|
||||||
|
expect(dto.reason).not.toContain("<svg");
|
||||||
|
expect(dto.reason).not.toContain("onload");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# QA Remediation Report
|
||||||
|
|
||||||
|
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/common/decorators/sanitize.decorator.ts
|
||||||
|
**Tool Used:** Write
|
||||||
|
**Epic:** general
|
||||||
|
**Iteration:** 1
|
||||||
|
**Generated:** 2026-02-03 21:44:16
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Pending QA validation
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
This report was created by the QA automation hook.
|
||||||
|
To process this report, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-common-decorators-sanitize.decorator.ts_20260203-2144_1_remediation_needed.md"
|
||||||
|
```
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# QA Remediation Report
|
||||||
|
|
||||||
|
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/common/decorators/sanitize.decorator.ts
|
||||||
|
**Tool Used:** Edit
|
||||||
|
**Epic:** general
|
||||||
|
**Iteration:** 1
|
||||||
|
**Generated:** 2026-02-03 21:45:25
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Pending QA validation
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
This report was created by the QA automation hook.
|
||||||
|
To process this report, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-common-decorators-sanitize.decorator.ts_20260203-2145_1_remediation_needed.md"
|
||||||
|
```
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# QA Remediation Report
|
||||||
|
|
||||||
|
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/common/decorators/sanitize.decorator.ts
|
||||||
|
**Tool Used:** Edit
|
||||||
|
**Epic:** general
|
||||||
|
**Iteration:** 2
|
||||||
|
**Generated:** 2026-02-03 21:45:31
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Pending QA validation
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
This report was created by the QA automation hook.
|
||||||
|
To process this report, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-common-decorators-sanitize.decorator.ts_20260203-2145_2_remediation_needed.md"
|
||||||
|
```
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# QA Remediation Report
|
||||||
|
|
||||||
|
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/common/decorators/sanitize.decorator.ts
|
||||||
|
**Tool Used:** Edit
|
||||||
|
**Epic:** general
|
||||||
|
**Iteration:** 1
|
||||||
|
**Generated:** 2026-02-03 21:46:21
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Pending QA validation
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
This report was created by the QA automation hook.
|
||||||
|
To process this report, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-common-decorators-sanitize.decorator.ts_20260203-2146_1_remediation_needed.md"
|
||||||
|
```
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# QA Remediation Report
|
||||||
|
|
||||||
|
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/common/utils/sanitize.util.spec.ts
|
||||||
|
**Tool Used:** Write
|
||||||
|
**Epic:** general
|
||||||
|
**Iteration:** 1
|
||||||
|
**Generated:** 2026-02-03 21:43:46
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Pending QA validation
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
This report was created by the QA automation hook.
|
||||||
|
To process this report, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-common-utils-sanitize.util.spec.ts_20260203-2143_1_remediation_needed.md"
|
||||||
|
```
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# QA Remediation Report
|
||||||
|
|
||||||
|
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/common/utils/sanitize.util.ts
|
||||||
|
**Tool Used:** Write
|
||||||
|
**Epic:** general
|
||||||
|
**Iteration:** 1
|
||||||
|
**Generated:** 2026-02-03 21:44:05
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Pending QA validation
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
This report was created by the QA automation hook.
|
||||||
|
To process this report, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-common-utils-sanitize.util.ts_20260203-2144_1_remediation_needed.md"
|
||||||
|
```
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# QA Remediation Report
|
||||||
|
|
||||||
|
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/common/utils/sanitize.util.ts
|
||||||
|
**Tool Used:** Edit
|
||||||
|
**Epic:** general
|
||||||
|
**Iteration:** 1
|
||||||
|
**Generated:** 2026-02-03 21:46:12
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Pending QA validation
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
This report was created by the QA automation hook.
|
||||||
|
To process this report, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-common-utils-sanitize.util.ts_20260203-2146_1_remediation_needed.md"
|
||||||
|
```
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# QA Remediation Report
|
||||||
|
|
||||||
|
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/common/utils/sanitize.util.ts
|
||||||
|
**Tool Used:** Edit
|
||||||
|
**Epic:** general
|
||||||
|
**Iteration:** 2
|
||||||
|
**Generated:** 2026-02-03 21:46:55
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Pending QA validation
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
This report was created by the QA automation hook.
|
||||||
|
To process this report, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-common-utils-sanitize.util.ts_20260203-2146_2_remediation_needed.md"
|
||||||
|
```
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# QA Remediation Report
|
||||||
|
|
||||||
|
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/common/utils/sanitize.util.ts
|
||||||
|
**Tool Used:** Edit
|
||||||
|
**Epic:** general
|
||||||
|
**Iteration:** 1
|
||||||
|
**Generated:** 2026-02-03 21:47:02
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Pending QA validation
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
This report was created by the QA automation hook.
|
||||||
|
To process this report, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-common-utils-sanitize.util.ts_20260203-2147_1_remediation_needed.md"
|
||||||
|
```
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# QA Remediation Report
|
||||||
|
|
||||||
|
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/common/utils/sanitize.util.ts
|
||||||
|
**Tool Used:** Edit
|
||||||
|
**Epic:** general
|
||||||
|
**Iteration:** 2
|
||||||
|
**Generated:** 2026-02-03 21:47:27
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Pending QA validation
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
This report was created by the QA automation hook.
|
||||||
|
To process this report, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-common-utils-sanitize.util.ts_20260203-2147_2_remediation_needed.md"
|
||||||
|
```
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# QA Remediation Report
|
||||||
|
|
||||||
|
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/dto/command.dto.ts
|
||||||
|
**Tool Used:** Edit
|
||||||
|
**Epic:** general
|
||||||
|
**Iteration:** 1
|
||||||
|
**Generated:** 2026-02-03 21:44:42
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Pending QA validation
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
This report was created by the QA automation hook.
|
||||||
|
To process this report, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-federation-dto-command.dto.ts_20260203-2144_1_remediation_needed.md"
|
||||||
|
```
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# QA Remediation Report
|
||||||
|
|
||||||
|
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/dto/command.dto.ts
|
||||||
|
**Tool Used:** Edit
|
||||||
|
**Epic:** general
|
||||||
|
**Iteration:** 2
|
||||||
|
**Generated:** 2026-02-03 21:44:50
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Pending QA validation
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
This report was created by the QA automation hook.
|
||||||
|
To process this report, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-federation-dto-command.dto.ts_20260203-2144_2_remediation_needed.md"
|
||||||
|
```
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# QA Remediation Report
|
||||||
|
|
||||||
|
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/dto/connection.dto.ts
|
||||||
|
**Tool Used:** Edit
|
||||||
|
**Epic:** general
|
||||||
|
**Iteration:** 1
|
||||||
|
**Generated:** 2026-02-03 21:44:21
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Pending QA validation
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
This report was created by the QA automation hook.
|
||||||
|
To process this report, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-federation-dto-connection.dto.ts_20260203-2144_1_remediation_needed.md"
|
||||||
|
```
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# QA Remediation Report
|
||||||
|
|
||||||
|
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/dto/connection.dto.ts
|
||||||
|
**Tool Used:** Edit
|
||||||
|
**Epic:** general
|
||||||
|
**Iteration:** 2
|
||||||
|
**Generated:** 2026-02-03 21:44:26
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Pending QA validation
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
This report was created by the QA automation hook.
|
||||||
|
To process this report, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-federation-dto-connection.dto.ts_20260203-2144_2_remediation_needed.md"
|
||||||
|
```
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# QA Remediation Report
|
||||||
|
|
||||||
|
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/dto/identity-linking.dto.ts
|
||||||
|
**Tool Used:** Edit
|
||||||
|
**Epic:** general
|
||||||
|
**Iteration:** 1
|
||||||
|
**Generated:** 2026-02-03 21:44:31
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Pending QA validation
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
This report was created by the QA automation hook.
|
||||||
|
To process this report, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-federation-dto-identity-linking.dto.ts_20260203-2144_1_remediation_needed.md"
|
||||||
|
```
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# QA Remediation Report
|
||||||
|
|
||||||
|
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/dto/identity-linking.dto.ts
|
||||||
|
**Tool Used:** Edit
|
||||||
|
**Epic:** general
|
||||||
|
**Iteration:** 2
|
||||||
|
**Generated:** 2026-02-03 21:44:37
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Pending QA validation
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
This report was created by the QA automation hook.
|
||||||
|
To process this report, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-federation-dto-identity-linking.dto.ts_20260203-2144_2_remediation_needed.md"
|
||||||
|
```
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# QA Remediation Report
|
||||||
|
|
||||||
|
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/dto/sanitization.integration.spec.ts
|
||||||
|
**Tool Used:** Write
|
||||||
|
**Epic:** general
|
||||||
|
**Iteration:** 1
|
||||||
|
**Generated:** 2026-02-03 21:45:15
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Pending QA validation
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
This report was created by the QA automation hook.
|
||||||
|
To process this report, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-federation-dto-sanitization.integration.spec.ts_20260203-2145_1_remediation_needed.md"
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user