Publish phishing v1.0
Browse files- v1.0/config.json +42 -0
- v1.0/eval_report.json +36 -0
- v1.0/model.safetensors +3 -0
- v1.0/model_card.md +143 -0
- v1.0/threshold.json +3 -0
- v1.0/tokenizer.json +0 -0
- v1.0/tokenizer_config.json +14 -0
- v1.0/training_config.yaml +50 -0
v1.0/config.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"add_cross_attention": false,
|
| 3 |
+
"architectures": [
|
| 4 |
+
"BertForSequenceClassification"
|
| 5 |
+
],
|
| 6 |
+
"attention_probs_dropout_prob": 0.1,
|
| 7 |
+
"bos_token_id": null,
|
| 8 |
+
"classifier_dropout": null,
|
| 9 |
+
"directionality": "bidi",
|
| 10 |
+
"dtype": "float32",
|
| 11 |
+
"eos_token_id": null,
|
| 12 |
+
"hidden_act": "gelu",
|
| 13 |
+
"hidden_dropout_prob": 0.1,
|
| 14 |
+
"hidden_size": 768,
|
| 15 |
+
"id2label": {
|
| 16 |
+
"0": "legit",
|
| 17 |
+
"1": "phishing"
|
| 18 |
+
},
|
| 19 |
+
"initializer_range": 0.02,
|
| 20 |
+
"intermediate_size": 3072,
|
| 21 |
+
"is_decoder": false,
|
| 22 |
+
"label2id": {
|
| 23 |
+
"legit": 0,
|
| 24 |
+
"phishing": 1
|
| 25 |
+
},
|
| 26 |
+
"layer_norm_eps": 1e-12,
|
| 27 |
+
"max_position_embeddings": 512,
|
| 28 |
+
"model_type": "bert",
|
| 29 |
+
"num_attention_heads": 12,
|
| 30 |
+
"num_hidden_layers": 12,
|
| 31 |
+
"pad_token_id": 0,
|
| 32 |
+
"pooler_fc_size": 768,
|
| 33 |
+
"pooler_num_attention_heads": 12,
|
| 34 |
+
"pooler_num_fc_layers": 3,
|
| 35 |
+
"pooler_size_per_head": 128,
|
| 36 |
+
"pooler_type": "first_token_transform",
|
| 37 |
+
"tie_word_embeddings": true,
|
| 38 |
+
"transformers_version": "5.0.0",
|
| 39 |
+
"type_vocab_size": 2,
|
| 40 |
+
"use_cache": false,
|
| 41 |
+
"vocab_size": 119547
|
| 42 |
+
}
|
v1.0/eval_report.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"checkpoint": "checkpoints\\phishing_v1",
|
| 3 |
+
"test_split": "data\\processed\\phishing\\test",
|
| 4 |
+
"metrics": {
|
| 5 |
+
"recall_phishing": 0.9695238095238096,
|
| 6 |
+
"precision_phishing": 0.9279854147675478,
|
| 7 |
+
"f1_phishing": 0.9482999534233815,
|
| 8 |
+
"confusion_matrix": [
|
| 9 |
+
[
|
| 10 |
+
4593,
|
| 11 |
+
79
|
| 12 |
+
],
|
| 13 |
+
[
|
| 14 |
+
32,
|
| 15 |
+
1018
|
| 16 |
+
]
|
| 17 |
+
],
|
| 18 |
+
"threshold": 0.95,
|
| 19 |
+
"n_test": 5722,
|
| 20 |
+
"n_phishing_test": 1050,
|
| 21 |
+
"n_legit_test": 4672,
|
| 22 |
+
"p95_ms": 434.70457002258627,
|
| 23 |
+
"latency": {
|
| 24 |
+
"mean_ms": 239.72067400012747,
|
| 25 |
+
"p50_ms": 220.47114999440964,
|
| 26 |
+
"p95_ms": 434.70457002258627,
|
| 27 |
+
"n": 200.0
|
| 28 |
+
}
|
| 29 |
+
},
|
| 30 |
+
"gates": {
|
| 31 |
+
"recall": true,
|
| 32 |
+
"precision": true,
|
| 33 |
+
"latency_p95": true
|
| 34 |
+
},
|
| 35 |
+
"all_passed": true
|
| 36 |
+
}
|
v1.0/model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3833500e476c2272e55bf008849e8afad126520073c308297c4af742a656fde5
|
| 3 |
+
size 711443432
|
v1.0/model_card.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Model Card — PriorMail Phishing Detector v1.0
|
| 2 |
+
|
| 3 |
+
**Model ID:** `phishing/v1.0`
|
| 4 |
+
**Owner:** Faiz (PJK-GM095)
|
| 5 |
+
**Date:** 2026-06-13
|
| 6 |
+
**Status:** Eval gates passed — pending export approval from Insan + Syafiq
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## What This Model Does
|
| 11 |
+
|
| 12 |
+
Binary email classifier: **legit (0)** vs **phishing (1)**.
|
| 13 |
+
|
| 14 |
+
Used by `prior-mail-backend` to flag potentially malicious emails before they reach the user's inbox. Runs at inference time on every incoming email.
|
| 15 |
+
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
## Base Model
|
| 19 |
+
|
| 20 |
+
`bert-base-multilingual-cased` (Google, 178M params)
|
| 21 |
+
|
| 22 |
+
Chosen over IndoBERT because the primary training data is English-dominant (~98% EN). mBERT handles English well and retains multilingual capability for future Indonesian phishing data.
|
| 23 |
+
|
| 24 |
+
---
|
| 25 |
+
|
| 26 |
+
## Training Data
|
| 27 |
+
|
| 28 |
+
| Source | Role | Rows (after dedup) |
|
| 29 |
+
|---|---|---|
|
| 30 |
+
| `ealvaradob/phishing-dataset` subset `texts` | Phishing + some legit | ~20,137 |
|
| 31 |
+
| Enron corpus (`emails.csv`) | Legit negative class | ~20,000 sampled |
|
| 32 |
+
|
| 33 |
+
**Total after deduplication:** ~38,000 rows
|
| 34 |
+
**Train / Val / Test split:** 75 / 12.5 / 12.5 (stratified by label)
|
| 35 |
+
|
| 36 |
+
Deduplication applied on the first 200 chars of body before splitting to prevent leakage.
|
| 37 |
+
|
| 38 |
+
**Note:** Dataset is almost entirely English. Indonesian phishing emails are not well-represented. Performance on Indonesian-language phishing may be lower — to be addressed in v1.1 with internal labeled data.
|
| 39 |
+
|
| 40 |
+
---
|
| 41 |
+
|
| 42 |
+
## Training Config
|
| 43 |
+
|
| 44 |
+
See [`configs/phishing_v1.yaml`](../configs/phishing_v1.yaml) for the full reproducible config.
|
| 45 |
+
|
| 46 |
+
Key hyperparameters:
|
| 47 |
+
|
| 48 |
+
| Parameter | Value |
|
| 49 |
+
|---|---|
|
| 50 |
+
| Learning rate | 2e-5 |
|
| 51 |
+
| Batch size | 16 per device |
|
| 52 |
+
| Max sequence length | 512 |
|
| 53 |
+
| Epochs | 4 (early stopped at 3) |
|
| 54 |
+
| Warmup ratio | 0.10 |
|
| 55 |
+
| Loss | Weighted cross-entropy (phishing_class_multiplier=3.0) |
|
| 56 |
+
| Early stopping metric | `recall_phishing` (patience=2) |
|
| 57 |
+
| Mixed precision | bf16 |
|
| 58 |
+
| Seed | 42 |
|
| 59 |
+
|
| 60 |
+
---
|
| 61 |
+
|
| 62 |
+
## Evaluation Results
|
| 63 |
+
|
| 64 |
+
### Threshold Selection (Validation Set)
|
| 65 |
+
|
| 66 |
+
Threshold selected on val set to maximize precision while keeping recall ≥ 0.95.
|
| 67 |
+
|
| 68 |
+
**Threshold: 0.95**
|
| 69 |
+
|
| 70 |
+
| Metric | Val Set |
|
| 71 |
+
|---|---|
|
| 72 |
+
| Recall | 0.981 |
|
| 73 |
+
| Precision | 0.933 |
|
| 74 |
+
|
| 75 |
+
### Final Results (Test Set, n=5,722)
|
| 76 |
+
|
| 77 |
+
| Metric | Result | Gate | Status |
|
| 78 |
+
|---|---|---|---|
|
| 79 |
+
| Recall | **0.970** | ≥ 0.95 | PASS ✅ |
|
| 80 |
+
| Precision | **0.928** | ≥ 0.80 | PASS ✅ |
|
| 81 |
+
| Latency p95 (CPU) | **435 ms** | < 500 ms | PASS ✅ |
|
| 82 |
+
| F1 | 0.948 | — | — |
|
| 83 |
+
|
| 84 |
+
### Confusion Matrix (Test Set)
|
| 85 |
+
|
| 86 |
+
| | Predicted Legit | Predicted Phishing |
|
| 87 |
+
|---|---|---|
|
| 88 |
+
| **Actual Legit** | 4,593 (TN) | 79 (FP) |
|
| 89 |
+
| **Actual Phishing** | 32 (FN) | 1,018 (TP) |
|
| 90 |
+
|
| 91 |
+
False negatives (missed phishing): **32 out of 1,050** — 3.0% miss rate.
|
| 92 |
+
False positives (false alarms): **79 out of 4,672** — 1.7% false alarm rate.
|
| 93 |
+
|
| 94 |
+
---
|
| 95 |
+
|
| 96 |
+
## Input / Output Format
|
| 97 |
+
|
| 98 |
+
**Input** (assembled by `src/data/preprocess.build_phishing_input()`):
|
| 99 |
+
```
|
| 100 |
+
FROM: {sender_email} [SEP] SUBJECT: {subject} [SEP] BODY: {body}
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
If sender or subject are unavailable (e.g. plain-body-only sources), those fields are empty strings. The model still performs well on body-only input — the HF training data was ~98% body-only.
|
| 104 |
+
|
| 105 |
+
**Output:**
|
| 106 |
+
- Raw: softmax probability for class 1 (phishing), range [0, 1]
|
| 107 |
+
- Final label: `phishing` if probability ≥ 0.95, else `legit`
|
| 108 |
+
- Threshold stored in `threshold.json` alongside the checkpoint
|
| 109 |
+
|
| 110 |
+
---
|
| 111 |
+
|
| 112 |
+
## Known Limitations
|
| 113 |
+
|
| 114 |
+
- **Language:** Trained primarily on English data. Lower recall expected on Indonesian-language phishing. Mitigated by mBERT's multilingual pretraining, but not validated.
|
| 115 |
+
- **Sender signal weak:** ~98% of HF phishing training samples had no RFC 2822 headers — sender email is not a reliable feature for this version. Enron legit samples do have sender headers, which may introduce a small asymmetry.
|
| 116 |
+
- **Adversarial robustness:** Not evaluated against adversarial phishing (obfuscated URLs, lookalike domains, Unicode tricks). Planned for v1.1.
|
| 117 |
+
- **Domain shift:** Trained on public phishing corpora. Real-world phishing targeting Indonesian corporate users may have different patterns.
|
| 118 |
+
|
| 119 |
+
---
|
| 120 |
+
|
| 121 |
+
## Artifacts
|
| 122 |
+
|
| 123 |
+
| File | Description |
|
| 124 |
+
|---|---|
|
| 125 |
+
| `model.safetensors` | Fine-tuned weights (best checkpoint, step 835 / epoch 1) |
|
| 126 |
+
| `config.json` | HuggingFace model config |
|
| 127 |
+
| `tokenizer.json`, `tokenizer_config.json` | Tokenizer (bert-base-multilingual-cased) |
|
| 128 |
+
| `threshold.json` | `{"threshold": 0.95}` |
|
| 129 |
+
| `training_config.yaml` | Full training config + git SHA |
|
| 130 |
+
| `val_metrics.json` | Val set metrics at end of training |
|
| 131 |
+
|
| 132 |
+
Eval artifacts (in `eval/results/phishing/`):
|
| 133 |
+
| File | Description |
|
| 134 |
+
|---|---|
|
| 135 |
+
| `eval_report.json` | Full test set metrics + confusion matrix + latency |
|
| 136 |
+
|
| 137 |
+
---
|
| 138 |
+
|
| 139 |
+
## Changelog
|
| 140 |
+
|
| 141 |
+
| Version | Date | Notes |
|
| 142 |
+
|---|---|---|
|
| 143 |
+
| v1.0 | 2026-06-13 | Initial release. All eval gates passed. English-only training data. |
|
v1.0/threshold.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"threshold": 0.95
|
| 3 |
+
}
|
v1.0/tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
v1.0/tokenizer_config.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"backend": "tokenizers",
|
| 3 |
+
"cls_token": "[CLS]",
|
| 4 |
+
"do_lower_case": false,
|
| 5 |
+
"is_local": false,
|
| 6 |
+
"mask_token": "[MASK]",
|
| 7 |
+
"model_max_length": 512,
|
| 8 |
+
"pad_token": "[PAD]",
|
| 9 |
+
"sep_token": "[SEP]",
|
| 10 |
+
"strip_accents": null,
|
| 11 |
+
"tokenize_chinese_chars": true,
|
| 12 |
+
"tokenizer_class": "BertTokenizer",
|
| 13 |
+
"unk_token": "[UNK]"
|
| 14 |
+
}
|
v1.0/training_config.yaml
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Phishing detector — v1 baseline config.
|
| 2 |
+
# Decision log (feat/phishing-model, 2026-06-09):
|
| 3 |
+
# Base model : bert-base-multilingual-cased — English-capable (§11 decision,
|
| 4 |
+
# approved by Insan; diverges from priority's IndoBERT because
|
| 5 |
+
# ealvaradob/phishing-dataset is English-heavy).
|
| 6 |
+
# Legit class : Enron corporate email corpus (emails.csv, 20 K sampled).
|
| 7 |
+
# Imbalance : weighted cross-entropy + 3× multiplier on phishing class.
|
| 8 |
+
#
|
| 9 |
+
# Run via:
|
| 10 |
+
# make data-phishing
|
| 11 |
+
# make train-phishing config=configs/phishing_v1.yaml
|
| 12 |
+
|
| 13 |
+
model_type: phishing
|
| 14 |
+
model_name: bert-base-multilingual-cased # §11 decision: English-capable base
|
| 15 |
+
|
| 16 |
+
dataset: ealvaradob/phishing-dataset # primary phishing training data (HF)
|
| 17 |
+
legit_source: emails.csv # Enron corpus — negative class source
|
| 18 |
+
|
| 19 |
+
num_labels: 2 # legit (0) | phishing (1) — matches PHISHING_LABELS in constants.py
|
| 20 |
+
|
| 21 |
+
hyperparameters:
|
| 22 |
+
learning_rate: 2.0e-5 # AdamW (mirrors priority protocol, ML_PIPELINE.md §3)
|
| 23 |
+
weight_decay: 0.01
|
| 24 |
+
batch_size: 16
|
| 25 |
+
gradient_accumulation_steps: 1
|
| 26 |
+
num_epochs: 4 # 3–5 with early stopping
|
| 27 |
+
warmup_ratio: 0.10
|
| 28 |
+
lr_scheduler_type: linear
|
| 29 |
+
max_seq_length: 512
|
| 30 |
+
# Early stopping on RECALL, not macro F1 — false negatives are worst case (§3).
|
| 31 |
+
early_stopping_metric: recall_phishing
|
| 32 |
+
early_stopping_patience: 2
|
| 33 |
+
class_weights: balanced # inverse-frequency base weights from train dist
|
| 34 |
+
phishing_class_multiplier: 3.0 # extra multiplier on phishing class weight
|
| 35 |
+
mixed_precision: bf16 # falls back to fp16 / none as needed
|
| 36 |
+
|
| 37 |
+
seed: 42
|
| 38 |
+
|
| 39 |
+
output_dir: checkpoints/phishing_v1
|
| 40 |
+
|
| 41 |
+
wandb:
|
| 42 |
+
project: priormail
|
| 43 |
+
tags:
|
| 44 |
+
- model:phishing
|
| 45 |
+
- stage:baseline
|
| 46 |
+
|
| 47 |
+
# --- run provenance (auto-recorded) ---
|
| 48 |
+
_git_sha: 94013c0
|
| 49 |
+
_git_dirty: True
|
| 50 |
+
_trained_at: 2026-06-13T10:59:03
|