From 4371b000358ea6f2b7f070e50ef68ea53a945194 Mon Sep 17 00:00:00 2001 From: James Date: Sat, 28 Feb 2026 05:40:58 -0500 Subject: [PATCH] Add watermark tests and update website content --- cmd/server/website/dpa.html | 1 + cmd/server/website/features.html | 1 + cmd/server/website/index.html | 1 + cmd/server/website/pricing.html | 1 + cmd/server/website/privacy.html | 1 + cmd/server/website/security.html | 9 +- cmd/server/website/soc2.html | 679 +++++++++++++++++++++++++++++++ cmd/server/website/terms.html | 1 + lib/watermark_test.go | 213 ++++++++++ 9 files changed, 903 insertions(+), 4 deletions(-) create mode 100644 cmd/server/website/soc2.html create mode 100644 lib/watermark_test.go diff --git a/cmd/server/website/dpa.html b/cmd/server/website/dpa.html index 1e710b7..bf51c7b 100644 --- a/cmd/server/website/dpa.html +++ b/cmd/server/website/dpa.html @@ -352,6 +352,7 @@
  • Privacy Policy
  • Terms of Service
  • DPA
  • +
  • SOC 2
  • diff --git a/cmd/server/website/features.html b/cmd/server/website/features.html index ef1985d..486380c 100644 --- a/cmd/server/website/features.html +++ b/cmd/server/website/features.html @@ -580,6 +580,7 @@
  • Privacy Policy
  • Terms of Service
  • DPA
  • +
  • SOC 2
  • diff --git a/cmd/server/website/index.html b/cmd/server/website/index.html index fe2d5ee..35e9d3e 100644 --- a/cmd/server/website/index.html +++ b/cmd/server/website/index.html @@ -546,6 +546,7 @@
  • Privacy Policy
  • Terms of Service
  • DPA
  • +
  • SOC 2
  • diff --git a/cmd/server/website/pricing.html b/cmd/server/website/pricing.html index eab4cc8..ca022ac 100644 --- a/cmd/server/website/pricing.html +++ b/cmd/server/website/pricing.html @@ -468,6 +468,7 @@
  • Privacy Policy
  • Terms of Service
  • DPA
  • +
  • SOC 2
  • diff --git a/cmd/server/website/privacy.html b/cmd/server/website/privacy.html index 45e9a18..a638d25 100644 --- a/cmd/server/website/privacy.html +++ b/cmd/server/website/privacy.html @@ -291,6 +291,7 @@
  • Privacy Policy
  • Terms of Service
  • DPA
  • +
  • SOC 2
  • diff --git a/cmd/server/website/security.html b/cmd/server/website/security.html index c743f11..f87407e 100644 --- a/cmd/server/website/security.html +++ b/cmd/server/website/security.html @@ -96,16 +96,16 @@
    - +

    SOC 2

    +

    Self-Assessed · Type II in progress

    +
    @@ -563,6 +563,7 @@
  • Privacy Policy
  • Terms of Service
  • DPA
  • +
  • SOC 2
  • diff --git a/cmd/server/website/soc2.html b/cmd/server/website/soc2.html new file mode 100644 index 0000000..87f4acd --- /dev/null +++ b/cmd/server/website/soc2.html @@ -0,0 +1,679 @@ + + + + + + SOC 2 Compliance — Dealspace + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + Self-Assessment · Type II Audit Planned Q4 2026 +
    +

    + SOC 2 Compliance +

    +

    + Dealspace has completed a comprehensive SOC 2 Type II self-assessment. We are preparing for formal audit certification in Q4 2026. +

    +
    +
    + + +
    +
    +

    + Note: This is a self-assessment document. Formal SOC 2 Type II audit is planned for Q4 2026. +

    +
    +
    + + +
    +
    +
    +
    +
    Overview
    +

    What is SOC 2?

    +

    + SOC 2 (System and Organization Controls 2) is an auditing framework developed by the AICPA that evaluates how organizations manage customer data based on five Trust Services Criteria. +

    +

    + For M&A platforms handling confidential deal data, SOC 2 compliance demonstrates a commitment to security, availability, and data protection that investment banks and advisors require. +

    +
    +
    +

    Self-Assessment Summary

    +
    +
    + Security (CC1-CC9) +
    +
    +
    +
    + 95% +
    +
    +
    + Availability (A1) +
    +
    +
    +
    + 95% +
    +
    +
    + Confidentiality (C1) +
    +
    +
    +
    + 98% +
    +
    +
    + Processing Integrity (PI1) +
    +
    +
    +
    + 95% +
    +
    +
    + Privacy (P1-P8) +
    +
    +
    +
    + 95% +
    +
    +
    +
    +

    Assessment Date: February 28, 2026

    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    Scope
    +

    What's Covered

    +

    + Our SOC 2 assessment covers all aspects of the Dealspace platform and infrastructure. +

    +
    + +
    +
    +
    + + + +
    +

    Infrastructure

    +
      +
    • • Production server (Zürich, Switzerland)
    • +
    • • Go application binary
    • +
    • • SQLite encrypted database
    • +
    • • Caddy reverse proxy
    • +
    +
    + +
    +
    + + + +
    +

    Data Types

    +
      +
    • • M&A deal documents
    • +
    • • Financial data
    • +
    • • Transaction details
    • +
    • • Participant information
    • +
    +
    + +
    +
    + + + +
    +

    User Types

    +
      +
    • • Investment bank admins/members
    • +
    • • Seller organizations
    • +
    • • Buyer organizations
    • +
    • • Observers
    • +
    +
    +
    +
    +
    + + +
    +
    +
    +
    Trust Services Criteria
    +

    The Five Pillars

    +

    + SOC 2 evaluates organizations against five Trust Services Criteria. Dealspace implements controls for all five. +

    +
    + +
    + +
    +
    +
    + + + +
    +
    +

    Security (CC1-CC9)

    +

    Protection against unauthorized access, both physical and logical.

    +
    +
    + + + + FIPS 140-3 encryption (AES-256-GCM) +
    +
    + + + + Per-project key derivation (HKDF-SHA256) +
    +
    + + + + Role-based access control (RBAC) +
    +
    + + + + MFA required for IB users +
    +
    +
    +
    +
    + + +
    +
    +
    + + + +
    +
    +

    Availability (A1)

    +

    Systems are available for operation and use as committed.

    +
    +
    + + + + 99.9% uptime SLA +
    +
    + + + + 4-hour recovery time objective +
    +
    + + + + Daily encrypted backups +
    +
    + + + + Swiss data center (Zürich) +
    +
    +
    +
    +
    + + +
    +
    +
    + + + +
    +
    +

    Confidentiality (C1)

    +

    Information designated as confidential is protected as committed.

    +
    +
    + + + + All deal data encrypted at rest +
    +
    + + + + Blind indexes for searchable encryption +
    +
    + + + + TLS 1.3 for all connections +
    +
    + + + + Dynamic document watermarking +
    +
    +
    +
    +
    + + +
    +
    +
    + + + +
    +
    +

    Processing Integrity (PI1)

    +

    System processing is complete, valid, accurate, timely, and authorized.

    +
    +
    + + + + Input validation on all data +
    +
    + + + + Parameterized SQL queries +
    +
    + + + + Optimistic locking (ETag) +
    +
    + + + + ACID transaction compliance +
    +
    +
    +
    +
    + + +
    +
    +
    + + + +
    +
    +

    Privacy (P1-P8)

    +

    Personal information is collected, used, retained, and disclosed in conformity with commitments.

    +
    +
    + + + + GDPR/FADP/CCPA compliant +
    +
    + + + + Data export on request +
    +
    + + + + No third-party tracking +
    +
    + + + + No data sales +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    Controls Summary
    +

    Key Security Controls

    +
    + +
    +
    +

    Encryption

    +

    FIPS 140-3 validated AES-256-GCM with per-project keys derived via HKDF-SHA256

    +
    +
    +

    Authentication

    +

    JWT tokens with 1-hour expiry, MFA required for IB users, session management

    +
    +
    +

    Authorization

    +

    Role hierarchy (IB → Seller → Buyer → Observer), invitation-only access

    +
    +
    +

    Infrastructure

    +

    Swiss data center, UFW firewall, SSH key-only, automatic security updates

    +
    +
    +

    Audit Logging

    +

    All access logged with actor, timestamp, IP. 7-year retention for compliance

    +
    +
    +

    Backup & Recovery

    +

    Daily encrypted backups, 4-hour RTO, 24-hour RPO, tested recovery procedures

    +
    +
    +
    +
    + + +
    + +
    + + +
    +
    +
    Status
    +

    Audit Timeline

    + +
    +
    +
    +
    + + + +
    +
    +

    February 2026 — Self-Assessment Complete

    +

    Comprehensive self-assessment against all five Trust Services Criteria completed. Policy documentation created.

    +
    +
    + +
    +
    + + + +
    +
    +

    Q2 2026 — Gap Remediation

    +

    Address recommended action items including backup restore testing and external penetration test.

    +
    +
    + +
    +
    + + + +
    +
    +

    Q4 2026 — Formal SOC 2 Type II Audit

    +

    Engage third-party auditor for formal SOC 2 Type II certification.

    +
    +
    +
    +
    +
    +
    + + +
    +
    +

    Questions About Compliance?

    +

    + Contact our security team for detailed documentation or to discuss your compliance requirements. +

    + +
    +
    + + + + + + + + diff --git a/cmd/server/website/terms.html b/cmd/server/website/terms.html index f702ca7..da03a92 100644 --- a/cmd/server/website/terms.html +++ b/cmd/server/website/terms.html @@ -325,6 +325,7 @@
  • Privacy Policy
  • Terms of Service
  • DPA
  • +
  • SOC 2
  • diff --git a/lib/watermark_test.go b/lib/watermark_test.go new file mode 100644 index 0000000..39fbd48 --- /dev/null +++ b/lib/watermark_test.go @@ -0,0 +1,213 @@ +package lib + +import ( + "archive/zip" + "bytes" + "image" + "image/color" + "image/png" + "testing" +) + +// TestWatermarkPDF tests PDF watermarking with a minimal PDF. +func TestWatermarkPDF(t *testing.T) { + // Minimal valid PDF (empty page) + minimalPDF := []byte(`%PDF-1.4 +1 0 obj +<< /Type /Catalog /Pages 2 0 R >> +endobj +2 0 obj +<< /Type /Pages /Kids [3 0 R] /Count 1 >> +endobj +3 0 obj +<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >> +endobj +xref +0 4 +0000000000 65535 f +0000000009 00000 n +0000000058 00000 n +0000000115 00000 n +trailer +<< /Size 4 /Root 1 0 R >> +startxref +192 +%%EOF`) + + label := "CONFIDENTIAL - Test User - 2026-02-28 - TestProject" + + out, err := WatermarkPDF(minimalPDF, label) + if err != nil { + t.Fatalf("WatermarkPDF failed: %v", err) + } + + // Basic checks + if len(out) == 0 { + t.Fatal("WatermarkPDF returned empty output") + } + + // Watermarked PDF should be larger than original + if len(out) <= len(minimalPDF) { + t.Errorf("Watermarked PDF (%d bytes) should be larger than original (%d bytes)", len(out), len(minimalPDF)) + } + + // Should still start with PDF header + if !bytes.HasPrefix(out, []byte("%PDF")) { + t.Error("Output doesn't look like a PDF (missing %PDF header)") + } +} + +// TestWatermarkImagePNG tests image watermarking with PNG. +func TestWatermarkImagePNG(t *testing.T) { + // Create a minimal test PNG (100x100 red square) + img := image.NewRGBA(image.Rect(0, 0, 100, 100)) + red := color.RGBA{255, 0, 0, 255} + for y := 0; y < 100; y++ { + for x := 0; x < 100; x++ { + img.Set(x, y, red) + } + } + + var buf bytes.Buffer + if err := png.Encode(&buf, img); err != nil { + t.Fatalf("Failed to create test PNG: %v", err) + } + originalPNG := buf.Bytes() + + label := "CONFIDENTIAL - Test User - 2026-02-28" + + out, err := WatermarkImage(originalPNG, "image/png", label) + if err != nil { + t.Fatalf("WatermarkImage failed: %v", err) + } + + // Basic checks + if len(out) == 0 { + t.Fatal("WatermarkImage returned empty output") + } + + // Verify it's still a valid PNG + _, err = png.Decode(bytes.NewReader(out)) + if err != nil { + t.Errorf("Output is not a valid PNG: %v", err) + } +} + +// TestWatermarkDOCX tests DOCX watermarking with a minimal document. +func TestWatermarkDOCX(t *testing.T) { + // Create a minimal DOCX (which is a ZIP file with specific structure) + minimalDOCX := createMinimalDOCX() + + label := "CONFIDENTIAL - Test User - 2026-02-28 - TestProject" + + out, err := WatermarkDOCX(minimalDOCX, label) + if err != nil { + t.Fatalf("WatermarkDOCX failed: %v", err) + } + + // Basic checks + if len(out) == 0 { + t.Fatal("WatermarkDOCX returned empty output") + } + + // Watermarked DOCX should be larger (we added header) + if len(out) <= len(minimalDOCX) { + t.Errorf("Watermarked DOCX (%d bytes) should be larger than original (%d bytes)", len(out), len(minimalDOCX)) + } + + // Should still be a valid ZIP + if !bytes.HasPrefix(out, []byte("PK")) { + t.Error("Output doesn't look like a DOCX/ZIP (missing PK header)") + } +} + +// TestWatermarkDispatch tests the main dispatch function. +func TestWatermarkDispatch(t *testing.T) { + tests := []struct { + name string + mimeType string + data []byte + }{ + {"unknown type passthrough", "application/octet-stream", []byte("hello world")}, + {"text passthrough", "text/plain", []byte("hello world")}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + out, err := Watermark(tt.data, tt.mimeType, "test label") + if err != nil { + t.Errorf("Watermark failed: %v", err) + } + // Unknown types should pass through unchanged + if !bytes.Equal(out, tt.data) { + t.Error("Unknown MIME type should pass through unchanged") + } + }) + } +} + +// TestWatermarkEmptyInput tests error handling for empty inputs. +func TestWatermarkEmptyInput(t *testing.T) { + _, err := WatermarkPDF(nil, "test") + if err == nil { + t.Error("WatermarkPDF should fail on nil input") + } + + _, err = WatermarkImage(nil, "image/png", "test") + if err == nil { + t.Error("WatermarkImage should fail on nil input") + } + + _, err = WatermarkDOCX(nil, "test") + if err == nil { + t.Error("WatermarkDOCX should fail on nil input") + } +} + +// createMinimalDOCX creates a minimal valid DOCX for testing. +func createMinimalDOCX() []byte { + var buf bytes.Buffer + w := zip.NewWriter(&buf) + + // [Content_Types].xml + contentTypes := ` + + + + +` + f, _ := w.Create("[Content_Types].xml") + f.Write([]byte(contentTypes)) + + // _rels/.rels + rels := ` + + +` + f, _ = w.Create("_rels/.rels") + f.Write([]byte(rels)) + + // word/document.xml + doc := ` + + + + + Test Document + + + +` + f, _ = w.Create("word/document.xml") + f.Write([]byte(doc)) + + // word/_rels/document.xml.rels + docRels := ` + +` + f, _ = w.Create("word/_rels/document.xml.rels") + f.Write([]byte(docRels)) + + w.Close() + return buf.Bytes() +}