Privacy Policy

Your data stays yours.

OrgDrift is built on a simple principle: we should see as little of your data as possible, and what we do see should be protected immediately. This page explains exactly how that works.

Last updated: May 9, 2026

The short version

  • Your CSV files are parsed entirely in your browser. They are never uploaded to any server.
  • PII fields (names, emails) are stripped to [REDACTED] before any processing begins.
  • Employee IDs are one-way hashed with SHA-256 before comparison. The original IDs are never stored.
  • No account is required to run a scan. We do not know who you are unless you choose to tell us.
  • If you submit your email (for scan results or contact), we store only that email — nothing from your files.
  • We do not sell, rent, or share your data with any third party for any commercial purpose.

1. What OrgDrift does — and what it sees

OrgDrift detects misalignment between HR systems, CRM platforms, and sales compensation tools. You upload CSV exports from these systems. Our engine compares them, identifies drift, and generates a Control Execution Record you can act on and use for audit evidence.

The scan engine runs entirely in your browser using JavaScript. When you upload a file, it is read into browser memory using the FileReader API. It is not transmitted to any server. Not now, not ever during the scan.

The only data that ever leaves your machine is data you explicitly choose to submit — your email address for scan delivery or a contact request.

2. PII is stripped before processing begins

The moment your CSV is parsed, the engine identifies PII-bearing columns — full name, first name, last name, email, and manager name — and replaces every value in those columns with [REDACTED] before any comparison or analysis runs. This happens synchronously, as the first step inside parseCSV().

The fields treated as PII are derived from the OrgDrift Trust & Data Governance Framework classifier — every field categorized as an Optional Identifier is stripped:

PII field set
// Derived from the governance classifier — every OPTIONAL_IDENTIFIER FieldType.
export const PII_FIELDS: FieldType[] = [...fieldsInCategory('OPTIONAL_IDENTIFIER')]
// Currently: FULL_NAME, FIRST_NAME, LAST_NAME, EMAIL, MANAGER_NAME

The stripping function runs against every row before any state is set:

stripPII()
function stripPII(
  rows: Record<string, string>[],
  fieldMap: Partial<Record<FieldType, string>>,
): Record<string, string>[] {
  const piiColumns = PII_FIELDS
    .map(f => fieldMap[f])
    .filter((col): col is string => Boolean(col))

  if (piiColumns.length === 0) return rows

  return rows.map(row => {
    const clean = { ...row }
    piiColumns.forEach(col => { clean[col] = '[REDACTED]' })
    return clean
  })
}

The result: by the time a row reaches any comparison, cohort analysis, or drift check function, it contains no employee names and no email addresses. The engine never sees them. They exist only in browser memory for the milliseconds it takes to parse and strip, then they are gone.

3. Employee IDs are hashed, not stored

Employee IDs are the key used to match records across systems (e.g., matching an HRIS record to an ICM record). We need to compare them — but we do not need to store the originals.

Before any cross-system comparison, every employee ID is passed through SHA-256, the same cryptographic hash function used by SSL certificates and most modern authentication systems. SHA-256 is a one-way function: given a hash, it is computationally infeasible to recover the original value. Two records with the same employee ID will produce the same hash — so comparisons still work — but the raw ID is never retained.

This runs natively in the browser via the Web Crypto API — no library, no external call:

hashId()
/**
 * SHA-256 via Web Crypto API — no library, works in every modern browser.
 * One-way — cannot be reversed. Employee IDs are hashed before any comparison.
 */
export async function hashId(rawId: string): Promise<string> {
  const normalized = rawId.trim().toLowerCase()
  const encoder = new TextEncoder()
  const data = encoder.encode(normalized)
  const hashBuffer = await crypto.subtle.digest('SHA-256', data)
  const hashArray = Array.from(new Uint8Array(hashBuffer))
  return hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
}

For datasets, every row is hashed in parallel before processing:

hashDataset()
export async function hashDataset(
  rows: Record<string, string>[],
  idColumn: string,
): Promise<{ rows: Record<string, string>[]; idMap: Map<string, string> }> {
  const idMap = new Map<string, string>()

  const hashed = await Promise.all(rows.map(async row => {
    const rawId = row[idColumn] ?? ''
    let hashedId = idMap.get(rawId)
    if (!hashedId) {
      hashedId = await hashId(rawId)
      idMap.set(rawId, hashedId)
    }
    return { ...row, [idColumn]: hashedId }
  }))

  return { rows: hashed, idMap }
}

The idMap (original → hash) exists only in local function scope during the analysis run and is garbage-collected when the function returns. It is never written to localStorage, sessionStorage, or any network destination.

4. The order of operations — what happens when you upload a file

