Kristian Rickert created OPENNLP-1901:
-----------------------------------------

             Summary: Verify and pin teacher model downloads in 
opennlp-embeddings
                 Key: OPENNLP-1901
                 URL: https://issues.apache.org/jira/browse/OPENNLP-1901
             Project: OpenNLP
          Issue Type: Improvement
            Reporter: Kristian Rickert



{panel:title=Status|borderStyle=solid}
The implementation is already written and pushed to the OPENNLP-1877 branch (PR 
#1152); this ticket records the change rather than requesting it. Nothing here 
is a defect in a released version: {{opennlp-embeddings}} does not exist in 
{{main}} and shipped in neither 3.0.0-M4 nor 3.0.0-M5.
{panel}

h2. Why

The distiller added in OPENNLP-1877 can name its teacher by hub id, in which 
case {{HuggingFaceModelCache}} downloads the teacher's {{tokenizer.json}} and 
{{onnx/model.onnx}} and the distiller then executes that ONNX graph. As first 
written it fetched from {{/resolve/main/}}, a mutable ref, and verified nothing.

That is out of step with the project on two counts.

{{DownloadUtil}} has always fetched a {{.sha512}} sidecar, computed SHA-512 
over the download, and thrown {{IOException}} both on mismatch and when the 
checksum cannot be retrieved at all. OpenNLP already refuses to use a model it 
cannot verify.

The security model published in OPENNLP-1900 states the same thing as a 
property of the project: under "Downloaded models" it records the trust level 
as "trusted, integrity-checked in transit," and it names as reportable 
weaknesses "a checksum that is not actually checked," "a redirect that bypasses 
verification," and "a path traversal in how a downloaded artifact is named or 
cached." It also says ONNX models should be treated exactly as native 
libraries, since OpenNLP adds no sandbox of its own.

An unverified download path in {{opennlp-embeddings}} would have made that 
document inaccurate on the day the module shipped.

h2. What changed

* The teacher's ref is resolved to a commit sha once, from the hub's 
{{x-repo-commit}} header, and every file is then requested at 
{{/resolve/<sha>/}}. A force-push midway can no longer mix two revisions into 
one cache directory. A teacher may now name a revision explicitly as 
{{org/model@revision}}.
* Every file is verified against the digest the hub publishes in 
{{x-linked-etag}}, choosing the algorithm by hex length: 40 is the git blob 
SHA-1 over {{"blob <len>\\0"}} and the bytes, 64 is the SHA-256 of the content. 
The digest is computed over the bytes written to disk. A mismatch deletes the 
partial file and throws, in {{DownloadUtil}}'s wording.
* A file whose digest the hub does not publish is refused rather than used, 
which is the point of the change and matches {{DownloadUtil}}'s posture.
* The resolved commit is recorded in {{.opennlp-revision}} in the cache 
directory, and carried into the distilled model's {{config.json}} as 
{{teacher_revision}}, so a table can name the teacher revision it came from.

Two findings from the review of the change are worth recording, because both 
map onto weaknesses OPENNLP-1900 explicitly asks to hear about:

* *A redirect that bypassed verification.* With {{Redirect.NORMAL}} the final 
CDN response carries neither the commit nor the etag header; only the 
redirecting hub response does. Headers are now read by walking 
{{HttpResponse.previousResponse()}} back to the origin hop. A test fails if 
that walk is removed.
* *Cache naming that was not injective.* The directory name replaced {{/}}, 
{{.}} and {{@}} without distinguishing them, so {{acme/model@v1}}, 
{{acme/model.v1}} and {{acme/model_v1}} all mapped to one directory. Because a 
complete cache directory is reused without contacting the hub, one teacher 
could have been served another repository's files. The name now carries a 
digest of the exact reference.

h2. Verification

Tests need no network: a loopback {{com.sun.net.httpserver.HttpServer}} serves 
canned replies and records the requests made. Coverage includes both digest 
forms, a corrupted body, a missing and six malformed etags, optional and 
required 404s, the redirect path, the zero request cached path, revision 
pinning, and the directory collision above. The git blob helper is pinned 
against a digest produced by {{git hash-object}} over the same bytes, and a 
separate test proves the plain SHA-1 of that content is rejected.

Module tests: 310, none skipped.

h2. Not addressed here

Two related decisions on the same PR are deliberately left open, since they are 
design calls rather than integrity gaps:

* {{onnxruntime}} is a compile-scope, non-optional dependency, so consumers who 
only load a distilled table pull a 42 MB native runtime they never call.
* The {{embeddings}} launcher hardcodes {{-Xmx1024m}}, which cannot run the 
{{BAAI/bge-m3}} example the training guide opens with.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to