Every report records the check-set version that produced it, in
scope.check_set_version. Two reports that carry the same version were graded
by the same rules, so their rows can be compared directly.
What a version covers
The rules are the code that decides what a check detects and how it grades:
the check packages (internal/checks/...), including embedded data such as the
reviewed-template allowlist, the parsers that feed them (internal/gguf,
internal/safetensors, internal/onnx, internal/openvino with its embedded
opset table, internal/hdf5, and internal/keras with the Keras class
registry it embeds), and the tensor fingerprints the Tensor reference row
compares (internal/tensors). Tests, test helpers, and
testdata are not rules.
TestCheckSetVersionTracksRules (internal/engine/checkset_test.go) hashes
those sources into a rule fingerprint. Go files are hashed without their
comments, so editing a comment does not move it. The test fails until this
file has a row for the current version and fingerprint.
When to bump
- Bump
CheckSetVersion(internal/engine/scan.go) when a change can alter a verdict: a new or changed pattern, threshold, allowlist entry, status mapping, or parser behaviour. Re-measure the affected corpora and record them indocs/false-positive-baseline.md. - Keep the version when the change cannot alter a verdict (a refactor, a performance change, an error message). Add a row for the same version with the new fingerprint, and say why it changes nothing.
Reference data from a signed feed (denylist, reviewed templates, canonical
tokenizers) is not part of the check set. The report names the feed it used
in scope.reference_data. An operator's license policy
(SOCAIR_LICENSE_POLICY) is not either, and is named there too; the license
catalogue it is matched against is, because it decides what is identified.
Nor is an organization's intake policy (SOCAIR_INTAKE_POLICY), named in
scope.intake_policy by file name and hash; the rules that check against it,
including how a file's format is identified, are.
Tier 2 measurements are not part of the check set either. Each records its own suite, suite version, and probe-set digest (tier2.md).
Versions
| Version | Rule fingerprint | Date | Changes |
|---|---|---|---|
tier1/0.11 |
5a5d7715b241a0f1002ed4e94d442cc5dc79a748d87d03194b0483f2b8b390c7 |
2026-10-15 | Tokenizer config, on a GGUF: a special-token id of -1 written as a 32-bit integer (0xFFFFFFFF, signed or not) is the "no token" sentinel, not an id outside the vocabulary, so it no longer FAILs as special-token-out-of-range, as tier1/0.7 did for a negative id in a directory's config.json. llama.cpp skips it with a warning, as it skips any id outside the vocabulary, and it names no token. Found on ggml-org/tiny-llamas stories260K.gguf (padding and separator ids), whose Tokenizer row FAILed on v0.1.0-rc.5. Any other id at or past the vocabulary's size still FAILs. The row's FAIL note no longer says llama.cpp "would misread or reject" every inconsistency: it says llama.cpp refuses a type or score table shorter than the vocabulary, skips a special-token id outside it with a warning, and reads a type it does not define as undefined. The change only removes a FAIL; the benchmark's outcomes are unchanged. See false-positive-baseline.md. |
tier1/0.10 |
b7a105c69b51c5acf6c9d23bd9b9b1e209d522a341e759bca4132ddefea38c5c |
2026-10-14 | A check whose input was not supplied is named, not run. Two rows no longer grade NOT_TESTED for want of an input, and instead are listed in the report's out_of_scope.not_run with the reason, as the Tier 2 checks are: Known-bad hash match runs only when a known-bad list is supplied (SOCAIR_DENYLIST, or a verified feed that carries one; a list the operator named that is empty or could not be read still gives a NOT_TESTED row), and Chat template runs only when the artifact carries a template (a GGUF's tokenizer.chat_template; a directory's tokenizer_config.json, chat_template.json, or .jinja file; a template file that could not be read still gives a row). Neither withholds promotion when its input is absent, as the License policy row does not without a policy: a clean model pulled through the airlock with its provenance is authorized without an acceptance. There is no standing public list of malicious model files to default to, and a model with no template (a base, embedding, or reranker model) has none to check; a template a serving stack supplies is outside the artifact. report.Validate refuses a check named both as a row and as not run. The benchmark's outcomes are unchanged: its known-bad case is scanned with a list. |
tier1/0.10 |
4faab69096968b98ea7c2ec0bec208e0c604b8196cb3b7b3f4d1c01acec79843 |
2026-10-14 | No rule change: the union of Keras models (#269, the row below) and OpenVINO IR models (#268, the two rows under it, merged as c3c747a), in one tree. The rule roots are both sides' (internal/openvino, internal/hdf5, and internal/keras). They meet in the format dispatch: a single file is read as a Keras model or other HDF5 file by its bytes first, then as an IR's XML, and the pickle row runs on neither (on an HDF5 file, only when it has a user block); a directory reads each .h5, .hdf5, or .keras whose bytes are one, and each IR .xml with its .bin. The Keras rules use the structure check's shared grade type under the name #268 gave it, and the notes for a format the scanner does not parse name both formats, which moves the fingerprint and no verdict. Neither side's rules change. |
tier1/0.10 |
114be0ac58f99e38f84c1a7b7b2b3a3a27d473ddfda5f3d6e6152e1ca9800123 |
2026-10-14 | Keras models (#269), made on main at e0df1d1, the tree under the OpenVINO IR rows below: two new readers, internal/hdf5 (HDF5 File Format Specification 4.0 at HDF5 2.2.0, stdlib only, bounded, reading as the library does where the two differ) and internal/keras (a .keras archive's members, and configs decoded as checks/pyjson decodes them), with an embedded registry of the class names keras 3.15.1 and tf_keras 2.21.0 register (keras-classes.txt, written by internal/keras/classgen). The Format and structure and File inventory rows read an HDF5 file (.h5, .hdf5, transformers' tf_model.h5) or a .keras archive, recognised by its bytes, on its own or in a model directory, where it was NOT_TESTED (".h5 is not a container this scanner parses"); a KerasHub preset's root JSON that is a Keras serialized object is checked by the config rules. Format and structure: FAIL keras-lambda for marshalled bytecode (a field whose <field>_type is "lambda", under any class; a legacy Lambda whose function is a list; a __lambda__ object; CVE-2024-3660, CVE-2025-9905); FAIL keras-pickle for a TorchModuleWrapper's pickled module; FAIL keras-dangerous-reference for a NON_MODELING_APIS name under a keras module, or a module and name the pickle check FAILs (CVE-2025-1550, CVE-2025-8747, CVE-2025-9906); LEAD keras-name-path for a name with a .. component (CVE-2026-12479); NOT_TESTED, named, for a class or function from outside Keras, tf-keras, TensorFlow, KerasHub, KerasCV, and KerasNLP, a legacy class name Keras does not register, a function named with no module, a legacy Lambda's function by name, a TFSMLayer path, or a lookup layer's vocabulary path; KerasHub, KerasCV, and KerasNLP classes are named, not judged. HDF5: FAIL tensor-layout for storage, or the bytes a shape needs, past the end-of-file address, overlapping storage or metadata, a chunk off its grid, and bytes after the HDF5 data; FAIL tensor-size for contiguous, compact, or unfiltered chunk storage of another size than its type and shape need; FAIL external-data-escape for external storage or an external link (CVE-2026-1669, CVE-2026-9335); LEAD hdf5-soft-link and hdf5-virtual-dataset (CVE-2026-12480); NOT_TESTED for chunks never written, filtered chunks, chunk indexes the reader does not read, a user block, a truncated file, and every structure the reader names as unread. Archive: LEAD keras-archive-entry for a member Keras does not read or a repeated name, LEAD archive-path-escape for a name with a root, drive, or .. (CVE-2026-11816); NOT_TESTED for model.weights.npz or compressed weights. File inventory scans the configs' keys and string values, metadata.json, string attributes (array elements of 64 bytes or more), object comments, an archive's comments, assets, and unread members, and a user block, with the existing payload patterns. The Pickle opcode scan no longer runs on a single Keras or HDF5 file, except one with a user block. New patterns: keras-lambda, keras-pickle, keras-dangerous-reference (critical), archive-path-escape, keras-archive-entry, keras-name-path (high), hdf5-soft-link, hdf5-virtual-dataset (medium). Outside the rules, the Format and structure PASS statement gains its Keras clause. Measured on 30 real files (20 HDF5, 10 archives) from 22 repositories: the reader reads exactly what h5py reads; 25 PASS, 4 NOT_TESTED naming code outside the file, 1 FAIL (a real Lambda's bytecode), no container finding; see false-positive-baseline.md and keras.md. Benchmark: seven cases (six detected, one withheld as a gap) and three controls (detection-benchmark.md). |
tier1/0.10 |
c5c4f693f5fd4b8756bfd5bb4db90d5e9af4d618ad027a8c11fbcb9785edabba |
2026-10-14 | No rule change: the union of OpenVINO IR models (#268, the row below) and the tree under it (uploader checksum lists, #263; configs read as Python reads them, #256; the File inventory listing graded once, #270; merged as e0df1d1), in one tree. They meet in one place: in a directory scan, File inventory grades each OpenVINO IR .xml's text as its own part (inventory.Options.ArtifactOnly, #270) beside the listing, graded once. The OpenVINO IR inventory note is also reworded, which moves the fingerprint and no verdict. Neither side's rules change. |
tier1/0.10 |
a143635dfd21acd4be6c5c2cd41f5d618547a67b59ea83b46d866f9e137831f8 |
2026-10-14 | OpenVINO IR (#268): a new reader, internal/openvino (now a rule root, with OpenVINO 2026.4.1's opset1 to opset17 embedded in opsets.txt, written by internal/openvino/opsetgen from the release's opset<N>_tbl.hpp), and the Format and structure and File inventory rows read an OpenVINO IR, on its own (recognised by its bytes: an XML document whose root element is net, in any case) or as a .xml with that root in a model directory, where the .xml was not read and its .bin reached the Pickle opcode scan by its extension and was NOT_TESTED there ("not a pickle opcode stream"). The reader follows OpenVINO 2026.4.1's IR frontend and xml_deserialize_util.cpp with encoding/xml (no DTD, no external entity), bounded (256 MiB, 256 levels, 2^23 elements), and builds the layers OpenVINO builds: every Parameter and what a Result or Assign depends on, a later layer with an id replacing an earlier one, and a built TensorIterator's, Loop's, or If's body. Format and structure: FAIL tensor-size when a built Const's size is not what OpenVINO allocates for its element type and shape (ov::util::get_memory_size; 1 byte is also allowed for an empty Const), short (OpenVINO refuses it) or long (OpenVINO loads it, the extra bytes read as no value); FAIL duplicate-key for an attribute repeated on an element (pugixml reads the first; XML forbids it) or a layer id given to two layers (OpenVINO builds the last). NOT_TESTED for a Const range past the end of its .bin (OpenVINO refuses it; truncation is as likely), a missing .bin, bytes of the .bin outside every built Const (OpenVINO never reads them; its writer leaves none), partly overlapping ranges, a layer OpenVINO does not build, an operation outside OpenVINO's opsets as its reader looks it up (an extension the deployment registers, such as openvino_tokenizers), an IR version other than 10 or 11, a string or dynamic Const, a Const whose <data> has a value attribute (OpenVINO builds it from that text and never reads the range it names), a value this reader does not read as OpenVINO does (a sign, a space, a shape of 4x), an element OpenVINO reads once repeated, an element where the IR has none, a pre-process block, a DOCTYPE, a layer typed Constant (built from the XML), a missing mandatory attribute, and a single-file scan of the .xml with Consts (the .bin is outside it). File inventory scans every attribute value, element and attribute name, text, and comment of an IR .xml with the existing payload patterns. In a directory, a .bin an IR names as its weights (the .xml's path with .bin) no longer reaches the Pickle opcode scan by its extension unless its bytes hold something the pickle check reads (pickle.Holds, new: a zip or tar archive, a NumPy array, or a stream that walks as a pickle of any protocol from its first byte), so a protocol 0 pickle named pytorch_model.bin beside an IR is still scanned; every other .bin, pytorch_model.bin included, is handled as before. The Pickle opcode scan no longer runs on a single IR .xml. No new pattern. Outside the rules, the Format and structure PASS statement gains its OpenVINO clause. Measured on 46 real IRs from 18 repositories (OpenVINO 2022.3 to 2026.4 writers): 34 move from NOT_TESTED to PASS, the 12 tokenizer and detokenizer IRs stay NOT_TESTED for their extension operations, none FAILs or LEADs, and no IR's .bin holds anything the pickle check reads; see false-positive-baseline.md and openvino.md. Benchmark: three detected cases, two withheld, two controls (detection-benchmark.md). |
tier1/0.10 |
91f03fb5f49afabb655ca1648066dd2566902fe4657ee82d97b88f4a87322cb0 |
2026-10-14 | No rule change: the union of the uploader checksum comparison (#263, the rows below) and configs read as Python reads them (#256, below those), in one tree. They touch different readers: a checksum list is not JSON, and the provenance manifest stays strict. Neither row's rules change. |
tier1/0.10 |
dfe585a64df4daf3883a4f264240d0e4e55d27b743e8ff7fcd4b0cd477ccdfb4 |
2026-10-14 | No rule change: the union of the uploader checksum comparison (#263, the rows below) and the File inventory listing graded once (#270), in one tree. Both change how a directory scan feeds a row (the provenance row now takes the snapshot's files; File inventory grades its listing once), and neither row's rules change. |
tier1/0.10 |
e3d717a09b4a5c1495c8a9e4a33c73fc29b3fa6e8cf37698e83de5b4ff013731 |
2026-10-14 | No rule change: the union of uploader checksum lists (#263, the row below) and the tree under it (ONNX models, #264, and the Tensor reference row, #262, merged as e0a7124). #263's rules are all in internal/checks/provenance, which neither the ONNX reader nor the Tensor reference row reads, and #263 reads neither of theirs; no side's rules change. Measured on the merged tree: the lists of the 1,652 repositories in #263's measurement give the same result in every repository, and the detection benchmark holds every case of all three. |
tier1/0.10 |
a155fb152941ab55a7c086a5decd3b529c09e371ad25f5406b557653a08715d3 |
2026-10-14 | Hash, provenance, lineage (#263): an uploader's own SHA-256 lists are compared with the bytes the scan hashed (provenance/checksums.go, provenance-bundle.md). A list is SHA256SUMS, SHA256SUMS.txt, or checksums.sha256, in any case, or a <file>.sha256 sidecar beside its file: in a directory every one at any depth, read from the snapshot; for a single file the ones beside it, named with the SHA-256 of what was read. It is read as GNU coreutils 9.12's sha256sum --check reads one (text and binary forms, the BSD reversed form, the tagged SHA256 (name) = hash and OpenSSL forms, escaped names, comments, CRLF), with one difference (a line holding a NUL is refused) and one addition (a sidecar holding one bare hash is that file's hash); TestParserAgreesWithSHA256SUM holds it to GNU sha256sum on 3,030 lists. Names resolve from the list's own directory only; a list none of whose names is a file there is named and not compared. Every read is bounded (4 MiB and 50,000 lines a list, 16 MiB a scan). A listed file whose bytes differ makes the row a LEAD (checksum-mismatch, medium), unless it is already a FAIL; a Markdown file that differs is named and not graded. Agreement, a listed file the scan does not hold (a selected pull's left-out files counted from a bound manifest), a rename, a list that names itself, names outside the directory, and malformed lines are noted and change nothing. The row's "looks for" and PASS statement say so. New benchmark case dir-checksum-swapped (LEAD, where the row was NOT_TESTED) and control control-dir-checksums. Measured on the lists of 1,652 Hugging Face repositories against the Hub's own hashes, no weights fetched: 47 are a LEAD, 108 more would be if Markdown were graded, and the rest only noted; see false-positive-baseline.md. |
tier1/0.10 |
beca8e955af1486864b8413982421693c7f41778e933b22c14b36e7c51aa9b68 |
2026-10-14 | No rule change: the union of the two tier1/0.10 rows below, configs read as Python reads them (#256) and the File inventory listing graded once (#270), in one tree. They touch different code (the config readers; the inventory's per-file and listing parts) and neither changes a verdict. |
tier1/0.10 |
ee8346b90c2e0e86ad8eb18b887747bf315cf1d27b5630fdf16671a62a3cada6 |
2026-10-14 | Configs a Python loader reads are decoded as Python's json module decodes them (#256): NaN, Infinity, and -Infinity outside strings read as values. A new package, internal/checks/pyjson, holds the decoder the Remote code row already used (#255), and every reader of a Python-loaded config now uses it: config.json and adapter_config.json (the artifact's name and architecture), tokenizer_config.json and special_tokens_map.json (Tokenizer config), tokenizer_config.json and chat_template.json (Chat template, through modeldir), and a safetensors shard index (Format and structure). A config holding one of them was unreadable: Nemotron-H's architecture came out empty, and a tokenizer_config.json holding one left its chat template unread. tokenizer.json, which the Rust tokenizers library reads and which refuses the constants, and Socair's own files stay strict. Measured on the 125 repositories of #254's corpus: one change, Nemotron-H-8B-Base-8K's architecture now read (NemotronHForCausalLM); no verdict changes. |
tier1/0.10 |
953e2d8874f42a37fbb319247c122f334b73f5b74634a3be39500cc220e65cf8 |
2026-10-14 | No rule change: in a directory scan, File inventory grades each file's metadata on its own (inventory.Options.ArtifactOnly) and the directory listing once, as its own part named "directory listing". It graded the listing with every file, so its note, and any finding in it, appeared once per file: a whole-repo pull of sentence-transformers/all-MiniLM-L6-v2 repeated it eleven times. The worst part still sets the row, so no verdict changes; the benchmark and every test hold. |
tier1/0.10 |
e0a71241826beaec47749e6c103ebcf850656e42877b67c8b43722504738cd44 |
2026-10-14 | No rule change: the union of the two tier1/0.10 rows below, ONNX models (#264) and the Tensor reference row (#262), in one tree. They meet in one place: tensor fingerprints read GGUF and safetensors files only, so a model directory's .onnx files are read by Format and structure and File inventory but their initializers are not fingerprinted, and the Tensor reference row compares the directory's other tensors. Neither row's rules change. |
tier1/0.10 |
39ca568fedc908f0eb3e0ddff60376f1ef1a9272cbb091749dae542aa67bf2d5 |
2026-10-14 | ONNX models (#264): a new reader, internal/onnx, and the Format and structure and File inventory rows read an ONNX model, on its own (recognised by its bytes: a protobuf top level that walks to the end of the file with an ir_version and a graph) or as a .onnx file in a model directory, where it was NOT_TESTED (".onnx is not a container this scanner parses"). The reader parses ModelProto against onnx.proto at onnx v1.23.2, stdlib only, bounded (protobuf's 2^31-1-byte and 100-level defaults, 262,144 tensors), and names a field it does not model (unknown, reserved, in another wire type, an enum value outside its enum, a singular field repeated), which is NOT_TESTED. Format and structure: FAIL external-data-escape for an external data location (or an EPContext path) that is absolute, rooted, a Windows drive, or climbs out of the model's own directory with .. (CVE-2022-25882, CVE-2024-27318); FAIL external-data-memory for onnxruntime's in-memory markers */_ORT_MEM_ADDR_/* and */_ORT_NATIVE_ENDIAN_MEM_ADDR_/*; LEAD external-data-path for a location systems read differently (a .. that stays inside, a backslash, a colon, a control character); FAIL tensor-size when a tensor's raw_data, typed-field count, or external_data length is not exactly what its type and dims need, or it carries no data for its elements; FAIL tensor-data for data in two fields, in a field its type does not use, or inline beside external data; FAIL duplicate-key for a repeated external_data location or two initializers of one graph with one name. NOT_TESTED for external data a single-file scan does not hold, a data file missing from the directory or too short, an unknown data type, a 4-bit or 2-bit type in int32_data, a segment, an external_data key or number loaders read differently, an EPContext node (a precompiled accelerator context), and an operator domain that is neither ONNX's own (ai.onnx, ai.onnx.ml, ai.onnx.training, ai.onnx.preview, ai.onnx.preview.training), onnxruntime's contrib domain com.microsoft (named in the notes and not judged), nor a model-local function. Every tensor is checked: initializers, sparse initializers, attribute tensors, in subgraphs, function bodies, and training graphs. File inventory scans an ONNX model's producer, domain, doc strings, metadata_props, string attributes, and STRING elements of 64 bytes or more with the existing payload patterns; an embedded EPContext context is not scanned and is named (NOT_TESTED). The Pickle opcode scan no longer runs on a single ONNX file. New patterns: tensor-size, tensor-data, external-data-escape, external-data-memory (high), external-data-path (medium). Outside the rules, the Format and structure PASS statement gains its ONNX clause. Measured on 43 real exports (38 distinct) from six repositories (sentence-transformers, Xenova, onnx-community; 11 with external data): every one moves from NOT_TESTED to PASS on both rows, none FAILs or LEADs; see false-positive-baseline.md and onnx.md. Benchmark: four detected cases and two controls (detection-benchmark.md). |
tier1/0.10 |
b9955c9d5f049565586abaa09b8b239210ae097763b1277d927a36b04cf7b8d9 |
2026-10-14 | Tensor reference (#262): a new package, internal/checks/tensorref, on a new tensor fingerprint package, internal/tensors, now a rule root. With SOCAIR_TENSOR_REFERENCE naming a socair.tensors/v1 manifest (socair tensors <path> writes one), a new Tensor reference row compares the scanned GGUF and safetensors tensors with the reference, one to one: by name first, so two same-shape tensors that exchanged places differ; then, for names the other side lacks, by identical bytes or identical values widened exactly to float32 (F32, F16, BF16; NaN payloads kept). PASS only when every tensor matches under its own name. LEAD, within one file format, when a tensor differs or is on one side only (patterns tensor-differs, tensor-not-in-reference, tensor-missing, medium). NOT_TESTED across formats (a converter renames, reorders, splits, quantizes: llama.cpp reorders the Q and K rows of Llama-style models), for a match only under another name, for a quantized type against a float, and for anything unread on either side. No FAIL. Without the input there is no row and nothing about a scan changes. The safetensors reader now keeps each tensor's range (Manifest.Entries, DataStart), and the GGUF reader gains Manifest.Span; neither changes what they report. The detection benchmark gains dir-weights-edited (PoisonGPT's shape: one weight edited) and the control control-dir-tensor-reference. Measured on real models in false-positive-baseline.md. |
tier1/0.9 |
7f15374877254baf9d23531b0d8819c53d67d720af9b26db885ec2608207ec1b |
2026-10-14 | No rule change: the union of the pickle grammar's bcomplex32 and torch.cuda storage classes (#244, the row below) and the tree under it (#245, #252, and #255, merged as 1861dea). #244's rules are all in internal/checks/pickle, which neither the chat-template nor the Remote code rules read, and neither side's rules change. Measured on the merged tree: every Pickle opcode scan result is identical to #244's alone on 35 real torch checkpoints (torch 2.2.2 and 2.14.1 output, and the converter's corpus: 32 PASS, 3 LEAD). |
tier1/0.9 |
cb0eea3eacd5b9a1a31ea72a39caa5c73ce6456a590c0a8fbd4b1b3341c0507a |
2026-10-14 | Pickle opcode scan (#241): the dtype table holds bcomplex32 (c10::complex<c10::BFloat16>, 4 bytes, new in torch 2.14 and in torch.storage._new_dtypes(); its Python name has no alias), so a torch 2.14 checkpoint holding one moves from LEAD to PASS when its storage holds exactly 4 bytes an element. A storage class name under torch.cuda is accepted only where torch.cuda defines one: its twelve legacy typed classes (Byte, Double, Float, Half, Long, Int, Short, Char, Bool, BFloat16, ComplexDouble, ComplexFloat). torch.cuda.UntypedStorage and the torch.cuda quantized storage names, which torch does not define, passed and are now a LEAD; torch.save in 2.x writes no torch.cuda.* storage name, so no file torch writes changes. Measured on torch 2.14.1 output; see false-positive-baseline.md. No benchmark case changed. |
tier1/0.9 |
1861dea2cbce536baae6373cd335bcfa524066c2fde3c4542c256d971a60cd23 |
2026-10-14 | No rule change: the union of Remote code limited to the Python a loader imports (#255, the row below) and the chat-template rows under it (#245 and #252, merged as 7550f3d), in one tree. #255's rules are all in internal/checks/remotecode, which no chat-template rule reads, and neither side's rules change. Measured on the merged tree: every result is identical to #255's alone on its 125 repositories (101 PASS, 24 LEAD), and to #252's on the 73 llama.cpp templates at bd4eeaa, the 70 at 7fe450e, and the 28 Hugging Face multimodal templates. |
tier1/0.9 |
f29e521df53c1a339eb1859f6a68a76cafb068558855109975fef6740cb567ef |
2026-10-14 | Remote code (#254): a LEAD only for the Python a loader would import, where any .py file was one. Named by a config, read from every JSON file: an auto_map at any depth (transformers v4.57.6 and v5.19.0 get_class_from_dynamic_module: module.Class is module.py in the directory the loader was given, repo--module.Class is code in another repository; also diffusers v0.41.0's AutoModel and modular blocks), config.json's custom_pipelines impl (transformers' pipeline()), a sentence-transformers v6.1.0 module class outside sentence_transformers.* (modules.json types, a Router's or legacy Asym's types, a WordEmbeddings tokenizer_class) and the dotted paths it imports for a Dense activation_function and a CrossEncoder activation_fn, and diffusers' model_index.json (a [file, class] _class_name, and a component whose <component>/<library>.py exists, which diffusers loads in place of the library). Named by its path: custom_generate/generate.py (transformers' load_custom_generate; v4.57.6 runs it from a local directory without trust_remote_code), and handler.py, pipeline.py, and code/inference.py at the root (huggingface-inference-toolkit 0.5.5, sagemaker-inference 1.10.1; no trust_remote_code). Every repository file those import is named too, transitively: a relative import as Python resolves it and as transformers' get_relative_import_files does (joined to the first module's directory), an absolute import or an importlib.import_module/__import__ call with a literal name when the directory holds the file, and a path matched without regard to case. A reference to another repository, to a path outside the directory (rooted, climbing out, or holding a backslash or a colon), or, for auto_map, custom_pipelines, and diffusers, to a module the directory does not contain is a LEAD. New patterns, all medium: custom-pipeline, custom-module, custom-component, custom-generate, inference-handler, imported-code; python-file is no longer emitted. A sentence-transformers class the directory holds no file for is named in the notes and not judged (an installed package's, or the load fails). Any other .py, .pyc, .pyw, .ipynb, or .sh file is listed in the notes and not judged. An empty auto_map names nothing and is no longer a LEAD. Configs are decoded as Python's json module decodes them, so NaN and Infinity no longer make a config unreadable. On 125 real repositories (fetched without weights, pinned by commit), 16 with scripts no loader imports move from LEAD to PASS (sentence-transformers' train_script.py, DeepSeek's inference/ and encoding/, wav2vec2 eval.py), one config holding Infinity from NOT_TESTED to PASS, and two whose custom_pipelines names a module they do not ship from PASS to LEAD; all 22 that load their own code stay LEADs, and for the 14 whose auto_map names a module in the directory the files named are exactly transformers' import closure. Benchmark: seven new detected cases, the control control-dir-training-script (a LEAD before), and the pinned miss dir-script-caller-named (a file only the caller's code names, a LEAD before). See false-positive-baseline.md. |
tier1/0.9 |
7550f3d72b79044a743523d448f760f71079fd9cacc8a086626005ed64bd47ab |
2026-10-14 | No rule change: the union of the two tier1/0.9 rows below, chat-template code points kept in Unicode's range (#245) and standard content-part types and tuple triggers (#252), in one tree. They change different parts of the chat-template rules (the lexer's escapes and format's %c; content conditions and the sequences they test), and neither row's rules change. Measured on the merged tree: every result is identical to #252's alone on the 73 llama.cpp templates at bd4eeaa, the 70 at 7fe450e, and the 28 Hugging Face multimodal templates. |
tier1/0.9 |
a1c78a4121d2552266f12b8348703d9c5ee3e5bb4e837a4489921674a0249f59 |
2026-10-14 | Chat template: code points stay in Unicode's range, as Python's do. A string literal's \x, \u, or \U escape past U+10FFFF is a parse error (Jinja decodes literals as unicode-escape, which refuses it: "illegal Unicode character"), so such a template is unreadable, a LEAD, where it parsed with U+FFFD in place of the escape. "%c" % n outside 0 to 0x10FFFF is a render error, as Python's OverflowError, and does not constant-fold, where its low 32 bits named a character: "%c" % (2**32 + 95) folded to _, a character Python never produces. Confirmed against Python 3 and Jinja2 3.1.6's sandbox. Found by CodeQL (go/incorrect-integer-conversion). Measured on 73 llama.cpp templates: every result identical before and after. |
tier1/0.9 |
9971c4f0bb3473fcad411991892e840899c08a69350d5a246a844e295b1abe00 |
2026-10-14 | Chat template (#246): a comparison of a content part's type field with a standard content-part type is structural, not a trigger. The standard types are text, image, image_url, audio, input_audio, video, and file: the parts of an OpenAI Chat Completions user message (openai-python v3.31.0) and the parts transformers v5.19.0's processors read. The forms are part.type == 'image', part['type'] != 'text', and part.type in ['image', 'video'] with every item standard, on a part read straight from message content (a name bound to it, message.content, or content[i]). Text that such a branch, or the else of its chain, adds is no longer a LEAD: it is listed in the row's notes, not judged, since no probe conversation carries content parts, and these literals no longer feed trigger probes. A system turn such a branch opens stays a LEAD, and one in the chain's else is newly a LEAD. Still a trigger: a value that is not standard, a list mixing one in, another field (source_lang_code), a standard type compared on another field (part.text == 'image'), and a type read through a filter or call (part.type|lower). Also: a tuple literal is a sequence like a list, so content in ('a', 'b') and content.startswith(('a', 'b')) are content conditions with trigger probes, where they PASSed. Moves google-translategemma-4b-it (llama.cpp bd4eeaa) from LEAD to PASS; no other verdict changes on the 70 llama.cpp templates at 7fe450e, the 73 at bd4eeaa, or 28 Hugging Face multimodal templates. The evasion corpus gains 10 fixtures, two of them known misses, and the detection benchmark the case gguf-template-tuple-trigger (a miss before). Outside the rules, the row's PASS statement and the published ceiling (v5) name the content-part exception. See false-positive-baseline.md. |
tier1/0.9 |
1d33ea7d8627224a89d8ec9af8870f4b33cfd3b1dedc8179b2b77ab12c5c6db1 |
2026-10-14 | Pickle opcode scan (#233): the grammar accepts torch.storage.UntypedStorage, the name torch.save writes for the untyped storage class (defined in torch/storage.py; persistent_id records type(obj)), with exactly the checks torch.UntypedStorage gets: the persistent id counts bytes, the record must hold exactly that many, and a _rebuild_tensor_v3 view must fit in elements of the dtype the call names. torch 2.2 and later write it for every tensor they rebuild with _rebuild_tensor_v3 (the float8 family from 2.2, uint16, uint32, and uint64 from 2.6; torch.storage._new_dtypes() since 2.4), and every torch 2.x for a saved UntypedStorage, so such a checkpoint moves from LEAD to PASS. No other name is accepted: a class from another module or a near miss (torch.storage.UntypedStorageX, torchx.storage.UntypedStorage) stays an unreviewed global. New: an untyped storage in a legacy (pre-zip) stream, under either name, is a LEAD (pickle-grammar), since torch's legacy loader takes the storage class's dtype, which UntypedStorage does not have, and stops; torch's own legacy output of this kind was a FAIL (its record read as unreferenced), and a legacy stream naming torch.UntypedStorage, which torch does not write, was a PASS. New benchmark control control-torch-zip-untyped. Measured on checkpoints written by torch 2.0.1, 2.2.2, and 2.14.1; see false-positive-baseline.md. |
tier1/0.9 |
ce49c1a45ada22c4d8223fa204193e7ead57b18df07ca196788689c6e702edf4 |
2026-10-14 | No rule change (follows #170): a shard the index names that cannot be read for a reason other than its absence is noted with the cause alone, "config.json/x (unreadable: not a directory)", where the note quoted the operating system's error and with it the path of the scan's snapshot on the host; the shard index's own read errors likewise. The shard is NOT_TESTED as before, and every status is unchanged. Outside the rules, the engine names the snapshot by the artifact's name wherever a check's text still carries its path, and the Tier 2 and keyless helpers' output is held to the paths Socair handed them (provenance-bundle.md, tier2.md). |
tier1/0.9 |
5d049d167df5a3e8a2c85835dc853dc865494a5b6e89e2cfadfc760c2ad46c20 |
2026-10-14 | No rule change, on top of #225's row below: the pickle check hands the airlock's converter (#205) what its grammar read. Each tensor the grammar models now also records its strides, element size, whether its metadata argument is non-empty, and whether it carries Python attributes; the check keeps the validated data.pkl of a torch zip; pickle.ReadStateDict exposes the root dict of a torch zip the check PASSed; and the row name is a constant, pickle.CheckName. None of these is read when the row is graded, so no verdict changes, and #225's rules are untouched. The fixture builder pickle/pickletest is a test helper and outside the fingerprint. |
tier1/0.9 |
ed79289180c0fd51cdded57d6bf77608272f8506ca0d27ecd930eddbaee30a18 |
2026-10-14 | Publisher signatures, download-tool metadata (#225). A directory scan does not read .cache/ or .huggingface/ (download-tool metadata, left out of the attested files), so a signed file under one, as the first directory on its path the scan skips, is now left out of the comparison and named, where it was reported missing and FAILed. hf download --local-dir writes its metadata into the directory (.cache/huggingface/ from huggingface_hub 0.24.0, .huggingface/ in 0.23.x), and model_signing 1.1.1 signs it (it leaves out only .git/, .gitattributes, .github/, and .gitignore at the top), so a directory signed after such a download FAILed with every model file intact. Such a PASS says "every signed file the scan read matches" and names each left-out directory with the number of signed files under it (provenance.Signature.NotCompared); the verdict is the same whether the metadata is on disk, rewritten by a later download, or gone. A changed, missing, or unsigned file anywhere else is still a difference, and so is a signed file under another directory the scan skips (.git/ below the top, __pycache__/, .ipynb_checkpoints/). Moves provenance from FAIL to PASS on such a directory, and meets the Intake policy's signature_required rule there where it broke it; no PASS becomes anything else, and a FAIL names only the real differences. Measured with the real tools: two files fetched with hf download --local-dir (huggingface_hub 2.2.0, and 0.23.4) and signed with model_signing sign key 1.1.1, which verifies them, FAILed and now PASS; with config.json changed, FAIL naming it; see provenance-bundle.md. |
tier1/0.9 |
7add7cc7bbf49e1f40fb198edee2f3eed3cf4350f13544b3bb8727bb90cf89e0 |
2026-10-14 | No rule change: the three tier1/0.9 rule changes in one tree, the Intake policy row (#202), torch archives counted as read in File inventory (#183), and NGC's result.sigstore in a directory scan (#199). The union rows below say how #202 and #183 each meet #199. #202 and #183 share no rule: the Intake policy row identifies a pickle with pickle.Sniff, which #183 does not change. No verdict differs from the one each row describes. |
tier1/0.9 |
0b3fc1aa80dca9eb6a0d122b349d41db8444d6dc85274e2a01c97617a4da94ce |
2026-10-14 | No rule change: the union of the two tier1/0.9 rows below, the Intake policy row (#202) and NGC's result.sigstore in a directory scan (#199), in one tree. They meet in one place: the Intake policy row's signature_required rule reads the same signature verification as the provenance row, so a directory's verified result.sigstore meets it, as a verified model.sig does, and one that does not hold breaks it. Neither row's rules change, and no other verdict differs from the one each row describes. |
tier1/0.9 |
c85ad0ae8bcc1a2bea2b299b89e8b2e322f17a9cb2600b1900301c73b7c4a2e3 |
2026-10-12 | Intake policy (#202): a new package, internal/checks/intake, and with SOCAIR_INTAKE_POLICY a new Intake policy row that can only withhold (intake-policy.md). FAIL when a model file is in a format the policy does not allow (format-not-allowed; the format is identified from the bytes: the GGUF magic, a safetensors header, the .npy magic or an archive of .npy arrays, or a pickle as pickle.Sniff recognises one, and a model directory's files count by their bytes as well as their weight extension), when the publisher read from a provenance manifest bound to the hash (its repo URL's organization, and its publisher field) is denied or not allowed (publisher-denied, publisher-not-allowed), or when a listed publisher's model carries no OpenSSF Model Signing signature or one that does not hold (publisher-signature-required). NOT_TESTED when a rule cannot be evaluated: no bound manifest names the publisher, a file's format is not identified while the policy allows pickle or numpy, or a signature was not verified. Every finding is medium. Without a policy no row is added and no verdict changes. |
tier1/0.9 |
b3cf894a7a69c02c34cd3c63f52892c39b12cdf7dbe0120afc4b728767db60a6 |
2026-10-14 | No rule change: the union of the two tier1/0.9 rows below, torch archives counted as read in File inventory (#183) and NGC's result.sigstore in a directory scan (#199), in one tree. Their rule files are disjoint apart from File inventory, where #170's edits change note text only, so no verdict differs from the one each row describes. |
tier1/0.9 |
778890314607ae7db9d444e9e0685003c0a9c24d1ab73fa668b960d0fb7675ab |
2026-10-12 | File inventory (#183): in a model directory scan, a torch.save zip the Pickle opcode scan PASSed is counted as read, not as an unscanned archive, when every entry in it is accounted for: data.pkl (which the grammar validated), a storage record of exactly its storage's size, or a metadata record torch.save writes (version, .data/version, byteorder, .format_version, .storage_alignment, .data/serialization_id, read from torch/serialization.py and caffe2/serialize/inline_container.cc) holding what torch writes there (little or big, or a decimal number). Anything else keeps it unscanned and is named: an entry outside that layout, a metadata record holding other content, a directory entry, a second record of one name, or an entry or archive comment (torch.save writes none). The pickle check hands the inventory what it read (pickle.InspectArchive, checks.ArchiveRead); the inventory does not re-read the archive. A zip the grammar did not PASS, a TorchScript archive, a gzip, and every archive in a single-file scan's SOCAIR_REPO_MIRROR (not the bytes the pickle check read) stay unscanned as before. Moves File inventory from NOT_TESTED to PASS on a directory whose only gap there was such a checkpoint; no PASS becomes anything else. The Pickle opcode scan row is unchanged. Measured on two real repositories; see false-positive-baseline.md. No benchmark case changed. |
tier1/0.9 |
16b478fe78cee77c0c0b259e844f14963c2a343209dfd16a20a1affee34212a1 |
2026-10-12 | Publisher signatures (#199). A directory scan reads result.sigstore at its root, the name NVIDIA's NGC CLI gives a model's OMS signature, as well as model.sig, so a directory carrying one is now judged: PASS when it is from a trusted root and every file matches, FAIL when a file differs. With both files at the root and SOCAIR_OMS_SIGNATURE naming neither, the signature is named and not judged; the root signature file not verified is left out of the comparison and named as not covered. A certificate chain is checked at the scan's time, as OMS v1.0 section 8.2 requires, not at the leaf's own notBefore: a chain holding an expired or not-yet-valid certificate is now unverified (the row falls to the manifest rules) where it verified. OMS's default exclusions (.git, .gitattributes, .github, .gitignore at the directory's top) leave a file the manifest does not list out of the comparison, named as not covered, where it was a "not signed" difference and a FAIL. A single file checked against a signature over several files that lists it is unverified, where it FAILed. The provenance row names each uncovered file with the reason. Measured on one real NGC model, nvidia/tao/dashcamnet:pruned_onnx_v1.0.5 as the NGC SDK downloads it: PASS with NVIDIA's root trusted, NOT_TESTED with the signature named without it (it was NOT_TESTED with no signature found). |
tier1/0.8 |
417c17f574267603046032e3b49c5c27d01e6ea781eee67f1f2d1735b4238572 |
2026-10-12 | No rule change (#170): reports name local inputs by file name and SHA-256, not by their path on the scanning host. denylist.Parse and license.ParsePolicy read the bytes the engine read and hashed, with the same rules as Load and LoadPolicy; the license policy's Path is now Source, the name a report shows; tokenizer.MaxReferenceBytes exports the existing bound for the engine's reader of reference tables. The provenance row names the manifest it read, and a manifest, mirror, or denylist that cannot be read is named by base name with the cause. Every status is unchanged. |
tier1/0.8 |
62abf249a4a0e92b3de66d023658a6f3424f77b9a8946ed1b35d607afbd8fbc3 |
2026-10-09 | No rule change: the chat-template row is named "Chat template" in every report, not "Chat template (hero)" (#181). "Hero" was the project's own name for its signature check and told a report's reader nothing. What the row detects and how it grades are unchanged. An acceptance names the exact NOT_TESTED rows it covers, so one signed against the old name does not match a report scanned after this change; acceptable before 0.8's first release. |
tier1/0.8 |
759b4ccb0fa8f414d67dfb589cbe9ed13b57b941f09dc2852932369ab9741404 |
2026-10-09 | Pickle opcode scan, before 0.8's first release (#186): a model directory sends a file to the pickle check by its bytes as well as its extension. pickle.Sniff recognises a protocol 2 to 5 stream that walks (it completes a pickle or imports a global within its first MiB) and a zip holding a pickle, so a pickle named notes.txt or a torch zip named weights.dat is scanned, where it was never opened. Not recognised: protocol 0 and 1 pickles, which begin with ordinary text (found by extension or not at all), safetensors and GGUF (their own readers), and TorchScript archives (code/ and constants.pkl beside the pickles), which torch.jit.load runs as TorchScript and which stay named as unscanned archives in File inventory. A directory with no pickle says so in File inventory's notes. New cases dir-pickle-renamed and dir-torch-zip-renamed (detected), dir-pickle-protocol0-renamed (a pinned miss), and the control-dir-torchscript control. Measured on 583 real files and seven real repositories: no verdict changed; see false-positive-baseline.md. |
tier1/0.8 |
368c0ed7331175aec36277a672617e5fb28865fa4c1aae97c96326a15e94192e |
2026-10-09 | Quant match: with no tensor table to read (a header-only or malformed GGUF), a declared quantization that agrees with the metadata file type is NOT_TESTED instead of PASS. Both are labels, and the row's PASS means a tensor of the declared base type was seen. Labels that disagree still FAIL. Found by the socair.ai example reports, whose defanged GGUF reproductions PASSed on labels alone. |
tier1/0.8 |
e361e623f1f9da248b59a99dd21d4851263f7c9ff85c05ae3cf53c3689737448 |
2026-10-09 | No rule change: an unused helper removed from the pickle grammar (staticcheck U1000). |
tier1/0.8 |
d547d40e25dcbe65166f38bc9703f79f39fdb22c4856bdeaf622d34a9c6f8078 |
2026-10-09 | No rule change: the union of the tier1/0.8 rows below, license identification and the License policy row (#160), chat-template rendering, the reviewed-template comparison, and the template-name path rule (#66), and the pickle grammar socair-wo/1 with the NumPy reader (#161), in one tree. Their rule files are disjoint, so no verdict differs from the one each row describes. |
tier1/0.8 |
f5c6aa97daaa400c8472fe6444778f598eba8406d465e8a45e91d07f5a6bea55 |
2026-10-09 | Pickle opcode scan: a typed grammar, socair-wo/1, replaces the import allowlist (#161). A PASS now means every pickle is protocol 2 or 3 in the encoding CPython writes, builds only tensors and plain containers through calls whose arguments match a signature, and every storage it references matches a record of exactly numel x itemsize bytes (torch zip and legacy torch stream). New FAILs: a code-like string where a constructor forbids one (an OrderedDict handed a command), a nested loader (torch.storage._load_from_bytes, torch.load, numpy.load, ...), a storage or tensor view larger than the bytes behind it, and Python 2 spellings CPython renames to a dangerous name (cPickle.loads, __builtin__.intern). New LEADs: a call or BUILD whose arguments match no signature, non-empty backward hooks, a non-canonical encoding in a protocol 2 or 3 stream (memo slot written twice, text INT, OBJ, INST, EXT, NEWOBJ_EX, NUL in a GLOBAL), bytes no loader reads (a record nothing references, bytes after STOP or after the last storage), and a reviewed class with state its review does not cover. Newly NOT_TESTED where they PASSed: pickles in protocols 0, 1, 4, and 5, calls of reviewed-harmless callables the grammar does not model (builtins.int, collections.defaultdict, sparse and quantized tensor rebuilds), TorchScript archives, and legacy tar checkpoints. NumPy .npy and .npz are read: a PASS needs no object dtype and data of exactly shape x itemsize bytes; object data is a pickle under the same grammar. |
tier1/0.8 |
a06dc059bea415caccb4ed4b8dfbc38499696a94c53cc73482e8549e918fce37 |
2026-10-09 | No rule change: the union of the tier1/0.8 rows below, license identification and the License policy row (#160) with chat-template rendering, the reviewed-template comparison, and the template-name path rule (#66), in one tree. Their rule files are disjoint, so no verdict differs from the one each row describes. |
tier1/0.8 |
cd453a5f184496ae0f0bfb8b16052cd56e2f5601280ebbb19cda3af8567d9077 |
2026-10-09 | No rule change: the chat-template row's "looks for" text names the rendered inventory, and its PASS statement and the published ceiling say that ordinary-guidance instructions are listed, not judged. The status is unchanged. |
tier1/0.8 |
385b3d779e02ccc580bdfb332f7a635f54b851f23bfec361531dc359dee8c0a0 |
2026-10-09 | Chat template, before 0.8's first release: a named template in a Hugging Face tokenizer_config.json or chat_template.json whose name holds a path separator FAILs (template-name-path). transformers' save_pretrained writes each named template to a file named after it, so such a name writes outside the save directory (CVE-2026-9856). No real template name has a separator. |
tier1/0.8 |
8205714836bcbfc474bd934d251dbf392cb73041414ef3b8bddc17168fcf8aaa |
2026-10-09 | Chat template (#66): a template with no code reach is rendered by Socair's own Jinja evaluator on fixed probe conversations (probes/v1), plus a trigger probe per literal a content condition tests. The text it adds to the prompt is listed in the notes, not judged. A content condition that makes the template's own text open a system turn is a LEAD (content-conditional-injection), and so is a template that renders like a reviewed template's text on every standard probe but differs under a condition the reviewed one does not test (reviewed-template-mismatch). A template the evaluator cannot render keeps its static verdict, with the reason. The parser keeps whitespace control and a block set's filter, which the analysis now reads (code in it FAILs), and a unary minus binds before a filter, as in Jinja. The 70 real templates in the baseline are unchanged: 70 PASS. |
tier1/0.8 |
df6f6539efdf4268e4e014e636d234255ef339fe279436b0d5b7bb696fc6487b |
2026-10-09 | License (#160): a new package, internal/checks/license, identifies the license an artifact states (model card front matter, top-level LICENSE files, GGUF general.license keys, and declared base models) against an embedded catalogue, for the identity section. With SOCAIR_LICENSE_POLICY, a new License policy row grades it: FAIL when the statements agree on a license the policy does not allow, LEAD when they disagree and one is not allowed, NOT_TESTED when none is identified or one statement is unrecognized. Without a policy no row is added and no verdict changes. The GGUF parser now reads general.license, general.license.name, general.license.link, and general.base_model.N.{name,organization,repo_url}. Measured in docs/false-positive-baseline.md. |
tier1/0.7 |
db14f772c64f1d1a2b71038ede6594941164c28619c08b25941505f0a8e02044 |
2026-10-09 | Tokenizer config, before 0.7's first release: a negative special-token id in config.json or generation_config.json is transformers' "unset" sentinel (older LlamaConfig defaulted pad_token_id to -1), not an id outside the tokenizer, so it no longer FAILs as special-token-out-of-range. Found on hf-internal-testing/tiny-random-LlamaForCausalLM. |
tier1/0.7 |
e155d2d1d4c85c625eaa89a8bf30b58a05432d7a296179bdc458052eb7b4e49f |
2026-10-09 | No rule change: a repo-binary finding reads "is a native executable (ELF)" instead of "is a ELF executable". The status is unchanged. |
tier1/0.7 |
91fcd2488025c742f519526e20496711c9abe6fcd8abcc7b020378a1ed19b76e |
2026-10-09 | No rule change: the chat-template row's LEAD note says "needs review" instead of "escalate", because Socair has no escalation path. The status is unchanged. |
tier1/0.7 |
34575e14264dfe967a4ed0adb607be6464f6e8975141c5ecf3e055260336f5ce |
2026-10-09 | File inventory: a repo mirror that is missing, not a directory, unreadable, or empty is NOT_TESTED with the reason (it PASSed as "repo: 0 files"); an entry the walk cannot read is named and leaves the row NOT_TESTED, while a FAIL elsewhere still stands; and every file is read for an executable or archive signature whatever its name, so an ELF named setup.py FAILs (.py, .sh, .js, and .rb files were counted without being read). Known-bad hash match: a local denylist line that does not start with a SHA-256 refuses the list, as the feed loader refuses a feed, so a list of garbage is NOT_TESTED naming the line instead of PASSing "among 1 known-bad hashes"; and a configured list that cannot be read leaves the row NOT_TESTED even when a feed's list was matched, unless that match FAILs. |
tier1/0.6 |
da2aa793e9863b5b430a85ec3db8313a6f4e546ed4071af1e409ae10d3fa1c41 |
2026-10-09 | No rule change: the provenance row's notes say when a pulled model directory is a selection of the repo at its commit, with the patterns and the files left out (airlock pull --include/--exclude). The status is unchanged. |
tier1/0.6 |
dc0aeeb796c93da346493228ae2600a654f114d9a8d5afbc660871f668203689 |
2026-10-04 | Tokenizer: with a canonical reference table (feed tokenizers/<name>.json or SOCAIR_TOKENIZER_REFERENCE), every token is compared with the best-matching family table; a changed ordinary token or a shortened vocabulary is a LEAD (#135). |
tier1/0.5 |
074eb82595b7eea04d367c17ba31def45e653e8fde3da1ff6180d1f64a4d3fa2 |
2026-10-04 | Jinja parser: {% filter name %} takes its first filter without a pipe, as Jinja writes it, so real filter blocks parse and are analysed instead of being reported as unreadable (#137). |
tier1/0.4 |
69baef5e476c3b0c39f3c60e85af83dffaec5096d323e9df25fb705b52fe6b78 |
2026-10-04 | File inventory: string-array elements of 64+ bytes in GGUF metadata are scanned for payloads (they were skipped), and an inventory cut off at its cap is NOT_TESTED instead of PASS (#135). |
tier1/0.3 |
e63d4be3a7e1ce5cb1631a98c46cf582e69b84a09c98d60e21cc8a6b646666a0 |
2026-10-04 | No rule change: the chat-template row's "looks for" text now says what the check inspects (#131). |
tier1/0.3 |
3cae86387201e2303e8dfe9d04383d6f43a15ee4824fdcc363232aad75a5247c |
2026-10-04 | Chat template: process execution is judged from the parsed template (a module, builtin, or process function referenced in an expression), not from a regex over the raw text, so code words in prose no longer FAIL. Dunder reach was already tree-based; its raw-text regex is removed too (#130). |
tier1/0.2 |
23be10362de6e20be2eaf179e3d3910be6491ce71ebd37eec654ccef1f43b22c |
2026-10-04 | First fingerprinted version. Covers every detector retune recorded in docs/false-positive-baseline.md up to this date. |
tier1/0.1 |
(not fingerprinted) | before 2026-10-04 | The original Tier 1 set. Reports carrying it may come from different rules, because it was not bumped across retunes. |