For full transparency, here is the exact sequence every file goes through:

  1. File is selected in the browser. The raw File object is held in React state — in memory only.
  2. parseCSV() is called. PapaParser reads the file into row objects.
  3. stripPII() runs immediately — names and emails become [REDACTED] before the rows are stored anywhere.
  4. autoDetectAllFields() maps your column headers to OrgDrift field types (EMPLOYEE_ID, STATUS, etc.).
  5. The cleaned rows are stored in AnalysisContext (browser memory only).
  6. When you run the scan, runAnalysis() receives the cleaned session. stripPII() runs a second time inside each engine function as a defensive measure.
  7. hashDataset() converts all employee IDs to SHA-256 hashes.
  8. The analysis runs on hashed, PII-stripped data.
  9. Results are displayed in your browser. Nothing is transmitted.
  10. When you close or refresh the tab, all session data is lost. There is no persistence.

5. What is stored server-side

Almost nothing.

  • Email addresses — collected only if you submit the email capture form for scan results or contact. Stored in our email service provider (currently Resend). Used only to respond to your request.
  • Contact form responses — the text you enter in the contact form, submitted via Resend and reviewed only by Ken Lannon.
  • Standard server logs — Vercel (our hosting provider) logs standard HTTP request metadata: IP address, user agent, page path, timestamp. This is standard web infrastructure behavior and is not data we actively collect or analyze.

We do not use remarketing pixels. We do not sell or share your data with advertising networks.

We use Microsoft Clarity for session replay on our marketing pages, and nowhere else — never on the scan workspace or any page that can display your data. See Section 7 for what it captures and how to opt out.

6. What we will never do

  • Sell, rent, or license your data to any third party.
  • Use your employee or compensation data to train any AI or machine learning model.
  • Request more access than the task requires.
  • Store file contents on any server without your explicit knowledge and opt-in.
  • Transmit your file contents — any row, any cell, any employee record — outside your browser.

One thing does leave, and we would rather tell you than have you find it. When you map columns, the column header names from your file (for example EE_ID, GRS_PAY) are sent to our mapping service, which asks a language model what each header probably means. Header names only — never a row, never a cell value, never an employee record. If you would rather send nothing at all, turn on Private Mode and column mapping runs entirely in your browser. See Section 7.

7. Third-party services

OrgDrift uses a minimal set of third-party infrastructure:

  • Vercel — hosting and edge delivery. Subject to Vercel's Privacy Policy.
  • Resend — transactional email delivery for form submissions and scan results. Subject to Resend's Privacy Policy.
  • Anthropic — column-mapping suggestions. Receives your CSV column header names, the source-system role you selected, and our target schema. Receives no rows, no cell values, and no employee records. Disabled entirely in Private Mode. Subject to Anthropic's Privacy Policy.
  • Vercel Analytics and Speed Insights — aggregate page-view counts and page-load performance. Not loaded on any page that can display your data. Subject to Vercel's Privacy Policy.
  • Microsoft Clarity — product-quality session replay, on our marketing pages only. Used by us to watch where visitors click, hover, and abandon, so we can fix UX defects. Subject to Microsoft's Privacy Statement.

Session replay never runs where your data is. Clarity records mouse movement, clicks, scrolls, and a reconstructed view of the rendered page. That is fine on a marketing page and unacceptable on a page showing payroll records, so we do not load it on one. The scan workspace, the verification console, findings, remediation, and governance surfaces load no session-replay script at all — not a masked one, none. You can confirm this yourself: open any of those pages, view source, and search for clarity. As a second line of defense those routes are also wrapped in data-clarity-mask, and their Content-Security-Policy header permits no third-party connections whatsoever.

Private Mode. Append ?private=1 to any scan URL. Column mapping then runs entirely in your browser, the mapping service is never called, and no analytics or replay script loads. The scan page makes no request carrying any part of your file. To verify: load the page, disconnect your network, and run a scan. It completes.

How to opt out. You can block clarity.ms with any standard tracker-blocking extension (uBlock Origin, Privacy Badger, Ghostery, Brave's built-in shields, etc.) or by enabling your browser's third-party-script blocker. You can also email us at ken.lannon@orgdrift.com to request that we exclude your account from session replay.

The services listed above are the complete set. We do not use Segment, Amplitude, Mixpanel, Heap, FullStory, HubSpot, or Intercom. Microsoft Clarity is the only session-replay tool in use and it is scoped to marketing pages. If you find an outbound request from an OrgDrift page to an origin not named on this list, that is a bug and we want to hear about it.

8. Data retention

Email addresses collected through form submissions are retained until you request deletion. Scan data is never retained — it exists only in your browser session memory.

To request deletion of any data we hold about you, email ken.lannon@orgdrift.com. We will respond within 5 business days and confirm deletion within 30 days.

9. Changes to this policy

If we make material changes — particularly anything that affects how file data is handled — we will update the “Last updated” date at the top of this page and, where appropriate, notify users who have submitted their email. We will not retroactively apply weaker protections to data already collected.

10. Contact

Questions about this policy or your data? Reach us at ken.lannon@orgdrift.com or write to:

OrgDrift, LLC
Attn: Privacy
ken.lannon@orgdrift.com
← Back to OrgDrift© 2026 OrgDrift, LLC