Security at MapKraken
Territory Mapper is a territory-management application for Salesforce, published by MapKraken Labs LLC.
This page describes how Territory Mapper handles your data, what it stores, what it does not, and which security controls are in place today. It is written to be checked. Where a control does not exist, this page says so rather than implying otherwise — there is a section for exactly that, and it is not short.
Territory Mapper is pre-release. It is not yet listed on the Salesforce AppExchange and has no production subscribers. Several of the controls an enterprise security team expects are in progress rather than in place, and they are listed in What we do not claim yet. We would rather you read that section first than find it later.
1. Architecture
Territory Mapper has three tiers.
A managed package installs in your Salesforce org and holds the territory model, the assignment logic and the administrative setup. A map application — a browser application served from a content delivery network — runs inside your Salesforce org in a Canvas frame. A geospatial service, hosted by MapKraken on Amazon Web Services, performs the spatial work: turning a set of selected census boundaries into a dissolved territory polygon, and answering the map's queries about geography.
Your CRM records stay in Salesforce. The geospatial service holds census geography — public reference data — together with the identifiers and territory names it needs to associate a polygon with one of your territories. Section 2 lists exactly which.
┌─ your Salesforce org ─────────────────────────────┐
│ │
│ Territory Mapper managed package │
│ territory model · assignment · setup │
│ │
│ ┌─ Canvas frame ────────────────┐ │
│ │ the map application │ │
│ └───────────┬───────────────────┘ │
└───────────────┼───────────────────────────────────┘
│
signed handshake, verified server-side
│
▼
┌─ MapKraken geospatial service (AWS, us-east-1) ───┐
│ │
│ spatial operations · territory geometry │
│ census geography (public reference data) │
│ │
└───────────────────────────────────────────────────┘
Two directions of traffic are worth naming, because a security team will ask about both. The map application reads and writes your Salesforce data as the signed-in user, over Salesforce's own APIs, with that user's field-level security and sharing rules enforced by the platform. Separately, the geospatial service reads your territory hierarchy back from Salesforce using a per-org credential your administrator authorises at install.
2. What we store — and what we do not
The geospatial service persists eight things. This is the complete list, read from the running database rather than from a design document.
| What | Why it is there |
|---|---|
| Your Salesforce Org Id | Identifies which org a territory belongs to; it is also the partition key for your data |
| Your Territory Model Ids | Associates geometry with the right territory model |
| Your Territory record Ids | Associates a polygon with a territory |
| Your territory names | Labels the polygon on the map |
| The dissolved territory polygons themselves | The product |
| The integration user's username | Identifies the account the service authenticates as when it reads your hierarchy |
| Your org's My Domain login URL | The endpoint the service authenticates against |
| The connected-app consumer key for the integration | Part of the credential your administrator authorises at install |
What is not stored, stated as specifically as we can make it. We checked every column of every table, not our expectations:
- No account, contact or lead records. No CRM object name is held in any column.
- No addresses. There is no street, city, postal-code or formatted-address column anywhere.
- No geocoded locations of your records. Territory Mapper's assignment path geocodes an account inside Salesforce and uses the result to ask the service which territory contains this point. The point is never sent to the service and never stored. There is no point geometry in the database at all — only polygons. ⚠️ This is a statement about your records, and not about the map's search box. Read on its own it could be taken to mean no geocoding of any kind leaves the platform, which is not true: the map's city-search control sends the text a user types to a third-party place-name service. That is a different path, it involves none of your record data, and it is disclosed in §10. 🔴 Changed 2026-10-07 — this bullet previously stood without that qualification.
- Your user identity is sent to the geospatial service when the map loads. It is not stored there. The signed token the map launches with carries your Salesforce user id, name, username and email address. It travels in a form POST body, never in a URL. None of it is written to the database — no user identifier appears in any column of any table. The service uses the token to confirm the request came from your Salesforce org.
- A Salesforce session id is sent to the geospatial service when the map loads. It travels in a form POST body — never in a URL, a query string, or a link — and it is what lets the map read your territory data back from Salesforce for that session. Nothing else carries one: the assignment path, the tile requests and the city search do not.
Territory names are free text that you control. They are stored exactly as entered and are not inspected or validated. If you put personal information in a territory name, it will be persisted. We mention this because it is the one field on the list where what gets stored is your choice rather than ours.
How long we keep it. See §7. The honest answer is short and it is not the one you want.
3. Tenant isolation
Each subscriber org's territory data is held in a separate set of database tables, partitioned by your Salesforce Org Id. On every data route, the org identity used to select those tables is taken from a cryptographically verified token and never from anything the caller supplies; a request that names one org while carrying a token for another is rejected, not served.
The partition is enforced in the application layer. It is not enforced by the database itself: there is no row-level security and no per-org database role. We state this plainly because a security team will find it, and because "application-layer" is a materially different guarantee from "database-enforced."
We do not yet claim complete tenant isolation. One isolation defect is open and tracked, identified by our own security review: the token the Salesforce side presents to the geospatial service is signed with one shared secret rather than a per-subscriber key. That means the service can verify that a token is genuine, but not that it belongs to the subscriber it names. (A second defect, an unauthenticated path to the same outcome, was found in the same review and closed on 2026-08-19.)
So the service currently serves no external subscriber, and a second active subscriber credential cannot be inserted. The one organisation provisioned on it is our own Salesforce Developer Edition org. There are no external subscribers and no paying subscribers today, and we say that rather than write "a single subscriber", which would invite you to picture a customer.
That second statement is enforced, not merely intended. Since 2026-08-23 the credential table carries a uniqueness constraint scoped to active credentials, so a second active one cannot exist; we re-probed it live against the running database on 2026-08-24 — the constraint is present, it is unique, it applies to active rows, and there is exactly one active row. Onboarding a second organisation fails at the database, by design, until per-org signing keys ship. That work is designed and scheduled to the day it is first needed. The full position, including the wording we give a security reviewer, is written down and we will share it on request. See also What we do not claim yet.
4. Encryption
In transit.
- Traffic between your browser and Salesforce, and between the map application and Salesforce's APIs, is HTTPS, terminated by Salesforce.
- The geospatial service reads your Salesforce hierarchy over HTTPS.
- The service connects to its database over TLS 1.3 (
TLS_AES_256_GCM_SHA384), and since 2026-08-24 the database server requires it: an unencrypted connection is refused by the server rather than merely avoided by the application. We proved it both ways — a connection asking for no TLS was rejected, and a TLS 1.3 connection was accepted.
HTTPS is enforced on the tier that serves subscribers. Since 2026-08-21 a plaintext request to the geospatial service is redirected rather than served: http:// returns a 301, the load balancer listens on 443 only — the plaintext and auxiliary listeners were removed — and the content delivery network in front of it redirects to HTTPS and reaches its origin over HTTPS only. Four independent probes confirm it.
🔴 Changed 2026-10-07 — this passage understated the control, and we are publishing the change for the same reason we publish the overstatements (167-F4). It used to say: "Two non-production stages still accept a plaintext viewer connection rather than refusing it … masked by an availability fault rather than closed by a control," with refusal "dated 2026-09-05."
Measured against the running system on 2026-10-07, every stage we run redirects plaintext. A census of all 17 content-delivery distributions in the hosting account found none configured to accept a plaintext viewer connection. Probed as well as read: a plaintext request to the production geospatial service returns a 301 redirect and the service behind it answers an authenticated request — it is live, not timing out, so there is no availability fault left there to do any masking.
One remainder, stated precisely. On a single non-production stage the hop from the content delivery network to its origin is still plaintext. That stage serves no content, carries no subscriber data, and is not reachable by a subscriber. It is listed in What we do not claim yet and it is the last plaintext leg in the estate.
At rest.
- Secrets are held in AWS Secrets Manager, encrypted with AWS-managed keys (§6).
- The census geography served as map tiles is public reference data from the U.S. Census Bureau. It contains no subscriber content.
🔴 Changed 2026-10-07 — this section claimed a control the production database does not have, and saying so is the whole point of publishing a page like this (166-F17). What it used to say: "The service database is encrypted at rest … the database your data reaches is encrypted at rest under a customer-managed key with annual rotation," with the unencrypted remainder described as "a frozen legacy demonstration instance." Measured against the running service on 2026-10-07, that was wrong twice.
The production database is NOT encrypted at rest. Our staging database is. Measured, not inferred:
| instance | encrypted at rest? | what it is |
|---|---|---|
prd-koop-postgis | 🔴 No — StorageEncrypted: false, no key | The database the production service reads and writes. The live task definition sets PG_HOST to it, and the service is running |
sit-koop-postgis | ✅ Yes — customer-managed key, annual rotation, since 2026-08-24 | Our staging database |
So the 2026-08-24 migration was real, and it was a migration of staging. It took 7 minutes 43 seconds with the data proven byte-identical across the cutover — 17 row counts, 4 checksums and 2 geometry areas compared before and after. What was wrong was calling its result "the database your data reaches."
🔴 And the second error was the label. The unencrypted instance was described as "a frozen legacy demonstration instance." It is the production database. It is frozen in the sense that we deliberately stopped changing it pending this work, and today it holds demonstration data only — we have no external and no paying subscribers, and the one provisioned org is our own Developer Edition org (§3) — but "frozen legacy demonstration instance" describes something decommissioned, and this is not that. An unencrypted production database is the kind of thing a page like this exists to state plainly.
What is true today, stated without rounding:
- Data in transit is encrypted everywhere a subscriber reaches us, and the production database requires TLS — a connection asking for none is refused (§4, In transit). 🔴 Transport is not storage, and this page will not let one stand in for the other.
- Production storage encryption is not enabled, and it is scheduled. Enabling it is not a setting; it needs a snapshot restore and a cutover, which is why it is dated rather than immediate.
- 🔴 The date we previously published for it — 2026-10-05 — has passed, and we are recording that here rather than quietly moving it (
166-F18). A commitment with a date that lapses in silence becomes an acceptance nobody ever agreed to. The new date is owner-set and appears in the Maintenance log below when it is set; until then this row carries no date, and that absence is deliberate and visible. - Database snapshots taken before the staging cutover are still unencrypted, and they were scheduled for deletion on 2026-08-31 — a date that has also passed. 🔴 **Changed 2026-10-08 — this bullet said "Twelve", and that figure did not describe any set we can count. Counted against the live account today: 26 snapshots exist, of which 15 are unencrypted and 11 encrypted. Of the unencrypted, 6 were taken deliberately and 9 are automatic daily backups. The set this bullet is about — unencrypted and older than the staging cutover — is 4. Every automatic unencrypted backup is newer than the cutover, because they expire on a rolling schedule, so none of them belongs here. The old figure was wrong in both directions at once: it overstated the pre-cutover set it named, and understated how many unencrypted snapshots exist in total. The set to hold us to is the 6 deliberate unencrypted snapshots**, which are the ones we can delete; the automatic population cannot carry a deletion commitment, because a new one is created every night.
**All of this is in What we do not claim yet. 🔴 The previous wording of this section is quoted above rather than deleted, because a trust page that silently rewrites a claim it got wrong is asking to be trusted on exactly the thing it just got wrong** — the same rule this page's 2026-08-24 entry applied to itself.
5. Authentication and access control
How a request is proven to come from your org.
The map application is launched by Salesforce inside a Canvas frame. Salesforce signs the launch payload, and the geospatial service verifies that signature server-side — checking the signature itself with a constant-time comparison, pinning the signing algorithm, and rejecting a payload outside a five-minute window. Your org's identity is read from the verified payload and carried through to the database table name; it is never read from caller-supplied input on a data route.
Every data route on the geospatial service requires a verified bearer token. A request without one is refused. Cross-origin policy is not the control and is not relied on as one.
No token, org identifier or territory data travels in a URL on any hop. They travel in signed payloads and request bodies, so they do not land in browser history, referrer headers or access logs.
What runs as you, and what does not.
- Writes to Salesforce run as the signed-in user, with field-level security and sharing rules enforced by the platform. This is the most strongly enforced surface in the product.
- Every REST entry point in the managed package is gated by a custom permission at runtime, with a read/write split — not by profile, and not by assumption.
- The hierarchy read-back does not run as you. When the geospatial service reads your territory model back from Salesforce, it runs as the integration user your administrator authorised, in system field context rather than the viewing user's. This is deliberate — the map needs the whole model to draw it — and it is compensated by the custom-permission gate on the entry points and by sharing enforcement on every write. We state it rather than leave it to be discovered.
Authorization at the geospatial service is per-org, not per-user. A session token proves which org a request belongs to. It does not carry the user's Territory Mapper permissions, so inside the map, the service does not distinguish a read-tier user from a manager. Capability is governed by Salesforce permissions on the Salesforce side and by org scope on the map side. Adding per-user authorization to the service is planned. If per-user enforcement inside the map is a requirement for you, it is not there today.
Session lifetime. A map session lasts 8 hours and does not refresh.
6. Secrets and key management
Credentials used by the geospatial service are held in AWS Secrets Manager and supplied to the service at container start by the AWS platform.
🔴 Changed 2026-10-08 — this paragraph claimed a restriction that production does not have, and we are publishing the change for the same reason we publish the overstatements. It used to end: "The application process itself holds no permission to read Secrets Manager at runtime." Measured against the task definition actually in service, that was not true of production. The production service's own identity carries a permission to read one secret — the credential for the review organisation — and so the application process can read that one secret at runtime.
What is true, stated precisely. The sensitive credentials — the signing keys and the database credential — are read by a separate platform identity that the application process does not hold, and are placed into the service's environment before the process starts. The one secret the service's own identity can fetch for itself is the review-organisation credential, which is neither a database credential nor a signing key. On our non-production stage the service identity holds no secret-retrieval permission at all. ⚠️ And because credentials are delivered through the process environment, a credential is readable by anything that can read that environment — we say so rather than let the delivery path be read as an isolation boundary it is not.
Secret injection is the delivery path, and we have proved it is load-bearing — an end-to-end test completes a Canvas login on an image that has nothing to fall back on. On 2026-08-08 we inspected a container image directly and confirmed it carried no environment file, no private key material, and a clean result from a secret scanner run across the whole application directory.
We do not claim that the image running today carries no embedded credentials. Re-inspecting our registry on 2026-08-20, we found that our image build had begun including an environment file again after that clean build, and that the images now running include one. We found this ourselves, we are stating it rather than waiting until it was fixed, and correcting the build is our highest-priority open item. Two things a security team should weigh alongside it: the credentials involved are the ones already covered by our written credential-exposure disclosure, and nothing automatically detected the regression — which is the more important finding, and is why image scanning is now on the list in What we do not claim yet.
Source-history secret scanning. A full-history scan of all four of our repositories with gitleaks 8.30.1 on 2026-08-06 produced seven hits: two false positives, one already redacted, and four in deleted or superseded files. No live credential is exposed by our source history. The tool version and the exact command are recorded so the result can be reproduced rather than taken on trust.
We do not operate automated secret rotation. Rotation is manual today. Automating it is planned.
7. Logging and retention
Application logs. The geospatial service writes application logs to Amazon CloudWatch with a thirty-day retention period. Every log group in our hosting account carries an explicit retention period and none is unbounded. Sampling found operational events and no personal data; we have not completed a formal audit of log contents and do not claim one.
The Salesforce-side failure log. Territory Mapper records assignment failures in your org so they can be worked rather than lost. That log has a real, enforced policy:
- A failure record is purged 90 days after it is closed — the clock runs from the day someone finished with it, not from the day it was created.
- An open record is never deleted, at any age. Deleting one would silently un-report a failure that is still true.
- The purge is permanent and unrecoverable — not a recycle bin. (A platform-level tombstone remains visible to Salesforce's own deleted-records view for the platform's own window; no application can shorten that, and we will not claim that no trace remains.)
- It is enforced by a scheduled daily job registered at install, not by a promise.
Each record holds Salesforce record ids, the object's API name, a failure category, the service's own error text, a latitude and longitude, a geocode accuracy value, a territory model id and a correlation id. No names, no addresses, and no account fields beyond coordinates the record already holds.
Data held by the geospatial service — the retention policy, and the two things it does not do. This is the section a procurement team will stop on, so it is written without softening:
- Territory geometry is destroyed 30 days after it is marked for deletion. Every table the service creates is recorded, and a marked table is dropped by a scheduled sweep that runs every 24 hours. The 30 days are a recovery window, not a processing delay: the selection that produced a territory is not stored, so once the geometry is gone it cannot be rebuilt, and an accidental deletion would otherwise be unrecoverable. The window is enforced by the storage layer rather than by a setting.
- 🔴 There is no self-service delete control in the product, and that is a deliberate decision rather than missing work. Our geospatial service authorises per organisation, not per user, so a delete button in the application would be usable by any user of your org with the same effect as your administrator. A deletion request sent to the security contact in §13 is carried out by us, against a written procedure, and we confirm the date it completes. We will add self-service deletion when per-user enforcement reaches that service, and not before.
- 🔴 Uninstalling the managed package deletes none of the geospatial data, and we will not claim an uninstall-time deletion we cannot make run reliably. Uninstalling removes the package and the assignment-failure log it ships. It also does not delete your Salesforce territory records — those are platform data in your org and remain yours; Salesforce does not permit an application to delete a territory record at all, and your administrator removes them from Setup whenever you choose.
- Deletion applies to our live database. Database backups taken before a deletion still contain the data and expire on their own retention schedule; we do not selectively edit backups.
Application logs cannot be deleted per subscriber — they are shared log groups. They expire at 30 days.
Audit logging and alerting. We do not currently operate infrastructure audit logging, security monitoring or alerting on the hosting account. The two halves are in different states and we would rather split them than average them. Audit logging is decided and dated: 2026-08-31. The destination the trail writes to already exists; what is missing is the trail itself. Alerting is not dated. Metric filters, alarms and a path that pages a person are an accepted risk with no date against them, and we are not going to imply otherwise. We do not claim monitoring we do not have.
8. Vulnerability management
What actually runs, stated as what runs rather than what we intend:
| Control | State |
|---|---|
| Secret scanning in CI | ◐ Configured. gitleaks is wired into GitHub Actions in all three application repositories and blocks a secret committed to a scanned branch. ⚠️ Updated 2026-08-25 — one of the two stated limits is fixed, the other is not, and a third was found while fixing the first. ✅ The branch list is fixed, and as of 2026-08-25 it is VERIFIED, not merely changed: the enumerated filter was removed rather than extended — extending a list is what let it rot in the first place — and the workflows have now been observed running on GitHub Actions against the actual task branches, which had never happened before. ⚠️ One of the four runs FAILED, and we are reporting that rather than the headline: territorymap-koop exited non-zero on two hits, both test fixtures containing no key material (a -----BEGIN PRIVATE KEY----- envelope wrapped around the literals MIIE and nope, the second one asserting that such input is rejected). It is a true positive by rule and a false positive by substance, it is filed as 44-F16 rather than quietly allowlisted — and it is the first live proof that this gate genuinely blocks a build rather than merely reporting. 🔴 The clean-checkout limit stands and cannot be fixed here: a file excluded from source control is invisible to any repository scan, because the scan runs on a checkout of committed history. The control that closes it inspects the built container image, and we have not built it yet. 🔴 And read "all three application repositories" precisely, because it is doing quiet work: our infrastructure repository has no secret scanning at all, and it is the one holding deployment templates. We are stating the denominator rather than letting the phrase imply full coverage ✅ Updated 2026-08-25 (later): the second limit is now CLOSED — the built container image is scanned. A CI job exports the image’s flattened filesystem and runs two independent checks: a secret scan of the application code, and an assertion that no .env or *.pem file exists in it. Both failure paths were proved deliberately before the gate was trusted. This closes the gap that source-side scanning is structurally unable to cover — a file excluded from source control can still be copied into an image, and only looking inside the built image finds it. ⚠️ Two limits we state rather than imply away. The job scans a representative build of the image, not the exact artefact published to our registry — verifying the published artefact remains a manual step. And the scan covers application code only: the scanner excludes vendored third-party dependency directories by default, so a clean result is a statement about the code we write, not about every byte in the image. Where the tool does not report enough to substantiate a coverage figure, the job now prints that the coverage is undeterminable rather than printing a number (44-F18). 🔴 Unchanged: our infrastructure repository still has no secret scanning at all, and it is the one holding deployment templates. We continue to state the denominator rather than let the phrase imply full coverage |
| Full-history secret scan | ✅ Performed 2026-08-06 across all four repositories, tool version and command recorded, result reproducible (§6) |
| Dependency vulnerability review | ◐ ~~Performed manually against the published advisory database. It is not a CI gate, and we do not claim one~~ ⚠️ Updated 2026-08-25 — a CI gate now exists and RAN, and the honest statement is narrower than "we have one." dependency-audit.yml runs in all four repositories, installs with npm ci never npm install (an npm install audits a tree the lockfile would never deploy), and compares against a committed baseline. ✅ Verified on GitHub Actions 2026-08-25: every baseline reproduced exactly, zero drift. 🔴 But read the coverage, not the green check: it measured 4 of 25 declared roots. The other 21 have no committed lockfile, so they are unauditable — npm audit exits ENOLOCK — and the job is configured not to treat that as fatal, so it passes while measuring nothing. In our infrastructure repository that is 20 of 20 roots skipped and a green tick (44-F15). It is also report-only by design, so today it reports and does not block |
| Container image scanning | ✖️ Not enabled |
| Defined patch cadence | ✖️ Not yet defined |
| Dynamic application security testing (DAST) | ◐ Performed 2026-08-25 against our staging environment, in two passes the same day. An industry-standard DAST scanner was run against all three in-scope staging hosts (the API host, the web application, and the tile-serving host); dated reports are retained for both passes. No high or critical findings, across either pass. The findings were header-level hardening items — a permissive cross-origin policy, a missing HSTS header, a missing MIME-sniffing header, a missing content-security-policy/anti-framing header on the web application, and a handful of other missing modern browser-isolation headers — each triaged individually rather than accepted at the tool's default rating, and no injection-class issue (SQL injection, cross-site scripting, SSRF, path traversal, command or template injection) was found by any of the ~140 active rules per target. One finding is flagged for follow-up rather than dismissed: the web application persists its session client-side and does not currently restrict which pages may frame it, which is a narrower version of a well-known browser attack (UI redress / "clickjacking") against an already-signed-in user — under review for a fix. ⚠️ We state the limits rather than imply full coverage: the scan was unauthenticated, so it demonstrates that authenticated routes correctly reject unauthenticated callers and nothing about behaviour within an authenticated session. The authenticated pass is planned and requires a real application session plus explicit sign-off before it runs. Production was not scanned |
| Third-party penetration test | ✖️ Not performed |
| Automated build, test and lint gates in CI | ✖️ Not in place for the application repositories |
Territory Mapper's own security review is internal, evidence-based and adversarial — every claim on this page traces to a measurement, and the gaps on it were found by that review rather than reported to us. That is what we have instead of a certification, and we would rather say so than dress it up.
9. Incident response
We do not yet operate a formal incident-response program. What we commit to today:
- A named contact. Reports go to the address in §13 and are read by a person, not a queue.
- Acknowledgement within two business days of a report reaching that address.
- Notification without undue delay, and within 72 hours of confirmation, to any subscriber whose data we confirm was affected by a security incident — including what we know, what we do not yet know, and what we are doing.
- We will not wait for certainty to tell you something is wrong.
We will not claim a documented incident-response plan, an on-call rotation, or a tested runbook, because we do not have them. Establishing them is planned.
10. Subprocessors
Third parties that process data on our behalf, or that receive end-user requests directly. This list was derived by reading our infrastructure and our application source, not by listing what we expected to find.
| Subprocessor | Service provided | What it receives | Location |
|---|---|---|---|
| Amazon Web Services, Inc. | Compute, database, object storage and content delivery for the geospatial service and the map application | All data the geospatial service holds (§2) | United States — us-east-1 |
| OpenStreetMap Foundation | Default basemap tiles | Requested directly by the end user's browser: their IP address and the map area being viewed. No Salesforce data | Provider-operated global CDN |
| Stadia Maps | Optional basemap styles, user-selectable | As above | Provider-operated global CDN |
| Esri | Optional satellite imagery basemap, user-selectable | As above | Provider-operated global CDN |
| OpenStreetMap Foundation — Nominatim | Place-name search for the map's city-search box | Requested directly by the end user's browser: the text they type into the search box and their IP address. No Salesforce data, no record data, no identity | Provider-operated, EU |
Two things worth being precise about.
Basemap tiles are requested by the end user's browser, directly from the provider. They do not pass through MapKraken infrastructure. The provider therefore sees the viewer's IP address and which part of the map they are looking at. It receives no Salesforce data, no territory data and no identity. Three of the four are user-selectable from the map's layer control; OpenStreetMap is the default. If your security policy restricts which external tile providers may be contacted from within Salesforce, tell us — the set is a configuration decision and it is not yet fixed.
The map's city-search box sends what the user types to a third party. Typing a place name into the map's search control issues a request to the OpenStreetMap Foundation's Nominatim service carrying that text and the viewer's IP address. It is how the search box finds a city. It carries no Salesforce data, no record data and no identity, and it fires only when a user actually types in that box. 🔴 It is listed above because it is a real third-party contact, and because the same search control is present on our public marketing demo, where a visitor has no account with us at all. If your security policy restricts which external services may be contacted from within Salesforce, tell us — like the tile providers, this is a configuration decision and is not yet fixed.
🔴 Changed 2026-10-07. This table previously listed four subprocessors and did not include Nominatim, while the paragraph below said "Nothing else." That was wrong, and the search box had been contacting it all along (136-F1). The omission was ours; the behaviour was not new. We are recording the change here rather than correcting the list silently.
The census boundary tiles the product draws territories from are served from our own infrastructure, not a third party. They are public U.S. Census Bureau reference data and carry no subscriber content.
Salesforce is not listed as a subprocessor. Your Salesforce org is your platform, under your contract with Salesforce; Territory Mapper is a tenant of it, not a processor for it.
Nothing else, and this sentence is now scoped the way it should always have been. We verified against the application source that there is no analytics service, no error-tracking or crash-reporting service, no font CDN, no session-recording tool and no email service in the shipped product. There is no telemetry to disclose because there is none. ⚠️ The parties listed above are the complete set of third parties the product contacts — the four basemap providers and the place-name search. A bare "nothing else" is what let a missing row stand, so it is written here as a claim about telemetry, which is what we actually verified, while the table above is the claim about third-party contact.
What this table does not yet state. The contracting jurisdiction for each subprocessor, and our position on international data transfer, are not published here. Every subprocessor above is named, and what each receives is stated; the legal framing around them is not settled and we would rather leave it blank than draft it ourselves. If either matters to your assessment, ask — the underlying facts are the ones in this table and they will not change when the framing arrives.
11. Compliance
Every answer in this section is no or not yet. That is the whole section, and it is why it is short.
SOC 2 — no. MapKraken Labs does not hold a SOC 2 report, and no audit is under way. We are a small organisation and a Type II report is a meaningful undertaking; it is on the roadmap, without a date we would ask you to rely on. What we offer instead, today: this page, a completed security questionnaire, and direct access to the people who built the system. If a SOC 2 report is a hard procurement requirement, it is not something we can satisfy now, and we would rather you know that in the first conversation than the fourth.
Data Processing Agreement — not yet. A DPA template is not yet available. One will be provided before any production subscriber agreement is signed. If your procurement process requires a specific DPA form, we will work from yours.
Accessibility (VPAT / ACR) — no. MapKraken has not produced a VPAT or an Accessibility Conformance Report, and we do not claim WCAG conformance. The product has low-cost hedges rather than a conformance programme. We treat accessibility work as a scoped engagement rather than a baseline claim, and we will scope it honestly against your requirement — including the parts of a map interface where conformance is genuinely hard, which we would rather discuss than paper over.
Data residency. All MapKraken-operated infrastructure runs in Amazon Web Services us-east-1, in the United States. There is no non-U.S. deployment option today.
12. What we do not claim yet
Collected in one place, so you do not have to assemble it from the sections above. Each of these is either absent or incomplete today. None of them is written in a way that lets a reader mistake a plan for a control.
| State today | |
|---|---|
| Encryption at rest on the service database | 🔴 NOT ENABLED IN PRODUCTION — Changed 2026-10-07 (166-F17). This row read ◐ "Enabled 2026-08-24"; measured against the running service, prd-koop-postgis is StorageEncrypted: false and is what the production service connects to. ✅ Staging (sit-koop-postgis) is encrypted under a customer-managed key with annual rotation since 2026-08-24, migrated with the data proven byte-identical. 🔴 The published date for production, 2026-10-05, has passed (166-F18); this row carries no date until the owner sets one, and the absence is deliberate. Twelve pre-cutover snapshots remain unencrypted, deletion date 2026-08-31 also passed |
| HTTPS-only on the geospatial service | ✅ Enforced on every stage — Changed 2026-10-07. Plaintext is redirected, not served, and a census of all 17 distributions in the account found none accepting a plaintext viewer connection (§4). One remainder: on a single non-production stage that serves no content, the hop from the delivery network to its origin is still plaintext |
| Complete tenant isolation | ◐ Partitioned and enforced in the application layer; one isolation defect is open — the Salesforce → service token is signed with a shared secret, not a per-subscriber key. No external subscriber is served today — the one provisioned org is our own Developer Edition org — and a second active subscriber credential cannot be inserted, enforced by a database constraint deployed 2026-08-23 and re-probed live 2026-08-24, until per-org signing keys ship (§3; the full position is available on request) |
| Per-subscriber signing keys | ✖️ Not built. Designed; scheduled to the arrival of a second subscriber, which the constraint above cannot happen without |
| Database-enforced isolation | ✖️ No row-level security, no per-org database role |
| Per-user authorization inside the map | ✖️ Authorization at the geospatial service is per-org (§5) |
| Data retention and deletion | ◐ A policy exists for the geospatial service and a 30-day deletion window is enforced by a scheduled sweep — but there is no self-service delete control, deletion is a request we carry out, uninstall deletes nothing, and existing backups are not edited (§7) |
| Audit logging, monitoring and alerting | ✖️ None that can raise an alert, and the committed date for the audit trail has passed — stated plainly 2026-10-07. There is no audit trail on the hosting account; it was committed for 2026-08-31 and that date went by without it being started, which we are recording rather than re-dating. The destination it will write to already exists. One alarm exists, on a licensing metric; it has no notification target, and the account has no notification topics, so nothing can page anyone today. Alarms, metric filters and paging remain undated and are an accepted risk (§7) |
| Automated secret rotation | ✖️ Manual today |
| A running image free of embedded credentials | ✖️ Not true today. A clean build was achieved and the build later regressed; found by our own re-inspection on 2026-08-20 and stated in §6. Correcting the build is our highest-priority open item |
| Dependency scanning as a CI gate | ◐ ~~✖️ Manual review only~~ Changed 2026-08-25: the gate exists and has run — but it is report-only and it audits 4 of 25 roots. Where a lockfile is committed it works and its baselines reproduced exactly on Actions; where one is not — 21 roots, including all 20 in the infrastructure repository — it skips the root and still passes (44-F15). We are stating the denominator rather than letting a green check imply coverage ⚠️ Updated 2026-08-25 (later): the gate’s recorded baseline is now current, and the remaining gap is narrower but real. The baseline was re-measured from a clean, reproducible install after remediation — the service’s advisory count fell 22 → 11 overall and 13 → 0 for dependencies that ship in the running product. Until that re-measurement, the stored baseline overstated the tree and would have pre-authorised eleven advisories that no longer exist. 🔴 The coverage gap is NOT closed and we are not implying otherwise: the gate still audits 4 of 25 roots, because a root without a committed lockfile is skipped rather than failed — and that still includes all 20 roots in the infrastructure repository. A green result here means “the roots we can measure are at their recorded baseline”, not “every dependency is reviewed”. We are stating the denominator rather than letting a green check imply coverage |
| Secret scanning on every push | ◐ Configured in all three application repositories. ✅ Branch coverage fixed AND VERIFIED 2026-08-25 — the enumerated branch filter was removed, and the workflows were then observed firing on GitHub Actions against the real task branches (task/25, task/26, task/24, task/37), the first time this has ever been demonstrated rather than asserted. ⚠️ One repository's run went red on two keyless test fixtures (44-F16) — which is the gate proving it blocks. 🔴 Still not "every push" in two respects, both stated rather than smoothed over: it cannot see files excluded from source control (§8), and our infrastructure repository has no secret scanning at all — so the phrase "all three application repositories" is the accurate scope, not a full-coverage claim ✅ Updated 2026-08-25 (later): the gate is now proven to BLOCK, not merely to run. It failed a real build, the failure was triaged to two test fixtures containing no key material, and it was resolved with narrowly scoped exceptions carrying their written justification — deliberately not a blanket exclusion of the test directory, which is the kind of over-broad filter that previously let coverage rot unnoticed. We verified the exceptions did not blind the rule: a genuine private key placed in the same file is still detected. ⚠️ Both limits stay stated: it cannot see files excluded from source control (§8) — now mitigated by the image scan above — and our infrastructure repository has no secret scanning at all, so “all three application repositories” remains the accurate scope, not a full-coverage claim |
| Container image scanning | ✖️ Not enabled — and it is what would have caught the regression above |
| Dynamic application security testing (DAST) | ◐ Performed 2026-08-25 against our staging environment, in two passes the same day. An industry-standard DAST scanner was run against all three in-scope staging hosts (the API host, the web application, and the tile-serving host); dated reports are retained for both passes. No high or critical findings, across either pass. The findings were header-level hardening items — a permissive cross-origin policy, a missing HSTS header, a missing MIME-sniffing header, a missing content-security-policy/anti-framing header on the web application, and a handful of other missing modern browser-isolation headers — each triaged individually rather than accepted at the tool's default rating, and no injection-class issue (SQL injection, cross-site scripting, SSRF, path traversal, command or template injection) was found by any of the ~140 active rules per target. One finding is flagged for follow-up rather than dismissed: the web application persists its session client-side and does not currently restrict which pages may frame it, which is a narrower version of a well-known browser attack (UI redress / "clickjacking") against an already-signed-in user — under review for a fix. ⚠️ We state the limits rather than imply full coverage: the scan was unauthenticated, so it demonstrates that authenticated routes correctly reject unauthenticated callers and nothing about behaviour within an authenticated session. The authenticated pass is planned and requires a real application session plus explicit sign-off before it runs. Production was not scanned |
| Third-party penetration test | ✖️ Not performed |
| Formal incident-response programme | ✖️ Commitments in §9 only |
| SOC 2 | ✖️ Not certified, no audit under way |
| DPA | ✖️ Not yet available |
| VPAT / ACR | ✖️ Not produced |
| A published safe harbour for security research | ✖️ Not published. It is a legal commitment and it is with counsel; §13 says what we ask of you in the meantime and does not imply permission we have not granted |
| Subprocessor jurisdictions and the international-transfer position | ✖️ Not published (§10). Every subprocessor is named and what each receives is stated; only the legal framing is absent |
Why this section exists. A trust page that lists only controls is a marketing page. Every item above was found by our own review, is tracked with an owner, and is written here in the same words we use internally. If any of them is a blocker for you, say so — we would rather lose the evaluation early than pass it on a misreading.
13. Security contact
Report a vulnerability to security@mapkraken.com. It is a monitored mailbox, read by a person, and mail sent to it is authenticated — mapkraken.com publishes SPF, DKIM and DMARC, so a reply from us can be verified as ours and a forgery of us can be rejected as not.
What to include: what you found, where, and the steps to reproduce it. A proof-of-concept helps. If you believe the issue is being actively exploited, say so in the subject line.
What to expect: acknowledgement within two business days, an assessment and a remediation plan, and credit in our disclosure record if you want it. That record exists as of 2026-08-24, and it is empty — nothing has been reported to us yet. It is not published at a public address; ask and we will tell you what is in it. We do not operate a paid bug bounty.
Testing in good faith — we have not published a safe-harbour position yet, and we are not going to imply one. A statement that we will not pursue legal action over good-faith research is a legal commitment, and ours is with counsel rather than written by us. Until it is published here, please treat this section as describing where to send a report, not as a grant of permission to test. Two requests in the meantime, and they are the ones that would be in any such statement anyway: do not access, modify or exfiltrate another subscriber's data or degrade the service for others, and do not test against a subscriber's Salesforce org — it is theirs, not ours. If you want to test and the absent safe harbour is what is stopping you, write to the address above and ask; we would rather have that conversation than not get the report.
Everything else: the same address reaches us.
Last updated: 2026-10-08 · Owner: MapKraken Labs LLC
This page changes whenever our security posture changes. It is derived from an internal review document that carries the evidence for every claim on it, and it is re-verified against that document rather than edited independently. A claim that stops being true is removed the same day it stops being true.
Last updated: 2026-10-08 · Owner: MapKraken Labs LLC
This page is derived from an internal review document that carries the evidence for every claim on it, and is re-verified against that document rather than edited independently.