GoatsPass
Offline desktop password manager · claims "AES-256-GCM · Argon2id"
github.com/aditya123-coder/GoatsPass⚡ What the forum found
The vault keys with Argon2id (128 MiB, t=4) — but on every create/unlock the code also caches
a single-round, unsalted SHA-256(master) and uses that for re-authentication. Any memory dump, swap file,
or core dump yields a hash crackable at ~10⁹ guesses/second — the expensive KDF evaporates.
pip install --break-system-packages runs at import — supply-chain RCE before the vault opens_ensure_deps() shells out to pip at module import, errors swallowed to DEVNULL, with floor-only version pins.
A compromised mirror or typosquat owns the process before the user types a password.
Vault & config written at default umask (no chmod 600) · GCM called with AAD=None (version byte unauthenticated) ·
the 5-attempt lockout counter is in-memory only and resets on restart · the release is an unsigned zip.
The debate — positions that moved
A clean two-camp split in round 1; under cross-examination in round 2 the strong generalists swapped sides while the dissenter held the line.
“The single most dangerous weakness is the unsalted SHA-256 cache of the master password in memory … the auto-pip installer is a theoretical supply-chain risk, but less critical than directly compromising the master password.” — changed its pick after seeing the others.
“The auto-pip installer is the most dangerous — it runs before main(), a supply-chain RCE that owns the host before any unlock.” — the protected dissenter kept Camp B alive.
Camp A — silent crypto betrayal
The SHA-256 cache defeats Argon2id and leaks a fast-crackable secret to memory/swap. In-protocol, permanent, invisible.
Camp B — supply-chain RCE first
The auto-pip installer owns the host at import, before the vault opens. Immediate, whole-system, pre-unlock.
_pw_hash (verify by KDF + decryption probe) and remove the auto-pip installer. The one named cost: an Argon2id re-derive adds ~1–2 s and may tempt users to disable the gate.Verdict on the developer
approaching intermediate”
| What they got right | Where they slipped |
|---|---|
Argon2id t=4·128MiB·p=4; PBKDF2 600k fallback | Fatal: unsalted SHA-256 master cache as the verify oracle |
AES-256-GCM with fresh secrets.token_bytes(12) nonce; 32-byte salt; hmac.compare_digest | pip install --break-system-packages at import; default-umask files; AAD=None; in-memory lockout; unsigned zip |
vault-tar (vtar)
Streaming AES-256-GCM file/directory encryption CLI · ships a test suite
github.com/Nobab007/vault-tar⚡ What the forum found
The format streams [len][ciphertext] chunks and the decryptor reads until EOF. Each chunk's GCM tag verifies fine —
but nothing authenticates that the sequence is complete. An attacker drops trailing chunks; decryption exits cleanly with a
truncated plaintext and no error, no warning, no failed tag.
“Encrypt a tarball containing run.sh across three chunks. Attacker drops chunk 3. Victim decrypts, extracts, runs a truncated script. No alarm fires.” — Architect
The fallback checks os.path.abspath(dest).startswith(abs_out) with no trailing separator — a sibling like
out_evil satisfies the prefix and escapes the target dir; symlink members aren't filtered. Fix: abs_out + os.sep.
The unconventional base_nonce XOR counter nonce drew scrutiny, but the panel acquitted it: the 12-byte base is
random per file, so nonces never repeat. The forum doesn't just pile on — it also clears false flags.
The debate — near-unanimous
“GCM authenticates each chunk's integrity, but nothing authenticates completeness of the sequence. This is a classic truncation attack on a streaming AEAD — it defeats the core security promise silently. This is how TLS 1.3, age, and most production streaming AEADs guard the boundary.”
“Primitives are solid (PBKDF2 1.2M, AES-256-GCM, per-chunk AAD), but protocol design is slipshod … shipping test_security.py without catching a missing MAC on the stream boundary shows the dev can assemble crypto libraries but hasn't internalized adversarial robustness.”
is_final flag bound into AAD plus the abs_out + os.sep traversal fix. The one named risk: a format v2 breaks backward compatibility with v1 archives.Verdict on the developer
not a security engineer”
| What they got right | Where they slipped |
|---|---|
PBKDF2 1.2M iterations; AES-256-GCM AEAD; per-chunk AAD binds order; filter="data" on Py 3.12 (read the CVE); ships a test suite | Critical: truncation gap — the canonical streaming-AEAD failure mode; legacy startswith traversal bug |
SecureKey-Vault
Electron + TypeScript password manager · WebCrypto · strong Electron hardening
github.com/Bragii044/SecureKey-Vault⚡ What the forum found
localStorage fieldThe legacy decryptor runs unauthenticated AES-CBC and throws a distinguishable "Invalid PKCS7 padding" on bad
padding — the textbook oracle signal. The unlock flow auto-downgrades to it whenever isLegacyVerificationHash(db.verificationHash)
is true — and verificationHash is mutable data in localStorage. An attacker flips it, forces legacy mode,
and recovers plaintext block-by-block.
“POODLE/BEAST-class against a local vault. Electron's sandbox hardening is irrelevant here — the oracle is exercised through the application's own API.” — Architect
unlock → if isLegacyVerificationHash(db.verificationHash): decryptLegacyData() # AES-CBC, no MAC ↑ mutable localStorage field = the downgrade switchThe legacy KDF is hand-rolled MD5 (OpenSSL EVP_BytesToKey) and tries both SHA-256 and SHA-1 candidates (a downgrade),
while verifyLegacyPassword is a single fast SHA-256(password+salt) — trivial offline brute-force on any legacy vault.
localStorage, not OS safeStorage; dead CBC writer left inThe encrypted blob sits in Electron's plaintext leveldb rather than OS-keychain-bound safeStorage — PBKDF2 strength becomes
the only at-rest layer. And encryptLegacyData (AES-CBC) is defined but unwired — latent hazard / dead code.
The debate — unanimous on the killer
“The attacker's chain: craft a verificationHash to force legacy mode → submit adaptive chosen-ciphertext queries → the padding exception leaks plaintext block-by-block. The security model was designed for the new path; the legacy path was treated as a compatibility chore rather than an attack surface. In a password manager, that asymmetry is the most dangerous kind.”
“The developer shows real skill — contextIsolation, sandbox, AES-GCM, domain-separated salts, constant-time compare — but this one legacy downgrade path negates it all. Good primitives, bad trust architecture.”
safeStorage. The one named risk: users who never run the migrator before the cutoff lose their old vaults.Verdict on the developer
junior legacy discipline”
| What they got right | Where they slipped |
|---|---|
Excellent Electron hardening (contextIsolation + sandbox + nodeIntegration:false); AES-GCM; PBKDF2 310k; domain-separated salts; constantTimeEqual | Critical: legacy AES-CBC padding oracle via mutable localStorage; MD5/SHA-1 KDF + fast legacy verify; localStorage at rest |