HubSpot Attribution — New Logo Direct (Direct Sales pipeline, excluding expansion)
How this dashboard is designed so that AON3D data does not become a security liability
| Password gate | Every page checks sessionStorage on load and redirects unauthenticated visitors to the login screen. The password is validated server-side via a POST to /auth — no hash or cryptographic material exists anywhere in the client JS. A PBKDF2-SHA256 derived key (600,000 iterations) is stored only on the server. |
| Session scope | sessionStorage is tab-scoped and clears automatically when the tab or browser closes. No persistent cookies. No server-side session tokens. |
| Lockout policy | After 10 consecutive incorrect attempts across all browsers and devices, the auth endpoint returns 429 and the site locks until the next UTC day. The counter is a server-side state file — incognito mode, different browsers, and different devices all share the same limit. A successful login resets the counter immediately. |
| No username | Single shared password for internal use. Appropriate for an internal analytics dashboard — avoids the attack surface of a username/password credential store. |
| Names & emails | Contact names and email addresses are never pulled from HubSpot. They do not exist anywhere in this system — not in raw CSVs, not in the deployed JSON, not in the browser. |
| Deal names | SHA-256 hashed immediately at pull time. Used for deduplication only, never displayed. The original deal name is discarded before any file is written. |
| Owner identity | HubSpot owner names are converted to two-letter initials at pull time. No full names or emails stored. |
| Company names | Never pulled. Only industry, country, and employee count range are fetched — categorical fields with no PII value. |
| What reaches the server | Only the dist/ folder is deployed — pre-aggregated JSON, static HTML, CSS, and JS. Nothing else. |
| What stays local | Raw HubSpot CSVs, Python pipeline scripts, API credentials, and logs are never deployed. They are also gitignored and will not appear in version control. |
| Deploy scope | rsync uses an explicit include of dist/ only. Parent directories are excluded by design, not by accident. |
| HTTPS | Let's Encrypt certificate with automated renewal. All traffic encrypted in transit. |
| Static site | No server-side code execution. No database. No application server. The attack surface is limited to serving static files — there is no server-side logic to exploit. |
| No tracking | No analytics pixels, no Google Analytics, no third-party tracking scripts. The only external requests from this page are Google Fonts (typography) and jsDelivr (Chart.js library). |
| HubSpot token | Private App token with read-only scopes only: crm.objects.contacts.read, crm.objects.deals.read, crm.objects.companies.read, crm.objects.owners.read. The token cannot write to or modify HubSpot data. |
| Stored location | Token lives in a local environment file on the machine running the pipeline. It is gitignored and never deployed to the web server. |
| Google Ads token | OAuth2 refresh token stored locally. Read-only reporting access. Not deployed to the server. |
These are architectural constraints, not just policy decisions. They are structurally enforced by the system design.
| Write to HubSpot | The HubSpot API token has read-only scopes. Even if the dashboard were compromised, it cannot create, edit, or delete CRM records. |
| Expose individual contacts | Names and emails are never pulled into the system. A compromised server would yield only pre-aggregated JSON with industry/channel counts — no individual identities. |
| Leak raw pipeline data | Raw CSVs containing HubSpot record-level data reside only on the local machine running the pipeline. They are never deployed and never committed to git. |
| Run server-side code | The server hosts static files only. There is no application runtime, no shell access surface exposed by the app, and no database connection string to steal. |
Risk posture summary. The worst-case breach of this dashboard is exposure of aggregated marketing metrics (deal counts by channel, win rates by industry). No PII, no CRM write access, no financial data beyond aggregate revenue ranges. This is intentional — the dashboard was architected to minimize blast radius from the start.