BentoUniAcc commited on
Commit
cba4874
Β·
verified Β·
1 Parent(s): 324dc87

add App_Creation build-log notebook; fix README heading

Browse files
Files changed (2) hide show
  1. App_Creation.ipynb +484 -0
  2. 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 on free CPU
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