Adversarial Attack (FGSM/PGD)
Make an ImageNet classifier see a cat as a bald eagle
- model
- image
- adversarial_image
- original_class_name
- adversarial_class_name
- original_class_idx
- adversarial_class_idx
The README demo tells you everything you need to know: a tabby cat goes in, ResNet18 calls the output a bald eagle, and to your eye the image hasn't changed. That's an adversarial example - a perturbation so small it's invisible to you, but loud enough that a classifier completely changes its mind. This node is the attack itself.
What it is and why you'd reach for it
AdversarialAttack is the flagship node of the bemoregt/ComfyUI_AdversarialAttack pack. Feed it the model from Load ResNet18 and an image, and it rewrites that image just enough to flip the model's prediction. It implements the two textbook attacks: FGSM (Fast Gradient Sign Method, Goodfellow et al. 2014) and PGD (Projected Gradient Descent, Madry et al. 2017).
Honest framing: nobody is putting this in a production pipeline. It's a teaching toy and a research curiosity - a way to see how fragile a real classifier is, the same math behind image-poisoning schemes like Glaze and Nightshade. If you found it in a downloaded workflow, it's there as a self-contained demo. It's also just a fun way to spend ten minutes making the model confidently call things the wrong name.
How it works
Both attacks run gradient descent on the image itself, not on the model weights. The node computes cross-entropy loss between ResNet18's prediction and a label, backpropagates into the pixels, and nudges the input along the gradient's sign.
- FGSM takes a single step: add
ε · sign(∇loss)to the image. Fast, weak, good for a first look. - PGD iterates. It starts the image at a random point inside an
ε-sized ball, takesalpha-sized steps, and re-projects into the ball after every step - strictly stronger than FGSM.
One neat implementation detail: the gradient work runs in a fresh thread because ComfyUI executes nodes inside torch.inference_mode(), which would otherwise block autograd. You don't need to care, but it's why this pack works on current ComfyUI.
target_class decides the goal. Leave it at -1 and the attack is untargeted - it just maximizes loss on the true class. Set it to any ImageNet index (0–999) and it becomes targeted: the loss flips sign and the model gets steered toward that exact class.
The inputs that matter
Most of these you can leave alone, but these four are the ones you'll actually touch:
model- theRESNET_MODELoutput of Load ResNet18. Nothing works without it.image- any standard ComfyUI IMAGE.epsilon- the attack budget, default0.03. This is the dial you'll turn. Too small and nothing flips;0.1is a sledgehammer but the noise starts to show.attack_method-FGSMorPGD. Switch to PGD (and raiseiterationsfrom its default40) when FGSM can't fool the model.
alpha is the per-step PGD size, and resize_to_224 resizes for inference, then resizes the output back to your original image dimensions. Leave that one on.
Outputs
You get five: adversarial_image (the perturbed image, same size as your input) plus the original and adversarial class names and their ImageNet indices as separate string/int outputs. Wire adversarial_image into a Preview node to see the (visually identical) result, hook the class-name strings into text display - or just read the console, which prints both [AdversarialAttack] lines for you.
Installation
Install once and you get all three nodes in the pack under the AdversarialAttack category. ComfyUI Manager, searching "ComfyUI_AdversarialAttack", is the easy route. Otherwise:
cd ComfyUI/custom_nodes
git clone https://github.com/bemoregt/ComfyUI_AdversarialAttack
then restart ComfyUI. Its only requirements are torch>=2.0 and torchvision>=0.15 - both already in any ComfyUI environment, so there's no dependency hell here. The one real download is the ImageNet ResNet18 weights, pulled by Load ResNet18 on first run, so you need internet the first time.
Common issues
- "Attack did not change class" prints to the console. The classic fix: raise
epsilonto0.05or0.1, or switch from FGSM to PGD with moreiterations. Some images are just stubborn. - It's slow. The pack loads the model on CPU with no device input on the node, so a 40-iteration PGD run can take a while. If you have a GPU you'll want to get the model onto it another way before attacking.
- Targeted attack does nothing. You're probably picking a
target_classtoo far from your starting image, or the budget is too small. Crank epsilon and iterations.
If your goal is just "what does this image actually classify as," that's the other node in the pack - Classify Image (ResNet18) - which is the honest way to verify the attack worked at all.
Inputs (8)
| Name | Type | Default | Description |
|---|---|---|---|
| model | RESNET_MODEL | — | |
| image | IMAGE | — | |
| attack_method | COMBO | 2 options: FGSM, PGD | |
| epsilon | FLOAT | 0.0300.001–0.3 | — |
| iterations | INT | 401–500 | — |
| alpha | FLOAT | 0.0070.001–0.1 | — |
| target_class | INT | -1-1–999 | — |
| resize_to_224 | BOOLEAN | true | — |
Outputs (5)
| Name | Type | Description |
|---|---|---|
| adversarial_image | IMAGE | — |
| original_class_name | STRING | — |
| adversarial_class_name | STRING | — |
| original_class_idx | INT | — |
| adversarial_class_idx | INT | — |