Share
## https://sploitus.com/exploit?id=090923AF-6D8A-5AFA-9AFD-82B08520323E
# SafeVault โ€” Secure Coding, Authentication & Vulnerability Remediation

> **Capstone project โ€” Coursera "Security and Authentication" (Microsoft Full-Stack Developer, Course 8).**
> A small ASP.NET Core application that stores sensitive user records, built to demonstrate three security disciplines end to end: **secure input handling**, **authentication + role-based authorization**, and a **debug-and-remediate audit** of real SQL-injection and stored-XSS vulnerabilities.

**.NET 10 ยท xUnit ยท 89 passing tests ยท advisory-clean build**

---

## What this capstone demonstrates

This repository is the combined result of the three graded activities from the course. Each maps to a pillar of the application:

| Activity | Theme | Where it lives |
| --- | --- | --- |
| **1 โ€” Writing Secure Code** | Allow-list input validation, parameterized SQL, output encoding | `SafeVault.Core/Validation`, `SafeVault.Data/Sql` |
| **2 โ€” Authentication & Authorization** | bcrypt password hashing, RBAC, live access matrix | `SafeVault.Core/Security`, `SafeVault.Core/Authorization`, `SafeVault.Web/Authorization` |
| **3 โ€” Debugging & Resolving Security Issues** | Reproduce โ†’ fix โ†’ prove SQL injection & stored XSS | `SafeVault.Data/Search`, `SafeVault.Core/Notes`, `SafeVault.Web/wwwroot/audit.html` |

Full write-ups for each activity โ€” with attack payloads, screenshots, and design rationale โ€” are in [`docs/`](docs/).

---

## The three security pillars

### 1. Secure input (Activity 1)

- **`SafeVaultInputValidator`** โ€” *allow-list* validation, never blocklist. Usernames match `^[A-Za-z0-9._-]+$`; emails match a bounded standard pattern. Input is trimmed and control characters are stripped **before** validation. Unsafe values are rejected outright rather than "cleaned".
- **`SafeVaultSqlBuilder`** โ€” builds fixed SQL text and passes every user value as a **parameter** (`@Username`, `@Email`). User input never becomes part of the SQL grammar.
- **XSS** โ€” accepted values are returned through `WebUtility.HtmlEncode`, and the UI writes them with `textContent`, so returned data is never interpreted as HTML.

### 2. Authentication & authorization (Activity 2)

- **`BCryptPasswordHasher`** โ€” bcrypt with work factor 12; each hash carries its own random salt. Passwords are **only** stored and compared as hashes โ€” never plaintext.
- **`AuthenticationService`** โ€” one generic error for both "wrong password" and "unknown user", plus a **timing guard** (verifies against a throwaway hash for unknown usernames) so response time can't be used to enumerate accounts.
- **`RoleAuthorizer.Evaluate(role, level)`** โ€” a pure function that is the *single source of truth* for access. The web pipeline's `RoleAuthorizationHandler` calls it and only ever `Succeed`s on `Allow`, letting ASP.NET Core naturally return **401** (not signed in) vs **403** (signed in, wrong role).
- A **live access matrix** in `webform.html` probes every protected endpoint with the current session cookie and shows the real `200 / 401 / 403` results per demo account.

### 3. Debug & remediate (Activity 3)

Two features added *after* the auth work carried the vulnerabilities โ€” and each risky operation ships in **two implementations behind one interface** so the "before" is honest and executable:

| Concern | Vulnerable (audit subject, `[Obsolete]`) | Secure (shipped) |
| --- | --- | --- |
| User search | `ConcatenatingUserSearchQuery` โ€” `LIKE '%{term}%'` string-built | `ParameterizedUserSearchQuery` โ€” bound `@pattern`, LIKE-wildcards escaped |
| Note rendering | `RawNoteHtmlRenderer` โ€” content interpolated into HTML | `EncodedNoteHtmlRenderer` โ€” content routed through `EncodeForHtml` |

- The SQL injection is reproduced against a **real in-memory SQLite database** โ€” the tautology (`' OR '1'='1`) and a UNION payload that **exfiltrates password hashes** genuinely execute against the vulnerable query and return **0 rows** against the parameterized one.
- The vulnerable classes are `[Obsolete]` and are **never bound by the running app** โ€” they exist only so the tests and the audit console can run the real attack and show the contrast.
- A transitive NuGet advisory (**NU1903 / GHSA-2m69-gcr7-jv3q**) surfaced by `Microsoft.Data.Sqlite` was resolved by *upgrading* to a patched `SQLitePCLRaw.bundle_e_sqlite3 3.0.3` โ€” not by suppressing the warning. The build is advisory-clean.

---

## The Security Audit Console

`SafeVault.Web/wwwroot/audit.html` (the landing page) is a live audit tool that fires the real attacks and shows the **vulnerable build beside the shipping build**. The vulnerable HTML is displayed as *escaped text* โ€” never executed โ€” and each vulnerable search runs against a throwaway, freshly-seeded database. The comparison endpoints (`/audit/*`) are **Development-only**; the shipping surface never runs the vulnerable code.

The Activity 2 access-control console remains available at `/webform.html`.

---

## Architecture

