Spaces:
Running on Zero
Running on Zero
add App_Creation build-log notebook; fix README heading
Browse files- App_Creation.ipynb +484 -0
- README.md +1 -1
App_Creation.ipynb
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# App Creation β PDF Injection Detector (MiMo-7B)\n",
|
| 8 |
+
"\n",
|
| 9 |
+
"The build log for the Hugging Face Space at\n",
|
| 10 |
+
"[`BentoUniAcc/Mimo_Injection_detector`](https://huggingface.co/spaces/BentoUniAcc/Mimo_Injection_detector):\n",
|
| 11 |
+
"what it does, which decisions were forced rather than chosen, what broke, and the checks that say\n",
|
| 12 |
+
"it is still faithful to the notebooks it quotes.\n",
|
| 13 |
+
"\n",
|
| 14 |
+
"The Space is four Python files and this notebook is not one of them β it does not define the app,\n",
|
| 15 |
+
"it records how the app came to be what it is. Every cell below runs against the *deployed* files,\n",
|
| 16 |
+
"so if one fails, the Space is wrong and not the notebook.\n"
|
| 17 |
+
]
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"cell_type": "markdown",
|
| 21 |
+
"metadata": {},
|
| 22 |
+
"source": [
|
| 23 |
+
"## 1 β What it is\n",
|
| 24 |
+
"\n",
|
| 25 |
+
"Upload a PDF. It is rendered to text with the extractor that built the project corpus, the regions\n",
|
| 26 |
+
"carrying structural markers are ranked, the whole document is cut into **batches that fit one run\n",
|
| 27 |
+
"of the model**, and **MiMo-7B-RL** reads the batch you choose β reporting whether a payload is\n",
|
| 28 |
+
"hidden there and quoting the substring that convinced it.\n",
|
| 29 |
+
"\n",
|
| 30 |
+
"| File | Role |\n",
|
| 31 |
+
"|---|---|\n",
|
| 32 |
+
"| `app.py` | Gradio UI, batching, report aggregation. No detection logic. |\n",
|
| 33 |
+
"| `corpus_text.py` | PDF bytes to skeleton to candidate regions. |\n",
|
| 34 |
+
"| `mimo.py` | Prompt, prefill, parser, and both model runtimes. |\n",
|
| 35 |
+
"| `neighbours.py` | Part A embedding index and nearest-neighbour lookup. |\n",
|
| 36 |
+
"| `test_fidelity.py` | Proves the extraction reproduces the published corpus exactly. |\n"
|
| 37 |
+
]
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"cell_type": "markdown",
|
| 41 |
+
"metadata": {},
|
| 42 |
+
"source": [
|
| 43 |
+
"## 2 β The provenance rule\n",
|
| 44 |
+
"\n",
|
| 45 |
+
"The published numbers only describe this app if the app reads a PDF and asks the question exactly\n",
|
| 46 |
+
"as the notebooks did. So the code was lifted from the **dataset repos themselves**, not rewritten\n",
|
| 47 |
+
"and not copied from the earlier archived app:\n",
|
| 48 |
+
"\n",
|
| 49 |
+
"| Borrowed | From |\n",
|
| 50 |
+
"|---|---|\n",
|
| 51 |
+
"| `build_skeleton`, `mask_leaks`, `detect_markers`, `payload_window`, `ANY_MARKER_RE`, `INJECTION_MARKERS`, `LEAK_STRINGS` | EDA notebook, cells 88 / 89 / 107 |\n",
|
| 52 |
+
"| `SYSTEM`, `PREFILL`, `build_messages`, `scan_objects`, `parse_response`, `MAX_NEW`, `BATCH` | Part B notebook, cell 48 |\n",
|
| 53 |
+
"| embedder repo, prefix, dims, normalisation, input column | `Part_A_Outputs/part_a_results.json` |\n",
|
| 54 |
+
"\n",
|
| 55 |
+
"One decision worth stating: **the parser is the narrow original**, not the widened version that\n",
|
| 56 |
+
"appears in the archived app. A wider salvage recovers more verdicts and would also mean the F1\n",
|
| 57 |
+
"quoted in the interface describes a parser that is not the one running.\n"
|
| 58 |
+
]
|
| 59 |
+
},
|
| 60 |
+
{
|
| 61 |
+
"cell_type": "markdown",
|
| 62 |
+
"metadata": {},
|
| 63 |
+
"source": [
|
| 64 |
+
"### 2.1 β The prompt and the prefill, as deployed\n"
|
| 65 |
+
]
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"cell_type": "code",
|
| 69 |
+
"execution_count": null,
|
| 70 |
+
"metadata": {},
|
| 71 |
+
"outputs": [],
|
| 72 |
+
"source": [
|
| 73 |
+
"import mimo\n",
|
| 74 |
+
"\n",
|
| 75 |
+
"print(f'system message : {len(mimo.SYSTEM)} chars')\n",
|
| 76 |
+
"print(f'families : {len(mimo.FAMILIES)} (closed set, fixed order)')\n",
|
| 77 |
+
"print(f'max new tokens : {mimo.MAX_NEW}')\n",
|
| 78 |
+
"print(f'batch : {mimo.BATCH}')\n",
|
| 79 |
+
"print()\n",
|
| 80 |
+
"print('prefill:', repr(mimo.PREFILL))\n"
|
| 81 |
+
]
|
| 82 |
+
},
|
| 83 |
+
{
|
| 84 |
+
"cell_type": "markdown",
|
| 85 |
+
"metadata": {},
|
| 86 |
+
"source": [
|
| 87 |
+
"MiMo is the only model in Part B that carries a prefill. It is reasoning-trained and opens every\n",
|
| 88 |
+
"answer with `<think>`; at a 200-token budget it never closed the block, so not one of its 1,100\n",
|
| 89 |
+
"answers reached the JSON. An empty, already-closed think-block says the deliberation is finished\n",
|
| 90 |
+
"before it begins, and the opening brace puts the model inside the answer.\n"
|
| 91 |
+
]
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
"cell_type": "markdown",
|
| 95 |
+
"metadata": {},
|
| 96 |
+
"source": [
|
| 97 |
+
"### 2.2 β The parser survives the bug that mattered\n",
|
| 98 |
+
"\n",
|
| 99 |
+
"Part B's first parser used a regex to find the JSON object. Every injected file in this corpus\n",
|
| 100 |
+
"carries an EICAR-style marker containing a closing brace, so the moment a model quoted its\n",
|
| 101 |
+
"evidence the match truncated and the verdict was thrown away β **the bug fired exactly when the\n",
|
| 102 |
+
"model was right**. The replacement counts braces and tracks string literals.\n"
|
| 103 |
+
]
|
| 104 |
+
},
|
| 105 |
+
{
|
| 106 |
+
"cell_type": "code",
|
| 107 |
+
"execution_count": null,
|
| 108 |
+
"metadata": {},
|
| 109 |
+
"outputs": [],
|
| 110 |
+
"source": [
|
| 111 |
+
"import json\n",
|
| 112 |
+
"\n",
|
| 113 |
+
"evidence = r'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE'\n",
|
| 114 |
+
"body = json.dumps({'injected': True, 'injection_type': 'javascript_injection',\n",
|
| 115 |
+
" 'evidence': evidence, 'reasoning': 'marker present'})\n",
|
| 116 |
+
"\n",
|
| 117 |
+
"# what MiMo actually emits: the prefill, then the remainder of that object\n",
|
| 118 |
+
"raw = mimo.PREFILL + body[body.index(':') + 1:]\n",
|
| 119 |
+
"r = mimo.parse_response(raw)\n",
|
| 120 |
+
"\n",
|
| 121 |
+
"print('route :', r['parsed_by'])\n",
|
| 122 |
+
"print('verdict / family :', r['pred_injected'], r['pred_family'])\n",
|
| 123 |
+
"print('brace inside evidence ok :', '}' in r['evidence'])\n",
|
| 124 |
+
"\n",
|
| 125 |
+
"# reasoning-trained models restate the schema while thinking: the LAST object must win\n",
|
| 126 |
+
"two = '{\"injected\": false, \"injection_type\": \"none\"} ... then really ' + raw\n",
|
| 127 |
+
"print('last-object-wins :', mimo.parse_response(two)['pred_injected'] == 1)\n"
|
| 128 |
+
]
|
| 129 |
+
},
|
| 130 |
+
{
|
| 131 |
+
"cell_type": "markdown",
|
| 132 |
+
"metadata": {},
|
| 133 |
+
"source": [
|
| 134 |
+
"## 3 β Fidelity: does this read a PDF the way the corpus was read?\n",
|
| 135 |
+
"\n",
|
| 136 |
+
"This is the check the whole design rests on. It pulls real PDFs out of the generation repo, runs\n",
|
| 137 |
+
"them through the deployed `corpus_text.py`, and compares against the published parquet **character\n",
|
| 138 |
+
"by character** β not 'close enough', identical. Injected and clean files both, because masking and\n",
|
| 139 |
+
"binary-stream handling differ between them and a test that saw only one would pass on a broken\n",
|
| 140 |
+
"extractor.\n"
|
| 141 |
+
]
|
| 142 |
+
},
|
| 143 |
+
{
|
| 144 |
+
"cell_type": "code",
|
| 145 |
+
"execution_count": null,
|
| 146 |
+
"metadata": {},
|
| 147 |
+
"outputs": [],
|
| 148 |
+
"source": [
|
| 149 |
+
"!python test_fidelity.py 8\n"
|
| 150 |
+
]
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"cell_type": "markdown",
|
| 154 |
+
"metadata": {},
|
| 155 |
+
"source": [
|
| 156 |
+
"## 4 β The embedding lookup, and the silent failure it avoids\n",
|
| 157 |
+
"\n",
|
| 158 |
+
"A query embedded with the wrong model, or without Part A's `search_document: ` prefix, lands in a\n",
|
| 159 |
+
"different vector space and returns meaningless neighbours β silently, with no error anywhere. So\n",
|
| 160 |
+
"`check_provenance()` asserts the embedder repo, prefix, dimension, normalisation and input column\n",
|
| 161 |
+
"against Part A's own results file before any lookup runs.\n"
|
| 162 |
+
]
|
| 163 |
+
},
|
| 164 |
+
{
|
| 165 |
+
"cell_type": "code",
|
| 166 |
+
"execution_count": null,
|
| 167 |
+
"metadata": {},
|
| 168 |
+
"outputs": [],
|
| 169 |
+
"source": [
|
| 170 |
+
"import neighbours\n",
|
| 171 |
+
"\n",
|
| 172 |
+
"print('Part A winner, asserted against part_a_results.json:')\n",
|
| 173 |
+
"for k, v in neighbours.check_provenance().items():\n",
|
| 174 |
+
" print(f' {k:15s}: {v}')\n",
|
| 175 |
+
"\n",
|
| 176 |
+
"idx = neighbours.load_index()\n",
|
| 177 |
+
"print()\n",
|
| 178 |
+
"print('index:', idx['matrix'].shape, idx['matrix'].dtype)\n"
|
| 179 |
+
]
|
| 180 |
+
},
|
| 181 |
+
{
|
| 182 |
+
"cell_type": "markdown",
|
| 183 |
+
"metadata": {},
|
| 184 |
+
"source": [
|
| 185 |
+
"### 4.1 β Proof the query lands in the index's own space\n",
|
| 186 |
+
"\n",
|
| 187 |
+
"Embed a corpus row's `payload_window` and dot it against that same row's stored vector. Anything\n",
|
| 188 |
+
"below 1.0 means the app and the index disagree about what an embedding is.\n"
|
| 189 |
+
]
|
| 190 |
+
},
|
| 191 |
+
{
|
| 192 |
+
"cell_type": "code",
|
| 193 |
+
"execution_count": null,
|
| 194 |
+
"metadata": {},
|
| 195 |
+
"outputs": [],
|
| 196 |
+
"source": [
|
| 197 |
+
"import pandas as pd\n",
|
| 198 |
+
"\n",
|
| 199 |
+
"CORPUS = ('https://huggingface.co/datasets/Cyber-security-final-project/'\n",
|
| 200 |
+
" 'HARMLESS_Synthetic_Injected_PDFs_EDA/resolve/main/Datasets/'\n",
|
| 201 |
+
" 'synthetic_corpus_part2_clustered.parquet')\n",
|
| 202 |
+
"\n",
|
| 203 |
+
"corpus = pd.read_parquet(CORPUS)\n",
|
| 204 |
+
"pos = {str(f): i for i, f in enumerate(idx['ids'])}\n",
|
| 205 |
+
"\n",
|
| 206 |
+
"sample = corpus[['file_id', 'payload_window', 'injection_type']].sample(5, random_state=0)\n",
|
| 207 |
+
"for fid, window, family in sample.itertuples(index=False):\n",
|
| 208 |
+
" self_sim = float(neighbours.embed(window) @ idx['matrix'][pos[fid]])\n",
|
| 209 |
+
" print(f'{family[:24]:24s} self-cosine = {self_sim:.4f}')\n"
|
| 210 |
+
]
|
| 211 |
+
},
|
| 212 |
+
{
|
| 213 |
+
"cell_type": "markdown",
|
| 214 |
+
"metadata": {},
|
| 215 |
+
"source": [
|
| 216 |
+
"Measured: **1.0000 on every file**. The query vectors are literally the index vectors.\n",
|
| 217 |
+
"\n",
|
| 218 |
+
"What the lookup is worth is a separate question, and a smaller number: Part A's winning embedder\n",
|
| 219 |
+
"reaches **precision@5 of 35.6%** against a 6.8% random baseline. Fewer than 2 of the 5 files\n",
|
| 220 |
+
"returned are the same kind of attack. Far better than chance, and not good β which is why the\n",
|
| 221 |
+
"interface says *nearest files in the corpus* and never *the same attack*.\n"
|
| 222 |
+
]
|
| 223 |
+
},
|
| 224 |
+
{
|
| 225 |
+
"cell_type": "markdown",
|
| 226 |
+
"metadata": {},
|
| 227 |
+
"source": [
|
| 228 |
+
"## 5 β Fitting the free tier: what was forced, not chosen\n",
|
| 229 |
+
"\n",
|
| 230 |
+
"### 5.1 β MiMo cannot be called remotely\n",
|
| 231 |
+
"\n",
|
| 232 |
+
"The first plan was to run the app on a free CPU Space and call MiMo through an inference provider.\n",
|
| 233 |
+
"That is not available at any price short of a dedicated endpoint:\n"
|
| 234 |
+
]
|
| 235 |
+
},
|
| 236 |
+
{
|
| 237 |
+
"cell_type": "code",
|
| 238 |
+
"execution_count": null,
|
| 239 |
+
"metadata": {},
|
| 240 |
+
"outputs": [],
|
| 241 |
+
"source": [
|
| 242 |
+
"import requests\n",
|
| 243 |
+
"\n",
|
| 244 |
+
"for repo in ['XiaomiMiMo/MiMo-7B-RL', 'Qwen/Qwen2.5-7B-Instruct']:\n",
|
| 245 |
+
" r = requests.get(f'https://huggingface.co/api/models/{repo}',\n",
|
| 246 |
+
" params={'expand[]': 'inferenceProviderMapping'}).json()\n",
|
| 247 |
+
" providers = r.get('inferenceProviderMapping') or {}\n",
|
| 248 |
+
" print(f'{repo:32s} providers:', list(providers) or 'NONE')\n"
|
| 249 |
+
]
|
| 250 |
+
},
|
| 251 |
+
{
|
| 252 |
+
"cell_type": "markdown",
|
| 253 |
+
"metadata": {},
|
| 254 |
+
"source": [
|
| 255 |
+
"So the weights run locally, and the only question left was on what.\n",
|
| 256 |
+
"\n",
|
| 257 |
+
"### 5.2 β Two runtimes, chosen at startup\n",
|
| 258 |
+
"\n",
|
| 259 |
+
"A Space's hardware is not the code's decision, so `mimo.py` carries both paths and picks one at\n",
|
| 260 |
+
"import time. The prompt, prefill, decoding parameters and parser are byte-identical on both.\n",
|
| 261 |
+
"\n",
|
| 262 |
+
"| | `gpu` | `cpu` |\n",
|
| 263 |
+
"|---|---|---|\n",
|
| 264 |
+
"| weights | BF16 checkpoint, 4-bit NF4 via `bitsandbytes` | `quantflex/MiMo-7B-RL-nomtp-Q4_K_M.gguf` |\n",
|
| 265 |
+
"| runs on | ZeroGPU, inside one `@spaces.GPU` call | `llama.cpp`, plain CPU |\n",
|
| 266 |
+
"| speed | ~4.2 s / region | ~130 s / region |\n",
|
| 267 |
+
"| limit | ~5 min of ZeroGPU per day | none |\n",
|
| 268 |
+
"| Part B's configuration? | **yes, exactly** | no β see below |\n",
|
| 269 |
+
"\n",
|
| 270 |
+
"The CPU path is a genuine deviation and is labelled as one in the interface. MiMo carries\n",
|
| 271 |
+
"multi-token-prediction layers that `llama.cpp` cannot load, so the only GGUF that runs at all is\n",
|
| 272 |
+
"one with those layers deleted. MTP is a speculative-decoding accelerator that the ordinary forward\n",
|
| 273 |
+
"pass does not use, so greedy output *should* be unaffected β but 'should be' is doing real work in\n",
|
| 274 |
+
"that sentence.\n"
|
| 275 |
+
]
|
| 276 |
+
},
|
| 277 |
+
{
|
| 278 |
+
"cell_type": "code",
|
| 279 |
+
"execution_count": null,
|
| 280 |
+
"metadata": {},
|
| 281 |
+
"outputs": [],
|
| 282 |
+
"source": [
|
| 283 |
+
"print('backend selected here :', mimo.BACKEND)\n",
|
| 284 |
+
"print('seconds per region :', mimo.SECONDS)\n",
|
| 285 |
+
"print('GPU grant requested :', mimo.GPU_DURATION, 's ->',\n",
|
| 286 |
+
" int(mimo.GPU_DURATION * 1.5), 's reserved by the scheduler')\n",
|
| 287 |
+
"print('regions per GPU batch :', mimo.MAX_WINDOWS_GPU)\n",
|
| 288 |
+
"print()\n",
|
| 289 |
+
"print(mimo.CAVEATS[mimo.BACKEND])\n"
|
| 290 |
+
]
|
| 291 |
+
},
|
| 292 |
+
{
|
| 293 |
+
"cell_type": "markdown",
|
| 294 |
+
"metadata": {},
|
| 295 |
+
"source": [
|
| 296 |
+
"### 5.3 β Batching, and why the user picks the batch\n",
|
| 297 |
+
"\n",
|
| 298 |
+
"Part B scored one window per document, because it already knew where the payload was. An uploaded\n",
|
| 299 |
+
"file offers no such promise, and neither runtime can read every window of a real PDF inside its\n",
|
| 300 |
+
"limit. So:\n",
|
| 301 |
+
"\n",
|
| 302 |
+
"1. the marker alternation that located the corpus payload is run over the **whole** skeleton, not\n",
|
| 303 |
+
" stopped at the first hit; every match becomes a candidate with the identical +/-1,500-character\n",
|
| 304 |
+
" shape, and overlapping ones are merged;\n",
|
| 305 |
+
"2. the rest of the document is tiled into windows of the same size;\n",
|
| 306 |
+
"3. the list is cut into batches sized to fit one run, and **you choose which batch to spend a run\n",
|
| 307 |
+
" on**. The report always states how much is still unread.\n",
|
| 308 |
+
"\n",
|
| 309 |
+
"Ranking decides reading order, never the verdict.\n"
|
| 310 |
+
]
|
| 311 |
+
},
|
| 312 |
+
{
|
| 313 |
+
"cell_type": "code",
|
| 314 |
+
"execution_count": null,
|
| 315 |
+
"metadata": {},
|
| 316 |
+
"outputs": [],
|
| 317 |
+
"source": [
|
| 318 |
+
"import corpus_text\n",
|
| 319 |
+
"from huggingface_hub import hf_hub_download\n",
|
| 320 |
+
"\n",
|
| 321 |
+
"path = hf_hub_download('Cyber-security-final-project/Generated_Injected_PDFs_HARMLESS',\n",
|
| 322 |
+
" 'Output_PDFs/javascript_injection_WICAR_0001.pdf', repo_type='dataset')\n",
|
| 323 |
+
"skeleton, truncated, dropped = corpus_text.build_skeleton(open(path, 'rb').read())\n",
|
| 324 |
+
"\n",
|
| 325 |
+
"for cover_all in (False, True):\n",
|
| 326 |
+
" regions = corpus_text.candidate_windows(skeleton, cover_all=cover_all)\n",
|
| 327 |
+
" print(f'cover_all={str(cover_all):5s} -> {len(regions)} region(s):',\n",
|
| 328 |
+
" [w['source'] for w in regions])\n"
|
| 329 |
+
]
|
| 330 |
+
},
|
| 331 |
+
{
|
| 332 |
+
"cell_type": "markdown",
|
| 333 |
+
"metadata": {},
|
| 334 |
+
"source": [
|
| 335 |
+
"### 5.4 β The sweep regions are honestly worse, and the app says so\n",
|
| 336 |
+
"\n",
|
| 337 |
+
"Marker-only triage leaves most of a file unread, and the marker set only knows the twelve families\n",
|
| 338 |
+
"this project generated β so a payload shaped like none of them would sit in text the model never\n",
|
| 339 |
+
"saw while the report said *clean*. The sweep closes that hole. It also introduces a real problem.\n",
|
| 340 |
+
"\n",
|
| 341 |
+
"Part B only ever showed MiMo marker-centred windows or the head of a document. Handed an arbitrary\n",
|
| 342 |
+
"mid-file content stream β a page of font-positioning operators β **MiMo does not answer**: it\n",
|
| 343 |
+
"carries on copying the input after the prefill, and the response parses as unrecoverable, which\n",
|
| 344 |
+
"scores as *not injected*.\n",
|
| 345 |
+
"\n",
|
| 346 |
+
"Observed live on one injected corpus PDF:\n",
|
| 347 |
+
"\n",
|
| 348 |
+
"| region | kind | verdict | evidence |\n",
|
| 349 |
+
"|---|---|---|---|\n",
|
| 350 |
+
"| 1 | marker | **PAYLOAD**, `javascript_injection` | `/JS (var payload = 'eicar-standard-...')` |\n",
|
| 351 |
+
"| 2-6 | sweep | clean *(unreadable answer)* | β |\n",
|
| 352 |
+
"\n",
|
| 353 |
+
"So: sweep regions buy coverage of text that would otherwise never be looked at, and a *clean*\n",
|
| 354 |
+
"verdict on one is close to no evidence at all. Rows are labelled `marker` / `sweep`, the report\n",
|
| 355 |
+
"counts sweep parse failures separately and explains them, and the sweep can be switched off to\n",
|
| 356 |
+
"keep the app strictly inside the shape Part B measured.\n",
|
| 357 |
+
"\n",
|
| 358 |
+
"The tempting fixes β widening the parser, or editing the prompt to insist harder on JSON β are\n",
|
| 359 |
+
"both refused on purpose. Either one would break the provenance argument that justifies quoting\n",
|
| 360 |
+
"F1 0.945 anywhere in the interface.\n"
|
| 361 |
+
]
|
| 362 |
+
},
|
| 363 |
+
{
|
| 364 |
+
"cell_type": "markdown",
|
| 365 |
+
"metadata": {},
|
| 366 |
+
"source": [
|
| 367 |
+
"## 6 β What broke on the way, and what it cost\n",
|
| 368 |
+
"\n",
|
| 369 |
+
"| # | Symptom | Cause | Resolution |\n",
|
| 370 |
+
"|---|---|---|---|\n",
|
| 371 |
+
"| 1 | no provider serves MiMo | `inferenceProviderMapping` empty for every variant | run the weights locally |\n",
|
| 372 |
+
"| 2 | `Quota exceeded for flavor cpu-basic: limit=0` | the **org** has no compute allowance; its old Space only ran because it was `sdk: static` | moved to the personal Space |\n",
|
| 373 |
+
"| 3 | `Textbox.__init__() got an unexpected keyword 'show_copy_button'` | no `sdk_version` pinned, so HF installed gradio 6, which removed the argument | pinned `sdk_version: 5.50.0` **and** dropped the argument |\n",
|
| 374 |
+
"| 4 | `No @spaces.GPU function detected during startup` | the Space is ZeroGPU, and the CPU-only app had no decorated function | added the GPU path; the decorator is applied at import, because ZeroGPU scans at startup |\n",
|
| 375 |
+
"| 5 | `requested GPU duration (450s) is larger than the maximum allowed` | the `spaces` client asks for **1.5x** the declared duration, against a 300 s cap | lowered the duration; also moved the 15.7 GB weight download *outside* the grant |\n",
|
| 376 |
+
"| 6 | `Value: 0 is not in the list of choices: []` | the batch dropdown validates against choices that are empty until a PDF is uploaded | `allow_custom_value=True` plus a tolerant `resolve_batch()` |\n",
|
| 377 |
+
"| 7 | `exceeded your free ZeroGPU quota (270s requested vs 264s left)` | ~5 min/day, and the scheduler **reserves** the full requested duration up front | shorter grant (110 s), smaller batch (8), and a user-facing runtime picker so the CPU path can take over |\n",
|
| 378 |
+
"| 8 | `libc.musl-x86_64.so.1: cannot open shared object file` | the prebuilt `llama-cpp-python` wheels are tagged `linux_x86_64` but linked against **musl**; a Space is glibc | build from the PyPI sdist, the only binary-free route |\n",
|
| 379 |
+
"\n",
|
| 380 |
+
"Two of these are worth carrying into any future Space in this project: **pin `sdk_version`**, and\n",
|
| 381 |
+
"**never pin `torch`** β the image supplies one matching its own driver.\n"
|
| 382 |
+
]
|
| 383 |
+
},
|
| 384 |
+
{
|
| 385 |
+
"cell_type": "markdown",
|
| 386 |
+
"metadata": {},
|
| 387 |
+
"source": [
|
| 388 |
+
"## 7 β The numbers, and what they do not cover\n",
|
| 389 |
+
"\n",
|
| 390 |
+
"On the 1,100-document corpus Part B measured, MiMo-7B-RL scored **F1 0.945**, precision 0.988,\n",
|
| 391 |
+
"recall 0.906, naming the family correctly 43.3% of the time, with 10 false alarms on 200 clean\n",
|
| 392 |
+
"files and 155 unparsable answers.\n",
|
| 393 |
+
"\n",
|
| 394 |
+
"**A detector that calls every file malicious scores F1 0.900 on this corpus**, because 82% of it\n",
|
| 395 |
+
"is injected. Read 0.945 against 0.900, not against zero β it is a 5% relative improvement over\n",
|
| 396 |
+
"doing no work at all. Gemma-2-9B scored 0.969 and is the real Part B winner; MiMo is used here\n",
|
| 397 |
+
"because it is ungated, needs no token, and is 2.6x faster.\n",
|
| 398 |
+
"\n",
|
| 399 |
+
"And the corpus is synthetic: harmless EICAR/AMTSO/WICAR/RANSIM test markers injected into ordinary\n",
|
| 400 |
+
"PDFs. Real malware does not announce itself the same way. **This is not a general malware\n",
|
| 401 |
+
"scanner.**\n"
|
| 402 |
+
]
|
| 403 |
+
},
|
| 404 |
+
{
|
| 405 |
+
"cell_type": "code",
|
| 406 |
+
"execution_count": null,
|
| 407 |
+
"metadata": {},
|
| 408 |
+
"outputs": [],
|
| 409 |
+
"source": [
|
| 410 |
+
"import pandas as pd\n",
|
| 411 |
+
"\n",
|
| 412 |
+
"PART_B = ('https://huggingface.co/datasets/Cyber-security-final-project/'\n",
|
| 413 |
+
" 'Evaluation_of_OpenSource_Models_for_PDF_Injection_Recognition/'\n",
|
| 414 |
+
" 'resolve/main/Part_B_Outputs/part_b_results.json')\n",
|
| 415 |
+
"print(pd.read_json(PART_B).set_index('detector').to_string())\n"
|
| 416 |
+
]
|
| 417 |
+
},
|
| 418 |
+
{
|
| 419 |
+
"cell_type": "markdown",
|
| 420 |
+
"metadata": {},
|
| 421 |
+
"source": [
|
| 422 |
+
"## 8 β Deploying\n",
|
| 423 |
+
"\n",
|
| 424 |
+
"`VCURR/HF_SPACE/` is the source of truth; the Space is a deployment target, not a working copy.\n"
|
| 425 |
+
]
|
| 426 |
+
},
|
| 427 |
+
{
|
| 428 |
+
"cell_type": "code",
|
| 429 |
+
"execution_count": null,
|
| 430 |
+
"metadata": {},
|
| 431 |
+
"outputs": [],
|
| 432 |
+
"source": [
|
| 433 |
+
"# run from VCURR/HF_SPACE/\n",
|
| 434 |
+
"from huggingface_hub import HfApi, CommitOperationAdd\n",
|
| 435 |
+
"\n",
|
| 436 |
+
"FILES = ['README.md', 'requirements.txt', 'app.py', 'corpus_text.py',\n",
|
| 437 |
+
" 'mimo.py', 'neighbours.py', 'test_fidelity.py', 'App_Creation.ipynb']\n",
|
| 438 |
+
"\n",
|
| 439 |
+
"api = HfApi()\n",
|
| 440 |
+
"commit = api.create_commit(\n",
|
| 441 |
+
" repo_id='BentoUniAcc/Mimo_Injection_detector', repo_type='space',\n",
|
| 442 |
+
" operations=[CommitOperationAdd(f, f) for f in FILES],\n",
|
| 443 |
+
" commit_message='describe the change here')\n",
|
| 444 |
+
"print(commit.commit_url)\n"
|
| 445 |
+
]
|
| 446 |
+
},
|
| 447 |
+
{
|
| 448 |
+
"cell_type": "code",
|
| 449 |
+
"execution_count": null,
|
| 450 |
+
"metadata": {},
|
| 451 |
+
"outputs": [],
|
| 452 |
+
"source": [
|
| 453 |
+
"# build status\n",
|
| 454 |
+
"import requests\n",
|
| 455 |
+
"\n",
|
| 456 |
+
"runtime = requests.get(\n",
|
| 457 |
+
" 'https://huggingface.co/api/spaces/BentoUniAcc/Mimo_Injection_detector').json()['runtime']\n",
|
| 458 |
+
"print(runtime['stage'], '|', runtime.get('errorMessage'))\n"
|
| 459 |
+
]
|
| 460 |
+
},
|
| 461 |
+
{
|
| 462 |
+
"cell_type": "markdown",
|
| 463 |
+
"metadata": {},
|
| 464 |
+
"source": [
|
| 465 |
+
"---\n",
|
| 466 |
+
"\n",
|
| 467 |
+
"*Space: [BentoUniAcc/Mimo_Injection_detector](https://huggingface.co/spaces/BentoUniAcc/Mimo_Injection_detector)*\n"
|
| 468 |
+
]
|
| 469 |
+
}
|
| 470 |
+
],
|
| 471 |
+
"metadata": {
|
| 472 |
+
"kernelspec": {
|
| 473 |
+
"display_name": "Python 3",
|
| 474 |
+
"language": "python",
|
| 475 |
+
"name": "python3"
|
| 476 |
+
},
|
| 477 |
+
"language_info": {
|
| 478 |
+
"name": "python",
|
| 479 |
+
"version": "3.10"
|
| 480 |
+
}
|
| 481 |
+
},
|
| 482 |
+
"nbformat": 4,
|
| 483 |
+
"nbformat_minor": 5
|
| 484 |
+
}
|
README.md
CHANGED
|
@@ -11,7 +11,7 @@ license: mit
|
|
| 11 |
short_description: MiMo-7B finds payloads hidden inside PDF files
|
| 12 |
---
|
| 13 |
|
| 14 |
-
# PDF Injection Detector β MiMo-7B
|
| 15 |
|
| 16 |
Upload a PDF. It is rendered to text with the extractor that built the project corpus, the regions
|
| 17 |
carrying structural markers are ranked, and **MiMo-7B-RL** reads the most promising ones and says
|
|
|
|
| 11 |
short_description: MiMo-7B finds payloads hidden inside PDF files
|
| 12 |
---
|
| 13 |
|
| 14 |
+
# PDF Injection Detector β MiMo-7B
|
| 15 |
|
| 16 |
Upload a PDF. It is rendered to text with the extractor that built the project corpus, the regions
|
| 17 |
carrying structural markers are ranked, and **MiMo-7B-RL** reads the most promising ones and says
|