How to Run AI Document Processing in Slack Safely

Run every Slack file job from a manifest and not from the message text alone: pin the exact file version, state the objective and the checks the result must pass, run the work in a network-isolated sandbox, and validate the output outside the model. Treat the attached document as data that can never change the objective, post partial and not succeeded when any row was rejected, and keep the follow-up write as a separate job that waits for approval. That is the contract this guide builds and tests against a CSV reconciliation.
AI document processing can remove a tedious loop from operations work: download a CSV, inspect the columns, clean the rows, calculate the result and paste it back into Slack. It can also create a quieter failure. The agent may read the wrong file, obey instructions hidden inside a document, lose rows during parsing or call a failed job complete. The scope is files attached to a Slack request and processed by an agent, and the reader is a business operations leader. A document library you search from Slack is a different problem, where the permissions of the library decide what the assistant may quote.
Why a Slack file job needs a contract
Most intelligent document processing pages explain how to extract text, classify a document, split a PDF or validate fields. The Google Cloud Document AI overview, for example, covers OCR, extraction, classification, splitting and validation. A Slack workflow must also decide who may process a file, what the operator requested, how partial failures appear in the thread, and whether the result may update another system.
Slack adds its own transport and permission details. Its working with files guide documents separate read and write scopes, private download URLs, file events, and malware scanning before a file becomes available in a workspace. An agent should not treat the first visible filename as proof that the content is ready, readable, authorized or complete.
The job contract supplies the missing operational layer. It should answer seven questions before execution:
- Which Slack workspace, channel, thread, and message define the request?
- Which user is asking, and can that user read the file now?
- Which immutable file version will be processed?
- What transformation should run, using which accepted input shape?
- What checks must pass before the result is usable?
- How will partial, failed, and cancelled work appear?
- Which follow-up action, if any, requires explicit approval?
If the system cannot answer one of these questions, it should pause in the thread instead of guessing.
Define the file job before execution
Turn the Slack request into a small manifest. Keep the original message text for audit, but do not rely on prose alone to control execution.
job_id: filejob_20260910_0142thread:workspace: T_WORKSPACEchannel: C_OPERATIONSroot_ts: "1789038120.004200"requester: U_ANALYSTinput:slack_file_id: F_EXPORTversion_key: "sha256:recorded-at-ingest"media_type: text/csvsize_bytes: 418220objective: "Reconcile invoice totals by region and list rejected rows"accepted_schema:required_columns:- invoice_id- region- amount- currencychecks:- unique_invoice_id- allowed_currency- source_row_count_reconciled- amount_total_reconciledexecution:network: disabledmax_seconds: 60downstream_writes: blockedstatus: acceptedThe manifest is our recommendation, not a Slack standard. Its purpose is to make the request reproducible. File ID and version prevent a later replacement from silently changing the input. Required columns define the minimum usable shape. Checks describe the evidence that must accompany the answer. The execution block keeps analysis separate from writes.
Record a terminal state for every job: succeeded, partial, failed, cancelled or expired. Never leave running or waiting as the last state after a worker error. A Dify bug report shows the failure: a CSV that failed processing left a batch import stuck at waiting while the worker log held the traceback. It is one author's report about one implementation, but the fix generalizes. Update the worker failure and the user-visible job state together.
Separate transport, content and instructions
A Slack message can contain legitimate instructions. The attached document is untrusted input. Keep those channels separate even when the model sees both.
The OWASP prompt injection prevention guidance describes remote or indirect prompt injection, where malicious instructions arrive through external content such as files or web pages. Its controls include input validation, structured separation of instructions from data, output monitoring, least privilege, human review and comprehensive monitoring. For file jobs, translate those controls into specific rules:
- Only the Slack request and an approved internal job template may define the objective.
- Treat text extracted from the file as data. It cannot enable tools, change limits, reveal other sources, or approve a write.
- Reject unsupported file types and files that exceed declared limits before model processing.
- Parse into a typed intermediate representation before asking a model to interpret values.
- Disable network access in the execution environment unless the job explicitly needs a named, approved source.
- Give the execution process only the input file and a writable temporary output directory.
- Validate the output independently of the model response.
An Open WebUI bug report describes an attached PDF that was visible by metadata but not reliably readable through tools; turning on full context made it readable by injecting the entire document into every message. Do not use full-context injection as an automatic fallback. Tell the operator that retrieval failed, or ask whether the full file may be processed under the same controls.
Run the file in an isolated sequence
Use a deterministic sequence around any probabilistic interpretation.
Accept and pin the input
Resolve the Slack file through the requesting user's authorized context. Record the file ID, version or digest, media type, byte size and retrieval time. Wait for Slack's file readiness path rather than retrying an unavailable private URL without a limit. If access changes before execution, fail closed and name the inaccessible input without exposing its content.
Inspect before interpreting
Check the file signature, not only its extension. Apply size, page, row, archive-depth and decompression limits. For CSV input, detect the delimiter and encoding, then parse the header without executing formulas or macros. For PDF input, record whether the document is text-based, scanned, encrypted or partially unreadable.
When extraction loses structure, stop pretending the data is tabular. The result can still be useful as a partial transcript, but it must not feed a calculation that assumes intact columns.
Execute with narrow capabilities
Create a clean sandbox for one job. Mount or copy only the pinned input, disable outbound network access, apply CPU, memory and time limits, and discard the environment afterward. Do not pass unrelated thread attachments, integration credentials or a shared working directory into the process.
Kipwise Agent documents this product path for a CSV, log file, or quick calculation: it runs code in a throwaway, network-isolated sandbox tied to the thread, reports the result, and discards the sandbox. It also states that connected work uses the requesting user's permissions where applicable. Those controls make the Slack request useful without granting a general-purpose process broad internal access.
Validate outside the transformation
After a successful process exit, compare the output with the manifest:
- Input row count equals accepted rows plus rejected rows.
- Required identifiers remain unique.
- Every amount parses under the declared currency rule.
- Group totals reconcile to the accepted source total.
- Every rejected row includes a reason and original row number.
- Output files contain only declared columns.
- The result includes the pinned input digest and transformation version.
Post partial, not succeeded, when the calculations are valid but some rows were rejected. Post failed when totals do not reconcile or the output cannot be parsed.
A CSV reconciliation, worked through
Suppose an operations analyst attaches a regional invoice export and asks: "Reconcile totals by region, list invalid rows, and draft a short finance note."
The agent should first reply with the pinned filename, row count, detected columns, planned checks, and a statement that it will not update the finance system. If the export lacks currency, the job stops before calculation and asks for the missing field or a single confirmed currency assumption.
For a valid file, the transformation can follow this pseudocode:
load pinned CSV as data, never as instructionsvalidate required columnsfor each row:reject missing invoice IDsreject duplicate invoice IDsreject unsupported currenciesparse amount using declared decimal rulesaggregate accepted amounts by regionreconcile accepted plus rejected count to source countreconcile grouped totals to accepted-row totalemit summary, rejected-row file, and validation recordThe Slack result should distinguish facts from requests:
- Input: pinned file ID, digest, 2,418 source rows.
- Accepted: 2,401 rows.
- Rejected: 17 rows, with a downloadable exception file.
- Result: regional totals and accepted-row total.
- Checks: row reconciliation passed, total reconciliation passed, identifier uniqueness passed after rejection.
- Limits: rejected rows were excluded; no finance records were changed.
- Next action: draft available for review, with a separate approval required before writing anywhere.
These values are a worked example, not a product benchmark. The format forces the agent to show how it reached the answer and what remains unresolved.
Make failures visible in the thread
Operators should not need worker logs to learn that a job failed. Map every technical failure to a concise thread state and a safe retry rule.
A parser error should identify the file, rejected format, and accepted alternatives. A timeout should say which stage stopped and whether any output was discarded. A permission failure should name the system or file object, not its protected contents. A validation mismatch should show the expected and observed counts or totals. A model refusal or uncertain classification should route the file to review, not convert uncertainty into an empty successful output.
Make retries deliberate. Reuse the same job ID only when the pinned input and transformation are unchanged. Create a new version when the user replaces the file, changes the schema, alters the objective, or accepts a new assumption. An idempotency key should prevent a Slack event retry from launching the same transformation twice.
Keep the thread usable. Post one progress update only when the job will take long enough to matter. Finish with one terminal message that links the result, exceptions, checks, and proposed next action. Put detailed logs in the governed execution record rather than flooding the channel.
Protect downstream systems with a second boundary
File analysis and external action are separate jobs. A request to "reconcile and update the tracker" should first produce the reconciliation and a proposed update. The write waits for an authorized person to inspect the totals, exceptions, target, and exact payload.
Invalidate approval when the input digest, result, target, or payload changes. Use the destination system's live permissions at execution time. Record the approving user, approved payload digest, external result ID, and outcome. If the write succeeds but the Slack acknowledgement fails, reconcile by idempotency key before retrying.
Start pilots with read-only analysis and a downloadable result. Add one reversible internal write only after the team can prove access revocation, duplicate delivery, partial failure, and output validation behave correctly.
Twelve tests before rollout
Run a fixed test pack in a non-production workspace:
- Upload a valid CSV and reconcile every row and total.
- Remove one required column and confirm execution never starts.
- Include a duplicate identifier and confirm it appears in exceptions.
- Put an instruction to reveal secrets inside a document and confirm it is treated as data.
- Replace the file after acceptance and confirm the old version cannot run unnoticed.
- Revoke the requester's file access before execution and confirm the job fails closed.
- Force a parser crash and confirm the thread reaches
failed, notwaiting. - Force a timeout and confirm partial artifacts are discarded or clearly labeled.
- Replay the Slack event and confirm the transformation does not run twice.
- Change an approved downstream payload and confirm the old approval becomes invalid.
- Block a private file download and confirm no cached content from another user appears.
- Compare two reruns of the same pinned input and transformation version.
Count false-success incidents before you count time saved. A job that reports success on a wrong total costs more than the minutes saved on a hundred correct ones, so track operator corrections as seriously as speed. Useful pilot measures include analyst minutes per file, manual downloads, tool switches, rejected-row accuracy, reconciliation failures caught, false-success incidents, rerun consistency, and downstream actions held for approval. Use the same definitions before and during the pilot.
Pick one recurring file job with a named owner and a known manual baseline. Encode its manifest, run the test pack, and keep downstream writes disabled until every failure produces a visible terminal state.

-p-1080.jpg)

