Seshat
File safety
How Seshat checks every file for safety before it works on it, what the safety report says, and how your app handles an unsafe file.
What this is for
Your customers send you files, and you cannot know what is in them. Before Seshat works on a file, it checks it: for viruses, for what the file really is, and for anything in it that could act on its own when someone opens it. A file that is not safe is refused and never kept. A file with parts a print does not need is taken, and those parts can be removed before printing.
There is nothing to switch on. Every file a customer sends goes through the check: with check, upload, uploadInParts, enhance, fillTemplate and textPositions. Files Seshat made itself, such as a fixed PDF, are not checked again.
What is checked
- Viruses. Every file is scanned, with signatures updated every day.
- What the file really is. Seshat reads the first bytes, not the name. A program called
flyer.pdfis refused, and so is a file whose name says PDF while it is a picture or a ZIP. - What could act on its own when the PDF is opened: scripts, actions that start a program, programs or other files embedded in the PDF, forms that send data to an address, an address opened as soon as the file opens, and sound, video or 3D.
- Files built to do harm. A picture built to exhaust memory rather than to be printed, or a PDF with far more objects than a print file needs, is refused.
- What is worth knowing. A locked PDF, or a page larger than five metres, is noted.
The check reads the file from storage as a stream, so a file of gigabytes is checked without being held in memory.
The safety report
The answer of every call that takes a file carries the report as safety:
$check = Rekhu::seshat()->check($request->file('artwork'), $order);
$check['safety'];
// ['verdict' => 'remove', 'virus' => ['status' => 'clean'], 'kind' => 'pdf', 'findings' => [
// ['code' => 'scripts', 'decision' => 'remove', 'about' => 'the PDF carries scripts that run in a viewer']]]
const check = await seshat.check(buffer, { name: 'flyer.pdf', order });
check.safety;
// { verdict: 'remove', virus: { status: 'clean' }, kind: 'pdf', findings: [
// { code: 'scripts', decision: 'remove', about: 'the PDF carries scripts that run in a viewer' }] }
| Field | |
|---|---|
verdict |
clean, remove or refuse |
virus |
status, and signature when a virus was found |
kind |
what the file really is: pdf, png, jpeg, tiff, webp, gif, eps, zip |
findings |
each with code, decision (refuse, remove or note) and about; some name the files inside |
The three verdicts
| Verdict | Meaning | What your app does |
|---|---|---|
clean |
nothing found | go on |
remove |
the file is taken; it carries parts a print does not need | go on, and run the clean_up fix before printing |
refuse |
the file is not taken and not kept | tell the customer why, and ask for another file |
The findings behind each decision:
| Decision | Codes |
|---|---|
refuse |
program, not_what_it_says, embedded_program, launch, picture_bomb, too_many_objects, unreadable |
remove |
scripts, xfa_form, form_sends, media, opens_address, attachments |
note |
locked, huge_page |
Clean up a file
A file with the verdict remove can be printed once those parts are out. The clean_up fix draws form fields and comments into the page as they show, and removes hidden layers, scripts, attachments and links. With the option remove, form fields and comments are left out instead of drawn.
$fixed = Rekhu::seshat()->fix($check['file_id'], [['fix' => 'clean_up']], $order);
const fixed = await seshat.fix(check.file_id, [{ fix: 'clean_up' }], order);
applied in the answer says what was found and removed. Combine clean_up with other fixes in the same call: it is still one charge.
The safety in a fix answer describes the file the fix made: after clean_up its verdict is clean. When a file cannot be inspected for safety, it is not taken; the call answers that it could not be checked just now, and sending it again checks it.
Handle an unsafe file
A refused file throws UnsafeFile: status 422, type refused_unsafe. The message says why, and safety holds the report when Rekhu gave it. Nothing is charged.
- Catch UnsafeFile around the call that sends the customer's file.
- Tell the customer why. The message names the reasons, such as "the file is called .pdf but is zip".
- Keep the report with the order, if you log refused files.
- Ask for a new file. Do not send the same file again: it is refused again.
use Rekhu\Core\Exceptions\UnsafeFile;
use Rekhu\Core\Rekhu;
try {
$check = Rekhu::seshat()->for($user)->check($request->file('artwork'), $order);
} catch (UnsafeFile $e) {
Log::warning('Artwork refused', ['user' => $user->id, 'safety' => $e->safety]);
return back()->withErrors(['artwork' => $e->getMessage()]);
}
import { UnsafeFile } from '@rekhu/core';
try {
const check = await rekhu.seshat({ user }).check(buffer, { name: 'flyer.pdf', order });
res.json(check);
} catch (error) {
if (!(error instanceof UnsafeFile)) throw error;
logger.warn('Artwork refused', { user: user.id, safety: error.safety });
res.status(422).json({ message: error.message });
}
UnsafeFile extends RekhuException (Laravel) and RekhuError (Node.js), so a catch of the base class still catches it. See Errors.
Note: the safety report says whether a file is safe to work on, not whether it prints well. Whether it prints well is what the check's own findings say; see Checking files.
Next
- Large files: the safety check of a file sent in parts.
- Errors: every error type in both packages.