From 41595605b9500118e745452b9e7c5602c05d1520 Mon Sep 17 00:00:00 2001 From: cesnimda Date: Sat, 11 Apr 2026 16:57:15 +0200 Subject: [PATCH] Add hostile fixture setup for authz testing --- .../M015-hostile-fixture-setup.json | 9 ++++ .../M015-hostile-fixture-setup.md | 47 +++++++++++++++++ scripts/m015-hostile-fixture.sh | 28 +++++++++++ .../HostileFixtureDb.csproj | 11 ++++ tools/hostile-fixture-db/Program.cs | 50 +++++++++++++++++++ 5 files changed, 145 insertions(+) create mode 100644 docs/security-assessments/M015-hostile-fixture-setup.json create mode 100644 docs/security-assessments/M015-hostile-fixture-setup.md create mode 100644 scripts/m015-hostile-fixture.sh create mode 100644 tools/hostile-fixture-db/HostileFixtureDb.csproj create mode 100644 tools/hostile-fixture-db/Program.cs diff --git a/docs/security-assessments/M015-hostile-fixture-setup.json b/docs/security-assessments/M015-hostile-fixture-setup.json new file mode 100644 index 0000000..00efb0b --- /dev/null +++ b/docs/security-assessments/M015-hostile-fixture-setup.json @@ -0,0 +1,9 @@ +{ + "alice_email": "alice.m015@example.com", + "bob_email": "bob.m015@example.com", + "company_id": 1, + "job_id": 1, + "correspondence_id": 1, + "attachment_id": 1, + "api_base": "http://localhost:5202/api" +} \ No newline at end of file diff --git a/docs/security-assessments/M015-hostile-fixture-setup.md b/docs/security-assessments/M015-hostile-fixture-setup.md new file mode 100644 index 0000000..cd83ff1 --- /dev/null +++ b/docs/security-assessments/M015-hostile-fixture-setup.md @@ -0,0 +1,47 @@ +# M015 Hostile Fixture Setup + +## Goal + +Produce a trustworthy local runtime for cross-user authorization probes. + +## Key discovery + +The default development SQLite database in `JobTrackerApi/jobtracker.db` is **not** a trustworthy authorization-test target: + +- it contains Identity and some later feature tables +- it does **not** contain the core domain tables needed for real cross-user job/correspondence/attachment probing +- current startup `Migrate()` behavior is therefore insufficient as the only hostile-test setup path + +## Chosen fixture strategy + +Use a dedicated clean SQLite fixture database created from the current EF model with `EnsureCreated()` semantics through a tiny helper program: + +- helper project: `tools/hostile-fixture-db/` +- bootstrap script: `scripts/m015-hostile-fixture.sh` + +This keeps the hostile runtime inside repo code and the real API host while avoiding ad-hoc manual SQL. + +## What the helper does + +- creates a clean `jobtracker.db` under a caller-provided data root +- builds the schema from the current `JobTrackerContext` model +- verifies the presence of core tables needed for M015: + - `Companies` + - `JobApplications` + - `Correspondences` + - `Attachments` + - `RuleSettings` + - `AspNetUsers` + +## Runtime plan for S02 + +1. Run `scripts/m015-hostile-fixture.sh`. +2. Start the API with `Data__Root` pointing at that clean fixture root. +3. Mint an admin dev token against the fixture DB. +4. Create/reuse Alice and Bob through real API paths. +5. Seed Alice-owned company/job/correspondence/attachment fixtures through the real API. +6. Capture ids for cross-user hostile probes. + +## Honest boundary + +This slice establishes the trusted runtime path and fixture strategy. The full two-user seeded dataset and exploit execution belong in the next slice. diff --git a/scripts/m015-hostile-fixture.sh b/scripts/m015-hostile-fixture.sh new file mode 100644 index 0000000..3deab70 --- /dev/null +++ b/scripts/m015-hostile-fixture.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +FIXTURE_ROOT="${FIXTURE_ROOT:-$REPO_ROOT/.tmp/m015-fixture}" +API_PORT="${API_PORT:-5202}" +API_BASE="http://localhost:${API_PORT}/api" +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TMP_DIR"' EXIT + +mkdir -p "$FIXTURE_ROOT" + +echo "[m015] creating clean hostile-test db under $FIXTURE_ROOT" +dotnet run --project "$REPO_ROOT/tools/hostile-fixture-db/HostileFixtureDb.csproj" -- "$FIXTURE_ROOT" > "$TMP_DIR/db.json" +python3 - <<'PY' "$TMP_DIR/db.json" +import json, sys +payload=json.load(open(sys.argv[1])) +required={'Companies','JobApplications','Correspondences','Attachments','RuleSettings','AspNetUsers'} +missing=sorted(required-set(payload['tables'])) +if missing: + raise SystemExit(f"missing tables after fixture db init: {', '.join(missing)}") +print('[m015] db tables ok') +PY + +echo "[m015] ready to start API with:" +echo " Data__Root=$FIXTURE_ROOT ASPNETCORE_ENVIRONMENT=Development dotnet run --project JobTrackerApi/JobTrackerApi.csproj" +echo "[m015] API base: $API_BASE" diff --git a/tools/hostile-fixture-db/HostileFixtureDb.csproj b/tools/hostile-fixture-db/HostileFixtureDb.csproj new file mode 100644 index 0000000..ed196e7 --- /dev/null +++ b/tools/hostile-fixture-db/HostileFixtureDb.csproj @@ -0,0 +1,11 @@ + + + Exe + net9.0 + enable + enable + + + + + diff --git a/tools/hostile-fixture-db/Program.cs b/tools/hostile-fixture-db/Program.cs new file mode 100644 index 0000000..2087fda --- /dev/null +++ b/tools/hostile-fixture-db/Program.cs @@ -0,0 +1,50 @@ +using JobTrackerApi.Data; +using JobTrackerApi.Services; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using System.Text.Json; + +if (args.Length < 1) +{ + Console.Error.WriteLine("Usage: dotnet run --project tools/hostile-fixture-db -- "); + return 1; +} + +var dataRoot = Path.GetFullPath(args[0]); +Directory.CreateDirectory(dataRoot); +var dbPath = Path.Combine(dataRoot, "jobtracker.db"); +if (File.Exists(dbPath)) +{ + File.Delete(dbPath); +} + +var config = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["Data:Root"] = dataRoot, + }) + .Build(); + +var currentUser = new StaticCurrentUserService(null); +var options = new DbContextOptionsBuilder() + .UseSqlite($"Data Source={dbPath}") + .Options; + +await using var db = new JobTrackerContext(options, currentUser); +await db.Database.EnsureDeletedAsync(); +await db.Database.EnsureCreatedAsync(); + +var tables = await db.Database.SqlQueryRaw("SELECT name AS Value FROM sqlite_master WHERE type='table' ORDER BY name;").ToListAsync(); +var payload = new +{ + dataRoot, + dbPath, + tables, +}; +Console.WriteLine(JsonSerializer.Serialize(payload)); +return 0; + +file sealed class StaticCurrentUserService(string? userId) : ICurrentUserService +{ + public string? UserId => userId; +}