KevLehman/tdf-processor

Rocket.Chat app enforcing TDF entitlement checks on uploads

★ 0Forks 0TypeScriptGitHub ↗Compare

README

TDF Entitlement Guard

A Rocket.Chat app that blocks messages sharing an encrypted TDF file unless the sender is entitled to share it, as decided by an OpenTDF/Virtru platform against the file's own policy.

How it works

The app implements the IPreMessageSentPrevent hook, so it runs before any message is delivered. For each message:

  1. Room gate — enforcement only applies in ABAC rooms: private groups carrying a non-empty abacAttributes array. Everywhere else the message passes untouched.

  2. Upload collection (TdfDetector) — gathers every file the message references: the primary message.file plus file-upload attachments (upload id parsed from the /file-upload/<id> link). Files whose name or mimetype look like a TDF (.tdf, application/tdf, …) are flagged as candidates.

  3. Content-based TDF detection (ManifestReader) — each upload's bytes are read and inspected rather than trusting the filename, so a renamed TDF is still caught:

    • not a ZIP (magic-byte check) → not a TDF, skipped instantly;
    • a ZIP without a manifest.json (e.g. a docx) → not a TDF;
    • a ZIP whose manifest carries an encryptionInformation.policy → a real TDF; its base64 policy is decoded and the dataAttributes are extracted and normalized to full attribute FQNs (bare identifiers are resolved against the configured namespace).

    With the Inspect every upload setting off, only name/mimetype candidates are inspected — lighter, but a renamed TDF can slip through.

  4. Entitlement check (EntitlementClient) — when a real TDF is present:

    • an OIDC token is minted via the client-credentials grant against the configured Keycloak realm;
    • the OpenTDF authorization service (GetDecisionBulk) is asked whether the sender's email may perform the configured action (default read) on a resource carrying the TDF's policy attributes.
  5. Verdict — PERMIT for every TDF lets the message through. Anything else throws an AppsEngineException, which prevents the message and shows the sender the reason (not entitled, integration unconfigured, unreadable file, …).

Fail closed: a TDF-looking file that can't be read, a malformed manifest/policy, missing configuration, a sender without an email, or any error from the OIDC/authorization services all block the message rather than letting it through.

Configuration

The app reads its actual values from core Rocket.Chat server settings (so they can be managed centrally); the app's own settings only hold the ids of those server settings:

App setting Default server setting id Holds
Platform URL ABAC_Virtru_Base_URL OpenTDF/Virtru platform base URL
OIDC client id ABAC_Virtru_Client_ID Client-credentials client id
OIDC client secret ABAC_Virtru_Client_Secret Client-credentials secret
OIDC endpoint ABAC_Virtru_OIDC_Endpoint Keycloak realm URL (/protocol/openid-connect/token is appended)
Attribute namespace ABAC_Virtru_Attribute_Namespace Base namespace used to normalize non-FQN policy attributes

Plus two direct settings:

  • Action evaluated — OpenTDF action checked against the policy (default read).
  • Inspect every upload — content-sniff all uploads in ABAC rooms (default on; each file is read into memory once, non-ZIP files are skipped instantly).

Required permissions: networking, message.read, user.read, upload.read, abac.read, server-setting.read.

Development

npm install
npm test          # jest suite
npm run build     # packages the app (rc-apps) from app/

Deploy the packaged zip from app/dist/ via rc-apps deploy or the admin Apps UI. Requires Rocket.Chat apps-engine ^1.44.0.

Layout

app/TdfEntitlementApp.ts   # hook orchestration: gate -> detect -> decide -> block/allow
app/src/AbacRoom.ts        # ABAC room predicate
app/src/TdfDetector.ts     # upload collection + name/mime hinting
app/src/ManifestReader.ts  # content-based TDF detection + policy attribute extraction
app/src/EntitlementClient.ts # OIDC token minting + GetDecisionBulk call
app/src/serverSettings.ts  # core server-setting reader (fail-closed)
tests/                     # jest suite

License

Proprietary — see LICENSE. No use without prior written approval.

Contributors

KevLehman

Issues