```
SafeVault.slnx
โ”œโ”€ SafeVault.Core/     Domain: validation, bcrypt auth, RBAC, notes/search abstractions (no I/O)
โ”‚  โ”œโ”€ Validation/      SafeVaultInputValidator (allow-list + EncodeForHtml)
โ”‚  โ”œโ”€ Security/        AuthenticationService, BCryptPasswordHasher, IUserAccountRepository
โ”‚  โ”œโ”€ Authorization/   RoleAuthorizer (pure decision logic), AccessLevel, AccessDecision
โ”‚  โ”œโ”€ Notes/           VaultNote, VaultNoteValidator, Raw[Obsolete]/Encoded HTML renderers
โ”‚  โ””โ”€ Search/          IUserSearchQuery, UserSearchMatch
โ”œโ”€ SafeVault.Data/     Adapters: parameterized SQL, SQLite, repositories, seed data
โ”‚  โ”œโ”€ Sql/             SafeVaultSqlBuilder, SqliteSafeVault (real in-memory DB)
โ”‚  โ”œโ”€ Search/          Concatenating[Obsolete] vs Parameterized query implementations
โ”‚  โ””โ”€ Repositories/    In-memory + ADO.NET user/account repositories
โ”œโ”€ SafeVault.Web/      ASP.NET Core minimal API + cookie auth + audit console
โ”‚  โ”œโ”€ Authorization/   AccessLevelRequirement, RoleAuthorizationHandler
โ”‚  โ””โ”€ wwwroot/         audit.html (default), webform.html (access matrix)
โ””โ”€ SafeVault.Tests/    89 xUnit tests
```

**Dependency direction** follows the Dependency Inversion Principle: `IUserAccountRepository` lives in `SafeVault.Core` (next to `AuthenticationService`), not in `SafeVault.Data`. Since `Data` already depends on `Core`, the abstraction stays in the domain and the service is unit-testable with a stub repository.

---

## Build, test, run

```bash
dotnet build SafeVault.slnx
dotnet test  SafeVault.slnx        # 89 tests
dotnet run --project SafeVault.Web # open http://localhost:5070/  โ†’ Security Audit Console
```

`dotnet run` defaults to the Development environment, which the `/audit/*` comparison endpoints require.

### Demo accounts

Public sandbox credentials (shown on quick-login buttons โ€” **not production secrets**; passwords are only ever stored as bcrypt hashes):

| Username | Password | Role |
| --- | --- | --- |
| `sarah.admin` | `Sarah!Admin123` | Admin |
| `payroll_viewer` | `Payroll!View123` | User |

---

## Test coverage โ€” 89 passing

| Suite | Tests | Proves |
| --- | --- | --- |
| `UserSearchInjectionTests` | 9 | Tautology & UNION injection succeed against the vulnerable query, neutralised by the parameterized one โ€” on a real SQLite DB |
| `NoteXssRenderingTests` | 4 | Raw renderer emits live ``; encoded renderer neutralises script/author/attribute payloads |
| `VaultNoteValidatorTests` | 6 | Content kept verbatim (not blocklisted); required/length rules; control chars stripped |
| `AuditEndpointTests` | 8 | `/search` gates 401/403/200 and neutralises payloads; `/notes` encodes stored XSS over real HTTP |
| `ExistingSurfaceAuditTests` | 4 | The carried-over SQL builder and encoder were already secure |
| `BCryptPasswordHasherTests` | 10 | Hash โ‰  plaintext, salt differs per call, malformed hash โ†’ `false` not throw, >72 bytes rejected |
| `AuthenticationServiceTests` | 10 | Valid login succeeds; wrong password & unknown user fail with the *same* message; case-insensitive username |
| `AuthEndpointTests` | 13 | Login 200/401; `/admin` 401/403/200; logout ends session; response never leaks a hash |
| `RoleAuthorizerTests` | 7 | anonymous โ†’ 401, non-admin โ†’ 403, admin โ†’ 200 for admin resources |
| `InMemoryUserAccountRepositoryTests` | 4 | Demo accounts store bcrypt hashes; case-insensitive lookup; round-trips through `SaveAsync` |
| `SafeVaultInputValidatorTests` | 9 | Allow-list rejects SQLi/XSS usernames & unsafe emails; encodes XSS payloads |
| `SafeVaultSqlBuilderTests` | 4 | Injection payloads never appear in command text, only as parameter values |
| `SubmitEndpointTests` | 3 | `/submit` returns 400 for attack payloads, 200 for valid submissions |

Live end-to-end verification (Playwright) confirmed the audit console fires both attacks and that the neutralised XSS renders with **zero executable `` nodes** in the browser DOM.

---

## Lab boundaries (out of scope)

- Persistence is **in-memory** (plus an in-memory SQLite DB for the search demo). The ADO.NET repositories + `database.sql` are the parameterized production data layer but are not wired to a live server in the lab.
- Registration (`/submit`) and login are separate user stores; uniting them is future work.
- Role assignment is static (seed accounts only); there is no user-management endpoint.
- The `http` profile runs under Development; a production host would add `UseHttpsRedirection()`, `CookieSecurePolicy.Always`, and a non-developer exception page.

---

## Documentation

- [Activity 1 โ€” Writing Secure Code](docs/Using%20Microsoft%20Copilot%20to%20Generate%20Secure%20Code.md)
- [Activity 2 โ€” Implementing Authentication and Authorization](docs/Using%20Microsoft%20Copilot%20to%20Implement%20Authentication%20and%20Authorization.md)
- [Activity 3 โ€” Debugging and Resolving Security Vulnerabilities](docs/Using%20Microsoft%20Copilot%20to%20Debug%20and%20Resolve%20Security%20Vulnerabilities.md)