Article 15 of the GDPR (and § 15 BDSG, the German implementation) gives
every natural person whose personal data is processed the right to
receive a copy of that data on request. For a packaging tool, the
relevant personal data is the DOMAIN\user actor identifier that the
Memory Database stores against every change — who built which package,
who pushed which distribution, who renamed which software identity.
MAS turns the Article 15 reply into a five-click workflow: enter the subject, preview the bundle, save the JSON, archive it with the request record. The bundle is signed (audit-chain integrity), self-contained (no external lookups needed to interpret it), and deterministic (the same subject against the same database produces a byte-equivalent bundle).
This chapter covers what the bundle contains, how to run the export, how to interpret the result, and how the workflow fits into the wider SAR response process.
When this matters #
Every employee whose Windows account has touched the Memory Database
appears as a DOMAIN\user actor in at least one of:
software_identities.LastModifiedBy— last edit to an app-vendor rowpackage_records.PackagedBy— author of a specific builddistribution_records.DistributedBy— operator of a specific pushaudit_log.Actor— every security-relevant write
That data is personal data under the GDPR. A current or former employee can request a copy of it. Under § 15 BDSG, MAS' role is the data processor's tool — your organisation is the controller and owes the reply within one month of the request.
The GDPR Export feature ships in every MAS 2.0 install — the workflow is small, the compliance value is high, and there is no reason to gate it behind a tier. Even a single-operator install can produce useful SAR bundles when the operator's own account has to be documented.
What the bundle contains #
The bundle is a single UTF-8 JSON file with the following structure (v2, since MAS 1.26.5.35):
| Section | Content |
|---|---|
| Header | Subject identifier, export-time UTC timestamp, operator who triggered the export, MAS installation hardware fingerprint, schema version. |
softwareIdentitiesAsLastModifier |
Every SoftwareIdentity row where the subject was the most recent editor. Includes app name, vendor, upgrade code, first-seen, last-seen, and the optimistic-lock row version. |
auditEntriesAsActor |
Every AuditEntry row produced by the subject. Each entry carries event type, entity type, entity id, timestamp, and the optional payload JSON that describes the change (before/after snapshots, deletion records, etc.). |
relatedPackageRecords (v2) |
Every PackageRecord that the subject's audit entries reference. Joined via AuditEntry.EntityId for entries with EntityType = "PackageRecord". Surfaces the actual build snapshots the subject worked on, not just the audit-trail-pointers. |
relatedDistributionRecords (v2) |
Every DistributionRecord referenced by the subject's audit entries (EntityType = "DistributionRecord"). Same logic as related package records. |
summary |
Counts per section, plus the earliest and latest activity timestamps that bracket all returned data. |
Why the v2 cross-reference matters. The v1 bundle proved the
audit trail (the subject did X at time Y). The v2 bundle additionally
attaches the current state of the records the subject touched, so the
compliance officer can answer "and what was X actually about?"
without a second tool. Records that have been deleted between the
original audit-emit and the export are silently skipped from the
related sections — the audit row's payloadJson still carries a
forensic snapshot, so the SAR trail stays intact even when the live
row is gone. The summary counts reflect what was actually loaded,
not what was referenced, so a delta between
auditEntryCount (with EntityType = "PackageRecord") and
relatedPackageRecordCount is the count of records the subject
acted on that have since been removed.
What the bundle does NOT contain. No personal data about other
operators (their actor strings appear only when the requested subject
edited a row that another operator had previously last-touched, in
which case that other operator's DOMAIN\user shows up as
LastModifiedBy on a different snapshot — that's intrinsic to the
data, not an export choice). No license keys, no email addresses, no
SCCM domain credentials. No PSADT package binaries — the bundle
references package output folders by path, but the disk contents are
out of scope.
How to run an export #
Navigation: side-rail → Settings → Database → Subject access export.
Enter the subject. The field is an auto-suggest combo populated from
audit_log.distinct(actor)— start typing a domain prefix and the matching actors surface. The match against the database is case-sensitive verbatim, because Windows allows bothCONTOSO\userandcontoso\useras legal forms and silently merging them would risk attaching the wrong person's data to the reply.Preview. Click Preview and MAS runs the collect step against the live database. The preview card shows the bundle header, the row counts per section, and the earliest/latest activity timestamp. No file has been written yet — this step is read-only and safe to run repeatedly while you confirm you have the right subject.
Save. Click Save bundle and a Windows file picker opens with a pre-filled name like
contoso-user-sar-20260608.gdpr.json. The picker enforces the.jsonextension. Pick the destination folder (see § Compliance storage path) and confirm.Archive the request and the reply together. The bundle is the reply artefact. Store it next to the subject's request letter or ticket — your retention policy for SAR documents covers both.
Verify the audit trail. The export itself is audit-logged as a
GdprExportentry (event type and entity type bothGdprExport, entity id00000000-0000-0000-0000-000000000000because the export spans many rows). The hash chain (chapter 650) carries the proof that no row was added or removed between the export and the verification.
An empty bundle is a valid Article 15 reply: it documents that you searched and found no data on file for the named subject. The Save button stays armed even when the preview shows zero rows, so an empty bundle can be produced as a proof-of-search artefact.
Bundle format spec (for compliance teams) #
The bundle is plain UTF-8 JSON, indented for readability, camelCase
keys. The producer code lives in the open in
src/MAS.Core/Models/GdprExportBundle.cs and
src/MAS.Infrastructure/Compliance/GdprExportService.cs, so an
external reviewer can verify the bundle shape against the source.
Top-level keys:
{
"schemaVersion": 2,
"subject": "CONTOSO\\jdoe",
"generatedAt": "2026-06-08T14:23:11.4521234+00:00",
"generatedBy": "CONTOSO\\compliance-officer",
"masInstallationFingerprint": "a4f2e1b3...c8d9",
"softwareIdentitiesAsLastModifier": [ /* SoftwareIdentity rows */ ],
"auditEntriesAsActor": [ /* AuditEntry rows */ ],
"relatedPackageRecords": [ /* PackageRecord rows, v2+ */ ],
"relatedDistributionRecords": [ /* DistributionRecord rows, v2+ */ ],
"summary": {
"softwareIdentityCount": 12,
"auditEntryCount": 47,
"relatedPackageRecordCount": 9,
"relatedDistributionRecordCount": 14,
"earliestActivity": "2026-01-04T08:11:00+00:00",
"latestActivity": "2026-06-07T16:42:11+00:00"
}
}
Schema versioning follows the same additive rule as the rest of MAS' persisted formats: a v2-aware reader gets the enriched view, a v1-aware reader sees the new fields as unknown JSON properties and ignores them. A future v3 would do the same — add new top-level sections without removing or renaming existing ones. A breaking change would bump to a major version and ship with a migration tool.
Compliance storage path #
The GDPR-export file ends up in the path the operator picks. There is no MAS-managed compliance vault — the file is yours from the moment it's saved. Recommendation, based on what audit-friendly customers do:
| Aspect | Recommendation |
|---|---|
| Location | A dedicated subfolder under your compliance share, structured by year and SAR-ID: \\compliance.lab\sar\2026\REQ-0042\. |
| Naming | Keep the MAS-suggested filename (<subject-slug>-sar-YYYYMMDD.gdpr.json) so the file is self-documenting in directory listings. |
| Retention | Aligns with your SAR-retention policy. The bundle is the documented evidence of the Article 15 reply; under § 41 BDSG and standard data-protection-officer guidance, three years from the date of the reply is the typical floor. |
| Access control | Read-restricted to the data protection team. The bundle contains personal data about the subject — same handling as the original SAR request letter. |
| Integrity check | Optional but recommended: compute the SHA-256 of the file at save time and store it in the SAR ticket. If the file is ever modified, the hash diverges and the tamper is visible. |
| Backup | Covered by your standard compliance-share backup policy. The bundle is small (typically under 100 KB for a single active operator), so backup volume is not a concern. |
What the operator sees vs. what gets recorded #
The export workflow itself is audit-logged. Two audit rows are produced per export:
GdprExport— written by the export service every time the Save step completes. Payload contains the subject identifier, the per-section counts, and the activity-span timestamps. This is the row a compliance auditor uses to prove that a specific SAR was actually fulfilled on a specific date by a specific operator.Hash-chain continuation — the
GdprExportrow is hashed into the audit chain like any other event (chapter 650), so removing it later breaks the chain at that row index. An auditor can verify from the chain that noGdprExportrows have been deleted to hide that an export happened.
The operator who runs the export sees a confirmation toast with the output path and the row counts. The operator does not see and cannot suppress the audit row — emission is built into the service.
Limits and known caveats #
Case-sensitive subject match. Mentioned above; worth repeating
because it's the single most likely operator mistake. If the subject
combo shows CONTOSO\jdoe but the data was originally written as
contoso\jdoe, the preview returns an empty bundle. Always pick from
the auto-suggest list rather than hand-typing.
Single-database scope. An export covers exactly the database MAS is
currently connected to. If your organisation runs MAS instances against
multiple databases (one per region, one per department), the operator
must run the export once per database and concatenate the bundles into
the SAR reply. The bundle header identifies the source database via
the masInstallationFingerprint, so de-duplication on the receiving
side is unambiguous.
Restored databases reset the chain. If the database has been
restored from a portable backup (chapter 680), the audit-chain
integrity verifies clean from the restore boundary forward, not all
the way back. An auditor reviewing a GdprExport row that pre-dates
the restore should cross-check the .masdb archive that captured the
state before the restore.
Cross-reference drift. A relatedPackageRecordCount lower than
the count of PackageRecord-typed audit entries reflects records
that have been deleted since the original audit event. This is
expected — the audit row's payloadJson is the canonical forensic
record. If the request explicitly asks for the full state of every
record the subject worked on, the operator should mention the drift
in the SAR reply ("X records have been deleted in the normal course
of operations; the audit log preserves a snapshot of each deletion").
External verification #
A compliance team that doesn't want to trust MAS' own output can verify the bundle's integrity independently:
Audit chain. The bundle ships with the
auditEntriesAsActorsection that includes each row'sprevHashandrowHashbytes (when the producing database has been backfilled — chapter 650 covers backfill). The Python reference verifier indocs/user-docs/A-Appendices/A4-Audit-Chain-Verify-Reference.pyre-computes the chain over those bytes; the bundle is intact when the verifier reportsIsValid = True.Subject filter. Every row in
auditEntriesAsActormust haveActor == subject. Every row insoftwareIdentitiesAsLastModifiermust haveLastModifiedBy == subject. A trivial grep over the JSON confirms the filter held.Cross-reference join. For each entry in
auditEntriesAsActorwithEntityType = "PackageRecord", the correspondingrelatedPackageRecords[].ideither exists or the row is documented as deleted (count drift, see above). Same forDistributionRecord.Producer source. The producer code is open in the MAS distribution (
src/MAS.Core/Models/GdprExportBundle.csandsrc/MAS.Infrastructure/Compliance/GdprExportService.cs). A compliance team can read it, build it locally, and confirm the bundle would have been identical if they had run the export themselves. This is the same logic as the Audit Chain chapter's "external Python verifier" — vendor-lock-in counter-evidence.
Related chapters #
- 650 Audit Log and Hash Chain — the proof-of-integrity layer the GDPR-export bundle relies on.
- 680 Portable Backup and Restore —
what happens to the audit chain when a database is restored, and
why a
.masdbarchive is the cleanest evidence anchor. - A4 Audit-Chain Verify Reference (Python) — ~80-line independent verifier for the audit chain inside the SAR bundle.