Add a leakage risk assessment model, replacing the AUC risk bands - #445
Draft
TheColdIce wants to merge 4 commits into
Draft
TheColdIce wants to merge 4 commits into
TheColdIce wants to merge 4 commits into
Conversation
LeakPro measured attack success but had no defensible way to state risk. The webapp scored it as hardcoded AUC bands with no provenance, and AUC is an average-case measure that hides the worst-case per-record leakage which actually determines exposure. The core library had no risk concept at all, so CLI and notebook users got nothing. This adds leakpro/risk/, which reports a decomposition rather than a single opaque score. Measured quantities come from the audit and are reproducible; harm parameters are declared by the data controller and echoed verbatim in the output; combined figures keep every factor traceable to one of the two. Structure follows Sion et al. (IWPE 2019), LM = DTS x NR x DST x NDS, whose Loss Magnitude factors the paper deliberately leaves for the analyst to set — so they are user-supplied numbers defaulting to a neutral 1.0 and there is no combined risk band at all, only an advisory vulnerability band over measured lift, marked heuristic. Precision follows Jayaraman et al. (PoPETs 2021, Thm 4.2): balanced audits imply a prior of 0.5, and the same measurement can mean a very different threat at a realistic prior, so both are reported. assess_risk is a separate call rather than part of run_audit, so one expensive audit can be re-assessed cheaply under different operating points and priors, and run_audit keeps its signature. Four guards, each covering a way a naive version reports a confidently wrong number: an operating point the audit set cannot resolve is refused instead of reading as TPR 0; results with AUC < 0.5 are rejected by name rather than averaged in; the strongest attack is selected by TPR at the operating point rather than by AUC; and a percentage-valued fixed-FPR table is rejected rather than inflating a risk figure a hundredfold. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
riskLevel() scored risk from hardcoded AUC thresholds (>=0.75 HIGH, >=0.60
MEDIUM) with no provenance, in TypeScript where it could not be tested. All
scoring now happens in leakpro.risk behind POST /jobs/{id}/risk; the frontend
posts a declared use-case profile and renders what comes back.
The first view shows numbers, not prose: explanations live behind a reusable
InfoButton, and RiskDiagram is an inline themed SVG of the model, encoding role
by border style (given vs computed) and provenance by colour (measured vs
declared). Warnings deliberately stay visible as a caveat chip — a caveat like
"this operating point was not measurable" must not need a click to discover.
Risk state lives in Step7Results rather than Summary because the tab bar
unmounts the tab components, which discarded a completed assessment as soon as
the user looked at the ROC curves. Assessments are keyed by job and model since
the endpoint is per job and model names are not unique across compared jobs;
previously only the first job's models were ever assessed.
The worker forwards num_train, which the risk layer needs to scale exposure to
the population and which saved results cannot recover.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds a commented use_case: block to audit.yaml documenting every harm factor, and two notebook cells that call assess_risk on the audit results and save the JSON. Declaring the block changes nothing on its own: assessment is always an explicit call, so an existing config keeps working untouched. Sensitivity is left neutral here because CIFAR-10 is public benchmark data, which is also the honest default to show. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The first pass showed the whole model at once: nine boxes in the schematic and info modals running four paragraphs. Correct, but it read as documentation and buried the parts a user has to act on. The schematic is now three boxes — the measured branch, the declared branch, and their product — with factor names spelled out instead of abbreviated, since the reader is meeting them for the first time. The input nodes, the role tags, the legend and the centre divider are gone; the column headings already carry the measured-versus-declared point that matters. Info modals now open with a sentence or two and put the depth behind a nested Detail disclosure: 174 words visible across all nine, down from about 1450, with the jargon (gamma, retention period, threat event frequency, the CNIL scale) one click further in rather than deleted. Deliberately unchanged: the caveat chip and the assumption list, which come from the library and are the auditable record rather than UI copy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Check out this pull request on See visual diffs & provide feedback on Jupyter Notebooks. Powered by ReviewNB |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #443. Jira: LDG-30.
What this adds
leakpro/risk/, which turns MIA audit results plus a declared use-case profile into a risk assessment, and replaces the webapp's AUC-threshold risk bands with it.The problem it solves: LeakPro measured attack success but had no defensible way to state risk.
Summary.tsxscored it from hardcoded AUC bands (>= 0.75HIGH,>= 0.60MEDIUM) with no provenance, and AUC is an average-case measure that hides the worst-case per-record leakage that actually determines exposure — a model can sit at AUC 0.58 and still have training records that are perfectly identifiable. The core library had no risk concept at all, so CLI and notebook users got nothing.Design: a decomposition, not a score
Risk is reported in three blocks that are never collapsed:
Risk = LEF × LM, and an advisory band.Structure follows Sion et al. (IWPE 2019):
LM = DTS × NR × DST × NDS. That paper supplies the factor structure but deliberately no numeric values — §III-G states every input is an analyst estimate. So the four factors are user-supplied numbers defaulting to a neutral 1.0, and there is no combined risk band at all, only an advisoryvulnerability_bandover the measured lift, explicitly marked heuristic. The suggested sensitivity scale is cited to CNIL PIA-3, which §VI-A points at.Precision follows Jayaraman et al. (PoPETs 2021, Thm 4.2):
PPV = TPR / (TPR + γ·α),γ = (1−π)/π. This is not decoration. Our audits run on a balanced split, so every TPR and AUC LeakPro reports implicitly assumes π = 0.5. At TPR(1% FPR) = 0.10 that is 91% precision, but about 1% at π = 0.001. Both are always reported so the flattering figure cannot be quoted by accident.Two assumptions the published model forces into the open, emitted in
assumptionson every assessment:LEF = Vonly because retention period × threat event frequency is assumed to be 1 (a single attempt on a retained model), and the LM product treats its factors as independent, which Sion §VI-B itself flags as a simplification.API
assess_riskis a separate call, not part ofrun_audit, so one expensive audit can be re-assessed cheaply under different α and π — exactly the parameters users vary.run_auditkeeps its signature and return type.A profile can be declared in
audit.yamlunder an optional top-leveluse_case:block. Declaring it triggers nothing; absent it, behaviour is unchanged.Four guards, each a way a naive version reports a confidently wrong number
MIAResult._get_result_fixed_fprusesmax(..., default=0.0), so TPR at an FPR the audit set cannot express comes back as0.0. A 200-non-member audit asked for α = 0.001 would read as zero risk. The layer refuses below1 / n_non_members, or (non-strict, used by the webapp) withholds derived figures with a warning.attack_pcurrently reports an inverted ROC for every target (measured AUC 0.214 where correctly oriented is 0.786). Results with AUC < 0.5 are rejected by name rather than averaged in. The underlying bug is out of scope here.MIAResult.get_strongestmaximises ROC AUC; risk needs the attack that wins at the user's operating point. These disagree routinely.MIAResulttabulates fractions (TPR@1%FPR), the legacymetrics/attack_result.pyhelper returns percentages (TPR@1.0%FPR). The layer accepts fractions and rejects a percentage-valued table rather than letting a 100× error reach a risk figure.Webapp
All scoring moved to Python behind
POST /jobs/{job_id}/risk; the frontend posts a profile and renders the response, so no thresholds live in TypeScript. The results view leads with numbers and puts explanations behind info buttons, with a three-box schematic of the model. Warnings deliberately stay visible as a caveat chip — a caveat like "this operating point was not measurable" must not require a click to discover.Two bugs found and fixed while building it: risk state was lost when switching result tabs (the tab bar unmounts the tab components), and in cross-job compare mode only the first job's models were ever assessed, with model-name keys that collide across jobs.
Scope
MIA only, behind a family-agnostic
VulnerabilityMeasurementschema so MINV, GIA and synthetic data can implement it later without reworking the risk layer.assessment.pyimports nothing fromleakpro.attacks.Not doing the FAIR Bayesian network: it needs published priors we do not have, and it buys a defensible combine for inputs that are the controller's own judgement either way. Sion's PERT + Monte Carlo treatment of uncertainty is the natural v2 and would let us report a distribution rather than a point.
Testing
85 tests in
leakpro/tests/risk/, all passing, and the full suite is 362 passed / 0 failed.run_auditand then assess it, asserting the JSON is self-contained and the PDF gains a Risk section.ruffclean overleakpro/; frontendtscandvite buildpass.License compliance
pyproject.tomlandpackage.jsonunchanged).NOTICEupdate needed.Review notes
examples/mia/cifar/gains a commenteduse_case:block and two notebook cells as documentation; no example behaviour changes.