Skip to main content

Find My Data — Preliminary Threat Model (Vertical Slice)

Status: Preliminary · 2026-07-14 (revised 2026-07-15 for the follow-on build: live read-only CDX Graph connector, rich-format extraction, change-notification receiver, Teams/Exchange connectors) Scope of this document: the vertical slice and its follow-on work — a Bun + TypeScript modular monolith with SQLite behind role interfaces, a deterministic mock M365 connector as the primary/CI-exercised path (with the live Microsoft Graph connector additionally validated read-only against the Contoso CDX tenant, ADR-0013), dev identity auth with production lockout, capability-based authorization, hash-chained audit, connectors for SharePoint/OneDrive plus feature-gated Teams/Exchange, change-notification ingestion, and the minimized-evidence retention profile (ADR-0011). This is a preliminary model for a single-tenant, locally-run slice. It describes what is actually implemented, honestly separates implemented / mocked / config-gated / future, and does not claim validation against any live Microsoft tenant or AI provider. It should be revised when the live Graph connector, Entra sign-in, or any multi-tenant control plane becomes real. Companion sources: find_my_data_handoff/FIND_MY_DATA_SECURITY_RBAC_AND_GOVERNANCE.md (the target posture), docs/adr/ADR-0001..0014 (the decisions), STATUS.md (what was exercised).

1. Scope and trust boundaries

1.1 Deployment shape in this slice

One server process (packages/server/src/main.ts) hosts the HTTP API (Hono) and an in-process durable worker loop over a SQLite-backed work queue (ADR-0002). The web UI is a separate Vite/React SPA (packages/web/) that talks to the API. Everything runs on one machine with one SQLite file and one artifact directory. There is no network segmentation, no separate identity provider, and no cloud service in the exercised path.

1.2 Boundaries

1.3 What “single-tenant SQLite slice” means for the model

  • Every row carries tenant_id and repositories refuse unscoped queries (ADR-0003), but only one tenant is ever seeded and exercised. Cross-tenant isolation is enforced logically, not physically, and has not been tested under adversarial multi-tenant load.
  • The OS user running the process is effectively root of the whole system: DB, artifacts, audit chain, and session secret (env var) are all reachable. Threats below that reduce to “attacker has local filesystem access” are out of scope for the slice and in scope for the Azure deployment design.
  • There is no TLS termination in the slice; transport security is a deployment concern.

2. Assets to protect

Mapping of the handoff SECURITY §2 asset list to what actually exists in this build. Per the handoff: derivative data (fingerprints, paths, questions) is still sensitive — none of these tables is “safe” merely because it holds no full text.

3. Threat actors and scenarios

Conventions: Implemented = enforced in code in this build, with automated tests where noted. Residual = what remains true even with the mitigation. Planned = documented follow-up, not built.

4. Data-retention profile (default: minimized evidence, ADR-0011)

The single seeded tenant runs the minimized_evidence profile (tenants.data_handling_profile, migration 0001; metadata_only and enhanced_evidence are schema-ready values only — neither is implemented beyond configuration validation).

4.1 Persisted by default

  • Stable source IDs, source/content versions, change types, safe locator fields (physical_assets, asset_versions, asset_locations).
  • Label GUID/state observations and catalog references (asset_label_obs, sensitivity_labels).
  • Normalized permission summaries with completeness state — digests, broad categories, principal counts — not unbounded per-user expansion (permission_sets, permission_grants).
  • Activity aggregates (activity_aggregates).
  • Exact hashes (SHA-256 over raw bytes and normalized text) and SimHash64 similarity fingerprints with band index (fingerprints, fingerprint_bands).
  • Keyphrases, language, detector hits, and bounded redacted excerpts (feature_artifacts, evidence).
  • Assertions with confidence, provenance kind, evidence links, and model/template versions; review decisions; findings; actions; audit (assertions, review_decisions, findings, action_*, audit_events).
  • AI generation runs record task, provider, model, template version, and an input hash — not prompt bodies (generation_runs).

4.2 Not persisted by default

  • Complete source originals (never written to disk).
  • Plaintext credentials or tokens (config stores env-var references only).
  • Unrestricted permission expansion per user.
  • Raw prompts/responses for debugging.

4.3 Honest caveats against ADR-0011

  1. Bounded extracted text is retained. The pipeline stores extracted text up to the 20,000-character extraction cap as an extracted_text_bounded artifact (packages/server/src/scan/pipeline.ts content stage; packages/server/src/intelligence/extract.ts limits). For any file under the cap, this is effectively the complete extracted text of that file. It is purpose-flagged and purpose-checked, but reviewers of this model should treat “complete extracted text is not persisted” as holding only for content larger than the cap.
  2. Scratch directory is described, not exercised. ADR-0011 describes a per-work-unit scratch area under data/scratch/ with cleanup on completion and startup recovery. The current slice processes content — including the rich-format parsers (OOXML via the bounded ZIP reader, PDF, RTF, HTML) — entirely in memory on the fetched bytes (bounded to 5 MB per item by CONTENT_MAX_BYTES) and never writes raw source bytes to disk, so no scratch directory is created or cleaned. The scratch + sandbox mechanism remains the hardening path to isolate that in-process rich-format extraction (see T6); it is not yet implemented.
  3. Retention sweeps are not implemented. Artifacts carry a retention_class column, but no scheduled deletion job exists in this build. Retention is a recorded intent, not an enforced behavior.

4.4 Purpose flags (content-use flags)

Every artifact row carries use_flags_json from the set classification_inference, owner_review, search, analyst, evaluation, training, troubleshooting (packages/server/src/kernel/objectstore.ts). Absence means not permitted: ObjectStore.get requires a declared purpose and throws when the artifact’s flags do not include it. Extracted-text artifacts are written with classification_inference and owner_review only — they are not readable for search, Analyst retrieval, evaluation, training, or troubleshooting purposes. No artifact in this build carries training or evaluation flags.

4.5 Excerpt bounds and redaction

Owner-review excerpts are bounded to ≤ 240 characters each, ≤ 3 per asset version (packages/server/src/scan/pipeline.ts: EXCERPT_MAX_CHARS, EXCERPT_MAX_COUNT) and pass through digit masking for SSN-like patterns, currency amounts, and dates before storage (redactExcerpt). Excerpt reads additionally require the domain-scoped asset.evidence.excerpt.read capability (packages/server/src/review/service.ts); callers without it receive summaries with excerpts: null. The redaction patterns are fixture-tuned (US SSN format, $ amounts) — they are a demonstration of the mechanism, not a production-grade PII redactor.

5. Explicitly deferred items (handoff SECURITY §21)

These are flagged, not silently assumed. None of them is resolved by this slice. Additional deferrals specific to this build, recorded for honesty:
  • Live Graph connector validation — the read-only path was validated against the Contoso CDX tenant (ADR-0013; run record in docs/cdx-test-runbook.md §0). Change-sequence deltas, label read/write, Teams/Exchange, and write actions remain unvalidated live (STATUS.md).
  • Live AI provider governance — Azure OpenAI/Anthropic adapters compiled but unexercised; provider retention/region policy enforcement (ADR-0005 §provider policy) activates only when a live provider is configured.
  • Entra OIDC — no OIDC code-flow or token-validation code exists yet; the Entra path is a 501 seam to implement against (ADR-0004, B8).
  • Purview protected API enablement — live label write requires Microsoft protected/metered API approval; the slice reports blocked honestly rather than simulating success (packages/server/src/actions/service.ts, docs/permissions-manifest.md).