Files
dify/api/models/api_based_extension.py
T
JyongandClaude Fable 5.2 7d6c1845b8 feat(file-scan): scan uploaded knowledge base files for malware
Add a shared file malware-scanning module and use it in the Classic
Knowledge Base, RAG Pipeline local files and the Agent Knowledge Base.

- core/file_scan: scanner adapters for ClamAV (deployment-configured),
  Cloudmersive, Scanii, OPSWAT MetaDefender Cloud and API-based
  extensions (new `file.malware_scan` extension point).
- services/file_scan: FileScanService is the single entry point other
  modules call. Providers are workspace-wide; a policy belongs to a
  scope (dataset, knowledge_fs_space), so further modules can adopt
  scanning without schema changes.
- Each file is scanned on its own: a rejected file fails only its own
  document. Malware always blocks and deletes the stored original;
  invalid credentials and exhausted quota stop processing regardless of
  the "allow" fallback.
- Console: workspace scanner management and a per-knowledge-base
  policy section in both settings forms; the Agent Knowledge Base
  upload keeps going after a rejected file and explains each failure.
- Off by default behind FILE_SCAN_ENABLED.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 18:37:03 +08:00

38 lines
1.3 KiB
Python

import enum
from datetime import datetime
from uuid import uuid4
import sqlalchemy as sa
from sqlalchemy import DateTime, String, func
from sqlalchemy.orm import Mapped, mapped_column
from .base import TypeBase
from .types import LongText, StringUUID
class APIBasedExtensionPoint(enum.StrEnum):
APP_EXTERNAL_DATA_TOOL_QUERY = "app.external_data_tool.query"
PING = "ping"
APP_MODERATION_INPUT = "app.moderation.input"
APP_MODERATION_OUTPUT = "app.moderation.output"
FILE_MALWARE_SCAN = "file.malware_scan"
class APIBasedExtension(TypeBase):
__tablename__ = "api_based_extensions"
__table_args__ = (
sa.PrimaryKeyConstraint("id", name="api_based_extension_pkey"),
sa.Index("api_based_extension_tenant_idx", "tenant_id"),
)
id: Mapped[str] = mapped_column(
StringUUID, insert_default=lambda: str(uuid4()), default_factory=lambda: str(uuid4()), init=False
)
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
name: Mapped[str] = mapped_column(String(255), nullable=False)
api_endpoint: Mapped[str] = mapped_column(String(255), nullable=False)
api_key: Mapped[str] = mapped_column(LongText, nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, server_default=func.current_timestamp(), init=False
)