# Is This File Safe? True Type, Macros, PDF JavaScript & Metadata (`that_red_bird/file-safety-inspector`) Actor

Before an agent opens a file: true type by magic bytes vs its extension, Office macros, PDF JavaScript and launch actions, archive bombs, embedded files and hidden author/GPS metadata. One safe, review or block verdict per file. Static analysis only, files are never executed.

- **URL**: https://apify.com/that_red_bird/file-safety-inspector.md
- **Developed by:** [mohamed alaya](https://apify.com/that_red_bird) (community)
- **Categories:** Developer tools, AI, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 file inspecteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Is This File Safe? True Type, Macros, PDF JavaScript & Metadata

Checks files before an AI agent, RAG pipeline or upload form opens them and returns one verdict per file: safe, review or block. It finds the true file type from the bytes (not the extension), Office macros, PDF JavaScript and launch actions, archive bombs, embedded files and hidden author or GPS metadata.

Built for people who feed untrusted files into pipelines: RAG and agent builders, upload services, e-discovery and security teams. Files are only read and parsed. They are never executed, opened by Office or a PDF reader, or saved to disk.

### Input examples

Check a few files by URL:

```json
{ "fileUrls": ["https://example.com/report.pdf", "https://example.com/invoice.docx", "https://example.com/photo.jpg"] }
```

Check everything a Website Content Crawler run saved, and treat every macro as a block:

```json
{ "keyValueStoreId": "CRAWLER_STORE_ID", "macroPolicy": "block", "maxFiles": 300 }
```

Check the file URLs in a dataset column:

```json
{ "datasetId": "YOUR_DATASET_ID", "urlField": "url", "blockExecutables": false }
```

Leave the input empty to inspect 8 built-in demo files (a PDF with auto-run JavaScript, an EXE named .jpg, a macro workbook named .xlsx, a 60 MB bomb, clean files).

### Input guide

| Field | What it does |
|---|---|
| `fileUrls` | Public http(s) file URLs. |
| `keyValueStoreId`, `recordKeys` | Inspect records of a key-value store (all records by default). |
| `datasetId`, `urlField` | Inspect file URLs listed in a dataset column. |
| `macroPolicy` | `review` (default) or `block` for macros without clearly malicious behaviour. |
| `blockExecutables` | Flag executables and scripts; block them when the name claims to be harmless. |
| `maxFileMb`, `maxFiles` | Size and count limits. Larger files are inspected up to the limit and marked for review. |

### How the verdict works

| Verdict | Examples |
|---|---|
| **block** | EXE/ELF/script disguised as .jpg or .txt; PDF JavaScript that runs on open (`/OpenAction`, `/AA`, `/Launch`); VBA that auto-runs and calls shell, download or PowerShell; DDE fields; decompression bombs, overlapping zip entries, zip-slip paths; SVG with scripts |
| **review** | Any macro; PDF with JavaScript, embedded files, forms that submit, encryption; type does not match extension; external template or linked OLE content; ActiveX; GPS in photos; data appended after an image; encrypted or nested archives; very high compression ratios |
| **safe** | Nothing above found. This is not a guarantee of safety. |

### Output

One row per file.

| Field | Meaning |
|---|---|
| `verdict`, `reasons`, `reasonCodes` | The decision and every finding in plain words |
| `detectedType`, `claimedExtension`, `typeMismatch` | True type from magic bytes vs the file name |
| `macros` | VBA modules, auto-run entry points, suspicious calls (Office files) |
| `pdf` | Counts of risky PDF keywords, pages and Info metadata |
| `archive` | Entry count, unpacked size, ratio, nested archives, executables inside |
| `metadata` | Author, last editor, company, software, camera and GPS where present |
| `sha256`, `sizeBytes` | Fingerprint and size |

Sample row (shortened):

```json
{ "type": "file", "name": "quarterly-report.pdf", "verdict": "block", "detectedType": "pdf", "typeMismatch": false,
  "reasons": ["[block] PDF contains JavaScript that runs automatically (/JavaScript 2, /OpenAction 1, /AA 0)"],
  "pdf": { "keywords": { "javascript": 2, "openAction": 1, "pages": 1 } }, "sha256": "..." }
```

### Use it with

- Website Content Crawler (saves downloaded files in its key-value store) -> this Actor -> only ingest files with `verdict: "safe"` into your vector store.
- An agent tool: call the Actor with the URL the agent was about to download and branch on `verdict`.
- Any scraper that outputs a `url` column of documents -> this Actor with `datasetId`.

### FAQ

**Does it run antivirus?** No. It is a static triage of known risk patterns, similar to oletools and pdfid. It does not use signature databases, so brand-new malware that avoids these patterns can pass as safe.

**Does it open or execute the file?** Never. Bytes are parsed in memory.

**What about password-protected files?** They are flagged for review because their contents cannot be inspected.

### Honest limits

- Not an antivirus. Treat `safe` as "no known red flags", not "clean".
- Macro source is decoded for standard VBA projects; heavily obfuscated or stomped VBA, and Excel 4.0 macros inside OOXML sheets, are detected only by structure.
- Archives are checked from their directory (sizes, names, overlap). 7z, rar, bz2, xz and tar contents are not unpacked; gzip is checked by its size footer.
- Images: only JPEG/TIFF/HEIC/WebP EXIF is read for GPS; PNG text chunks are not.
- Files are held in memory (default limit 25 MB).

### Price

Pay per event: **$0.002 per file inspected** (`file-inspected`). 1,000 files cost $2.00. Files that cannot be downloaded or read are not charged.

# Actor input Schema

## `fileUrls` (type: `array`):

Public http(s) URLs of the files to check (PDF, Office, images, archives, anything). Leave all inputs empty to inspect 8 harmless built-in demo files that show every verdict.

## `keyValueStoreId` (type: `string`):

Pick a key-value store, for example the one filled by Website Content Crawler, and every record in it (up to Max files) is inspected. Each record key is used as the file name.

## `recordKeys` (type: `array`):

Only inspect these records of the selected key-value store. Leave empty to inspect all records except INPUT, OUTPUT and SUMMARY.

## `datasetId` (type: `string`):

Pick a dataset that has a column of file URLs, for example a crawler output. The URLs are downloaded and inspected.

## `urlField` (type: `string`):

Name of the dataset column that holds the file URL. Only used when a dataset is selected.

## `macroPolicy` (type: `string`):

What to do with files that contain VBA macros without clearly malicious behaviour. 'review' flags them, 'block' treats every macro as a block. Macros that start automatically and call shell or download functions are always blocked.

## `blockExecutables` (type: `boolean`):

Flag files that are executables or scripts (exe, dll, bat, ps1, js, lnk and similar) for review, and block them when their file name claims to be something harmless. Turn off for source-code repositories.

## `maxFileMb` (type: `integer`):

Files larger than this are inspected only up to this size and marked for review. Files are held in memory.

## `maxFiles` (type: `integer`):

Upper limit of files inspected per run, taken from URLs first, then the store, then the dataset.

## Actor input object example

```json
{
  "fileUrls": [],
  "recordKeys": [],
  "urlField": "url",
  "macroPolicy": "review",
  "blockExecutables": true,
  "maxFileMb": 25,
  "maxFiles": 100
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `downloadCsv` (type: `string`):

No description

## `summary` (type: `string`):

No description

## `count` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "fileUrls": [],
    "recordKeys": [],
    "urlField": "url",
    "macroPolicy": "review",
    "blockExecutables": true,
    "maxFileMb": 25,
    "maxFiles": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("that_red_bird/file-safety-inspector").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "fileUrls": [],
    "recordKeys": [],
    "urlField": "url",
    "macroPolicy": "review",
    "blockExecutables": True,
    "maxFileMb": 25,
    "maxFiles": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("that_red_bird/file-safety-inspector").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "fileUrls": [],
  "recordKeys": [],
  "urlField": "url",
  "macroPolicy": "review",
  "blockExecutables": true,
  "maxFileMb": 25,
  "maxFiles": 100
}' |
apify call that_red_bird/file-safety-inspector --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,that_red_bird/file-safety-inspector"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/JeyjLPcKoX0OxT4dx/builds/QrTBalujwOJJ8B4gL/openapi.json
