Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ Use `pwsh.exe` (PowerShell 7+) for all scripts. Do not use `powershell.exe`.
### Session Start (Run Once Per Session)

1. **Read `AGENTS-README-FIRST.yaml`** in the repo root for the current API key, endpoints, and base URL
2. **Verify marker signature** using HMAC-SHA256 with the workspace API key before contacting the server
2. **Verify marker signature** using `Initialize-McpSession` from `McpSession.psm1` (preferred), or by manually recomputing HMAC-SHA256 over the exact fields listed in `signature.fields` of the marker file in that order, each as `key=value\n`, trailing LF on the final line, UTF-8 encoded. Do not infer the payload shape from the YAML structure — use only the `signature.fields` list.
3. **GET `/health`** with a random nonce — confirm the response echoes that exact nonce
4. **Review recent session history** and current TODOs only after verification succeeds
5. **POST an initial session log turn** for the session
Expand Down
57 changes: 30 additions & 27 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,40 @@
<ItemGroup>
<PackageVersion Include="BlazorMonaco" Version="3.4.0" />
<PackageVersion Include="bunit" Version="2.6.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.12" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.12" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.3.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.5" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.4.0" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="10.4.0" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.0" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.15.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.15.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.15.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.12" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.12" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.12" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.12" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.12" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Analyzers" Version="9.0.12" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.12" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.5" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.5" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.5" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.5" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.5" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Analyzers" Version="10.0.5" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.5" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="5.3.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.3.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="5.3.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="5.3.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="5.3.0" />
<PackageVersion Include="Microsoft.Build.Framework" Version="17.11.48" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.12" />
<PackageVersion Include="Microsoft.Build" Version="18.0.2" />
<PackageVersion Include="Microsoft.Build.Framework" Version="18.0.2" />
<PackageVersion Include="Microsoft.Build.Tasks.Core" Version="18.0.2" />
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="18.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.5" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageVersion Include="xunit.v3" Version="3.2.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
Expand All @@ -50,8 +53,8 @@
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageVersion Include="Serilog.Sinks.Http" Version="9.2.1" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.1.2" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.5" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="10.0.5" />
<PackageVersion Include="Microsoft.Agents.AI" Version="1.0.0-rc3" />
<PackageVersion Include="Microsoft.Agents.AI.OpenAI" Version="1.0.0-rc3" />
<PackageVersion Include="Microsoft.Agents.AI.Workflows" Version="1.0.0-rc3" />
Expand All @@ -68,18 +71,18 @@
<PackageVersion Include="NetEscapades.Configuration.Yaml" Version="3.1.0" />
<PackageVersion Include="Microsoft.ML.OnnxRuntime" Version="1.24.1" />
<PackageVersion Include="HnswIndex" Version="1.6.0" />
<PackageVersion Include="System.Text.Json" Version="9.0.5" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.2" />
<PackageVersion Include="System.Text.Json" Version="10.0.5" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.5" />
<PackageVersion Include="Reqnroll" Version="3.0.0" />
<PackageVersion Include="Reqnroll.xUnit" Version="3.0.0" />
<PackageVersion Include="Reqnroll.Tools.MsBuild.Generation" Version="3.0.0" />
<PackageVersion Include="Handlebars.Net" Version="2.1.6" />
<PackageVersion Include="QRCoder" Version="1.6.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.7.0" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.5" />
<PackageVersion Include="Duende.IdentityServer" Version="7.4.7" />
<PackageVersion Include="Duende.IdentityServer.AspNetIdentity" Version="7.4.6" />
<PackageVersion Include="Duende.IdentityServer.EntityFramework" Version="7.4.6" />
<PackageVersion Include="Duende.IdentityServer.AspNetIdentity" Version="7.4.7" />
<PackageVersion Include="Duende.IdentityServer.EntityFramework" Version="7.4.7" />
<PackageVersion Include="Terminal.Gui" Version="2.0.0" />
<PackageVersion Include="Nuke.Common" Version="9.0.4" />
<PackageVersion Include="Microsoft.VSSDK.BuildTools" Version="17.14.2120" />
Expand Down
2 changes: 1 addition & 1 deletion GitVersion.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mode: ContinuousDelivery
next-version: 0.2.91
next-version: 0.2.92
assembly-versioning-scheme: MajorMinorPatch
assembly-informational-format: '{SemVer}+Branch.{BranchName}.Sha.{ShortSha}'
branches:
Expand Down
4 changes: 4 additions & 0 deletions build/_build.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

