File size: 2,642 Bytes
44c9031
 
a65a4db
 
44c9031
078eeb8
44c9031
a65a4db
078eeb8
a65a4db
078eeb8
 
 
 
 
 
 
 
44c9031
 
 
 
078eeb8
8cc3f6a
44c9031
 
 
 
8cc3f6a
078eeb8
44c9031
 
 
 
 
 
a65a4db
 
44c9031
 
 
 
 
 
078eeb8
 
44c9031
078eeb8
a65a4db
8cc3f6a
44c9031
 
 
 
 
078eeb8
 
 
44c9031
078eeb8
 
44c9031
 
 
 
8cc3f6a
44c9031
 
 
078eeb8
44c9031
 
 
 
8cc3f6a
44c9031
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
from pathlib import Path

import joblib
import numpy as np
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel

app = FastAPI(title="Web Attack Detection API")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Assuming models are in the same directory as the script for deployment
# For Colab, you might need to adjust paths or upload files.
MODEL_PATH = "web_attack_detection_model.pkl"
VECTORIZER_PATH = "url_vectorizer.pkl"

class InputData(BaseModel):
    features: list[float] # This assumes features are already numerical
    # If you want to input URLs, you'd change this to:
    # url: str


try:
    model = joblib.load(MODEL_PATH)
    vectorizer = joblib.load(VECTORIZER_PATH)
    EXPECTED_FEATURES = len(vectorizer.get_feature_names_out())
except Exception as ex:
    raise RuntimeError(f"Model or vectorizer failed to load: {ex}") from ex


@app.get("/")
def home() -> dict[str, str | int]:
    return {
        "message": "Web attack detection model running",
        "expected_features": EXPECTED_FEATURES,
    }


@app.get("/health")
def health() -> dict[str, str]:
    return {"status": "ok"}


@app.post("/predict")
def predict(data: InputData) -> dict[str, int | float | str]:
    # If InputData was 'url: str', you'd do:
    # x = vectorizer.transform([data.url]).toarray()
    
    if len(data.features) != EXPECTED_FEATURES:
        raise HTTPException(
            status_code=400,
            detail=f"Expected {EXPECTED_FEATURES} features, but got {len(data.features)}",
        )

    try:
        x = np.asarray(data.features, dtype=np.float64).reshape(1, -1)
        if not np.isfinite(x).all():
            raise HTTPException(status_code=400, detail="Features contain NaN or Inf")

        pred = int(model.predict(x)[0])
        # IsolationForest predicts -1 for anomalies (attacks), 1 for normal
        result = "attack_detected" if pred == -1 else "normal_request"

        response: dict[str, int | float | str] = {
            "prediction": result,
            "raw_prediction": pred,
        }

        # IsolationForest does not have predict_proba, use decision_function for anomaly score
        # Lower scores typically mean more anomalous
        anomaly_score = float(model.decision_function(x)[0])
        response["anomaly_score"] = anomaly_score

        return response
    except HTTPException:
        raise
    except Exception as ex:
        raise HTTPException(status_code=500, detail=f"Prediction failed: {ex}") from ex