Encrypted File Ingestion via an External Decryption Service
Automation Anywhere Enterprise Knowledge Base (AAEKB / EKB) — Administrator and Integration Guide
What this feature does
AAEKB can hand an uploaded file to an external decryption service that you host and control, receive the decrypted bytes back, and then run its normal Knowledge Base ingestion pipeline (text extraction, OCR, chunking, embedding, indexing) on the decrypted content.
This exists for organisations that apply enterprise DRM or envelope encryption to documents at rest and cannot ship decryption keys to a third-party application. The keys, the decryption logic, and the plaintext boundary all stay inside your network. AAEKB only knows how to call an HTTP endpoint and read a byte stream back.
The integration is deliberately generic and configuration-driven. There is no vendor-specific DRM SDK inside AAEKB. Any HTTP service that accepts a file and returns a file — synchronously or via a job/poll pattern — can be plugged in, provided it fits the contract in The contract your decryption service must implement.
Key characteristics:
- Opt-in per upload. Files are only sent to the decryption service when the user explicitly ticks a toggle. Normal uploads are completely unaffected.
- Single global configuration. One decryption service is configured per AAEKB deployment, by a Super Admin.
- In-memory only. The encrypted bytes, the request to your service, and the decrypted response are all handled in memory. AAEKB does not write the ciphertext to a temporary file on disk before calling your service.
- No key material in AAEKB. AAEKB stores an endpoint URL and, optionally, an API credential for calling your service. It never stores or handles document encryption keys.
How it works end to end
User (browser) AAEKB backend Your decryption service
| | |
| 1. Upload file + | |
| "Requires Decryption" | |
|----------------------------->| |
| | |
| 2. File security validation |
| (runs on the ENCRYPTED bytes) |
| | |
| | 3. POST encrypted file |
| |--------------------------------->|
| | |
| | 4a. sync: 200 + decrypted bytes |
| |<---------------------------------|
| | |
| | 4b. async: 202 + { job_id } |
| |<---------------------------------|
| | 5b. GET poll/{job_id} (repeat) |
| |--------------------------------->|
| | 6b. 200 + decrypted bytes |
| |<---------------------------------|
| | |
| 7. MIME type re-detected on decrypted bytes |
| 8. Decrypted file stored in KB raw storage |
| 9. Normal ingestion: extract, chunk, embed, index |
| | |
| 10. Resource appears in KB | |
|<-----------------------------| |
Step by step:
- Upload. The user selects files in the Knowledge Base upload modal and enables the Requires Decryption toggle. The frontend sends
needs_decryption=trueas an extra form field alongside the file. - File security validation. AAEKB's standard upload security middleware runs before decryption, against the encrypted bytes and the original filename. This is the single most important compatibility constraint — see Compatibility.
- Submit to your service. AAEKB reads the file into memory and sends it to your configured decrypt endpoint.
- Receive the plaintext. Either immediately (
syncmode) or after polling a result endpoint (asyncmode). - Re-detect the file type. AAEKB re-derives the MIME type from the decrypted bytes: first from the filename extension, then, if that yields nothing, by sniffing the first 2 KB of the decrypted content.
- Store and ingest. The decrypted file is written to the Knowledge Base raw file storage and then processed by the ordinary ingestion pipeline. Decryption metadata is merged into the document's metadata record.
Two upload paths exist and both support the feature identically:
| Path | Endpoint | Where decryption runs |
|---|---|---|
| Synchronous upload (legacy / Quick Upload) | POST /v3/project/knowledge/add/file | Inside the web request |
| Background upload (default) | POST /v5/project/knowledge/add/file | Inside a background worker process |
The practical implication: both your web/API containers and your background worker containers must be able to reach the decryption service.
Configuring the service (Super Admin)
- Sign in as a Super Admin.
- Open Super Admin → Platform & integrations → KB Decryption Service.
- Fill in the configuration described in Full configuration reference.
- Toggle Enabled on.
- Click Save Changes.
Notes on the admin screen:
- The Async Polling Configuration card is only shown when Response Mode is set to Asynchronous.
- The auth credential field is masked when the configuration is read back. Only the first four characters are shown; the rest is replaced with bullet characters. Re-saving without touching the field leaves the stored credential unchanged. To rotate the credential, clear the field and type the new value in full.
- Saving is atomic across the whole form — there is no per-field save.
- Setting Enabled to off immediately hides the end-user toggle and causes any upload that still claims
needs_decryption=trueto be rejected with a clear error.
The same configuration is available over the REST API for automated provisioning:
| Method | Path | Access | Purpose |
|---|---|---|---|
GET | /admin/kb-decryption-service | Super Admin | Read configuration (credential masked) |
PUT | /admin/kb-decryption-service | Super Admin | Partial update; only the fields you send are changed |
GET | /kb-decryption-service/status | Any authenticated user | Returns {"available": true} |
Example provisioning call:
curl -X PUT "https://ekb.example.com/admin/kb-decryption-service" \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"display_name": "Corporate DRM Gateway",
"description": "Intranet decryption service for classified documents",
"decrypt_endpoint": "https://drm.corp.internal/api/v1/async/decrypt/stream",
"decrypt_http_method": "POST",
"decrypt_content_type": "multipart/form-data",
"decrypt_file_field_name": "file",
"decrypt_extra_headers": {"X-Tenant": "aa-ekb"},
"decrypt_extra_body_fields": {"purpose": "ingestion"},
"response_mode": "async",
"poll_endpoint_template": "https://drm.corp.internal/api/v1/async/download/stream/{job_id}",
"poll_interval_seconds": 5,
"poll_max_attempts": 120,
"poll_job_id_json_path": "job_id",
"poll_status_json_path": "status",
"poll_completed_status": "COMPLETED",
"poll_failed_statuses": "FAILED,ERROR",
"auth_type": "api_key_header",
"auth_token": "s3cr3t-api-key",
"auth_header_name": "X-API-Key"
}'
Full configuration reference
General
| Field | Type | Default | Meaning |
|---|---|---|---|
enabled | boolean | false | Master switch. When off, the end-user toggle is hidden and decryption requests are refused. |
display_name | string | Default Decryption Service | Friendly name. Recorded in each decrypted document's metadata. |
description | string | (empty) | Free-text note for administrators. Not used at runtime. |
Decryption request
| Field | Type | Default | Meaning |
|---|---|---|---|
decrypt_endpoint | URL | (empty) | Absolute URL AAEKB posts the encrypted file to. Must be reachable from both API and worker containers. |
decrypt_http_method | POST | PUT | POST | HTTP verb used for the submit request. |
decrypt_content_type | multipart/form-data | application/octet-stream | multipart/form-data | How the file is transmitted. See the note below. |
decrypt_file_field_name | string | file | Multipart form field name carrying the file. Only used in multipart mode. |
decrypt_extra_headers | JSON object | {} | Extra headers merged into both the submit request and every poll request. |
decrypt_extra_body_fields | JSON object | {} | Extra form fields sent alongside the file. Only used in multipart mode. |
response_mode | sync | async | async | Whether the decrypted bytes come back on the submit call or via polling. |
application/octet-stream mode. When content type is application/octet-stream, AAEKB sends the raw file bytes as the entire request body. In this mode the filename is not transmitted and decrypt_extra_body_fields is ignored, because there is no multipart envelope to carry them. If your service needs the filename or additional context, either use multipart/form-data or pass the context through decrypt_extra_headers.
Async polling (only used when response_mode = async)
| Field | Type | Default | Meaning |
|---|---|---|---|
poll_endpoint_template | URL template | (empty) | Result URL. Must contain the literal placeholder {job_id}, which is substituted with the extracted job identifier. |
poll_interval_seconds | integer | 5 | Seconds to wait between polls. AAEKB sleeps before the first poll, so the minimum decryption latency is one interval. |
poll_max_attempts | integer | 120 | Maximum number of polls. Total budget = poll_interval_seconds × poll_max_attempts (default 600 s / 10 minutes). |
poll_job_id_json_path | dot path | job_id | Where to find the job identifier in the submit response, e.g. data.jobId. |
poll_status_json_path | dot path | status | Where to find the job status in a JSON poll response, e.g. result.state. |
poll_completed_status | string | COMPLETED | Status value meaning "done". Compared case-insensitively. |
poll_failed_statuses | comma-separated | FAILED,ERROR | Status values that abort the job immediately. Compared case-insensitively. |
Dot paths are simple nested-dictionary lookups. data.job.id resolves response["data"]["job"]["id"]. Array indexing is not supported — a path segment cannot be a list index.
Authentication
| Field | Type | Default | Meaning |
|---|---|---|---|
auth_type | none | bearer | api_key_header | custom_headers | none | Authentication scheme. See Authentication options. |
auth_token | secret string | (empty) | The bearer token or API key value. Masked on read. |
auth_header_name | string | Authorization | Header name used when auth_type = api_key_header. |
The contract your decryption service must implement
This is the specification to hand to whoever builds or operates the decryption service.
Non-negotiable requirements
- Speak HTTP(S). One endpoint that accepts a file; in async mode, a second endpoint that returns the result for a job.
- Return the decrypted file as raw bytes, not as base64, not wrapped in JSON, not as a redirect to a download URL. AAEKB streams the response body straight into the ingestion pipeline.
- Preserve the file's original format. If the ciphertext decrypts to a PDF, return a well-formed PDF. AAEKB does no format conversion.
- Return a non-empty body on success. An empty response is treated as a failure.
- Be reachable from AAEKB's API and worker containers, with a TLS certificate those containers trust.
- Be idempotent enough to survive a repeated GET on the poll endpoint. See The double-GET behaviour.
Submit endpoint
Multipart mode (recommended). AAEKB sends:
- Method: as configured (
POSTorPUT) Content-Type:multipart/form-datawith a generated boundary- One file part, named by
decrypt_file_field_name, withfilenameset to the original upload filename - Zero or more additional form fields from
decrypt_extra_body_fields - Auth and extra headers as configured
Octet-stream mode. AAEKB sends the raw bytes as the request body with Content-Type: application/octet-stream. No filename, no extra body fields.
Expected responses:
| Mode | Acceptable status | Body |
|---|---|---|
sync | 200 only | Decrypted file bytes |
async | 202 (typical) or 200 | JSON containing a job identifier, or — if 200 with a binary content type — the decrypted bytes directly |
In async mode AAEKB inspects the submit response before deciding whether to poll:
200and a binary-lookingContent-Type→ the bytes are used directly, no polling. (This lets a service short-circuit for small files.)- Anything else → the body is parsed as JSON and the job identifier is extracted using
poll_job_id_json_path.
A "binary-looking" content type is one that contains octet-stream, or that starts with application/ and does not contain json. So application/octet-stream and application/pdf are treated as binary; application/json is not. text/plain is not treated as binary — see Content-Type guidance.
Poll endpoint (async mode)
AAEKB issues a GET to poll_endpoint_template with {job_id} substituted, carrying the same auth and extra headers as the submit request. It repeats this every poll_interval_seconds, up to poll_max_attempts times.
Your service should respond with one of:
| Situation | Status | Body | AAEKB behaviour |
|---|---|---|---|
| Still working | 202 | Anything (ignored) | Waits and polls again |
| Still working | 200 | JSON whose status field is neither the completed nor a failed value | Waits and polls again |
| Done | 200 | Raw decrypted bytes with a binary Content-Type | Preferred. Bytes are used; polling stops |
| Done | 200 | JSON whose status field equals poll_completed_status | Issues one more GET to the same URL expecting binary; if that is not binary, the upload fails |
| Failed | 200 | JSON whose status field is in poll_failed_statuses | Aborts immediately with an error naming the status |
| Error | >= 400 | Anything | Aborts immediately. Does not retry |
Do not return 404 for a job that is merely not ready yet. Any status code of 400 or above ends the job with a hard failure. Use 202 for "not ready".
The double-GET behaviour (async mode)
If your poll endpoint answers 200 with a JSON body saying the job is complete, AAEKB assumes the file must be fetched separately and immediately issues a second GET to the very same URL, this time expecting a binary stream. If that second call also returns JSON, the upload fails with "Decryption job completed but no file stream was returned."
There are two ways to satisfy this:
- Preferred: have the poll endpoint return the raw bytes directly with
Content-Type: application/octet-streamas soon as the job is done. AAEKB detects this on the first poll and never issues the second request. This is the simplest and fastest design. - Acceptable: have the endpoint return a JSON status while pending/complete, and switch to returning the byte stream on a subsequent call. The job must remain retrievable for at least one extra GET after it first reports completion.
What is not supported is a design where the completed JSON contains a different download URL. AAEKB will not follow it.
Content-Type guidance
| Response | Recommended Content-Type |
|---|---|
| Decrypted file bytes | application/octet-stream |
| Job accepted / job status | application/json |
Returning the decrypted payload as text/plain or text/csv in async mode will cause AAEKB to try to parse it as a job-status JSON document and fail. In sync mode the content type of the response is not inspected at all — any 200 is taken as the decrypted bytes — but application/octet-stream is still recommended for consistency.
What AAEKB never does
- It does not send the decryption key, user credentials, or the end user's identity to your service. If your service needs to authorise per-document or per-user, that context must come from
decrypt_extra_headers/decrypt_extra_body_fields, which are static and deployment-wide. - It does not retry a failed submit or a failed poll. One failure ends the upload.
- It does not follow redirects to alternative download hosts as part of the completion handshake.
- It does not chunk or range-request large files. The whole file goes in one request and comes back in one response.
Worked examples: synchronous mode
Configuration:
{
"enabled": true,
"decrypt_endpoint": "https://drm.corp.internal/api/v1/decrypt",
"decrypt_http_method": "POST",
"decrypt_content_type": "multipart/form-data",
"decrypt_file_field_name": "file",
"decrypt_extra_body_fields": { "purpose": "ingestion" },
"decrypt_extra_headers": { "X-Tenant": "aa-ekb" },
"response_mode": "sync",
"auth_type": "bearer",
"auth_token": "eyJhbGciOi..."
}
Request AAEKB sends:
POST /api/v1/decrypt HTTP/1.1
Host: drm.corp.internal
Authorization: Bearer eyJhbGciOi...
X-Tenant: aa-ekb
Content-Type: multipart/form-data; boundary=----AAEKBBoundary7MA4YWxk
Content-Length: 2481037
------AAEKBBoundary7MA4YWxk
Content-Disposition: form-data; name="purpose"
ingestion
------AAEKBBoundary7MA4YWxk
Content-Disposition: form-data; name="file"; filename="Q3-Financials.pdf"
Content-Type: application/octet-stream
<...encrypted bytes...>
------AAEKBBoundary7MA4YWxk--
Successful response your service must return:
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 2394112
Content-Disposition: attachment; filename="Q3-Financials.pdf"
%PDF-1.7
<...decrypted bytes...>
Content-Disposition is optional and ignored; AAEKB keeps the original upload filename.
Failure response — any non-200 ends the upload. Include a short, human-readable body; AAEKB surfaces the first 500 characters of it to the end user.
HTTP/1.1 403 Forbidden
Content-Type: application/json
{"error": "Document policy forbids export for this identity"}
The user sees: Decryption service returned HTTP 403: {"error": "Document policy forbids export for this identity"}
Worked examples: asynchronous (polling) mode
Configuration:
{
"enabled": true,
"decrypt_endpoint": "https://drm.corp.internal/api/v1/async/decrypt/stream",
"decrypt_content_type": "multipart/form-data",
"decrypt_file_field_name": "file",
"response_mode": "async",
"poll_endpoint_template": "https://drm.corp.internal/api/v1/async/download/stream/{job_id}",
"poll_interval_seconds": 5,
"poll_max_attempts": 120,
"poll_job_id_json_path": "job_id",
"poll_status_json_path": "status",
"poll_completed_status": "COMPLETED",
"poll_failed_statuses": "FAILED,ERROR",
"auth_type": "api_key_header",
"auth_token": "s3cr3t-api-key",
"auth_header_name": "X-API-Key"
}
Step 1 — Submit
POST /api/v1/async/decrypt/stream HTTP/1.1
Host: drm.corp.internal
X-API-Key: s3cr3t-api-key
Content-Type: multipart/form-data; boundary=----AAEKBBoundary7MA4YWxk
------AAEKBBoundary7MA4YWxk
Content-Disposition: form-data; name="file"; filename="Q3-Financials.pdf"
Content-Type: application/octet-stream
<...encrypted bytes...>
------AAEKBBoundary7MA4YWxk--
Response:
HTTP/1.1 202 Accepted
Content-Type: application/json
{
"job_id": "8f14e45f-ceea-467a-9f7a-2a1d4b0c9e33",
"status": "PENDING"
}
With a nested identifier you would instead set poll_job_id_json_path to data.job.id and return:
{ "data": { "job": { "id": "8f14e45f-ceea-467a-9f7a-2a1d4b0c9e33" } } }
Step 2 — Poll while pending
AAEKB waits 5 seconds, then:
GET /api/v1/async/download/stream/8f14e45f-ceea-467a-9f7a-2a1d4b0c9e33 HTTP/1.1
Host: drm.corp.internal
X-API-Key: s3cr3t-api-key
Either of these keeps AAEKB waiting:
HTTP/1.1 202 Accepted
Content-Type: application/json
{"status": "PROCESSING", "message": "File is still being processed."}
HTTP/1.1 200 OK
Content-Type: application/json
{"status": "PROCESSING", "progress": 42}
Step 3 — Poll when complete (recommended pattern)
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 2394112
Content-Disposition: attachment; filename="Q3-Financials.pdf"
%PDF-1.7
<...decrypted bytes...>
AAEKB stops polling and proceeds to ingestion.
Step 3 (alternative) — Complete via JSON, then stream
HTTP/1.1 200 OK
Content-Type: application/json
{"status": "COMPLETED"}
AAEKB immediately re-issues the same GET, and your service must then answer with the binary stream shown above. If it answers with JSON again, the upload fails.
Step 3 (failure)
HTTP/1.1 200 OK
Content-Type: application/json
{"status": "FAILED", "reason": "Key not available for principal"}
The user sees: External decryption job failed with status: FAILED
Note that only the status value is surfaced, not your reason field. If you want detail in the user-facing message, encode it into the status string or return a 4xx/5xx with a descriptive body instead.
Reference mock service
A minimal echo-back mock, useful for validating the plumbing before the real service is ready:
# Minimal AAEKB-compatible async decryption mock (FastAPI).
# Returns the uploaded bytes unchanged.
import io, uuid
from fastapi import FastAPI, UploadFile, File
from fastapi.responses import JSONResponse, StreamingResponse
app = FastAPI()
JOBS: dict[str, tuple[bytes, str]] = {}
@app.post("/api/v1/async/decrypt/stream")
async def submit(file: UploadFile = File(...)):
job_id = str(uuid.uuid4())
JOBS[job_id] = (await file.read(), file.filename or "file")
return JSONResponse(status_code=202, content={"job_id": job_id, "status": "PENDING"})
@app.get("/api/v1/async/download/stream/{job_id}")
async def download(job_id: str):
entry = JOBS.pop(job_id, None)
if entry is None:
# Not ready (or unknown). Must NOT be a 4xx — that would abort the job.
return JSONResponse(status_code=202, content={"status": "PROCESSING"})
data, filename = entry
return StreamingResponse(
io.BytesIO(data),
media_type="application/octet-stream",
headers={
"Content-Disposition": f'attachment; filename="{filename}"',
"Content-Length": str(len(data)),
},
)
Authentication options
auth_type | What AAEKB sends | Notes |
|---|---|---|
none | Only decrypt_extra_headers | Use with network-level controls (mTLS terminated at a proxy, IP allowlist, service mesh). |
bearer | Authorization: Bearer <auth_token> | Plus decrypt_extra_headers. |
api_key_header | <auth_header_name>: <auth_token> | Plus decrypt_extra_headers. Typical: X-API-Key. |
custom_headers | Only decrypt_extra_headers | Put the complete header set, including any credential, in the extra headers JSON. Use for signature schemes or multi-header auth. |
In every mode, decrypt_extra_headers is merged in, and the resulting header set is applied to both the submit request and every poll request.
Not supported natively: OAuth 2.0 client-credentials flows with token refresh, HMAC request signing, and mutual TLS with a client certificate presented by AAEKB. All three can be handled by placing a small authenticating reverse proxy or sidecar between AAEKB and the decryption service, and pointing decrypt_endpoint at the proxy. Static tokens configured here do not auto-refresh; a rotating credential must be re-saved in the admin screen or pushed via the PUT API.
End-user experience
Visibility. The Knowledge Base upload modals call GET /kb-decryption-service/status when they open. The Requires Decryption toggle is rendered only when that returns {"available": true}, which requires the configuration row to exist and enabled to be true. Users of a deployment without a configured service never see it. The status result is cached briefly in the browser (about 30 seconds), so enabling or disabling the feature may take up to that long to appear or disappear in an already-open session.
Where it appears. Both KB upload entry points expose the toggle:
- The Add Resources modal (drag-and-drop / file picker within a Knowledge Base)
- The Selected Files modal (used when files are dropped onto the KB view)
Scope. The toggle is per upload batch, not per file. If it is on, every file in that batch — including every file inside a selected folder — is sent to the decryption service. Mixed batches of encrypted and unencrypted files are not supported; upload them separately.
Default state. Off. It resets to off each time the modal is closed and reopened.
Failure behaviour. If decryption fails, that file's upload fails and the error is shown in the upload progress list / toast. Other files in the batch are unaffected. Nothing partial is indexed.
Progress. The browser's progress bar tracks the upload of bytes to AAEKB only. Time spent in your decryption service happens server-side afterwards and appears as processing time, not upload progress. With the default polling settings, expect at least 5 seconds of additional latency per file even for an instant decryption.
Compatibility: what will and will not work
Read this section carefully. It is the source of nearly all integration surprises.
The critical constraint: validation happens before decryption
AAEKB runs its standard upload security validation against the encrypted bytes and the original filename, before the file is sent to your decryption service. There is no bypass for needs_decryption. This means an encrypted file must still look like a legitimate, allowed file type at the moment of upload.
The validation performs, in order:
- Filename checks — no path traversal, no null bytes, no Windows-illegal characters, max 255 characters, no suspicious double extensions.
- Extension allowlist — the file must have an extension, and it must be one of the supported types.
- Size limit — 4096 MB (4 GB) ceiling.
- Content sniffing (libmagic) — the first 2 KB are inspected and compared against the claimed extension. Executable signatures (PE / ELF / Mach-O) are rejected outright.
- Type-specific content checks — e.g. images are opened and verified, text files must decode cleanly as text.
Supported file extensions
Only these extensions are accepted (script types only if your deployment explicitly enables script uploads):
- Documents:
pdf,doc,docx,xls,xlsx,pptx - Text and data:
txt,md,csv,tsv,json,xml,dita,yaml,yml,html,htm - Mail:
eml,msg - Archives:
zip - Images:
png,jpg,jpeg,gif,webp - Audio:
mp3,wav,m4a,ogg - Video:
mp4,mov,avi,wmv,webm - Scripts (opt-in only):
py,sh,bash,js,php,rb,pl,bat,cmd,ps1
The live list for your deployment is available at GET /upload-capabilities.
Filename and wrapper compatibility
| Uploaded filename | Result |
|---|---|
Report.pdf (ciphertext inside) | Depends on the ciphertext envelope — see below |
Report.pdf.enc | Rejected. .enc is not an allowed extension |
Report.enc, Report.drm, Report.aes | Rejected. Not allowed extensions |
Report (no extension) | Rejected. "File must have an extension" |
Report.pdf.gpg | Rejected. .gpg is not allowed |
Your encryption scheme must preserve the original file extension. A wrapper format that appends .enc, .drm, .p7m or similar will be rejected at the door. If your DRM system produces such filenames, rename to the original extension before upload, or configure the client-side export to retain it.
Ciphertext envelope compatibility
This is the second gate. The encrypted bytes must survive content sniffing while still claiming the original extension.
| Encrypted format | Compatible? | Why |
|---|---|---|
Office documents (.docx, .xlsx, .pptx) encrypted with Microsoft Information Protection / OOXML password encryption | Yes | These are wrapped in an OLE2 / CDF compound container. AAEKB explicitly accepts OLE2, CDFV2, application/vnd.ms-office and application/octet-stream when the extension is an Office type. This is the best-supported case. |
Legacy Office (.doc, .xls) encrypted in place | Yes | Same OLE2 allowance. |
PDF with standard PDF encryption (/Encrypt dictionary, password-protected) | Yes | The file remains a structurally valid PDF; %PDF- is still the leading signature. |
PDF wrapped in an opaque envelope, but with %PDF- present within the first 1024 bytes | Yes | AAEKB searches the first 1 KB for the %PDF- marker and accepts on that basis. A DRM envelope that keeps a PDF preamble/header will pass. |
PDF fully encrypted into opaque ciphertext with no %PDF- in the first 1 KB | No | Sniffing returns application/octet-stream (or something unrelated) for a .pdf extension → rejected with "File content does not match extension" or "File detected as generic binary type." |
Plain text formats (.txt, .csv, .json, .md, .xml, .html) encrypted into binary ciphertext | No | Sniffing does not return a text/* type, so the extension/content check fails. |
| ZIP archives encrypted as a whole | No | The outer archive must be a readable ZIP; AAEKB inspects its entries. Additionally, ZIP files containing encrypted members are rejected by the archive validator. |
| Images, audio, video encrypted into opaque ciphertext | No | Same sniffing mismatch. |
Rule of thumb: the integration is designed for, and reliably works with, encrypted Office documents and encrypted/DRM-protected PDFs that retain a recognisable header. Formats where encryption destroys all structural markers will be blocked before your service is ever called.
If your DRM system produces fully opaque ciphertext, the supported workaround is to have the exporting system wrap the payload in an OOXML or PDF container that retains the appropriate header bytes, so the file remains recognisable in transit. Alternatively, contact your AAEKB representative — the content-validation layer is configurable at the deployment level and can be adjusted for on-premise installations.
After decryption
Once the decrypted bytes come back, AAEKB determines the type as follows:
mimetypes.guess_type()on the original filename — so the extension you uploaded with determines the ingestion path in almost all cases.- If that yields nothing, libmagic sniffs the first 2 KB of the decrypted content.
The decrypted content is then routed to the appropriate handler: PDF, Word, Excel, PowerPoint, HTML, XML/DITA, plain text, JSON, CSV/TSV, EML, MSG, audio (transcription), video (transcription), or ZIP (extract and ingest members).
If the type cannot be determined, the file falls through to a generic handler that attempts best-effort text extraction. The upload will still succeed and the file will be stored and downloadable, but it may be indexed with empty text content — meaning it will not be retrievable by semantic search. This is the usual symptom of a decryption service returning something other than the expected format (for example, base64 text instead of raw bytes).
Feature interactions
| Feature | Interaction |
|---|---|
| Quick Upload toggle | Compatible. Decryption runs inline in the request; the user waits for the whole decryption round-trip before the modal completes. |
| Folder upload | Compatible; the toggle applies to every file in the folder. |
| Encrypted ZIP as a container of encrypted files | Not supported. Decryption is applied to the uploaded file as a whole, before archive extraction. Members inside a ZIP are not individually decrypted. |
| URL / connector ingestion (SharePoint, Confluence, web crawl, Google Drive, etc.) | Not supported. Decryption applies only to direct file uploads through the Knowledge Base upload modals and the add/file API endpoints. |
| Chat-attachment uploads | Not exposed in the UI. |
| Re-sync / re-processing of an existing document | The stored file is already decrypted, so re-processing works normally and does not call your service again. |
Timeouts, limits and sizing
| Operation | Limit | Configurable |
|---|---|---|
| Submit request to the decryption endpoint | 300 seconds | No |
| Each individual poll request | 60 seconds | No |
| The follow-up binary fetch after a JSON "completed" | 120 seconds | No |
| Total polling window | poll_interval_seconds × poll_max_attempts — 600 s (10 min) by default | Yes |
| Maximum upload size | 4096 MB (4 GB) | Deployment-level |
| Retries on failure | None | No |
Sizing guidance:
- Decryption is entirely in-memory. Peak memory per concurrent decryption is roughly 2× the file size (ciphertext plus plaintext), on top of normal request overhead. Size your API and worker containers accordingly, or cap practical upload sizes well below the 4 GB ceiling for encrypted uploads.
- If your service can take longer than 5 minutes to accept a submission, it must use async mode — the 300-second submit timeout is not adjustable.
- If decryption itself can take longer than 10 minutes, raise
poll_max_attempts(orpoll_interval_seconds) accordingly. - Each in-flight decryption occupies a worker thread for its whole duration, including polling sleep time. High-volume bulk ingestion of encrypted files with long decryption times will consume worker capacity; plan concurrency with that in mind.
Storage, security and audit behaviour
What gets stored
AAEKB stores the decrypted file in the Knowledge Base raw file storage, not the ciphertext.
This is because the raw stored file is read back by several parts of the product after ingestion: in-app document preview, download, citation rendering, table and spreadsheet extraction, and any subsequent re-processing or re-indexing of the document. Storing the plaintext keeps all of those working directly, and means the decryption service is called exactly once per document — at upload — rather than on every read. It also removes your decryption service from the critical path for everyday user activity, so an outage there never affects access to documents that are already in the Knowledge Base.
Once ingested, the document is protected by AAEKB's own controls: encryption at rest provided by the underlying object storage, plus AAEKB's project-, team- and role-level access controls governing who can view, search or download it.
Metadata written to each decrypted document
{
"originally_encrypted": true,
"decryption_service": "Corporate DRM Gateway",
"decrypted_at": 1774000000.123
}
decryption_serviceis the configured Display Name.decrypted_atis a Unix epoch timestamp (seconds, float).originally_encryptedis an informational marker, letting administrators and end users identify which documents arrived encrypted. It does not affect processing: because the stored file is already decrypted, re-processing a document never involves the decryption service again.
Credential handling
auth_tokenis stored in the AAEKB application database.- It is masked whenever the configuration is read back through the admin API or UI: only the first four characters are returned, the remainder replaced with bullet characters. Saving a masked value is detected and ignored, so re-saving the form never corrupts the stored credential.
- Every change to the configuration is written to the Super Admin audit log as event
kb_decryption_service_updated, recording before/after values for all fields except the credential, which is recorded only asauth_token_rotated: true. The token value never appears in audit records or logs.
Access control
- Reading and writing the configuration requires Super Admin privileges.
- The availability status endpoint requires an authenticated session but no special role, and returns only a boolean.
- Non-admin users can never see the endpoint URL, headers, or credential.
Logging
AAEKB logs the filename, byte counts, and job identifiers at INFO level during decryption. It does not log file content or credentials. Error messages returned to users include up to the first 500 characters of your service's error response body — avoid returning sensitive material in error bodies.
Error reference and troubleshooting
| Message the user sees | HTTP | Cause | Fix |
|---|---|---|---|
File type '.enc' not allowed. Allowed types include: ... | 400 | Non-allowlisted extension | Preserve the original extension on export |
File must have an extension | 400 | Filename has no extension | Add the correct extension |
File content does not match extension | 400 | Ciphertext doesn't sniff as the claimed type | See Ciphertext envelope compatibility |
File detected as generic binary type. Cannot verify file safety. | 400 | Opaque ciphertext with a non-Office extension | See Ciphertext envelope compatibility |
Encrypted ZIP files are not allowed | 400 | A ZIP with password-protected members | Upload the documents individually, or use an unencrypted archive |
File too large (max 4096MB) | 400 | Over the size ceiling | Split the document |
File was marked as needing decryption, but no external decryption service is currently enabled... | 400 | needs_decryption=true sent while the service is disabled | Enable the service, or refresh the page so the toggle disappears |
Failed to reach the external decryption service: ... | 502 | DNS, TLS, connection refused, or timeout | Check network path, firewall, and TLS trust from both API and worker containers |
Decryption service returned HTTP 4xx/5xx: ... | 502 | Your service rejected the submit | Inspect the quoted body; check auth headers |
Decryption service returned a non-JSON response without file content. | 502 | Async submit returned neither a binary body nor parseable JSON | Return 202 with a JSON job object, or 200 with application/octet-stream |
Could not extract job_id from decryption response using path 'job_id'. | 502 | poll_job_id_json_path doesn't match your response shape | Correct the dot path, e.g. data.jobId |
Decryption poll returned HTTP 404: ... | 502 | Poll endpoint returned 4xx for a not-yet-ready job | Return 202 while pending |
External decryption job failed with status: FAILED | 502 | Your service reported a failed status | Investigate in your service's logs |
Decryption job completed but no file stream was returned. | 502 | Poll reported completion in JSON but the follow-up GET didn't return binary | Return the byte stream on completion — see The double-GET behaviour |
Decryption service returned empty content. | 502 | Zero-length response body | Return the actual decrypted bytes |
Decryption service timed out after 600s of polling. | 504 | Exceeded interval × max_attempts | Raise poll_max_attempts, or speed up decryption |
Decryption service config not found. Run the migration first. | 404 | Database migration hasn't been applied | Run AAEKB database migrations |
Diagnostic tips
- The toggle doesn't appear → check
GET /kb-decryption-service/status; if it returns{"available": false}, the config row is missing orenabledis false. Also allow ~30 s for the browser-side cache to expire. - Works on Quick Upload but not on normal upload (or vice versa) → this is almost always a network reachability difference between the API container and the background worker container.
- Upload succeeds but the document has no searchable content → your service is probably returning something other than raw bytes of the original format (commonly base64 or a JSON wrapper). Verify with
curlthat the response body starts with the expected magic bytes.
Deployment and networking requirements
- Reachability from two places. Both the AAEKB API containers and the AAEKB background worker containers must be able to resolve and reach
decrypt_endpointandpoll_endpoint_template. In Kubernetes deployments, verify that network policies permit egress from both workloads. - TLS trust. AAEKB validates TLS certificates using the standard trusted CA bundle in its container image. A decryption service presenting a certificate from a private/internal CA will fail to connect unless that CA is added to the AAEKB container trust store as part of your deployment. Plan for this — it is a common first-run blocker on intranet deployments. Certificate verification cannot be disabled from the admin UI.
- Plain HTTP.
http://endpoints work but are strongly discouraged, as plaintext documents would traverse the network unprotected on the response leg. - Stable hostname. The endpoint is stored as a literal URL. Use a stable internal DNS name or service address rather than a pod IP.
- Latency budget. The submit call holds a connection for up to 300 seconds. Ensure any intermediate load balancers, ingress controllers or proxies have idle/read timeouts at least that long, otherwise they will sever the connection before AAEKB gives up.
- Database migration. The feature requires the
kb_decryption_service_configtable, created by a standard AAEKB migration that also seeds a single disabled default row. The feature is inert until a Super Admin enables it.
Rollout and validation checklist
Before enabling in production
- Decryption service deployed and reachable from a test pod in the AAEKB namespace (
curlfrom inside the API pod and a worker pod) - TLS certificate chain trusted by the AAEKB containers
- AAEKB database migrations applied; the KB Decryption Service tab loads without a "table not found" error
- Confirmed with a representative sample that your encrypted files pass upload validation — take three real encrypted documents of each format you intend to ingest and upload them. This validates Ciphertext envelope compatibility, which is the most common failure point
Configuration
-
decrypt_endpointset, method and content type match your service -
decrypt_file_field_namematches the form field your service expects (multipart mode) -
response_modematches your service's behaviour - For async:
poll_endpoint_templatecontains{job_id}; JSON paths verified against a real response captured from your service -
poll_interval_seconds × poll_max_attemptscomfortably exceeds your worst-case decryption time - Auth configured and verified with
curlusing exactly the headers AAEKB will send - Display Name set to something meaningful — it is recorded in every decrypted document's metadata
Functional testing
- Upload an encrypted file with the toggle on → document ingests, content is searchable
- Upload an unencrypted file with the toggle off → unaffected
- Upload an unencrypted file with the toggle on → your service should return a clear error; verify the message surfaces sensibly to the user
- Force a service failure (stop the service) → user sees a
502-class error, no partial document is indexed - Test with your largest expected file and confirm timing sits inside the polling budget
- Test a multi-file batch and a folder upload
- Verify the decrypted document downloads correctly from the Knowledge Base and that the metadata shows
originally_encrypted: true
Operational
- Credential rotation procedure documented (re-save via the admin UI or
PUTAPI) - Monitoring/alerting on the decryption service, including its own error rate and latency
- Super Admin audit log reviewed to confirm configuration changes are being captured
Known limitations
- One service per deployment. There is no per-project, per-team or per-file-type routing to different decryption services.
- Batch-level toggle only. Encrypted and unencrypted files cannot be mixed in a single upload batch.
- Direct uploads only. Connector-based ingestion (SharePoint, Confluence, Google Drive, web crawl, and similar) does not invoke decryption.
- No retries. A single transient network blip fails the upload; the user must retry manually.
- Static credentials. No OAuth token refresh, HMAC signing, or client-certificate mTLS from AAEKB itself. Use an authenticating proxy for those schemes.
- No per-user or per-document authorisation context is forwarded to the decryption service. It cannot enforce per-principal DRM policy based on information AAEKB sends.
- Files are stored decrypted after ingestion, under AAEKB's own access controls and storage encryption. See Storage, security and audit behaviour.
- No "test connection" button. Validation is done by performing a real upload.
- Fully opaque ciphertext is blocked by pre-decryption content validation for most formats. See Ciphertext envelope compatibility.
- Nested decryption is not supported. Members inside a ZIP are not individually decrypted.
FAQ
Does AAEKB ever see our encryption keys? No. AAEKB sends ciphertext to an endpoint you control and receives plaintext back. It has no knowledge of key material, algorithms, or your DRM policy engine.
Can we run the decryption service entirely on our intranet? Yes — that is the primary use case. The endpoint only needs to be reachable from the AAEKB API and worker containers. For an on-premise AAEKB deployment, nothing needs to leave your network.
Which response mode should we use?
Use sync if decryption reliably completes within a few seconds and the service can hold the connection. Use async for anything slower, batched, or queue-backed. async with the poll endpoint returning the byte stream directly on completion is the most robust configuration.
What happens if the decryption service is down? Uploads with the toggle enabled fail with a clear error. Normal uploads are unaffected. Already-ingested documents are unaffected — they are stored decrypted and never need your service again.
Can we disable the feature temporarily?
Yes. Toggle Enabled off. The end-user toggle disappears within about 30 seconds, and any upload still claiming needs_decryption=true is rejected with an explanatory message.
Can users tell which documents were originally encrypted?
Yes — decrypted documents carry originally_encrypted: true, the decryption service's display name, and a decryption timestamp in their metadata.
Our encrypted files come out of the DRM system named document.pdf.encrypted. Will that work?
No. Rename to document.pdf before upload. AAEKB validates the extension before decryption and does not recognise wrapper extensions. See Filename and wrapper compatibility.
Our ciphertext is fully opaque — no recognisable header at all. What are our options? The upload will be rejected before reaching your service. Options: (a) have the exporting system preserve or prepend the original format header, (b) use OOXML/OLE2-based envelope encryption for Office documents, which is explicitly supported, or (c) discuss a deployment-level adjustment to content validation with your AAEKB representative for your on-premise installation.
Can the decryption service return a download URL instead of bytes? No. AAEKB reads the decrypted content from the response body and does not follow a URL returned in JSON.
Does this work with Quick Upload? Yes, though the user waits for the full decryption round-trip before the upload modal completes.
Document version 1.0. Feature reference: EN-3043.