<ItemGroup>
<PackageReference Include="Nuke.Common" />
<!-- Override vulnerable MSBuild 17.12.6 transitive dependency from Nuke.Common -->
<PackageReference Include="Microsoft.Build" />
<PackageReference Include="Microsoft.Build.Tasks.Core" />
<PackageReference Include="Microsoft.Build.Utilities.Core" />
</ItemGroup>

</Project>
6 changes: 6 additions & 0 deletions docs/Project/Functional-Requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -606,3 +606,9 @@ The server shall provide full CRUD operations for explicit graph entity nodes an

The server shall provide endpoints to list indexed documents with chunk counts and token totals, retrieve chunks for a specific document ordered by chunk index, and delete a document with cascade removal of its chunks and corresponding vector index entries. All operations shall be workspace-scoped and available via REST endpoints, MCP tools, and REPL commands.

## FR-MCP-081 Self-Describing Marker Signature Canonicalization

The generated `AGENTS-README-FIRST.yaml` marker file shall embed the complete `marker-v1` canonical field list and encoding contract inside the `signature` block so that any agent can reconstruct and verify the HMAC-SHA256 signature without consulting server source code or helper modules.

**Covered by:** `MarkerFileService`, `MarkerSignature`

7 changes: 7 additions & 0 deletions docs/Project/Technical-Requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,13 @@ When a session is completed, the module SHALL remove both the legacy wrapper cac

**Covered by:** `tools/powershell/McpSession.psm1`, `tools/powershell/McpTodo.psm1`, `tools/powershell/McpContext.psm1`, `docs/context/module-bootstrap.md`, `docs/USER-GUIDE.md`

## TR-MCP-SEC-005

**Self-Describing Marker Signature Payload** — The `MarkerSignature` model SHALL include an ordered `fields` array listing the 28 canonical field names used to construct the `marker-v1` HMAC-SHA256 payload, and a `format` string describing the `key=value\n` per-field encoding, trailing LF on the final line, and UTF-8 encoding. The `fields` array SHALL be the authoritative source of truth for field order; `BuildSignaturePayload` SHALL derive its output order from the same array so the two cannot diverge.
**Status:** ✅ Complete

**Covered by:** `MarkerFileService`, `MarkerSignature`, `MarkerFileServiceTests`

## TR-MCP-SEC-004

**Provider-Native At-Rest Encryption with No-Loss Transition Procedures** — The storage layer SHALL support optional at-rest encryption using only provider-native or provider-extension facilities: SQLite SEE, PostgreSQL `pg_tde` on Percona Server for PostgreSQL, and native SQL Server TDE. The implementation SHALL detect desired-versus-actual encryption state at startup, SHALL refuse to silently continue when the configured state and live state differ, and SHALL require explicit no-data-loss enable/disable/rotation procedures that preserve existing data when configuration changes. SQL Server LocalDB may be used for provider and migration coverage, but SQL Server TDE validation requires a non-LocalDB SQL Server target.
Expand Down
1 change: 0 additions & 1 deletion src/McpServer.Services/McpServer.Services.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
<PackageReference Include="Serilog.Sinks.Http" />
<PackageReference Include="YamlDotNet" />
<PackageReference Include="Handlebars.Net" />
<PackageReference Include="Microsoft.Extensions.Http" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
<ProjectReference Include="..\McpServer.McpAgent\McpServer.McpAgent.csproj" />
<ProjectReference Include="..\McpServer.Storage\McpServer.Storage.csproj" />
Expand Down
84 changes: 57 additions & 27 deletions src/McpServer.Services/Services/MarkerFileService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,23 @@ public static class MarkerFileService
public const string MarkerFileName = "AGENTS-README-FIRST.yaml";
internal const string MarkerSignatureCanonicalization = "marker-v1";
internal const string MarkerSignatureVerifier = "workspace_api_key";
internal const string MarkerSignatureFormat = @"key=value\n per field in fields order; trailing LF on final line; UTF-8 encoded";

