Exempla · combat audits

AGORA on the loose — real repos, real findings

E disputatione veritas— from debate, truth

We point the forum at security projects pulled at random off GitHub and let a panel of diverse-model minds tear into the code. Below are live audits — each project named, each git link in plain sight, each flaw the forum surfaced highlighted in full.

The brief the reviewer received

"Find the first security project you come across on GitHub. Convene AGORA to name the single most dangerous flaw and how to close it, and to deliver a candid assessment of the developer's craft. Ground every claim in the code." Then — for breadth — two more random picks, audited the same way.

GoatsPass

Offline desktop password manager · claims "AES-256-GCM · Argon2id"

github.com/aditya123-coder/GoatsPass
Lang: Python · Tkinter Surface: single file · 1855 LOC Audited: 2026-06-09 · 03:13 UTC Panel: 8 summoned · 6 answered Format: debate · 2 rounds

⚡ What the forum found

CriticalAn unsalted SHA-256 of the master password lives in RAM — and silently defeats Argon2id

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.

self._pw_hash = hashlib.sha256(master.encode()).digest() # create / unlock verify_master → hmac.compare_digest(sha256(master), self._pw_hash) # bypasses Argon2id
HighA silent 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.

MediumThree smaller slips, each a real miss

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.

GPT-4o · OpenAI professorflipped → Abuilder

“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.

Hermes-405B · Nous professorheld dissent · Bskeptic

“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.

Architect · Nemotron · Kimi · (GPT-4o joined)

Camp B — supply-chain RCE first

The auto-pip installer owns the host at import, before the vault opens. Immediate, whole-system, pre-unlock.

Skeptic (dissent held) · GLM
Synthesis (Gemini): the facilitator integrated rather than voted — fix both: delete _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

“Upper-amateur,
approaching intermediate”
Right high-level choices — fatally undermined by a secondary path. “A gap between their crypto vocabulary and their crypto intuition.” — Architect (Claude-Sonnet)
What they got rightWhere they slipped
Argon2id t=4·128MiB·p=4; PBKDF2 600k fallbackFatal: unsalted SHA-256 master cache as the verify oracle
AES-256-GCM with fresh secrets.token_bytes(12) nonce; 32-byte salt; hmac.compare_digestpip 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
Lang: Python CLI Surface: ~1300 LOC · has tests Audited: 2026-06-09 · 04:00 UTC Panel: 8 summoned · 5 answered Format: debate · 1 round

⚡ What the forum found

CriticalNo authenticated chunk-count or terminal marker → a silent truncation attack

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

# decrypt: read [4B len][ct] in a loop until EOF — header has no total count, no final flag fix: bind is_final into AAD → aad = b"chunk_%d_final=%d" % (i, is_final)
MediumLegacy tar-extraction guard (Python < 3.12) has a known path-traversal bypass

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.

AcquittedThe XOR-counter nonce was flagged — then cleared by the forum

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

Claude-Sonnet-4.6 · Anthropic professorarchitect

“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.”

Kimi · Moonshot masterbuilder

“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.”

Synthesis (Gemini): no disagreement on severity — only on the fix. Recommended: an inline 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

“Competent enthusiast,
not a security engineer”
Better than most developer-built crypto — no homebrew ciphers, no ECB — but the chunked AEAD is structurally incomplete: it has the shape of secure design without fully internalizing why each piece exists.
What they got rightWhere 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 suiteCritical: 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
Lang: TypeScript · Electron Surface: WebCrypto · cryptoService 447 LOC Audited: 2026-06-09 · 04:04 UTC Panel: 8 summoned · 5 answered Format: debate · 1 round

⚡ What the forum found

CriticalA live padding oracle in the legacy path — reachable by tampering one localStorage field

The 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 switch
HighWeak, downgrade-able legacy key handling — MD5, SHA-1, and a single fast SHA-256 verify

The 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.

MediumVault at rest in localStorage, not OS safeStorage; dead CBC writer left in

The 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

Claude-Sonnet-4.6 · Anthropic professorarchitect

“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.”

Kimi · Moonshot masterbuilder

“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.

Synthesis (Gemini): remove the automatic downgrade routing from the unlock flow; move legacy support into an offline, user-triggered, sandboxed migrator that re-encrypts to AES-GCM and burns the old key; adopt OS safeStorage. The one named risk: users who never run the migrator before the cutoff lose their old vaults.

Verdict on the developer

“Senior instincts,
junior legacy discipline”
The modern path is high-caliber; the legacy path is an indefensible attack surface. The padding oracle isn't subtle — unauthenticated CBC with a typed padding exception is a textbook mistake.
What they got rightWhere they slipped
Excellent Electron hardening (contextIsolation + sandbox + nodeIntegration:false); AES-GCM; PBKDF2 310k; domain-separated salts; constantTimeEqualCritical: legacy AES-CBC padding oracle via mutable localStorage; MD5/SHA-1 KDF + fast legacy verify; localStorage at rest
The pattern across all three. Each developer got the hard primitives right — and each was undone by a secondary path a single reviewer skims past: a cached fast hash, an unmarked stream boundary, a legacy downgrade switch. A forum of different families argues until those blind spots surface — and even acquits a false flag (vault-tar's nonce) rather than just piling on. That is the boost. To learn when it's worth convening one, see Usage.