Security Sprint M7.1: Complete P1 Security Fixes (#284-#287) #320
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;
|
||||
});
|
||||
}
|
||||
54
apps/api/src/common/providers/redis.provider.ts
Normal file
54
apps/api/src/common/providers/redis.provider.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Redis Provider
|
||||
*
|
||||
* Provides Redis/Valkey client instance for the application.
|
||||
*/
|
||||
|
||||
import { Logger } from "@nestjs/common";
|
||||
import type { Provider } from "@nestjs/common";
|
||||
import Redis from "ioredis";
|
||||
|
||||
/**
|
||||
* Factory function to create Redis client instance
|
||||
*/
|
||||
function createRedisClient(): Redis {
|
||||
const logger = new Logger("RedisProvider");
|
||||
const valkeyUrl = process.env.VALKEY_URL ?? "redis://localhost:6379";
|
||||
|
||||
logger.log(`Connecting to Valkey at ${valkeyUrl}`);
|
||||
|
||||
const client = new Redis(valkeyUrl, {
|
||||
maxRetriesPerRequest: 3,
|
||||
retryStrategy: (times) => {
|
||||
const delay = Math.min(times * 50, 2000);
|
||||
logger.warn(
|
||||
`Valkey connection retry attempt ${times.toString()}, waiting ${delay.toString()}ms`
|
||||
);
|
||||
return delay;
|
||||
},
|
||||
reconnectOnError: (err) => {
|
||||
logger.error("Valkey connection error:", err.message);
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
client.on("connect", () => {
|
||||
logger.log("Connected to Valkey");
|
||||
});
|
||||
|
||||
client.on("error", (err) => {
|
||||
logger.error("Valkey error:", err.message);
|
||||
});
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redis Client Provider
|
||||
*
|
||||
* Provides a singleton Redis client instance for dependency injection.
|
||||
*/
|
||||
export const RedisProvider: Provider = {
|
||||
provide: "REDIS_CLIENT",
|
||||
useFactory: createRedisClient,
|
||||
};
|
||||
142
apps/api/src/common/utils/redact.util.spec.ts
Normal file
142
apps/api/src/common/utils/redact.util.spec.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Redaction Utility Tests
|
||||
*
|
||||
* Tests for sensitive data redaction in logs.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { redactSensitiveData, redactUserId, redactInstanceId } from "./redact.util";
|
||||
|
||||
describe("Redaction Utilities", () => {
|
||||
describe("redactUserId", () => {
|
||||
it("should redact user IDs", () => {
|
||||
expect(redactUserId("user-12345")).toBe("user-***");
|
||||
});
|
||||
|
||||
it("should handle null/undefined", () => {
|
||||
expect(redactUserId(null)).toBe("user-***");
|
||||
expect(redactUserId(undefined)).toBe("user-***");
|
||||
});
|
||||
});
|
||||
|
||||
describe("redactInstanceId", () => {
|
||||
it("should redact instance IDs", () => {
|
||||
expect(redactInstanceId("instance-abc-def")).toBe("instance-***");
|
||||
});
|
||||
|
||||
it("should handle null/undefined", () => {
|
||||
expect(redactInstanceId(null)).toBe("instance-***");
|
||||
expect(redactInstanceId(undefined)).toBe("instance-***");
|
||||
});
|
||||
});
|
||||
|
||||
describe("redactSensitiveData", () => {
|
||||
it("should redact user IDs", () => {
|
||||
const data = { userId: "user-123", name: "Test" };
|
||||
const redacted = redactSensitiveData(data);
|
||||
|
||||
expect(redacted.userId).toBe("user-***");
|
||||
expect(redacted.name).toBe("Test");
|
||||
});
|
||||
|
||||
it("should redact instance IDs", () => {
|
||||
const data = { instanceId: "instance-456", url: "https://example.com" };
|
||||
const redacted = redactSensitiveData(data);
|
||||
|
||||
expect(redacted.instanceId).toBe("instance-***");
|
||||
expect(redacted.url).toBe("https://example.com");
|
||||
});
|
||||
|
||||
it("should redact metadata objects", () => {
|
||||
const data = {
|
||||
metadata: { secret: "value", public: "data" },
|
||||
other: "field",
|
||||
};
|
||||
const redacted = redactSensitiveData(data);
|
||||
|
||||
expect(redacted.metadata).toBe("[REDACTED]");
|
||||
expect(redacted.other).toBe("field");
|
||||
});
|
||||
|
||||
it("should redact payloads", () => {
|
||||
const data = {
|
||||
payload: { command: "execute", args: ["secret"] },
|
||||
type: "command",
|
||||
};
|
||||
const redacted = redactSensitiveData(data);
|
||||
|
||||
expect(redacted.payload).toBe("[REDACTED]");
|
||||
expect(redacted.type).toBe("command");
|
||||
});
|
||||
|
||||
it("should redact private keys", () => {
|
||||
const data = {
|
||||
privateKey: "-----BEGIN PRIVATE KEY-----\n...",
|
||||
publicKey: "-----BEGIN PUBLIC KEY-----\n...",
|
||||
};
|
||||
const redacted = redactSensitiveData(data);
|
||||
|
||||
expect(redacted.privateKey).toBe("[REDACTED]");
|
||||
expect(redacted.publicKey).toBe("-----BEGIN PUBLIC KEY-----\n...");
|
||||
});
|
||||
|
||||
it("should redact tokens", () => {
|
||||
const data = {
|
||||
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
oidcToken: "token-value",
|
||||
};
|
||||
const redacted = redactSensitiveData(data);
|
||||
|
||||
expect(redacted.token).toBe("[REDACTED]");
|
||||
expect(redacted.oidcToken).toBe("[REDACTED]");
|
||||
});
|
||||
|
||||
it("should handle nested objects", () => {
|
||||
const data = {
|
||||
user: {
|
||||
id: "user-789",
|
||||
email: "test@example.com",
|
||||
},
|
||||
metadata: { nested: "data" },
|
||||
};
|
||||
const redacted = redactSensitiveData(data);
|
||||
|
||||
expect(redacted.user.id).toBe("user-***");
|
||||
expect(redacted.user.email).toBe("test@example.com");
|
||||
expect(redacted.metadata).toBe("[REDACTED]");
|
||||
});
|
||||
|
||||
it("should handle arrays", () => {
|
||||
const data = {
|
||||
users: [{ userId: "user-1" }, { userId: "user-2" }],
|
||||
};
|
||||
const redacted = redactSensitiveData(data);
|
||||
|
||||
expect(redacted.users[0].userId).toBe("user-***");
|
||||
expect(redacted.users[1].userId).toBe("user-***");
|
||||
});
|
||||
|
||||
it("should preserve non-sensitive data", () => {
|
||||
const data = {
|
||||
id: "conn-123",
|
||||
status: "active",
|
||||
createdAt: "2024-01-01",
|
||||
remoteUrl: "https://remote.com",
|
||||
};
|
||||
const redacted = redactSensitiveData(data);
|
||||
|
||||
expect(redacted).toEqual(data);
|
||||
});
|
||||
|
||||
it("should handle null/undefined", () => {
|
||||
expect(redactSensitiveData(null)).toBeNull();
|
||||
expect(redactSensitiveData(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should handle primitives", () => {
|
||||
expect(redactSensitiveData("string")).toBe("string");
|
||||
expect(redactSensitiveData(123)).toBe(123);
|
||||
expect(redactSensitiveData(true)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
107
apps/api/src/common/utils/redact.util.ts
Normal file
107
apps/api/src/common/utils/redact.util.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Redaction Utilities
|
||||
*
|
||||
* Provides utilities to redact sensitive data from logs to prevent PII leakage.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sensitive field names that should be redacted
|
||||
*/
|
||||
const SENSITIVE_FIELDS = new Set([
|
||||
"privateKey",
|
||||
"token",
|
||||
"oidcToken",
|
||||
"accessToken",
|
||||
"refreshToken",
|
||||
"password",
|
||||
"secret",
|
||||
"apiKey",
|
||||
"metadata",
|
||||
"payload",
|
||||
"signature",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Redact a user ID to prevent PII leakage
|
||||
* @param _userId - User ID to redact
|
||||
* @returns Redacted user ID
|
||||
*/
|
||||
export function redactUserId(_userId: string | null | undefined): string {
|
||||
return "user-***";
|
||||
}
|
||||
|
||||
/**
|
||||
* Redact an instance ID to prevent system information leakage
|
||||
* @param _instanceId - Instance ID to redact
|
||||
* @returns Redacted instance ID
|
||||
*/
|
||||
export function redactInstanceId(_instanceId: string | null | undefined): string {
|
||||
return "instance-***";
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively redact sensitive data from an object
|
||||
* @param data - Data to redact
|
||||
* @returns Redacted data (creates a new object/array)
|
||||
*/
|
||||
export function redactSensitiveData<T>(data: T): T {
|
||||
// Handle primitives and null/undefined
|
||||
if (data === null || data === undefined) {
|
||||
return data;
|
||||
}
|
||||
|
||||
if (typeof data !== "object") {
|
||||
return data;
|
||||
}
|
||||
|
||||
// Handle arrays
|
||||
if (Array.isArray(data)) {
|
||||
const result = data.map((item: unknown) => redactSensitiveData(item));
|
||||
return result as unknown as T;
|
||||
}
|
||||
|
||||
// Handle objects
|
||||
const redacted: Record<string, unknown> = {};
|
||||
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
// Redact sensitive fields
|
||||
if (SENSITIVE_FIELDS.has(key)) {
|
||||
redacted[key] = "[REDACTED]";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Redact user IDs
|
||||
if (key === "userId" || key === "remoteUserId" || key === "localUserId") {
|
||||
redacted[key] = redactUserId(value as string);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Redact instance IDs
|
||||
if (key === "instanceId" || key === "remoteInstanceId") {
|
||||
redacted[key] = redactInstanceId(value as string);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Redact id field within user/instance context
|
||||
if (key === "id" && typeof value === "string") {
|
||||
// Check if this might be a user or instance ID based on value format
|
||||
if (value.startsWith("user-") || value.startsWith("instance-")) {
|
||||
if (value.startsWith("user-")) {
|
||||
redacted[key] = redactUserId(value);
|
||||
} else {
|
||||
redacted[key] = redactInstanceId(value);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively redact nested objects/arrays
|
||||
if (typeof value === "object" && value !== null) {
|
||||
redacted[key] = redactSensitiveData(value);
|
||||
} else {
|
||||
redacted[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return redacted as T;
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -102,7 +102,7 @@ describe("ConnectionService", () => {
|
||||
provide: SignatureService,
|
||||
useValue: {
|
||||
signMessage: vi.fn().mockResolvedValue("mock-signature"),
|
||||
verifyConnectionRequest: vi.fn().mockReturnValue({ valid: true }),
|
||||
verifyConnectionRequest: vi.fn().mockResolvedValue({ valid: true }),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -441,7 +441,7 @@ describe("ConnectionService", () => {
|
||||
});
|
||||
|
||||
it("should create pending connection for valid request", async () => {
|
||||
vi.spyOn(signatureService, "verifyConnectionRequest").mockReturnValue({ valid: true });
|
||||
vi.spyOn(signatureService, "verifyConnectionRequest").mockResolvedValue({ valid: true });
|
||||
vi.spyOn(prismaService.federationConnection, "create").mockResolvedValue(mockConnection);
|
||||
|
||||
const result = await service.handleIncomingConnectionRequest(mockWorkspaceId, mockRequest);
|
||||
@@ -451,7 +451,7 @@ describe("ConnectionService", () => {
|
||||
});
|
||||
|
||||
it("should reject request with invalid signature", async () => {
|
||||
vi.spyOn(signatureService, "verifyConnectionRequest").mockReturnValue({
|
||||
vi.spyOn(signatureService, "verifyConnectionRequest").mockResolvedValue({
|
||||
valid: false,
|
||||
error: "Invalid signature",
|
||||
});
|
||||
@@ -462,7 +462,7 @@ describe("ConnectionService", () => {
|
||||
});
|
||||
|
||||
it("should log incoming connection attempt", async () => {
|
||||
vi.spyOn(signatureService, "verifyConnectionRequest").mockReturnValue({ valid: true });
|
||||
vi.spyOn(signatureService, "verifyConnectionRequest").mockResolvedValue({ valid: true });
|
||||
vi.spyOn(prismaService.federationConnection, "create").mockResolvedValue(mockConnection);
|
||||
const auditSpy = vi.spyOn(auditService, "logIncomingConnectionAttempt");
|
||||
|
||||
@@ -477,7 +477,7 @@ describe("ConnectionService", () => {
|
||||
});
|
||||
|
||||
it("should log connection created on success", async () => {
|
||||
vi.spyOn(signatureService, "verifyConnectionRequest").mockReturnValue({ valid: true });
|
||||
vi.spyOn(signatureService, "verifyConnectionRequest").mockResolvedValue({ valid: true });
|
||||
vi.spyOn(prismaService.federationConnection, "create").mockResolvedValue(mockConnection);
|
||||
const auditSpy = vi.spyOn(auditService, "logIncomingConnectionCreated");
|
||||
|
||||
@@ -492,7 +492,7 @@ describe("ConnectionService", () => {
|
||||
});
|
||||
|
||||
it("should log connection rejected on invalid signature", async () => {
|
||||
vi.spyOn(signatureService, "verifyConnectionRequest").mockReturnValue({
|
||||
vi.spyOn(signatureService, "verifyConnectionRequest").mockResolvedValue({
|
||||
valid: false,
|
||||
error: "Invalid signature",
|
||||
});
|
||||
|
||||
@@ -286,7 +286,7 @@ export class ConnectionService {
|
||||
});
|
||||
|
||||
// Verify signature
|
||||
const validation = this.signatureService.verifyConnectionRequest(request);
|
||||
const validation = await this.signatureService.verifyConnectionRequest(request);
|
||||
|
||||
if (!validation.valid) {
|
||||
const errorMsg: string = validation.error ?? "Unknown error";
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import { IsString, IsObject, IsNotEmpty, IsNumber } from "class-validator";
|
||||
import type { CommandMessage } from "../types/message.types";
|
||||
import { SanitizeObject } from "../../common/decorators/sanitize.decorator";
|
||||
|
||||
/**
|
||||
* DTO for sending a command to a remote instance
|
||||
@@ -21,6 +22,7 @@ export class SendCommandDto {
|
||||
|
||||
@IsObject()
|
||||
@IsNotEmpty()
|
||||
@SanitizeObject()
|
||||
payload!: Record<string, unknown>;
|
||||
}
|
||||
|
||||
@@ -42,6 +44,7 @@ export class IncomingCommandDto implements CommandMessage {
|
||||
|
||||
@IsObject()
|
||||
@IsNotEmpty()
|
||||
@SanitizeObject()
|
||||
payload!: Record<string, unknown>;
|
||||
|
||||
@IsNumber()
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { IsString, IsUrl, IsOptional, IsObject, IsNumber } from "class-validator";
|
||||
import { Sanitize, SanitizeObject } from "../../common/decorators/sanitize.decorator";
|
||||
|
||||
/**
|
||||
* DTO for initiating a connection
|
||||
@@ -20,6 +21,7 @@ export class InitiateConnectionDto {
|
||||
export class AcceptConnectionDto {
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
@SanitizeObject()
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
@@ -28,6 +30,7 @@ export class AcceptConnectionDto {
|
||||
*/
|
||||
export class RejectConnectionDto {
|
||||
@IsString()
|
||||
@Sanitize()
|
||||
reason!: string;
|
||||
}
|
||||
|
||||
@@ -37,6 +40,7 @@ export class RejectConnectionDto {
|
||||
export class DisconnectConnectionDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Sanitize()
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { IsString, IsEmail, IsOptional, IsObject, IsArray, IsNumber } from "class-validator";
|
||||
import { SanitizeObject } from "../../common/decorators/sanitize.decorator";
|
||||
|
||||
/**
|
||||
* DTO for verifying identity from remote instance
|
||||
@@ -81,6 +82,7 @@ export class CreateIdentityMappingDto {
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
@SanitizeObject()
|
||||
metadata?: Record<string, unknown>;
|
||||
|
||||
@IsOptional()
|
||||
@@ -94,5 +96,6 @@ export class CreateIdentityMappingDto {
|
||||
export class UpdateIdentityMappingDto {
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
@SanitizeObject()
|
||||
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");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -12,6 +12,7 @@ import { FederationAuditService } from "./audit.service";
|
||||
import { ConnectionService } from "./connection.service";
|
||||
import { AuthGuard } from "../auth/guards/auth.guard";
|
||||
import { AdminGuard } from "../auth/guards/admin.guard";
|
||||
import { WorkspaceGuard } from "../common/guards/workspace.guard";
|
||||
import { CsrfGuard } from "../common/guards/csrf.guard";
|
||||
import { SkipCsrf } from "../common/decorators/skip-csrf.decorator";
|
||||
import type { PublicInstanceIdentity } from "./types/instance.types";
|
||||
@@ -76,11 +77,11 @@ export class FederationController {
|
||||
|
||||
/**
|
||||
* Initiate a connection to a remote instance
|
||||
* Requires authentication
|
||||
* Requires authentication and workspace access
|
||||
* Rate limit: "medium" tier (20 req/min) - authenticated endpoint
|
||||
*/
|
||||
@Post("connections/initiate")
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(AuthGuard, WorkspaceGuard)
|
||||
@Throttle({ medium: { limit: 20, ttl: 60000 } })
|
||||
async initiateConnection(
|
||||
@Req() req: AuthenticatedRequest,
|
||||
@@ -99,11 +100,11 @@ export class FederationController {
|
||||
|
||||
/**
|
||||
* Accept a pending connection
|
||||
* Requires authentication
|
||||
* Requires authentication and workspace access
|
||||
* Rate limit: "medium" tier (20 req/min) - authenticated endpoint
|
||||
*/
|
||||
@Post("connections/:id/accept")
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(AuthGuard, WorkspaceGuard)
|
||||
@Throttle({ medium: { limit: 20, ttl: 60000 } })
|
||||
async acceptConnection(
|
||||
@Req() req: AuthenticatedRequest,
|
||||
@@ -127,11 +128,11 @@ export class FederationController {
|
||||
|
||||
/**
|
||||
* Reject a pending connection
|
||||
* Requires authentication
|
||||
* Requires authentication and workspace access
|
||||
* Rate limit: "medium" tier (20 req/min) - authenticated endpoint
|
||||
*/
|
||||
@Post("connections/:id/reject")
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(AuthGuard, WorkspaceGuard)
|
||||
@Throttle({ medium: { limit: 20, ttl: 60000 } })
|
||||
async rejectConnection(
|
||||
@Req() req: AuthenticatedRequest,
|
||||
@@ -149,11 +150,11 @@ export class FederationController {
|
||||
|
||||
/**
|
||||
* Disconnect an active connection
|
||||
* Requires authentication
|
||||
* Requires authentication and workspace access
|
||||
* Rate limit: "medium" tier (20 req/min) - authenticated endpoint
|
||||
*/
|
||||
@Post("connections/:id/disconnect")
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(AuthGuard, WorkspaceGuard)
|
||||
@Throttle({ medium: { limit: 20, ttl: 60000 } })
|
||||
async disconnectConnection(
|
||||
@Req() req: AuthenticatedRequest,
|
||||
@@ -171,11 +172,11 @@ export class FederationController {
|
||||
|
||||
/**
|
||||
* Get all connections for the workspace
|
||||
* Requires authentication
|
||||
* Requires authentication and workspace access
|
||||
* Rate limit: "long" tier (200 req/hour) - read-only endpoint
|
||||
*/
|
||||
@Get("connections")
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(AuthGuard, WorkspaceGuard)
|
||||
@Throttle({ long: { limit: 200, ttl: 3600000 } })
|
||||
async getConnections(
|
||||
@Req() req: AuthenticatedRequest,
|
||||
@@ -190,11 +191,11 @@ export class FederationController {
|
||||
|
||||
/**
|
||||
* Get a single connection
|
||||
* Requires authentication
|
||||
* Requires authentication and workspace access
|
||||
* Rate limit: "long" tier (200 req/hour) - read-only endpoint
|
||||
*/
|
||||
@Get("connections/:id")
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(AuthGuard, WorkspaceGuard)
|
||||
@Throttle({ long: { limit: 200, ttl: 3600000 } })
|
||||
async getConnection(
|
||||
@Req() req: AuthenticatedRequest,
|
||||
|
||||
@@ -20,6 +20,7 @@ import { OIDCService } from "./oidc.service";
|
||||
import { CommandService } from "./command.service";
|
||||
import { FederationAgentService } from "./federation-agent.service";
|
||||
import { PrismaModule } from "../prisma/prisma.module";
|
||||
import { RedisProvider } from "../common/providers/redis.provider";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -52,6 +53,7 @@ import { PrismaModule } from "../prisma/prisma.module";
|
||||
],
|
||||
controllers: [FederationController, FederationAuthController],
|
||||
providers: [
|
||||
RedisProvider,
|
||||
FederationService,
|
||||
CryptoService,
|
||||
FederationAuditService,
|
||||
|
||||
@@ -9,10 +9,12 @@ import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { SignatureService } from "./signature.service";
|
||||
import { FederationService } from "./federation.service";
|
||||
import { generateKeyPairSync } from "crypto";
|
||||
import type Redis from "ioredis";
|
||||
|
||||
describe("SignatureService", () => {
|
||||
let service: SignatureService;
|
||||
let mockFederationService: Partial<FederationService>;
|
||||
let mockRedis: Partial<Redis>;
|
||||
|
||||
// Test keypair
|
||||
const { publicKey, privateKey } = generateKeyPairSync("rsa", {
|
||||
@@ -37,6 +39,12 @@ describe("SignatureService", () => {
|
||||
}),
|
||||
};
|
||||
|
||||
mockRedis = {
|
||||
get: vi.fn().mockResolvedValue(null),
|
||||
set: vi.fn().mockResolvedValue("OK"),
|
||||
setex: vi.fn().mockResolvedValue("OK"),
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
SignatureService,
|
||||
@@ -44,6 +52,10 @@ describe("SignatureService", () => {
|
||||
provide: FederationService,
|
||||
useValue: mockFederationService,
|
||||
},
|
||||
{
|
||||
provide: "REDIS_CLIENT",
|
||||
useValue: mockRedis,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
@@ -168,16 +180,16 @@ describe("SignatureService", () => {
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should accept timestamps within 5 minutes", () => {
|
||||
const fourMinutesAgo = Date.now() - 4 * 60 * 1000;
|
||||
const result = service.validateTimestamp(fourMinutesAgo);
|
||||
it("should accept timestamps within 60 seconds", () => {
|
||||
const fiftySecondsAgo = Date.now() - 50 * 1000;
|
||||
const result = service.validateTimestamp(fiftySecondsAgo);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should reject timestamps older than 5 minutes", () => {
|
||||
const sixMinutesAgo = Date.now() - 6 * 60 * 1000;
|
||||
const result = service.validateTimestamp(sixMinutesAgo);
|
||||
it("should reject timestamps older than 60 seconds", () => {
|
||||
const twoMinutesAgo = Date.now() - 2 * 60 * 1000;
|
||||
const result = service.validateTimestamp(twoMinutesAgo);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
@@ -226,7 +238,7 @@ describe("SignatureService", () => {
|
||||
});
|
||||
|
||||
describe("verifyConnectionRequest", () => {
|
||||
it("should verify a valid connection request", () => {
|
||||
it("should verify a valid connection request", async () => {
|
||||
const timestamp = Date.now();
|
||||
const request = {
|
||||
instanceId: "instance-123",
|
||||
@@ -239,13 +251,14 @@ describe("SignatureService", () => {
|
||||
const signature = service.sign(request, privateKey);
|
||||
const signedRequest = { ...request, signature };
|
||||
|
||||
const result = service.verifyConnectionRequest(signedRequest);
|
||||
const result = await service.verifyConnectionRequest(signedRequest);
|
||||
|
||||
expect(result.valid).toBe(true);
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(mockRedis.setex).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should reject request with invalid signature", () => {
|
||||
it("should reject request with invalid signature", async () => {
|
||||
const request = {
|
||||
instanceId: "instance-123",
|
||||
instanceUrl: "https://test.example.com",
|
||||
@@ -255,13 +268,13 @@ describe("SignatureService", () => {
|
||||
signature: "invalid-signature",
|
||||
};
|
||||
|
||||
const result = service.verifyConnectionRequest(request);
|
||||
const result = await service.verifyConnectionRequest(request);
|
||||
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain("signature");
|
||||
});
|
||||
|
||||
it("should reject request with expired timestamp", () => {
|
||||
it("should reject request with expired timestamp", async () => {
|
||||
const expiredTimestamp = Date.now() - 10 * 60 * 1000; // 10 minutes ago
|
||||
const request = {
|
||||
instanceId: "instance-123",
|
||||
@@ -274,10 +287,92 @@ describe("SignatureService", () => {
|
||||
const signature = service.sign(request, privateKey);
|
||||
const signedRequest = { ...request, signature };
|
||||
|
||||
const result = service.verifyConnectionRequest(signedRequest);
|
||||
const result = await service.verifyConnectionRequest(signedRequest);
|
||||
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain("timestamp");
|
||||
});
|
||||
});
|
||||
|
||||
describe("replay attack prevention", () => {
|
||||
it("should reject replayed message with same signature", async () => {
|
||||
const timestamp = Date.now();
|
||||
const request = {
|
||||
instanceId: "instance-123",
|
||||
instanceUrl: "https://test.example.com",
|
||||
publicKey,
|
||||
capabilities: {},
|
||||
timestamp,
|
||||
};
|
||||
|
||||
const signature = service.sign(request, privateKey);
|
||||
const signedRequest = { ...request, signature };
|
||||
|
||||
// First request should succeed
|
||||
const result1 = await service.verifyConnectionRequest(signedRequest);
|
||||
expect(result1.valid).toBe(true);
|
||||
|
||||
// Mock Redis to indicate nonce was already used
|
||||
mockRedis.get = vi.fn().mockResolvedValue("1");
|
||||
|
||||
// Second request with same signature should be rejected
|
||||
const result2 = await service.verifyConnectionRequest(signedRequest);
|
||||
expect(result2.valid).toBe(false);
|
||||
expect(result2.error).toContain("replay");
|
||||
});
|
||||
|
||||
it("should store nonce with 60 second TTL", async () => {
|
||||
const timestamp = Date.now();
|
||||
const request = {
|
||||
instanceId: "instance-123",
|
||||
instanceUrl: "https://test.example.com",
|
||||
publicKey,
|
||||
capabilities: {},
|
||||
timestamp,
|
||||
};
|
||||
|
||||
const signature = service.sign(request, privateKey);
|
||||
const signedRequest = { ...request, signature };
|
||||
|
||||
await service.verifyConnectionRequest(signedRequest);
|
||||
|
||||
expect(mockRedis.setex).toHaveBeenCalledWith(expect.stringContaining("nonce:"), 60, "1");
|
||||
});
|
||||
|
||||
it("should allow different messages with different signatures", async () => {
|
||||
const timestamp1 = Date.now();
|
||||
const request1 = {
|
||||
instanceId: "instance-123",
|
||||
instanceUrl: "https://test.example.com",
|
||||
publicKey,
|
||||
capabilities: {},
|
||||
timestamp: timestamp1,
|
||||
};
|
||||
|
||||
const signature1 = service.sign(request1, privateKey);
|
||||
const signedRequest1 = { ...request1, signature: signature1 };
|
||||
|
||||
const result1 = await service.verifyConnectionRequest(signedRequest1);
|
||||
expect(result1.valid).toBe(true);
|
||||
|
||||
// Different timestamp creates different signature
|
||||
const timestamp2 = Date.now() + 1;
|
||||
const request2 = {
|
||||
instanceId: "instance-123",
|
||||
instanceUrl: "https://test.example.com",
|
||||
publicKey,
|
||||
capabilities: {},
|
||||
timestamp: timestamp2,
|
||||
};
|
||||
|
||||
const signature2 = service.sign(request2, privateKey);
|
||||
const signedRequest2 = { ...request2, signature: signature2 };
|
||||
|
||||
// Reset mock to simulate nonce not found
|
||||
mockRedis.get = vi.fn().mockResolvedValue(null);
|
||||
|
||||
const result2 = await service.verifyConnectionRequest(signedRequest2);
|
||||
expect(result2.valid).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Handles message signing and verification for federation protocol.
|
||||
*/
|
||||
|
||||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import { Injectable, Logger, Inject } from "@nestjs/common";
|
||||
import { createSign, createVerify } from "crypto";
|
||||
import { FederationService } from "./federation.service";
|
||||
import type {
|
||||
@@ -12,14 +12,19 @@ import type {
|
||||
SignatureValidationResult,
|
||||
ConnectionRequest,
|
||||
} from "./types/connection.types";
|
||||
import type Redis from "ioredis";
|
||||
|
||||
@Injectable()
|
||||
export class SignatureService {
|
||||
private readonly logger = new Logger(SignatureService.name);
|
||||
private readonly TIMESTAMP_TOLERANCE_MS = 5 * 60 * 1000; // 5 minutes
|
||||
private readonly TIMESTAMP_TOLERANCE_MS = 60 * 1000; // 60 seconds
|
||||
private readonly CLOCK_SKEW_TOLERANCE_MS = 60 * 1000; // 1 minute for future timestamps
|
||||
private readonly NONCE_TTL_SECONDS = 60; // Nonce TTL matches tolerance window
|
||||
|
||||
constructor(private readonly federationService: FederationService) {}
|
||||
constructor(
|
||||
private readonly federationService: FederationService,
|
||||
@Inject("REDIS_CLIENT") private readonly redis: Redis
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Sign a message with a private key
|
||||
@@ -153,7 +158,7 @@ export class SignatureService {
|
||||
/**
|
||||
* Verify a connection request signature
|
||||
*/
|
||||
verifyConnectionRequest(request: ConnectionRequest): SignatureValidationResult {
|
||||
async verifyConnectionRequest(request: ConnectionRequest): Promise<SignatureValidationResult> {
|
||||
// Extract signature and create message for verification
|
||||
const { signature, ...message } = request;
|
||||
|
||||
@@ -165,14 +170,30 @@ export class SignatureService {
|
||||
};
|
||||
}
|
||||
|
||||
// Check for replay attack (nonce already used)
|
||||
const nonceKey = `nonce:${signature}`;
|
||||
const nonceExists = await this.redis.get(nonceKey);
|
||||
|
||||
if (nonceExists) {
|
||||
this.logger.warn("Replay attack detected: signature already used");
|
||||
return {
|
||||
valid: false,
|
||||
error: "Request rejected: potential replay attack detected",
|
||||
};
|
||||
}
|
||||
|
||||
// Verify signature using the public key from the request
|
||||
const result = this.verify(message, signature, request.publicKey);
|
||||
|
||||
if (!result.valid) {
|
||||
const errorMsg = result.error ?? "Unknown error";
|
||||
this.logger.warn(`Connection request signature verification failed: ${errorMsg}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Store nonce to prevent replay attacks
|
||||
await this.redis.setex(nonceKey, this.NONCE_TTL_SECONDS, "1");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
200
apps/api/src/federation/workspace-access.integration.spec.ts
Normal file
200
apps/api/src/federation/workspace-access.integration.spec.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* Workspace Access Integration Tests
|
||||
*
|
||||
* Tests that workspace-scoped federation endpoints enforce workspace access.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
import { WorkspaceGuard } from "../common/guards/workspace.guard";
|
||||
import { PrismaService } from "../prisma/prisma.service";
|
||||
import { ForbiddenException } from "@nestjs/common";
|
||||
import type { AuthenticatedRequest } from "../common/types/user.types";
|
||||
|
||||
describe("Workspace Access Control - Federation", () => {
|
||||
let prismaService: PrismaService;
|
||||
let workspaceGuard: WorkspaceGuard;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockPrismaService = {
|
||||
workspaceMember: {
|
||||
findUnique: vi.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
prismaService = mockPrismaService as unknown as PrismaService;
|
||||
workspaceGuard = new WorkspaceGuard(prismaService);
|
||||
});
|
||||
|
||||
describe("Workspace membership verification", () => {
|
||||
it("should allow access when user is workspace member", async () => {
|
||||
const mockRequest: Partial<AuthenticatedRequest> = {
|
||||
user: {
|
||||
id: "user-123",
|
||||
email: "test@example.com",
|
||||
workspaceId: "workspace-456",
|
||||
},
|
||||
headers: {
|
||||
"x-workspace-id": "workspace-456",
|
||||
},
|
||||
params: {},
|
||||
body: {},
|
||||
} as AuthenticatedRequest;
|
||||
|
||||
// Mock workspace membership exists
|
||||
vi.spyOn(prismaService.workspaceMember, "findUnique").mockResolvedValue({
|
||||
workspaceId: "workspace-456",
|
||||
userId: "user-123",
|
||||
role: "ADMIN",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
} as any);
|
||||
|
||||
const canActivate = await workspaceGuard.canActivate({
|
||||
switchToHttp: () => ({
|
||||
getRequest: () => mockRequest,
|
||||
}),
|
||||
} as any);
|
||||
|
||||
expect(canActivate).toBe(true);
|
||||
});
|
||||
|
||||
it("should deny access when user is not workspace member", async () => {
|
||||
const mockRequest: Partial<AuthenticatedRequest> = {
|
||||
user: {
|
||||
id: "user-123",
|
||||
email: "test@example.com",
|
||||
},
|
||||
headers: {
|
||||
"x-workspace-id": "workspace-999",
|
||||
},
|
||||
params: {},
|
||||
body: {},
|
||||
} as AuthenticatedRequest;
|
||||
|
||||
// Mock workspace membership does not exist
|
||||
vi.spyOn(prismaService.workspaceMember, "findUnique").mockResolvedValue(null);
|
||||
|
||||
await expect(
|
||||
workspaceGuard.canActivate({
|
||||
switchToHttp: () => ({
|
||||
getRequest: () => mockRequest,
|
||||
}),
|
||||
} as any)
|
||||
).rejects.toThrow(ForbiddenException);
|
||||
});
|
||||
|
||||
it("should deny access when workspace ID is missing", async () => {
|
||||
const mockRequest: Partial<AuthenticatedRequest> = {
|
||||
user: {
|
||||
id: "user-123",
|
||||
email: "test@example.com",
|
||||
},
|
||||
headers: {},
|
||||
params: {},
|
||||
body: {},
|
||||
} as AuthenticatedRequest;
|
||||
|
||||
await expect(
|
||||
workspaceGuard.canActivate({
|
||||
switchToHttp: () => ({
|
||||
getRequest: () => mockRequest,
|
||||
}),
|
||||
} as any)
|
||||
).rejects.toThrow("Workspace ID is required");
|
||||
});
|
||||
|
||||
it("should check workspace ID from URL parameter", async () => {
|
||||
const mockRequest: Partial<AuthenticatedRequest> = {
|
||||
user: {
|
||||
id: "user-123",
|
||||
email: "test@example.com",
|
||||
},
|
||||
headers: {},
|
||||
params: {
|
||||
workspaceId: "workspace-789",
|
||||
},
|
||||
} as any;
|
||||
|
||||
vi.spyOn(prismaService.workspaceMember, "findUnique").mockResolvedValue({
|
||||
workspaceId: "workspace-789",
|
||||
userId: "user-123",
|
||||
role: "MEMBER",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
} as any);
|
||||
|
||||
const canActivate = await workspaceGuard.canActivate({
|
||||
switchToHttp: () => ({
|
||||
getRequest: () => mockRequest,
|
||||
}),
|
||||
} as any);
|
||||
|
||||
expect(canActivate).toBe(true);
|
||||
expect(prismaService.workspaceMember.findUnique).toHaveBeenCalledWith({
|
||||
where: {
|
||||
workspaceId_userId: {
|
||||
workspaceId: "workspace-789",
|
||||
userId: "user-123",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should check workspace ID from request body", async () => {
|
||||
const mockRequest: Partial<AuthenticatedRequest> = {
|
||||
user: {
|
||||
id: "user-123",
|
||||
email: "test@example.com",
|
||||
},
|
||||
headers: {},
|
||||
params: {},
|
||||
body: {
|
||||
workspaceId: "workspace-111",
|
||||
},
|
||||
} as any;
|
||||
|
||||
vi.spyOn(prismaService.workspaceMember, "findUnique").mockResolvedValue({
|
||||
workspaceId: "workspace-111",
|
||||
userId: "user-123",
|
||||
role: "ADMIN",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
} as any);
|
||||
|
||||
const canActivate = await workspaceGuard.canActivate({
|
||||
switchToHttp: () => ({
|
||||
getRequest: () => mockRequest,
|
||||
}),
|
||||
} as any);
|
||||
|
||||
expect(canActivate).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Workspace isolation", () => {
|
||||
it("should prevent cross-workspace access", async () => {
|
||||
const mockRequest: Partial<AuthenticatedRequest> = {
|
||||
user: {
|
||||
id: "user-123",
|
||||
email: "test@example.com",
|
||||
},
|
||||
headers: {
|
||||
"x-workspace-id": "workspace-attacker",
|
||||
},
|
||||
params: {},
|
||||
body: {},
|
||||
} as AuthenticatedRequest;
|
||||
|
||||
// User is NOT a member of the requested workspace
|
||||
vi.spyOn(prismaService.workspaceMember, "findUnique").mockResolvedValue(null);
|
||||
|
||||
await expect(
|
||||
workspaceGuard.canActivate({
|
||||
switchToHttp: () => ({
|
||||
getRequest: () => mockRequest,
|
||||
}),
|
||||
} as any)
|
||||
).rejects.toThrow(ForbiddenException);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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/providers/redis.provider.ts
|
||||
**Tool Used:** Write
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:40:52
|
||||
|
||||
## 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-providers-redis.provider.ts_20260203-2140_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/common/providers/redis.provider.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:42:52
|
||||
|
||||
## 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-providers-redis.provider.ts_20260203-2142_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/common/utils/redact.util.spec.ts
|
||||
**Tool Used:** Write
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:50:53
|
||||
|
||||
## 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-redact.util.spec.ts_20260203-2150_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/common/utils/redact.util.ts
|
||||
**Tool Used:** Write
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:51:08
|
||||
|
||||
## 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-redact.util.ts_20260203-2151_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/common/utils/redact.util.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 2
|
||||
**Generated:** 2026-02-03 21:51: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-common-utils-redact.util.ts_20260203-2151_2_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/common/utils/redact.util.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 3
|
||||
**Generated:** 2026-02-03 21:51:59
|
||||
|
||||
## 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-redact.util.ts_20260203-2151_3_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/common/utils/redact.util.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:52:03
|
||||
|
||||
## 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-redact.util.ts_20260203-2152_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/command.service.spec.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:32:08
|
||||
|
||||
## 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-command.service.spec.ts_20260203-2132_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/command.service.spec.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 2
|
||||
**Generated:** 2026-02-03 21:32:14
|
||||
|
||||
## 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-command.service.spec.ts_20260203-2132_2_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/command.service.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:32: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-federation-command.service.ts_20260203-2132_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/command.service.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 2
|
||||
**Generated:** 2026-02-03 21:32:33
|
||||
|
||||
## 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-command.service.ts_20260203-2132_2_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/connection.service.spec.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:41: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-connection.service.spec.ts_20260203-2141_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/connection.service.spec.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 2
|
||||
**Generated:** 2026-02-03 21:41: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-federation-connection.service.spec.ts_20260203-2141_2_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/connection.service.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:41:19
|
||||
|
||||
## 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-connection.service.ts_20260203-2141_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/crypto.service.spec.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:34:34
|
||||
|
||||
## 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-crypto.service.spec.ts_20260203-2134_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/crypto.service.spec.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 2
|
||||
**Generated:** 2026-02-03 21:34:43
|
||||
|
||||
## 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-crypto.service.spec.ts_20260203-2134_2_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/crypto.service.spec.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:35:03
|
||||
|
||||
## 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-crypto.service.spec.ts_20260203-2135_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/crypto.service.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:34:54
|
||||
|
||||
## 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-crypto.service.ts_20260203-2134_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:** 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"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/federation.controller.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:49:34
|
||||
|
||||
## 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-federation.controller.ts_20260203-2149_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/federation.controller.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 2
|
||||
**Generated:** 2026-02-03 21:49:39
|
||||
|
||||
## 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-federation.controller.ts_20260203-2149_2_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/federation.controller.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:50:03
|
||||
|
||||
## 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-federation.controller.ts_20260203-2150_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/federation.module.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:41:01
|
||||
|
||||
## 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-federation.module.ts_20260203-2141_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/federation.module.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 2
|
||||
**Generated:** 2026-02-03 21:41:04
|
||||
|
||||
## 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-federation.module.ts_20260203-2141_2_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/federation.service.spec.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:33:33
|
||||
|
||||
## 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-federation.service.spec.ts_20260203-2133_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/federation.service.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:33:44
|
||||
|
||||
## 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-federation.service.ts_20260203-2133_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/identity-linking.controller.spec.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:36: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-federation-identity-linking.controller.spec.ts_20260203-2136_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/identity-linking.controller.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:35:54
|
||||
|
||||
## 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-identity-linking.controller.ts_20260203-2135_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/identity-linking.controller.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:36:00
|
||||
|
||||
## 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-identity-linking.controller.ts_20260203-2136_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/query.service.spec.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:31: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-query.service.spec.ts_20260203-2131_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/query.service.spec.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 2
|
||||
**Generated:** 2026-02-03 21:31: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-query.service.spec.ts_20260203-2131_2_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/query.service.spec.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 3
|
||||
**Generated:** 2026-02-03 21:31:33
|
||||
|
||||
## 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-query.service.spec.ts_20260203-2131_3_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/query.service.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:31:53
|
||||
|
||||
## 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-query.service.ts_20260203-2131_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/query.service.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 2
|
||||
**Generated:** 2026-02-03 21:31:59
|
||||
|
||||
## 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-query.service.ts_20260203-2131_2_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/signature.service.spec.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:39:35
|
||||
|
||||
## 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-signature.service.spec.ts_20260203-2139_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/signature.service.spec.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 2
|
||||
**Generated:** 2026-02-03 21:39:40
|
||||
|
||||
## 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-signature.service.spec.ts_20260203-2139_2_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/signature.service.spec.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 3
|
||||
**Generated:** 2026-02-03 21:39:49
|
||||
|
||||
## 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-signature.service.spec.ts_20260203-2139_3_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/signature.service.spec.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:40:07
|
||||
|
||||
## 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-signature.service.spec.ts_20260203-2140_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/signature.service.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:40:34
|
||||
|
||||
## 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-signature.service.ts_20260203-2140_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/signature.service.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 2
|
||||
**Generated:** 2026-02-03 21:40:44
|
||||
|
||||
## 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-signature.service.ts_20260203-2140_2_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/workspace-access.integration.spec.ts
|
||||
**Tool Used:** Write
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:48: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-federation-workspace-access.integration.spec.ts_20260203-2148_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/workspace-access.integration.spec.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 2
|
||||
**Generated:** 2026-02-03 21:48: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-workspace-access.integration.spec.ts_20260203-2148_2_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/workspace-access.integration.spec.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 3
|
||||
**Generated:** 2026-02-03 21:48:56
|
||||
|
||||
## 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-workspace-access.integration.spec.ts_20260203-2148_3_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/workspace-access.integration.spec.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 1
|
||||
**Generated:** 2026-02-03 21:49:01
|
||||
|
||||
## 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-workspace-access.integration.spec.ts_20260203-2149_1_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/workspace-access.integration.spec.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 2
|
||||
**Generated:** 2026-02-03 21:49:06
|
||||
|
||||
## 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-workspace-access.integration.spec.ts_20260203-2149_2_remediation_needed.md"
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# QA Remediation Report
|
||||
|
||||
**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/federation/workspace-access.integration.spec.ts
|
||||
**Tool Used:** Edit
|
||||
**Epic:** general
|
||||
**Iteration:** 3
|
||||
**Generated:** 2026-02-03 21:49: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-workspace-access.integration.spec.ts_20260203-2149_3_remediation_needed.md"
|
||||
```
|
||||
73
docs/scratchpads/p1-security-fixes.md
Normal file
73
docs/scratchpads/p1-security-fixes.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# M7.1 P1 Security Fixes (#283-#290)
|
||||
|
||||
## Objective
|
||||
|
||||
Complete remaining P1 security issues in M7.1 Remediation Sprint
|
||||
|
||||
## Issues to Fix
|
||||
|
||||
### #283 - Enforce connection status validation in queries
|
||||
|
||||
- **Impact**: Authorization gap - operations proceed on non-ACTIVE connections
|
||||
- **Fix**: Add status check to Prisma queries
|
||||
- **Files**: command.service.ts, query.service.ts
|
||||
|
||||
### #284 - Reduce timestamp validation window
|
||||
|
||||
- **Impact**: 5-minute replay attack window
|
||||
- **Fix**: Reduce to 60s + add nonce tracking with Redis
|
||||
- **Files**: signature.service.ts
|
||||
|
||||
### #285 - Add input sanitization
|
||||
|
||||
- **Impact**: XSS risk on user-controlled fields
|
||||
- **Fix**: Sanitize connection metadata, identity metadata, rejection reasons, command payloads
|
||||
- **Files**: Multiple DTOs and services
|
||||
|
||||
### #286 - Add workspace access validation guard
|
||||
|
||||
- **Impact**: Authorization gap - no workspace membership validation
|
||||
- **Fix**: Create WorkspaceAccessGuard
|
||||
- **Files**: New guard + controllers
|
||||
|
||||
### #287 - Prevent sensitive data in logs
|
||||
|
||||
- **Impact**: Data leakage, PII exposure, GDPR violations
|
||||
- **Fix**: Use appropriate log levels + redact sensitive data
|
||||
- **Files**: All federation services
|
||||
|
||||
### #288 - Upgrade RSA key size
|
||||
|
||||
- **Impact**: Future-proofing against quantum computing
|
||||
- **Fix**: Change from 2048 to 4096 bits
|
||||
- **Files**: federation.service.ts
|
||||
|
||||
### #289 - Prevent private key decryption error leaks
|
||||
|
||||
- **Impact**: Sensitive data in error messages
|
||||
- **Fix**: Don't log error details with potential sensitive data
|
||||
- **Files**: crypto.service.ts
|
||||
|
||||
### #290 - Secure identity verification endpoint
|
||||
|
||||
- **Impact**: Public endpoint with no auth
|
||||
- **Fix**: Add AuthGuard + rate limiting
|
||||
- **Files**: identity-linking.controller.ts
|
||||
|
||||
## Progress
|
||||
|
||||
- [ ] #283 - Connection status validation
|
||||
- [ ] #284 - Timestamp validation window
|
||||
- [ ] #285 - Input sanitization
|
||||
- [ ] #286 - Workspace access guard
|
||||
- [ ] #287 - Sensitive data in logs
|
||||
- [ ] #288 - RSA key size upgrade
|
||||
- [ ] #289 - Decryption error leaks
|
||||
- [ ] #290 - Identity endpoint security
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
- Minimum 85% coverage for all changes
|
||||
- TDD approach: write tests first
|
||||
- Security-focused test cases
|
||||
- Integration tests for guards and validation
|
||||
Reference in New Issue
Block a user