/// <summary>
/// TR-MCP-SEC-005: The authoritative ordered field list for the <c>marker-v1</c> HMAC-SHA256 payload.
/// <see cref="BuildSignaturePayload"/> derives its field order from this array.
/// </summary>
/// <remarks>27 fields total: 10 top-level + 17 endpoint fields.</remarks>
internal static readonly string[] SignaturePayloadFields =
[
"canonicalization", "port", "baseUrl", "apiKey", "workspace", "workspacePath",
"pid", "startedAt", "markerWrittenAtUtc", "serverStartedAtUtc",
"endpoints.health", "endpoints.swagger", "endpoints.swaggerUi", "endpoints.mcpTransport",
"endpoints.sessionLog", "endpoints.sessionLogDialog", "endpoints.contextSearch",
"endpoints.contextPack", "endpoints.contextSources", "endpoints.todo",
"endpoints.repo", "endpoints.desktop", "endpoints.gitHub", "endpoints.tools",
"endpoints.workspace", "endpoints.serverStartupUtc", "endpoints.markerFileTimestamp",
];
private const string WorkspaceStateDirectoryGitIgnoreEntry = ".mcpServer/";

private static readonly ISerializer s_yamlSerializer = new SerializerBuilder()
Expand Down Expand Up @@ -110,6 +127,8 @@ public static async Task WriteMarkerAsync(
Algorithm = "HMAC-SHA256",
Canonicalization = MarkerSignatureCanonicalization,
Verifier = MarkerSignatureVerifier,
Fields = SignaturePayloadFields,
Format = MarkerSignatureFormat,
},
TrustBootstrap = new MarkerTrustBootstrap
{
Expand Down Expand Up @@ -289,34 +308,41 @@ internal static string BuildSignaturePayload(MarkerFile marker)
{
ArgumentNullException.ThrowIfNull(marker);

// Field values are resolved in the same order as SignaturePayloadFields (TR-MCP-SEC-005).
var fieldValues = new Dictionary<string, string>(StringComparer.Ordinal)
{
["canonicalization"] = marker.Signature.Canonicalization,
["port"] = marker.Port.ToString(CultureInfo.InvariantCulture),
["baseUrl"] = marker.BaseUrl,
["apiKey"] = marker.ApiKey,
["workspace"] = marker.Workspace,
["workspacePath"] = marker.WorkspacePath,
["pid"] = marker.Pid.ToString(CultureInfo.InvariantCulture),
["startedAt"] = marker.StartedAt,
["markerWrittenAtUtc"] = marker.MarkerWrittenAtUtc,
["serverStartedAtUtc"] = marker.ServerStartedAtUtc,
["endpoints.health"] = marker.Endpoints.Health,
["endpoints.swagger"] = marker.Endpoints.Swagger,
["endpoints.swaggerUi"] = marker.Endpoints.SwaggerUi,
["endpoints.mcpTransport"] = marker.Endpoints.McpTransport,
["endpoints.sessionLog"] = marker.Endpoints.SessionLog,
["endpoints.sessionLogDialog"] = marker.Endpoints.SessionLogDialog,
["endpoints.contextSearch"] = marker.Endpoints.ContextSearch,
["endpoints.contextPack"] = marker.Endpoints.ContextPack,
["endpoints.contextSources"] = marker.Endpoints.ContextSources,
["endpoints.todo"] = marker.Endpoints.Todo,
["endpoints.repo"] = marker.Endpoints.Repo,
["endpoints.desktop"] = marker.Endpoints.Desktop,
["endpoints.gitHub"] = marker.Endpoints.GitHub,
["endpoints.tools"] = marker.Endpoints.Tools,
["endpoints.workspace"] = marker.Endpoints.Workspace,
["endpoints.serverStartupUtc"] = marker.Endpoints.ServerStartupUtc,
["endpoints.markerFileTimestamp"] = marker.Endpoints.MarkerFileTimestamp,
};

var builder = new StringBuilder();
AppendPayloadLine(builder, "canonicalization", marker.Signature.Canonicalization);
AppendPayloadLine(builder, "port", marker.Port.ToString(CultureInfo.InvariantCulture));
AppendPayloadLine(builder, "baseUrl", marker.BaseUrl);
AppendPayloadLine(builder, "apiKey", marker.ApiKey);
AppendPayloadLine(builder, "workspace", marker.Workspace);
AppendPayloadLine(builder, "workspacePath", marker.WorkspacePath);
AppendPayloadLine(builder, "pid", marker.Pid.ToString(CultureInfo.InvariantCulture));
AppendPayloadLine(builder, "startedAt", marker.StartedAt);
AppendPayloadLine(builder, "markerWrittenAtUtc", marker.MarkerWrittenAtUtc);
AppendPayloadLine(builder, "serverStartedAtUtc", marker.ServerStartedAtUtc);
AppendPayloadLine(builder, "endpoints.health", marker.Endpoints.Health);
AppendPayloadLine(builder, "endpoints.swagger", marker.Endpoints.Swagger);
AppendPayloadLine(builder, "endpoints.swaggerUi", marker.Endpoints.SwaggerUi);
AppendPayloadLine(builder, "endpoints.mcpTransport", marker.Endpoints.McpTransport);
AppendPayloadLine(builder, "endpoints.sessionLog", marker.Endpoints.SessionLog);
AppendPayloadLine(builder, "endpoints.sessionLogDialog", marker.Endpoints.SessionLogDialog);
AppendPayloadLine(builder, "endpoints.contextSearch", marker.Endpoints.ContextSearch);
AppendPayloadLine(builder, "endpoints.contextPack", marker.Endpoints.ContextPack);
AppendPayloadLine(builder, "endpoints.contextSources", marker.Endpoints.ContextSources);
AppendPayloadLine(builder, "endpoints.todo", marker.Endpoints.Todo);
AppendPayloadLine(builder, "endpoints.repo", marker.Endpoints.Repo);
AppendPayloadLine(builder, "endpoints.desktop", marker.Endpoints.Desktop);
AppendPayloadLine(builder, "endpoints.gitHub", marker.Endpoints.GitHub);
AppendPayloadLine(builder, "endpoints.tools", marker.Endpoints.Tools);
AppendPayloadLine(builder, "endpoints.workspace", marker.Endpoints.Workspace);
AppendPayloadLine(builder, "endpoints.serverStartupUtc", marker.Endpoints.ServerStartupUtc);
AppendPayloadLine(builder, "endpoints.markerFileTimestamp", marker.Endpoints.MarkerFileTimestamp);
foreach (var field in SignaturePayloadFields)
AppendPayloadLine(builder, field, fieldValues[field]);
return builder.ToString();
}

Expand Down Expand Up @@ -360,6 +386,10 @@ internal sealed class MarkerSignature
public string Algorithm { get; set; } = string.Empty;
public string Canonicalization { get; set; } = string.Empty;
public string Verifier { get; set; } = string.Empty;
/// <summary>TR-MCP-SEC-005: Ordered field names for the marker-v1 canonical payload.</summary>
public string[] Fields { get; set; } = [];
/// <summary>TR-MCP-SEC-005: Encoding contract for each payload line.</summary>
public string Format { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
}

Expand Down
Loading
Loading