Skip to content
 
 

Repository files navigation

AI Input Trust Gateway (aiitg)

English | 日本語

The zero-trust input security layer for AI agents. Scan untrusted documents before they reach an LLM — detect hidden prompt-injection content, sanitize it, label its trustworthiness, and enforce policy. External content is data, never authority.

CI Python 3.11+ License: MIT Tests arXiv


Why the name

aiitg stands for AI Input Trust Gateway — and every letter is a design decision:

Letter Meaning Why it matters
AI The problem space is AI/LLM/Agent This is security for AI, not generic document tooling
Input We guard the input side Most AI security focuses on outputs/alignment; the input (untrusted documents) is the attacker's real entry point
Trust We make trust explicit and measurable A document isn't trusted by default — it earns a safe / caution / dangerous label with evidence
Gateway We sit between the document and the model Like an email gateway filters mail before it reaches the inbox, aiitg filters content before it reaches the LLM's context

The philosophy in one line: external content is data, never authority. A document should never be able to instruct an AI — it should only ever be read by an AI, after being checked.

That's also why the tool is deliberately named as a gateway, not a scanner: a scanner reports; a gateway enforces. aiitg doesn't just tell you a document is dangerous — it blocks it, sanitizes it, requires human approval, and audits every decision.


Why

2025–2026 research and real-world incidents repeatedly demonstrated a class of attack: instructions hidden inside documents that humans cannot see, but LLMs read and obey.

  • Peer-review manipulation: authors hide prompts in manuscripts to make AI reviewers give top scores (arXiv:2507.06185, 2508.20863, 2509.09912)
  • Resume injection: hidden instructions in resumes force AI screeners to "must hire" (arXiv:2605.28999)
  • Agent hijacking: hidden text in PDFs/web pages makes AI assistants leak secrets or execute dangerous actions

The document a human sees and the document an LLM reads can be completely different — that's the Human-AI Visibility Gap. This project turns that gap into auditable evidence.

Demo

aiitg demo

Scan → trust label → sanitize → policy. A document with a hidden zero-width instruction is detected, labeled dangerous, sanitized, and blocked by policy before it can reach an LLM.

What it does

Untrusted input (docx/xlsx/xls/pdf/html/pptx)
        │
        ▼
┌───────────────────────────────────────────────┐
│  FormatRegistry → parser → ParsedDocument      │
│  (format-agnostic intermediate model)          │
│                                               │
│  Detector chain (7 detectors)                 │
│  → Evidence list (coordinate-level)           │
│                                               │
│  Sanitizer (strip / redact)                   │
│  → Safe text for LLM context                  │
│                                               │
│  Trust label (safe / caution / dangerous)     │
│  Policy engine (allow/quarantine/approval/    │
│                  block)                       │
│  → Decision + audit log + human approval      │
└───────────────────────────────────────────────┘
        │
        ▼
   Safe LLM/Agent consumption

The pipeline (M0 → M1 → M2)

Stage Capability Interface
M0 Hidden content audit — 6 formats × 7 detectors → coordinate-level JSON evidence aiitg scan
M1 Sanitization (strip/redact) + trust labeling (safe/caution/dangerous) aiitg sanitize, aiitg trust
M2 Policy enforcement (allow/quarantine/human_approval/block) + audit + human-approval queue + MCP server for agent frameworks aiitg policy, aiitg audit, aiitg approvals, aiitg-mcp
M3 Zero-touch adoption — a Claude Code PreToolUse hook that enforces policy before untrusted content enters the model context (fail-closed, cached, audited) aiitg hook pretooluse, aiitg hook-config

Detectors

ID Name Detects Severity
DET-001 zero_width Zero-width / invisible Unicode (U+200B, U+2060, U+FEFF…) HIGH
DET-002 hidden_style White text, transparency, display:none, w:vanish HIGH
DET-003 tiny_font Extremely small font (≤2pt) MEDIUM
DET-004 hidden_sheet Hidden sheets/rows/columns (with data) in xlsx/xls MEDIUM
DET-005 ooxml_nodes OOXML hidden nodes (tracked-delete, altChunk, comments) MEDIUM
DET-006 annotations Word comments, PDF annotations, HTML comments LOW
DET-007 document_meta Metadata / VBA macros / JS signals LOW

Formats

docx · xlsx · xls (legacy) · pdf · html · pptx

Install

Use it (recommended for the Claude Code hook)

The hook runs as a separate process on every enforced tool call, so aiitg must be on PATH — a hook command that cannot start is a non-blocking error in Claude Code, and the document would pass through unscanned. Install it as a tool, not into a project venv:

uv tool install git+https://github.com/sscodeai/aiitg
# or
pipx install git+https://github.com/sscodeai/aiitg

Then wire it up and verify:

aiitg hook-config     # paste the snippet into .claude/settings.json
aiitg hook doctor     # confirm it is wired AND startable

Work on it (development)

uv venv .venv && uv pip install -e ".[dev]"
# or
python3.11 -m venv .venv && .venv/bin/pip install -e ".[dev]"

Usage

Scan

aiitg scan report.docx --format json        # JSON evidence report
aiitg scan report.xlsx --min-severity high  # only high+ findings
aiitg scan report.pdf --format rich         # terminal table
aiitg scan ./inbox --recursive --jsonl      # batch directory scan, one compact JSON object per line
aiitg list-detectors                        # list all 7 detectors

Exit codes: 0 = clean · 1 = findings at/above threshold · 2 = usage error · 3 = parse error (CI-friendly).

Sanitize + trust label (M1)

aiitg sanitize report.docx                  # strip hidden content → safe text
aiitg sanitize report.docx --mode redact    # replace with [REDACTED]
aiitg trust report.docx                     # safe / caution / dangerous

Policy + audit + approvals (M2)

aiitg policy report.docx --format rich      # allow / quarantine / human_approval / block
aiitg policy report.docx --audit audit.jsonl
aiitg audit audit.jsonl --format rich       # append-only JSONL audit trail
aiitg approvals queue.jsonl                 # list pending approvals
aiitg approvals queue.jsonl --action approve --id <id>

Agent integration (MCP)

aiitg-mcp                                   # stdio transport (Claude Code / Codex / OpenCode)
aiitg-mcp --transport http                  # streamable HTTP

Agents call policy_file before feeding untrusted content into context; anything non-allow gets blocked/sanitized/human-approved.

Zero-touch adoption — Claude Code hook (M3)

The hook enforces input trust for tools that never call the MCP server or the library: the agent keeps calling Read, and the document is scanned, labeled, and blocked/quarantined before it reaches the model context.

aiitg hook-config            # print the .claude/settings.json snippet (never writes it)
{
  "hooks": {
    "PreToolUse": [
      {"matcher": "Read", "hooks": [{"type": "command", "command": "aiitg hook pretooluse", "timeout": 30}]}
    ]
  }
}

Behaviour: a clean document is read normally; a dangerous one is denied with the rule id, trust label, and the exact evidence location; a quarantine decision rewrites the read to a sanitized .txt substitute and tells the model, in additionalContext, that the bytes are sanitized text (the "assume compromise" cost, surfaced rather than hidden). fail_closed=True by default means documents aiitg cannot parse (.doc, .rtf, ...) are denied instead of trusted, and any hook crash becomes an explicit deny with exit 2 — a crash must never fail open, because a non-blocking exit lets the raw file through. Add --audit audit.jsonl to reuse the shipped append-only audit log, and --queue queue.jsonl to route human_approval decisions into aiitg approvals.

Measured on a docx: a cache miss costs ~450 ms, a cache hit ~90 ms (same as the process-startup floor), because the (path, mtime_ns, size) decision cache short-circuits before the pipeline runs.

Verify the wiring before trusting it — a hook command that cannot start is a non-blocking error in Claude Code, so documents would reach the model unscanned while everything looks configured:

aiitg hook doctor        # executable on PATH, hook actually wired, directories writable

The decision cache and the audit log are local state, not a trust boundary — an attacker with write access to your home directory can forge a cached verdict, exactly as they could edit your settings. The cache is keyed on (path, mtime_ns, size) plus a namespace covering the build, sanitizer mode, detector set and policy rules, so an upgrade or a --mode change can never reuse an older verdict; a damaged entry is treated as a miss, never as a decision.

Library

from aiitg import process_file

result = process_file("report.docx")
if result.is_blocked:
    human_review(result.report)              # escalate to approval queue
elif result.decision.action.value == "quarantine":
    llm_context = result.sanitized.text      # sanitized text is safe
else:
    llm_context = result.sanitized.text      # allow

Design principles

  1. External content is data, never authority. Documents are parsed, sanitized, and policy-checked before ever becoming instructions.
  2. Assume compromise. Detection is never 100%; isolation + least privilege + evidence + human approval + audit are the real defense.
  3. Detector/format decoupling. Detectors consume a format-agnostic ParsedDocument; new format = new parser, new attack = new detector.
  4. Coordinate-level evidence. Every finding carries exact location (paragraph/run/sheet/row/page/char_range) for forensics and sanitization.
  5. Conservative trust caps. Any evidence of deliberate concealment can never yield safe; HIGH/CRITICAL concealment can never yield better than dangerous.

Research grounding

  • arXiv:2507.06185Hidden Prompts in Manuscripts Exploit AI-Assisted Peer Review
  • arXiv:2508.20863Misleading LLMs in Peer-Reviewing via Hidden Prompt-Injection
  • arXiv:2509.09912When Your Reviewer is an LLM: Prompt Injection Risks in Peer Review
  • arXiv:2605.28999Real-World Prompt-Injection Attacks in LLM-based Resume Screening

Testing

make test        # 183 tests
make lint        # ruff
make typecheck   # mypy
make build       # build wheel/sdist
make demo        # generate and scan a temporary malicious sample

All three gates run in GitHub Actions on every push to main and every pull request (.github/workflows/ci.yml, Python 3.11 and 3.12) — the test badge above is only a local count, the CI badge is the live status.

Malicious test fixtures are generated in code (never committed as binaries) — reproducible, auditable, diff-friendly.

Roadmap

  • M0 — Hidden content audit (6 formats × 7 detectors → coordinate-level evidence)
  • M1 — Sanitization + trust labeling (scan → sanitize → label closed loop)
  • M2 — Policy enforcement + audit + human approval + MCP server (agent gateway)
  • M3 — Zero-touch adoption: Claude Code PreToolUse/PostToolUse hook adapter (aiitg hook)
  • More formats (legacy .doc, .ppt, images via OCR) · multi-policy engine · distributed audit · benchmark dataset for hidden-injection detection
  • M3.1 — HTTP proxy / reverse-proxy interception for API clients, per-tool updatedToolOutput adapters, Bash heuristics

Design notes, measured findings and known gaps for M3 live in docs/plan-zero-touch-adoption.md. Contribution rules and the threat model are in CONTRIBUTING.md and SECURITY.md.

License

MIT

About

AI Input Trust Gateway — hidden content auditor for documents fed to LLMs/Agents

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages