Skip to content

feat(ai): implement secure image attachments for PostHog AI chat#27

Draft
0xluffyb wants to merge 1 commit into
masterfrom
capy/llma-secure-image-attachments
Draft

feat(ai): implement secure image attachments for PostHog AI chat#27
0xluffyb wants to merge 1 commit into
masterfrom
capy/llma-secure-image-attachments

Conversation

@0xluffyb

@0xluffyb 0xluffyb commented Jun 19, 2026

Copy link
Copy Markdown

This PR implements end-to-end PNG/JPEG image attachments for PostHog AI chat, featuring secure private storage, multimodal model input, and comprehensive guardrails.

Backend

  • ConversationAttachment model (products/posthog_ai/backend/models/assistant.py): Tenant-scoped storage with team, creator, and conversation UUID; fail-closed manager prevents unscoped queries; private unguessable object paths never use client filenames
  • Attachment validation (products/posthog_ai/backend/attachments.py): Pillow-verified PNG/JPEG formats; 4 MiB/file and 4 files/message caps; 25M decoded-pixel limit; server-side re-encoding strips EXIF/metadata; sanitized display names; random object paths prevent enumeration
  • Secure API endpoints (ee/api/conversation.py): Authenticated upload, content, and delete actions on ConversationViewSet; content returns private, max-age=3600 cache and X-Content-Type-Options: nosniff; every attachment ID is server-resolved against authenticated user, team, and conversation UUID
  • IDOR prevention (ee/api/conversation.py:_get_scoped_attachment): Fail-closed queryset never trusts client-provided IDs; rejects mismatches with 404
  • Reference-only workflow (posthog/schema.py, ee/hogai/core/agent_modes/executables.py): Extended HumanMessage schema with HumanMessageAttachment; Temporal/checkpoints carry only IDs; transient Anthropic base64 blocks hydrated at final model boundary
  • Mode validation (ee/api/conversation.py): Explicitly rejects attachments for Research (agent_mode=research), Sandbox (is_sandbox=true), and queue endpoints with 400 errors; no silent drops

Frontend

  • State management (frontend/src/scenes/max/maxThreadLogic.tsx): pendingAttachments reducer/listeners tracks upload state; addPendingAttachments action filters PNG/JPEG, enforces limits, revokes Blob URLs on unmount; guards double-submit during upload
  • Input UI (frontend/src/scenes/max/components/QuestionInput.tsx): Accessible file picker (accept="image/png,image/jpeg", multiple); drag/drop affordance with visual feedback; paste handler distinguishes images from text; removable previews with size/format validation; image-only submit allowed
  • Thread rendering (frontend/src/scenes/max/Thread.tsx): Displays attached images via authenticated content endpoint; derives conversationId from maxThreadLogic
  • Generated clients (frontend/src/lib/api.ts): api.conversations.attachments.upload/delete/content typed against backend serializers

Security Measures

  • Storage isolation: Private object paths scoped to teams; no presigned URLs; authentication required on every access
  • Input sanitization: Server re-encoding prevents polyglot payloads; filenames never reach storage paths; metadata stripped before encoding
  • Access control: Every attachment read/delete validates team, user, and conversation UUID; no cross-tenant leakage
  • Fail-closed defaults: Missing or invalid IDs return 404; partial uploads rolled back on error

Testing

  • Backend boundary tests (ee/api/test/test_conversation.py): Valid PNG/JPEG upload/re-encoding; spoofed MIME and invalid SVG rejected; size and decoded-pixel caps enforced; team/user/conversation IDOR returns 404; authenticated content validates Cache-Control and nosniff; rejected for Research, Sandbox, and queue
  • Hydration tests (products/posthog_ai/backend/tests/test_attachments.py): Transient base64 blocks load from storage; user/conversation mismatches raise InvalidConversationAttachment
  • Model boundary test (ee/hogai/core/agent_modes/test/test_executables.py): Verifies Anthropic blocks at final execution layer
  • Frontend tests (frontend/src/scenes/max/components/QuestionInput.test.tsx, maxThreadLogic.test.ts): Picker, drop, and paste workflows; preview rendering/removal; size/type rejections; image-only submit; double-submit guard; four-file limit; optimistic attachment preservation

Generated Artifacts

  • Schemas and clients: posthog.schema.py defines HumanMessageAttachment; schema_enums.py adds ContentType enum; OpenAPI generates ConversationAttachment in products/conversations/frontend/generated/api.schemas.ts and MCP tools
  • Kea types: Scoped typegen for maxThreadLogic and maxLogic
  • Migration: Additive CREATE TABLE conversation_attachments with FKs, unique object_path, and scoped indexes

Type Safety

  • OpenAPI: @extend_schema annotations with OpenApiParameter for path variables; correct MIME types on binary response
  • IDOR coverage: ConversationAttachment added to .semgrep/rules/idor-team-scoped-models.yaml
  • Frontend: TypeScript check passes; derived attachment types from generated HumanMessage schema

Open POST-082 POST-082


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

…e storage and multimodal model

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
@0xluffyb 0xluffyb added the capy Generated by capy.ai label Jun 19, 2026 — with Capy AI
Comment thread ee/api/conversation.py
conversation_id=uuid.UUID(conversation_id),
)
except InvalidConversationAttachment as error:
raise exceptions.ValidationError({"image": str(error)})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

capy Generated by capy.ai

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants