An official developer portal of the Government of the Republic of Zambia · Test environmentVerify a certificate
Pamodzi Developer HubBuild, integrate and learn the e-Services framework
On this page

Security and data protection

Security is the platform's first priority: no feature, deadline or convenience justifies weakening it. This guide explains the controls the platform applies and what you must do when you build on it or integrate with it.

The Data Protection Act No. 3 of 2021, as the platform applies it

ObligationHow the platform meets it
Collect the minimumForms ask only what a service needs. Data-sharing agreements limit a consumer to agreed fields. The Developer Hub never stores an NRC, only whether it was verified
Protect personal data in useSensitive fields are masked by role and purpose. Register fields are masked or withheld by the register pack's sensitivity
AccountabilityEvery state change and every read of restricted data is audited in a tamper-evident trail. A citizen can see when staff viewed their identity or contact details in full
Lawful sharingRegister data leaves its owner only under a data-sharing agreement approved by the Data Protection Officer, with a purpose, legal basis, retention and end date
Data subject rightsCitizens lodge access, correction, erasure, objection and restriction requests from their account; a DPO handles them against a clock
RetentionRecords follow a retention schedule; deleted items stay in a recycle bin for a set time; nothing under a legal hold is removed
Breach handlingA privacy incident opens with the breach-notification checklist and a due time for notifying the Data Protection Commissioner

Several periods (response times, retention periods, the breach notification deadline) are configurable defaults awaiting confirmation by the responsible authorities. Treat them as settings, not as legal advice.

Authorisation: the policy decision point

Every decision about access goes through one function in the API, can(user, action, resource, context) in zcm-backend/lib/policy. Each route names its policy action (for example document.read or register.agreement.approve), and a route-coverage test fails on any route that does not name one or carry a reviewed exemption. Hiding a button is never authorisation.

The decision runs ordered steps; the first that fails decides:

StepQuestionRefusal
0. moduleIs the module that owns this action switched on for the caller?404 MODULE_DISABLED
1. accountIs the caller signed in, with an active account?401
2. capabilityDoes the caller hold a role (or custom role) the action's rule grants?403
3. scopeIs the record in the caller's ministry, where the grant is scoped?403
4. classificationIs the record's security marking within the caller's clearance?403
5. aclDo access-list entries allow it (applicant of the case, tenant of the lease, explicit allow or deny)?403
5b. dutiesSeparation of duties: is the same person doing both halves of a pair?403
6. stateDoes the record's state allow it (closed, legal hold, locked)?409
7. fieldsWhich personal-data fields does the caller see in full?(masking)

A decision for an unknown action fails closed. Staff receive the step that refused them, and each refusal writes an access.denied audit row with the action and the step.

Least privilege and separation of duties

  • Ministry scope. Officers see their own ministry's records. Supervisors and executives may see wider where their

    rule allows.

  • No silent administrator access. An administrator as such is refused a citizen's case documents; only a staff

    role that handles cases opens them.

  • Maker-checker. Money, keys, modules and sensitive settings need a second person. Examples: switching a module on,

    changes to the federation node registry, fee approvals, data-sharing agreements (custodian reviews, DPO approves) and erasure of a citizen's data (executed by a second person).

  • The proposer never approves their own request. The rule is enforced in the code, not left to procedure.

Audit

  • The audit trail is append-only: the database refuses updates and deletes outside a recorded maintenance

    transaction.

  • Rows are hash-chained. Each closed day is anchored, and a daily job verifies the chain. A broken chain shows in

    the health check and alerts administrators.

  • Rows carry the event type, category, outcome (success, denied, failed), request id, channel and a keyed hash of the

    client address, never the address itself.

  • Rows never carry personal data, secrets or tokens: a reveal records which fields and why, not their values.
  • Audit retention is set per category. The defaults keep everything; authentication, lifecycle, security,

    administration and payment cannot be switched off.

Personal data and masking

Staff answers about cases, users, tenants and finance are masked by the caller's role and purpose:

TypeMasked as (synthetic)
NRC••••••/••/0
Phone•••••••456
E-mailj•••@example.com
Passport, bank account, TPINlast 4 characters

Case handlers see identity and contact details in full on a case they open; lists and exports are masked; bank and TPIN are always masked. Seeing a masked value needs a reveal with a reason of 10 to 500 characters, limited per officer per hour. Every reveal is written to the access log the citizen can read, and to the audit trail without the values. Sending a masked placeholder back in an update is refused (422 MASKED_VALUE), so masks never overwrite real data.

Identity and tokens

  • Trust only verified claims. An NRC counts only when the identity provider marked it verified. A self-declared NRC

    never links accounts or grants access. If the NRC changes afterwards, it is unverified again.

  • Validate every token fully: signature against the provider's published keys (asymmetric algorithms only),

    issuer, audience, expiry and nonce. Use the authorization code flow with PKCE and a nonce.

  • MFA for staff. By default administrators, supervisors, executives, auditors and information officers must use a

    second factor (TOTP), checked at sign-in and on every request. ZamPass counts as MFA only when its token says a second factor was used.

  • Sessions. Browser sessions use a 15-minute access token and an HttpOnly, Secure, SameSite=Strict refresh cookie

    with idle and absolute limits. Sessions can be ended per device or everywhere, including by back-channel logout from the identity provider.

  • Nothing personal in URLs. Tokens, NRCs and other personal data never go in a URL or query string.

Secrets

  • Secrets come from the secret store and reach the platform as environment variables. They are never in git, Helm

    values, images or logs.

  • In production the API refuses to start when a required secret is missing or holds a placeholder value. The message

    names the variable, never the value.

  • A repository check and secret scanning run in the build and fail on keys, tokens, connection strings with passwords

    and personal-data dumps.

  • A secret that was ever seen in a ticket, chat, log, screenshot or commit is rotated at once.

Uploads and XML

ControlRule
TypesAn allow-list (PDF, common images, Office documents, CSV). Nothing that renders as markup (HTML, SVG, XML)
ContentThe bytes are checked against the declared type: a program named .pdf is refused. Damaged and password-protected files are refused
Size15 MB for a document by default; limits on the number of photos per inspection
AntivirusScanned by ClamAV when a scanner is configured; infected files are set aside and never served
PrivacyThe GPS block of a JPEG photo is blanked
ServingUploaded files are served as attachments with nosniff and a restrictive content security policy
XMLParsed by the platform's own strict reader: a DOCTYPE is refused (no DTDs, no entities), with limits on size, depth and element count
ArchivesZipped geometry files (shapefiles, KMZ) are checked before they are unpacked: number of entries, total size and compression ratio (zip bombs)

Outbound calls

Every URL the server fetches goes through one guard. Cloud metadata addresses are always refused. In production, loopback, private and other internal ranges are refused unless the host is on the configured allow-list (for example government network hosts of MDA systems), and every redirect hop is checked before it is followed. The address is checked again when the connection is made, so a name cannot resolve to one address when checked and another when used.

Rate limits

Public endpoints are rate-limited. Examples of the defaults: every API request per address; sign-in per account and address (10 attempts per 15 minutes per account); register reads per caller, tighter for non-public registers; the public "check a document" upload (10 per 10 minutes per address); and each Partner API client's own per-minute limit and daily quota. Limits are shared across API replicas.

Federation

Messages between hub and nodes are signed (ES256), addressed to one receiver, valid for 5 minutes, single use and sent over mutual TLS. Answers are signed too. A hub never holds a node's payment keys or documents.

What integrators must do

  1. Ask for the minimum. Request only the registers, fields and scopes your purpose needs, for the shortest period.
  2. Keep secrets secret. Store your client secret in a secret store, never in code, configuration files in git,

    tickets or chat. The secret is shown once; if you lose it, ask the MDA for a new client.

  3. Validate tokens fully if you rely on platform sign-in: signature, issuer, audience, expiry and nonce.
  4. Never put personal data or tokens in URLs, and never log them.
  5. Respect the agreement. Use the data only for the agreed purpose, keep it no longer than the agreed retention,

    and do not share it onward unless the agreement allows it.

  6. Handle refusals. Expect 401, 403, 404 and 429 and back off; do not retry in a tight loop.
  7. Report incidents at once to the owning MDA's Data Protection Officer, so the breach checklist can start.
  8. Test with synthetic data only, against test doubles or the test environment, never with real citizens' data.

Next steps