Repository navigation
fix: correctness and security pass over attacks and defenses (0.2.0) - #3
Merged
Merged
Conversation
Several public functions either raised on every call or returned silently
wrong results. This makes each function do what its name claims, and adds the
guardrails (tests, lint, packaging, CI) that were missing.
Attacks
- jsma: was indexing a rank-4 Jacobian with 5 indices and mutating an eager
tensor in place; rewritten as a real saliency-map attack for any input rank.
- spsa: passed a tensor (not a tf.Variable) to the optimizer and hard-coded
rank-4 inputs; now a correct finite-difference estimate with Adam.
- ead: returned the input unchanged with default args (alpha == beta); now a
proper ISTA/proximal step on the elastic-net objective.
- cw: the L2 term and kappa were inert (x was already clipped); now a real
tanh-space C&W with a fixed target class.
- deepfool: ignored any budget and returned out-of-range values; now computes
per-class boundaries and respects epsilon/clip range.
- fgsm: output is clipped to the valid range.
- all attacks: accept sparse/one-hot/binary labels, honour from_logits,
call the model with training=False, support clip_min/clip_max, and no
longer freeze targeted attacks at step 0.
- deepfool/jsma/spsa no longer project against the whole batch.
- word_swap/char_swap preserve whitespace, newlines and punctuation.
Defenses
- every preprocessing defense can now be saved/reloaded (layers were defined
inside function bodies, so no defended model was serialisable).
- jpeg_compression, feature_squeezing, total_variation_minimization: restore
the gradient via straight-through so attacks against a defended model work.
- median_smoothing is a real median filter (was avg_pool2d); gaussian_blur
honours sigma (was avg_pool2d).
- pixel_deflection no longer raises; deterministic when seeded.
- adversarial_training batches attack generation and keeps the caller's
optimizer/loss; defensive_distillation stops double-applying softmax and
scales by T^2; ALP now generates adversarial examples; gradient_masking
warns that it is not a security defense.
- randomized_smoothing clips and is seedable, plus smooth_predict() with a
certified-radius estimate.
- invalid parameters raise instead of producing NaN; subclassed/multi-input
models get a clear TypeError.
Packaging / CI / docs
- replace setup.py ({{VERSION_PLACEHOLDER}}) with pyproject.toml; pip install .
works from a checkout again.
- bound dependencies, require Python >= 3.9.
- add 171 tests, ruff config and a CI workflow; rewrite the publish workflow
with SHA-pinned actions, a tag/version check, a test gate and PyPI Trusted
Publishing.
- add SECURITY.md, CODE_OF_CONDUCT.md, CHANGELOG.md, CODE_REVIEW.md,
Dependabot and pre-commit; correct the README support matrix.
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…brief - Remove gradient_masking()/GradientMaskingModel entirely. It only masked training gradients and had no effect on an attacker's gradients (attacks against a wrapped model were bit-identical to the base model), so it is a false defense. Removed from the package namespace, __all__, the module docstring, the README support matrix and the tests; a regression test now asserts it stays gone. - Delete CODE_REVIEW.md and move its findings into the README as a 'What was wrong before 0.2.0' section (attacks, defenses, packaging/CI tables) and a Limitations section. - Add a 'Documentation update brief' code cell to the README describing every 0.2.0 API change for the docs agent. - CHANGELOG: gradient_masking moves from Fixed to Removed with the rationale; test count updated to 170. Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
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.
Summary
A full review of the package found that 7 of 11 attacks and 4 of 15 defenses were broken, silently inert, or were not the algorithm they claimed to be — plus the package could not be installed from a checkout and had no tests at all. This PR fixes the correctness and security problems, removes a false defense entirely, and adds the missing guardrails.
All findings are now documented in the README under What was wrong before 0.2.0 and Limitations. (The standalone review document was folded into the README at the maintainer's request.)
What was broken (measured, not inferred)
jsma,spsa,pixel_deflectionraised on every calltf.Variable,maxvalnot scalareadreturned the input unchanged with default argsmax|δ| = 0.0000— a user would conclude their model is robustcw'scandkappahad no effectc=1,κ=0vsc=1000,κ=50deepfoolreturned invalid images[-0.90, 2.08]for[0,1]inputfgsmreturned unclipped output[-0.010, 1.007]median_smoothingwasavg_pool2d;gaussian_blurignoredsigmafgsm(defended_model, ...)→ValueError: NoneTypesave()/load_model()gradient_maskingwas a no-op at inferencefgsm(base) == fgsm(masked)— removedpip install .failed{{VERSION_PLACEHOLDER}}@masteractions with a long-lived PyPI token, deprecated::set_output, any tag publishesWhat changed
Removed —
gradient_masking. Deleted rather than deprecated. It only masked training gradients, never the gradients an attacker computes, so it had no measurable defensive effect. Gradient masking is a documented false defense (Athalye et al., 2018): it makes a model look robust while leaving it vulnerable. Stated in the README changelog, the support matrix, a dedicated Limitations bullet,CHANGELOG.md, and enforced by a regression test.Attacks —
jsma,spsa,ead,cw,deepfoolrewritten as the algorithms they claim to be;fgsmclipped; all attacks now accept one-hot/sparse/binary labels, auto-detectfrom_logits, call the model withtraining=False, and takeclip_min/clip_max(so normalised inputs are not clipped to[0,1]). Targeted attacks no longer freeze at step 0. Text helpers preserve whitespace and punctuation.Defenses — all layers hoisted to module level with
get_config()and Keras registration, so defended models save and reload; straight-through gradients for JPEG/squeezing/TV; real median filter and real Gaussian kernel;pixel_deflectionfixed and seedable;adversarial_trainingbatches attack generation and keeps the caller's optimiser/loss; distillation no longer double-applies softmax; ALP actually generates adversarial examples;smooth_predict()added for randomized smoothing with a certified radius.Packaging / CI / docs —
pyproject.tomlreplacessetup.py, bounded deps,requires-python >= 3.9; 170 tests + ruff + CI; publish workflow rewritten with SHA-pinned actions, a tag/version check, a test gate and PyPI Trusted Publishing;SECURITY.md,CODE_OF_CONDUCT.md,CHANGELOG.md, Dependabot, pre-commit.Docs handover — the README now ends with a self-contained
textcode cell brief for the docs agent, covering every changed signature, the removal, migration notes and the tone/format rules.Verification
Breaking changes
The numerical output of
cw,deepfool,ead,spsaandjsmachanges (they were wrong before), several defaults changed,gradient_maskingwas removed, and image-only defenses now raise on non-image inputs instead of silently returning the input. Released as 0.2.0 — seeCHANGELOG.md.Not addressed here: the library is still TensorFlow/Keras only, and preprocessing defenses remain defeatable by adaptive attacks (now documented).