Skip to content

Add tests, and fix the authorization, credential and concurrency defects they found - #1

Merged
kutsibalci merged 4 commits into
mainfrom
guvenlik-duzeltmeleri-ve-testler
Aug 5, 2026
Merged

Add tests, and fix the authorization, credential and concurrency defects they found#1
kutsibalci merged 4 commits into
mainfrom
guvenlik-duzeltmeleri-ve-testler

Conversation

@kutsibalci

Copy link
Copy Markdown
Owner

Adds a test suite to the project, and fixes what it found.

What the tests found

Finding Impact
AdminController had no [Authorize] The whole management area answered anonymous requests. POST /Admin/KursSil deleted a course and cascaded to its applications with no session.
Administrator credentials were string literals kullaniciAdi == "admin" && sifre == "1234" in the login action — the working password was published with the source.
Passwords stored in clear text o.Sifre == sifre. Reading the database was reading every password.
Cancellation never checked ownership Any signed-in student could cancel anyone else's place by incrementing an id.
Capacity was a read-then-write race Count, compare, then insert — concurrent requests all read the same count.
Identity resolved by display name Two students with the same name resolved to whichever row came back first.

The capacity bug, measured

The original logic was reproduced under the new test harness: 15 concurrent applications to a capacity-5 course enrolled all 15. Capacity is now claimed with a conditional UPDATE ... WHERE KayitliSayisi < Kontenjan, so the check and the write are a single statement. Same test, same load: exactly 5.

Also in this PR

  • .gitignore had no line endings — the whole file parsed as one comment, so nothing was ignored.
  • Passwords: PBKDF2-HMAC-SHA256, per-password salt, fixed-time comparison, iteration count embedded in the hash.
  • Antiforgery validation is global rather than per-action; logout is a POST; returnUrl restricted to local URLs.
  • Registration binds to a view model instead of the entity (over-posting).
  • Uniqueness checks run in SQL instead of .ToList().Any(...) over the whole table.
  • Unique indexes on student number, e-mail, course name, instructor name and (KursId, OgrenciId); instructor deletes are Restrict.
  • EF Core 10.0.7 → 10.0.10, and SQLitePCLRaw pinned past GHSA-2m69-gcr7-jv3q.
  • CI: Release build, 62 tests, coverage, and a job that fails on vulnerable packages including transitive ones.
  • MIT licence.

Upgrading

The migration drops the clear-text Sifre column. A hash cannot be derived from it, so existing student accounts must be created again.

🤖 Generated with Claude Code

Every rule sat on a single line beginning with ##, so git read the whole
file as one comment and ignored nothing. Rewritten with real newlines, plus
entries for test results and coverage output.
AdminController carried no [Authorize] attribute, so every action on the
management area answered anonymous requests -- including KursSil and
EgitmenSil, which delete rows and cascade. It now requires the Admin role.

Administrator credentials were compared against string literals in the login
action, so the working password shipped with the source. Administrators are
now rows in Yoneticiler, seeded from configuration; development generates a
random password and logs it once, production refuses to seed without one.

Student passwords were stored and compared in clear text. They are now
PBKDF2-HMAC-SHA256 with a per-password salt and a fixed-time comparison, with
the iteration count embedded in the stored hash so it can be raised later.

Cancelling an application took an id and deleted it with no ownership check,
letting any signed-in student cancel anyone else's place. Cancellation now
compares the owner against the caller's NameIdentifier claim.

Capacity was checked by counting applications, comparing against Kontenjan,
and then inserting -- concurrent requests could all read the same count. A
Kurs.KayitliSayisi counter is now claimed with a conditional UPDATE, so the
check and the write are one statement; measured against the old logic, 15
concurrent applications to a capacity-5 course enrolled all 15.

Also: identity is read from the NameIdentifier claim instead of the display
name, antiforgery validation is global rather than per-action, returnUrl is
restricted to local URLs, logout is a POST, registration binds to a view model
instead of the entity, and uniqueness checks run in SQL rather than after
pulling the table into memory.
62 tests. Each creates its own SQLite file and runs the real migrations
against it; the in-memory provider supports neither transactions nor
ExecuteUpdate, which are the mechanisms the capacity fix depends on.

AuthorizationIntegrationTests hosts the application through
WebApplicationFactory so routing, model binding, cookie authentication,
authorization and the antiforgery filter all run. A missing [Authorize]
attribute fails these; calling a controller method directly would not.

EnrollmentServiceTests includes a 15-way concurrent enrolment against a
capacity-5 course. The harness was verified by reproducing the original
count-then-insert logic under it, which enrolled all 15.
CI builds in Release, runs the tests and collects coverage. A second job
fails the build on any package with a known advisory, including transitive
ones -- the vulnerable SQLite native bundle in this project arrived through
EF Core rather than through a direct reference.

README documents what the tests found and how capacity is enforced now,
rather than restating the feature list.
@kutsibalci
kutsibalci merged commit 5670c78 into main Aug 5, 2026
2 checks passed
@kutsibalci
kutsibalci deleted the guvenlik-duzeltmeleri-ve-testler branch August 5, 2026 07:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant