api7 / api7/aisix

guardrails: scan batch input files per record, not as one lossy text blob

Open
#1,120 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
157
Forks
32
Avg merge
1h 25m
Merged PRs (30d)
145

Description

Single record for guardrail coverage on the files surface. The issues that led here stay closed; this is the one place the decision lives.

Where it stands

Guardrails no longer apply to /v1/files at all — neither the upload nor the download, and neither does the refusal that was briefly built on top of them. Removed in #1121.

What still scans: the JSON request bodies of /v1/batches and /v1/fine_tuning/jobs. Those are structured requests the gateway understands, not user-uploaded blobs, and both routes are now classified Enforced in the coverage census with fixtures driving them.

Why the previous shape was removed rather than kept

jobs::scan_input_blob decoded an entire uploaded file with String::from_utf8_lossy and scanned the result as one synthetic user message, while the original bytes went upstream verbatim; the download path did the same with no refusal at all. That cannot work as a guardrail. It feeds JSON syntax to keyword matchers, produces a single verdict for a file holding thousands of independent requests, cannot rewrite anything, and on a binary upload inspects a soup of replacement characters — all while the published documentation told customers the surface was covered.

A scan that cannot do its job is worse than no scan, because the documentation is what people act on. Removing it and saying so is honest; leaving it and saying it works is not.

The refusal added on top (a 422 on a text-purpose upload that was not valid UTF-8) went with it. It never appeared in a release tag, so no shipped gateway ever had it.

The right shape, if a customer ever needs this

A batch input file is not a document — it is a batch of deferred LLM requests, one per JSONL record. So the scan should decompose it: parse each record, resolve its call type from the record's target route, falling back to the body shape; run the ordinary request guardrail chain for that call type; then act per record — redact a masked record in place, drop a blocked one, and re-serialize the file that is actually forwarded. A record whose call type cannot be resolved is the one case that should fail the whole upload, since the alternative is forwarding a request nobody classified.

That gives what a blob scan cannot: per-record verdicts, real masking, and no false hits on JSON punctuation.

It is a feature rather than a fix, and it raises its own questions — how a rewritten file is billed and audited, and what the response reports about dropped records — so it deserves its own design rather than being answered in passing.

Scope boundaries when it is picked up

  • Only purpose values whose payload is contractually structured text.
  • Binary purposes stay out of guardrail scope permanently. There is no text to scan.
  • File download stays out permanently. A download carries provider-side bytes the caller never authored, so refusing on an undecodable one would reject legitimate downloads.

Operational consequence to be aware of

A /v1/files usage event is now byte-for-byte identical to a screened one: no enforced hit, and no guardrail_bypassed_reason — correct, because the chain is never resolved and nothing was bypassed, but it means an operator who attaches a fail-closed guardrail gets no signal from telemetry that uploads are not covered. Documentation is the only channel that carries this.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading jobs::scan_input_blob and the existing guardrail handling for /v1/batches and /v1/fine_tuning/jobs. Before implementation, settle record type resolution, per-record rewrite and drop behavior, billing, audit, and response reporting; done means an agreed design that respects the binary-purpose and download boundaries.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
api, backend, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.