{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# COAE Practical 02 — Training-Data Poisoning\n",
        "**Track:** AI Data Attacks | **Level:** Medium | **Environment:** Offline JupyterLite\n",
        "\n",
        "## Mission\n",
        "A binary classifier trains from trusted-looking labelled observations. You can change **at most two training labels**, but cannot alter feature values or the inference target. Make a malicious target be classified benign.\n",
        "\n",
        "This is a small **1-dimensional, 3-nearest-neighbour (3-NN)** model, not a real phishing product. The exercise isolates the poisoning concept.\n",
        "\n",
        "**Constraint:** Only change labels (0 = benign, 1 = malicious), maximum two flips."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "TRAIN = [\n",
        "    (0.10, 0), (0.18, 0), (0.28, 0), (0.40, 0),\n",
        "    (0.56, 1), (0.62, 1), (0.68, 1), (0.81, 1)\n",
        "]\n",
        "TARGET = 0.64\n",
        "\n",
        "def knn_predict(training, point, k=3):\n",
        "    neighbors = sorted(enumerate(training), key=lambda row: (abs(row[1][0]-point), row[0]))[:k]\n",
        "    votes = [row[1][1] for row in neighbors]\n",
        "    return 1 if sum(votes) > k // 2 else 0\n",
        "\n",
        "print(\"Original target prediction:\", knn_predict(TRAIN, TARGET))\n",
        "assert knn_predict(TRAIN, TARGET) == 1"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Task A — Poison the training labels\n",
        "Return a **new dataset**, preserving every feature value. Find at most two label changes that cause the target to be misclassified."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "def poison_labels(training):\n",
        "    poisoned = list(training)\n",
        "    # TODO: Flip no more than 2 labels.\n",
        "    # For example, to change a label:\n",
        "    # poisoned[index] = (poisoned[index][0], new_label)\n",
        "    return poisoned\n",
        "\n",
        "poisoned = poison_labels(TRAIN)\n",
        "print(\"After poisoning:\", knn_predict(poisoned, TARGET))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Task B — Verification\n",
        "This cell should fail until your poisoning attack is valid and successful."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "def verify_poisoning(original, poisoned):\n",
        "    assert len(original) == len(poisoned), \"Cannot add/remove rows.\"\n",
        "    assert all(a[0] == b[0] for a, b in zip(original, poisoned)), \"Features were modified.\"\n",
        "    assert all(item[1] in (0, 1) for item in poisoned), \"Labels must be binary.\"\n",
        "    changed = [i for i,(a,b) in enumerate(zip(original,poisoned)) if a[1] != b[1]]\n",
        "    assert 1 <= len(changed) <= 2, \"Flip one or two labels only.\"\n",
        "    assert knn_predict(poisoned, TARGET) == 0, \"Target still classified malicious.\"\n",
        "    return changed\n",
        "\n",
        "flipped = verify_poisoning(TRAIN, poisoned)\n",
        "print(\"PASS — targeted label-flipping attack\")\n",
        "print(\"Changed training rows:\", flipped)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Task C — Reporting\n",
        "What access would an adversary need to poison real data? What holdout metrics and provenance checks should the defender monitor?\n",
        "\n",
        "**Bonus:** Compare predictions on several untouched benign and malicious validation samples before and after poisoning.\n",
        "\n",
        "**Deliverable:** Updated `poison_labels`, checker output, and a finding with root cause, impact and recommended safeguards."
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python (Pyodide)",
      "language": "python",
      "name": "python"
    },
    "language_info": {
      "name": "python"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}