guardrails: scan batch input files per record, not as one lossy text blob
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
purposevalues 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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