DEV Community

Cover image for I Tried to Break My Own Security Suite. It Fought Back (and I Found 1 Thing I'm Not Happy About)
Akhouri Anmol Kumar
Akhouri Anmol Kumar

Posted on

I Tried to Break My Own Security Suite. It Fought Back (and I Found 1 Thing I'm Not Happy About)

Description: "8,432 lines of Python, 200 bit-flips, 460 fuzzed inputs, and one honest flaw. Here's what happened when I attacked ATLOCK v5 before releasing it."

TL;DR: I built a single-file security suite in pure Python. Before release, I locked it in a clean virtual environment and tried to wreck it. Most things held. One thing made me frown. You can try the current version (v4) today, and v5 is already built and waiting for a milestone. 👇


🎬 The setup

Everyone says "my tool is secure." Almost nobody publishes the receipts.

So I did something slightly uncomfortable: I took ATLOCK v5 (not public yet), dropped it into a fresh venv, and attacked it like a stranger would. No GUI clicking, no gentle demos. Raw crypto functions, corrupted files, wrong passwords, forged keys.

python -m venv atenv && source atenv/bin/activate
pip install cryptography argon2-cffi pillow numpy qrcode requests
python ATLOCK_v5.py --selftest
Enter fullscreen mode Exit fullscreen mode

First, some context on what we're even looking at:

ATLOCK v4 (public today) ATLOCK v5 (built, unreleased)
Size 3,074 lines · 23 classes 8,432 lines · 55 classes
File encryption Fernet (AES-128-CBC + HMAC) AES-256-GCM, chunked, key-committing
Password hashing PBKDF2-SHA256, 200k iterations Argon2id (64 MiB floor, 128 MiB for vault)
Sharing files none X25519 + Ed25519 public-key encrypt & sign
Recovery none Shamir secret sharing (3-of-5)
2FA none TOTP + FIDO2 hardware keys
Built-in self-test none 33 checks + a corruption fuzzer

Fun fact: v5 is nearly 3x the code of v4. So the real question was: did the extra code add security, or just extra places to crash?


🧪 Round 1: The built-in self-test

$ python ATLOCK_v5.py --selftest
  [OK] Zip-slip / traversal names rejected
  [OK] Doctored-header KDF ceiling is enforced (no DoS via .atl file)
  [OK] Empty (0-byte) file container round-trips
  [OK] Recovery key split into shares (3 of 5) rebuilds exactly
  [OK] Frozen v1 / v2 / v3.0 containers from ATLOCK 5.0 still open
  ...
----------------------------------------
ALL PASSED   (33/33)
Enter fullscreen mode Exit fullscreen mode

Passing your own tests is the easy part, of course. So I stopped trusting them and wrote my own.


💥 Round 2: Try to corrupt it

The attack: seal a 5 MB random file, then flip one random bit, anywhere in the encrypted blob. Do that 200 times. If even one flip decrypts to wrong data without complaint, that's silent corruption, the nightmare scenario.

bitflip_200: rejected=200  silent_corruption=0  unexpected_crash=0
truncation (5 cut points): rejected=5/5
wrong password: AtlockAuthError
same input sealed twice -> different ciphertext: True
Enter fullscreen mode Exit fullscreen mode

200 out of 200 rejected. Zero silent corruption. Zero crashes.

Then I let the built-in fuzzer loose:

$ python ATLOCK_v5.py --fuzz 300
fuzzed 300 container mutations, 100 garbage shares, 60 junk imports:
no crashes, no silent corruption.
Enter fullscreen mode Exit fullscreen mode

That's 460 hostile inputs, no drama.


⚡ Round 3: Is it slow? (Security that's painful gets switched off)

Operation Result
Seal 5 MB file 0.77 s
Open 5 MB file 0.63 s
Container overhead 134 bytes
Argon2id (64 MiB, t=3) 0.16 s
Argon2id (128 MiB, t=4, vault) 0.45 s
PBKDF2 200k (what v4 uses) 0.05 s

This is the part I find genuinely interesting. v4's PBKDF2 takes ~50 ms. An attacker with a GPU rig loves that. v5's Argon2id makes each guess cost 128 MiB of RAM, which is exactly what GPUs are bad at. Same login delay for you, a far worse deal for them.


🔐 Round 4: Public-key mode (the new toy)

Two identities, Alice and Bob. Alice encrypts and signs a file for Bob.

Scenario Outcome
Bob decrypts ✅ works, signature verified
Alice tries to open Bob's file ❌ "encrypted for a different identity"
Flip one byte in the file ❌ rejected
Edit the file after signing ❌ signature check fails
Shamir: any of the 10 possible 3-of-5 combos ✅ rebuilds the key exactly
Shamir: only 2 shares ❌ refused

Everything behaved. Except...


😬 The thing I'm not happy about

I tested sender pinning: Bob says "I only trust files from this key," then I hand him a file signed by someone else.

>>> atl_pk_decrypt_file(enc, out, "bobpass", expected_sender_pub=wrong_key)
{'signed': True, 'signer_matches': False}
Enter fullscreen mode Exit fullscreen mode

It decrypted anyway. The app shows WARNING: this is NOT the sender key you pasted!, but it does not stop.

Is that a bug? Honestly, it's a design choice, and I can argue both sides:

  • Warn-only: the user stays in control, and you might genuinely want to read a file first and ask the sender later.
  • Hard-fail: if you pinned a key, you already told the app you don't want anyone else. A warning is a polite way to get ignored.

I lean towards hard-fail when a key is pinned. Where do you land? This is my real question for the comments. 👇


🔎 Other things the scan flagged

I ran bandit over both files:

v4 v5
High 0 1
Medium 0 3
  • The 1 "High" is SHA-1, used for the Have I Been Pwned password check. That API requires SHA-1, and only the first 5 characters of the hash ever leave your machine (k-anonymity). Flagged, explained, fine.
  • The 3 "Mediums" are urlopen calls. Worth pinning URL schemes in the final release. Noted.

Also caught a docs bug while reading: the v5 header still says "UI is unchanged from v5". A copy-paste leftover from earlier versions. Embarrassing, tiny, getting fixed.


🧱 What I did NOT test (be honest, right?)

ATLOCK is Windows-first, and my sandbox was Linux. So:

  • ✅ Tested: all crypto, container format, fuzzing, Shamir, public-key, signatures, CLI
  • ❌ Not tested here: the GUI, Windows Hello, DPAPI (falls back to a dev mode off-Windows), BitLocker checks, camera/intruder capture, the startup watchdog, FIDO2 with a real YubiKey

The project's own docs say it plainly too: a pure-Python tool cannot stop an attacker who is already running code as you, and secure-delete can't be guaranteed on SSDs. ATLOCK raises the cost; it doesn't claim magic. I'd rather you hear that from me than discover it later.


🚀 Try it, then come back for v5

ATLOCK v4 is public right now:

👉 Download ATLOCK v4

Here's the deal I'm offering:

  1. Grab v4 and try it for a few days. Lock a file. Break a password on purpose. Tell me what annoys you.
  2. Wait a little. v5 is already finished (everything above came from it), but I'm releasing it when Akhouri Systems crosses 1,000 downloads.
  3. Right now we're at 910. That's 90 downloads away. 🎯

When it lands, you upgrade to Argon2id, AES-256-GCM, hardware-key 2FA and public-key encryption, with your existing v4 habits intact.


💬 Let's argue (nicely)

I'd really like your takes on these:

  1. Pinned sender key doesn't match: warn or hard-fail?
  2. Would you trust a pure-Python security tool with real files, or does that make you nervous? Be brutal.
  3. Argon2id at 128 MiB: too heavy for older laptops, or still not enough?
  4. What would make you switch from your current vault or file locker?

Drop a comment. The best criticism ends up in the v5 changelog with your name on it. 🏷️

Built by Akhouri Anmol Kumar · An Akhouri Systems product

Top comments (21)

Collapse
 
merbayerp profile image
Mustafa ERBAY •

Really nice write-up. I especially appreciate that you included what you did not test — that makes the security claims much more credible.

On sender pinning, I’d strongly prefer hard-fail.

If expected_sender_pub is explicitly provided, that is no longer just informational metadata; it is a security policy. Returning successfully with signer_matches=False means the cryptographic layer detected a policy violation but left enforcement to the caller/UI.

I’d probably make the semantics explicit:

  • no pinned sender → decrypt normally and report signer identity
  • pinned sender + match → decrypt successfully
  • pinned sender + mismatch → fail closed with a dedicated exception
  • if someone really wants “decrypt anyway”, require an explicit override rather than making that the default

That also makes the API safer for future integrations where there may be no human around to notice the warning.

One other thing I’d test before v5: make sure authentication happens before any plaintext becomes observable, especially with chunked AES-GCM. Corrupting random bits is a great test, but deliberately targeting chunk boundaries, lengths, nonces, tags, reordered/duplicated chunks, and the final truncated chunk would be interesting too.

Overall, publishing the failure cases alongside the successful tests is exactly the kind of security engineering write-up I like seeing. 👍

Collapse
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

Thank you brooo, this is exactly the review I was hoping for. I ran your chunk-level attacks: 91 targeted mutations (length prefixes, tags, reordering, duplication, dropped and truncated final chunks, cross-seal splices) were all rejected, and 82/82 header bit-flips with the hash recomputed were rejected toooo,
Your pinning point turned out to be worse than I'd written: a pinned sender with an unsigned file also decrypted silently. V5 now fails closed with a dedicated exception, and an explicit override is required to decrypt anyway.

You also made me look at plaintext release: the streaming path hands out authenticated chunks before later ones are verified, and one viewing helper left partiaL plaintext on Disk after A failure. Both are fixed....:)

Collapse
 
merbayerp profile image
Mustafa ERBAY •

Hahaha, now this is why I enjoy security reviews. 😄

I came looking for one pinning edge case and somehow we ended up finding an unsigned-sender path, streaming plaintext exposure, and partial plaintext left on disk after failure.

That’s a very productive rabbit hole. 😂

More importantly, huge respect for actually testing the suggestions, publishing what you found, and fixing the behavior instead of defending the original design.

This is exactly how security software should mature. 🔐👍

Thread Thread
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

I especially published the post and commented on post to trigger you
"Mustafa ERBAY hehehe" because I know you will use your security top tier mind and give me feedbacks, and more.
Bro Can I mention you later when ATLOCK v5 will publicly released in ATLOCK v5 repo your name?

Thread Thread
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

Also Bro How can i make this post viral too?

Thread Thread
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

mustafa bro, should I port ATLOCK v5 for linux and mac too?
currently it is only for windows.

Thread Thread
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

reply?

Thread Thread
 
merbayerp profile image
Mustafa ERBAY •

Hahaha, I knew that “Mustafa ERBAY hehehe” was bait. 😂

You deliberately triggered the security-review daemon, and unfortunately for you, it worked.

And yes, of course you can mention me in the v5 repo when it’s released. I’d actually be happy to be mentioned for the review/testing feedback — just keep it factual; I don’t want to accidentally become “ATLOCK independently audited by Mustafa” 😂. I reviewed a few design assumptions and helped you find some edge cases. That’s the accurate description.

About Linux and macOS: yes, I think you should port it, but don’t try to reproduce every Windows-specific feature immediately.

First make the crypto/container core truly cross-platform:

  • encrypt/decrypt
  • Argon2id
  • signatures and sender pinning
  • X25519/Ed25519
  • Shamir recovery
  • container compatibility
  • CLI and self-tests

Then put platform-specific security behind separate adapters: DPAPI/Windows Hello on Windows, Keychain/Secure Enclave where appropriate on macOS, and Secret Service/keyring or equivalent mechanisms on Linux.

Most importantly: the same .atl file should open on Windows, Linux and macOS. One format, one crypto behavior, different OS integrations.

And for making the post viral: don’t optimize for “viral.” You accidentally found the better formula already. 😂

“I built a security tool” is ordinary.

“I asked people to break my security tool, they found assumptions I had missed, I reproduced them, fixed them, and added regression tests” is a story.

When v5 launches, publish the before/after evidence. Show exactly what failed, what changed, and which tests now prevent regression. Security people love receipts more than marketing.

Now stop summoning me by name unless you’re prepared to add more tests. 😂🔐

Collapse
 
sinarezaei profile image
Sina Rezaei •

Really good write-up. What I like most is that you didn’t stop at “all tests passed.” You started attacking the assumptions behind the system.

That distinction is important in security. A function can behave correctly under normal tests while the overall flow still has a dangerous edge case.

The plaintext-release issue is a great example of that. Authentication can be correct at the chunk level, but if the system exposes part of the plaintext before the full verification flow finishes, the security property of the whole operation changes.

I’ve seen the same pattern outside file encryption too. For example, an API can successfully authenticate a request, but if it starts processing or exposing sensitive data before authorization is fully resolved, the individual checks may all be “working” while the system is still wrong.

That’s what makes this kind of adversarial testing valuable: you’re not only asking “does this function reject bad input?” You’re asking “at what point does the system actually trust the data?”

And honestly, publishing the failures and then showing how the design changed because of them makes this much more useful than a typical “my security tool passed 100% of its tests” post. 🔐

Collapse
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

Thanks, and that's what make ATLOCK different.
Have you downloaded v4? if not then it's okay you can wait for v5.
v5 is coming soon.

Collapse
 
normalnorma profile image
Norma •

Appreciate you publishing the receipts, especially the sender-pinning result. Most "I tested my own tool" posts only show the wins.

Warn or hard-fail on a pinned key: hard-fail, and I don't think it's close. Pinning is an explicit statement of intent, and expected_sender_pub is the caller saying "reject anyone else." A warning only helps if a human reads it, and the return value {'signed': True, 'signer_matches': False} is exactly the shape that gets ignored by a script or a wrapper UI that checks signed and moves on. That's a classic fail-open pattern. I'd do three things:

  1. Raise a dedicated exception (something like AtlockSenderMismatch) when a pin is supplied and doesn't match.
  2. Don't write plaintext to out before the sender check passes. If the file has already been decrypted to disk, the warning is cosmetic.
  3. Keep the "read it first, ask later" use case as an explicit opt-in, e.g. allow_unpinned_sender=True or a separate inspect mode, so the permissive path is a deliberate choice rather than the default.

The GUI can still offer an "I understand, open anyway" button on top of that. The API should be strict and the UI can be forgiving, not the other way around.

Pure-Python trust: the language matters less than what's underneath. If the primitives come from cryptography and argon2-cffi (both wrap audited C libraries), I'm not worried about timing or implementation bugs in AES-GCM or Argon2 themselves. My concerns are different:

  • Memory hygiene. Python can't reliably zero secrets. Strings and bytes get copied, interned, and left for the GC, so keys and passwords may linger in RAM or swap. Worth stating in the docs next to your SSD caveat.
  • Supply chain. A single-file tool that pulls requests, pillow, numpy and qrcode has a larger dependency surface than a vault needs. Consider splitting the crypto core from the optional features so the core can be audited on its own.
  • Audit status. A thorough self-test is great, but it isn't a third-party review. Say so plainly, and a short threat model doc would help people judge fit.

Argon2id at 128 MiB: fine for most laptops from the last ~8 years, but I'd make it adaptive rather than fixed. Benchmark on first run, store m, t, p in the header, and target something like 0.3 to 0.5 s on that machine. A low-RAM device could then use a lower floor with a clear warning, and you could raise parameters over time with a rekey-on-open path. Your doctored-header KDF ceiling check is a good sign you've already thought about the flip side, a malicious file demanding absurd memory.

A few other notes from the post:

  • 200 bit-flips is a good sanity check, but it's mostly testing GCM's tag. It would be worth adding targeted flips in the header, chunk boundaries and chunk counters. Chunked AEAD schemes usually fail at reordering, truncation at a chunk boundary, and chunk duplication, so make sure each chunk binds its index and a final-chunk flag. You mention 5 truncation points; confirm at least one lands exactly on a chunk boundary.
  • Key-committing is a nice touch that many designs skip. Worth a short explanation in the README, since it's a real differentiator.
  • For the three urlopen Mediums, pinning to https and rejecting other schemes is the right fix, and for the HIBP call, add a timeout and fail closed on errors rather than treating "couldn't check" as "not pwned."
  • Since you tested only on Linux, I'd label the Windows-specific pieces (DPAPI, Windows Hello) as experimental until they've been run on real hardware.

What would make me switch: a documented threat model, a reproducible build or signed releases, an independent review (even informal), and a stable, documented container format so files stay readable years from now. Your frozen v1/v2/v3.0 compatibility test is the right instinct there.

Good luck on the way to 1,000. Looking forward to seeing the v5 changelog.

Collapse
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

Thanksss, bro.. for the careful read, this is the most useful feedback the post got. Going through it:
Sender pinning: I agree, hard-fail. That's how it works in v5: if you pin a sender key and the file is unsigned or signed by someone else, it raises AtlockSenderMismatch before any plaintext is written. Reading it anyway needs an explicit allow_sender_mismatch=True. I've added a self-test that checks no output file is left behind on a mismatch..
Chunk attacks: Good point, the 200 bit-flips mostly test the GCM tag. Each chunk already binds the header hash, its index and a final-chunk flag, but I hadn't tested that properly. The self-test now covers:
a bit-flip in every header byte
chunk reorder, duplication, drop and an extra chunk
truncation exactly on a chunk boundary
a prefix of a file never opening as a whole file
urlopen / HIBP: All three calls now go through one helper that refuses any non-https URL, including redirects to one. A breach check that can't run (offline, timeout, bad reply) now shows as "could not check, not verified" instead of "safe". It used to fall through to "safe", which was a real bug.
Argon2: It's already benchmarked per machine, with m/t/p stored in each file header and a ceiling that rejects doctored headers. I lowered the target to about 0.5 s and added a warning on very slow machines. Rekey-on-open to raise parameters over time isn't built yet.
Docs: The docstring now says plainly that there's been no third-party audit (the self-test and fuzzing are my own), has a short threat model, notes that Python can't reliably wipe secrets from RAM, and explains key commitment.
Not done: Splitting the crypto core into its own auditable module. The core only needs cryptography and argon2-cffi, but it's still one file today. It's on the list.

Thanks again.... :)

Collapse
 
koda2026 profile image
Harun - solo dev •

@akhourianmolkumar Bro, this is the spirit of true engineering.

"I tried to break it... it fought back." That is the best compliment a security tool can receive. If it doesn’t fight back, it’s useless.

Finding "1 thing you’re not happy about" is actually a win. Most people hide those flaws until they explode in production. You surfaced it now, while you can fix it.

KODA and ATLOCK are siblings in chaos. I’m shipping fast and fixing bugs in hours; you’re stress-testing boundaries and finding cracks. We are covering the full spectrum of HYNAWEB: Velocity + Resilience.

Drop the link when you patch that flaw. I’ll be first in line to test it against my Worker’s injection defenses. Let’s see if KODA’s 1µs regex filter can catch what ATLOCK misses. 😂️🐯

Collapse
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

KODA & ATLOCK
2 greatest product ever made by 12-13

Collapse
 
xulingfeng profile image
xulingfeng •

You may refer to Jev, which works like an access control system. If identity verification fails, it blocks the operation entirely rather than just showing a warning.

Collapse
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

I will definitely look at it.

Collapse
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

bro how can I make this post viral too

Collapse
 
xulingfeng profile image
xulingfeng •

There’s no guaranteed trick to make a post go viral. Virality relies on a mix of topic, headline, timing and luck.
Your security suite project is interesting. Try writing a hands‑on article about breaking your own ATLOCK, similar to this post. Stories about finding flaws and fixing them always attract developers. But don’t chase virality as your main goal. Focus on telling a genuine technical story first.

Thread Thread
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

yeah, that's the point.
Mustafa ERBAY just helped me a lot.
due to him I fixed one flaw in ATLOCK v5.

Thread Thread
 
xulingfeng profile image
xulingfeng •

Great to hear! External feedback is priceless for tightening up security tools 🔐

Collapse
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

Mustafa ERBAY hehehe