feat: protect sensitive catalog samples
This commit is contained in:
@@ -11,6 +11,12 @@ import {
|
||||
type DescriptionGenerationWorker,
|
||||
} from "../catalog/description-generation-worker.js";
|
||||
import { MetadataGenerationModelUnavailableError } from "../catalog/metadata-generation-models.js";
|
||||
import { ModelCompletionProviderError } from "../catalog/model-completer.js";
|
||||
import {
|
||||
SensitiveDataSuggester,
|
||||
SensitiveDataSuggestionInvalidResponseError,
|
||||
SensitiveDataSuggestionTargetNotFoundError,
|
||||
} from "../catalog/sensitive-data-suggester.js";
|
||||
import {
|
||||
CatalogOperationInProgressError,
|
||||
CatalogUnavailableError,
|
||||
@@ -22,6 +28,7 @@ import {
|
||||
|
||||
const idSchema = z.uuid();
|
||||
const modelIdSchema = z.string().regex(/^[a-z][a-z0-9._-]{0,63}$/);
|
||||
const suggestionSchema = z.object({ modelId: modelIdSchema }).strict();
|
||||
const selectedTargetIdsSchema = z.array(idSchema).min(1);
|
||||
const startSchema = z.discriminatedUnion("scope", [
|
||||
z.object({
|
||||
@@ -116,6 +123,19 @@ function safeError(reply: FastifyReply, error: unknown) {
|
||||
message: "The selected metadata-generation model is unavailable.",
|
||||
});
|
||||
}
|
||||
if (error instanceof SensitiveDataSuggestionTargetNotFoundError) {
|
||||
return reply.code(404).send({
|
||||
code: "database_not_found",
|
||||
message: "Database configuration was not found.",
|
||||
});
|
||||
}
|
||||
if (error instanceof SensitiveDataSuggestionInvalidResponseError
|
||||
|| error instanceof ModelCompletionProviderError) {
|
||||
return reply.code(502).send({
|
||||
code: "sensitive_data_suggestion_failed",
|
||||
message: "Sensitive-data suggestions could not be prepared.",
|
||||
});
|
||||
}
|
||||
if (error instanceof DescriptionGenerationDuplicateTargetIdsError) {
|
||||
return reply.code(400).send({
|
||||
code: "description_generation_target_ids_duplicate",
|
||||
@@ -172,8 +192,28 @@ function safeError(reply: FastifyReply, error: unknown) {
|
||||
|
||||
export function catalogDescriptionGenerationRoutes(
|
||||
app: FastifyInstance,
|
||||
deps: { repository: CatalogRepository; worker: DescriptionGenerationWorker },
|
||||
deps: {
|
||||
repository: CatalogRepository;
|
||||
worker: DescriptionGenerationWorker;
|
||||
sensitiveDataSuggester: SensitiveDataSuggester;
|
||||
},
|
||||
): void {
|
||||
app.post("/catalog/databases/:databaseId/sensitive-data-suggestions", async (request, reply) => {
|
||||
if (!manage(request, reply)) return reply;
|
||||
try {
|
||||
const databaseId = idSchema.parse((request.params as { databaseId?: unknown }).databaseId);
|
||||
const input = suggestionSchema.parse(request.body);
|
||||
const suggestions = await deps.sensitiveDataSuggester.suggest(
|
||||
databaseId,
|
||||
input.modelId,
|
||||
new AbortController().signal,
|
||||
);
|
||||
return { suggestions };
|
||||
} catch (error) {
|
||||
return safeError(reply, error);
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/catalog/databases/:databaseId/description-generation-runs", async (request, reply) => {
|
||||
if (!manage(request, reply)) return reply;
|
||||
try {
|
||||
|
||||
@@ -18,6 +18,7 @@ const metadataSchema = z.object({
|
||||
version: z.number().int().positive(),
|
||||
description: z.string().max(20_000).nullable(),
|
||||
generatedDescription: z.string().max(20_000).nullable(),
|
||||
sensitive: z.boolean().optional(),
|
||||
}).strict();
|
||||
const createRunSchema = z.object({
|
||||
version: z.number().int().positive(),
|
||||
@@ -117,6 +118,7 @@ export function catalogSchemaRoutes(
|
||||
input.version,
|
||||
normalized(input.description),
|
||||
normalized(input.generatedDescription),
|
||||
input.sensitive,
|
||||
);
|
||||
if (!updated) return reply.code(409).send({ code: "column_stale", message: "Column metadata changed. Reload and try again." });
|
||||
return updated;
|
||||
|
||||
Reference in New Issue
Block a user