# Security & access model

## The map is metadata

Maplith stores the *shape* of your data landscape — object names, field
names, relation statistics, finding descriptions — never row-level data.
Uploaded export files are inputs to a scan: they are deleted from storage
after the scan completes, whether it succeeded or failed. PII *values* are
minimised, not absent: findings quote evidence, and field descriptions can
quote example values. Maplith tries to mask email local parts, Danish CPR
numbers, IBANs and payment card numbers — when they are well-formed: a CPR
number with a valid date, an IBAN or a card number that passes its checksum —
before anything is sent to the model, and again in the descriptions and
findings the model writes before they are stored. It also tries to mask the
names of people it reads from your export's own name columns — first name,
contact person and the like — wherever those names appear in what a build
sends to the model and stores, and the phone number a helpdesk export declares
on a contact; name masking is on by default, with a switch under Settings › Privacy & masking. It
is a best-effort safeguard, not a guarantee. A workspace can add its own
masking terms under Settings › Privacy & masking, and a build applies them wherever it applies the
four; a term added later applies from the next build on, not to what is
already stored. A name that appears only in free text, and any other phone
number, are not masked unless you add them as masking terms. You decide what
your export contains; leave out what must not be processed. PII *fields* carry a
`personal` classification — contact, identity, financial, special category,
other, or none, with where the verdict came from (the column name, the shape of
the values as counts, or you) and how sure it is — set by the scan and corrected
by a workspace admin under Business map › Personal data; a correction stays, and a later
scan that reads the field differently asks first. `pii` is true for any
category but none. A field the scan could not assess is shown as **not
checked**, first in Business map › Personal data, until someone marks it.

## Text already shaped like a masked value

Maplith tries to mask your masking terms, and the names of people it finds in
your export's name columns, wherever they appear. It writes a masked value as
its label, a space and three asterisks, all inside square brackets:
`[person ***]`, `[member number ***]`. Text that already has that shape
is treated as masked and left as it is. That is deliberate: masking is applied
at several steps in turn, and it only stays safe to do so if masking text that
is already masked changes nothing.

The shape is exact: an opening square bracket; 1 to 40 characters that are
only letters, spaces or hyphens; a space; three asterisks; a closing bracket.
The shape also holds with a six-character code of the digits 0–9 and the
lower-case letters a–f between the asterisks and the bracket, the form Maplith
uses during a build. So if your export itself contains text such as
`[Pia Sørensen ***]`, a term inside that bracket (here "Pia Sørensen") is
not masked there. The same term anywhere else in the text is
handled as usual.

What this does not affect:

- **Format terms.** Every format holds at least one digit, and a digit cannot
  stand in that shape.
- **Terms with a digit or with any character other than a letter, a space or a
  hyphen**: an order number, an email address, a name with a full stop.
- **The four identifiers masked without a term** (email local parts, CPR
  numbers, IBANs, card numbers), for the same reason.

What you can do: you decide what the export contains. If your exports write
names in that literal shape, change the export so they do not, or
[tell us](mailto:frp@maplith.com) and we will look at it with you.

## Access control in two dimensions

**Dimension 1 — roles.** Organisation roles (`owner`, `admin`, `member`)
govern the account. Workspace roles govern each map: `admin` (manage
members, keys, integrations), `builder` (run scans, work with the map),
`viewer` (read).

**Dimension 2 — grants.** Independently of role, a user can be narrowed to
specific **domains** (e.g. Finance but not HR) or down to specific
**objects**. A grant filters everything: objects, relations, search results
and findings. A finance controller sees the Finance slice of the map and only
the findings that touch it.

**What each workspace role may do** (enforced server-side on every action):

| Capability | viewer | builder | admin |
|---|---|---|---|
| Read the map, findings, processes, metrics (within grants) | ✓ | ✓ | ✓ |
| Upload exports, run scans, connect systems | — | ✓ | ✓ |
| Work with findings (change lifecycle status) | — | ✓ | ✓ |
| Manage members, roles and grants | — | — | ✓ |
| Issue and revoke agent keys; set agent policies | — | — | ✓ |
| Decide agent approval requests | — | — | ✓ |

An external consultant working across several customers is simply a member
of each of those workspaces — typically as `builder`, optionally
grant-narrowed. They can update what you let them touch, and can never
delete, manage members or issue keys.

**Member types.** Every member carries a type, visible in the member list:
`employee` (your own people), `consultant` (external, labeled so everyone
can see it), or `Maplith staff` — our personnel, a label only we can set,
so you always know when we are in your workspace. Type never grants
capability; roles and grants do.

**When Maplith staff look at your map.** A support case can need one of us
inside your workspace. That happens in one way only: a member of Maplith
staff opens read-only access with a written reason, your organisation's
owners — and your privacy or DPO contact, if you have registered one under
Settings › Organisation — are emailed the moment it opens (which workspace,
who, why, until when), and a notice with the same sits at the top of the
workspace for everyone in it. The access sees what a viewer sees — never
uploaded files, agent keys or connections — changes nothing, ends by itself
four hours after it opened and cannot be extended; any admin of the
workspace can end it sooner with one click. The opening, the notice, the
first request and the end are recorded; reply to the notice email and we
send you the record.

The same two dimensions apply to agent keys: scope (what the key sees) and
policy (what it may do) — see [Agent keys & policies](/docs/agent-keys).

## Enforcement

All access control is enforced in the database layer (PostgreSQL row-level
security) — not in application code that could be bypassed. Agent keys are
stored as hashes. GitHub webhook deliveries are HMAC-verified before
anything else happens. The hosted agent acts only in private repositories,
only under an explicit policy, with hard daily caps.

## Signing in

You sign in with a link sent to your email, or with Google, Microsoft or
GitHub. Anyone can add **two-factor authentication** under **Account
security** in the account menu: scan a code with an authenticator app (Google
Authenticator, Microsoft Authenticator, 1Password and similar), and from then
on Maplith asks for a fresh code from that app each time you sign in. Until
the code is entered, the app shows no workspace page and accepts no change,
and the database behind it refuses that session every read and change of your
workspace data, even when asked directly rather than through the app. Only the
checks of the session's own access to IDs it already holds still answer, such
as whether it belongs to a workspace, its role there and which parts of the
map it may see. So a stolen email or account login alone is not enough to
reach your workspaces. Our authentication provider keeps the app you register,
not Maplith's own tables. Removing an app needs a session that has already
passed the code step.

Where your workspace enables Slack decisions, you can link your own Slack
account after two-factor sign-in in the app. The linked account counts as
your second factor for decisions in Slack; your workspace permissions still
apply. Unlinking Slack or removing your last app factor revokes that link
immediately. Each Slack decision records your linked Slack identity. In those
workspaces, key, access and policy changes send an independent confirmation.

Keep a way back in: add the app on a second device too. There are no
recovery codes; if you lose every device, write to
[support@maplith.com](mailto:support@maplith.com) and we confirm it is you —
by more than an email from the same address — before we turn two-factor off
for your account.

Agent keys are separate credentials with their own scope and policy, so
assistants connected with a key are not asked for a code. Connecting a new
assistant does ask: the consent that issues its key is a sign-in like any
other.

## Hosting

Application, database and file storage are hosted in the EU (Supabase
Frankfurt, Railway EU). Mail is delivered through Resend's EU infrastructure in
Ireland, though Resend itself processes in the United States. Model inference
is not in the EU: during a
scan, file samples and, in the findings step, whole files go to Anthropic's
API in the United States. The [sub-processor list](/sub-processors) names
every provider, where it processes and on what basis; the [processing and
security description](/processing) states what each one receives and what is
kept, for how long; the [Data Processing Agreement](/dpa) and the [privacy
policy](/privacy) state the commitments and how to exercise your rights. Transactional mail is sent
from an isolated subdomain. We are
the product's own first customer — Maplith scans Maplith daily and publishes
the discrepancies it finds about itself to our own ops channel.