@@ -446,7 +446,8 @@ private static Command BuildAddWorkspaceCommand()
446446
447447 if ( File . Exists ( markerPath ) )
448448 {
449- Error ( $ "Workspace is already registered — { markerPath } exists.") ;
449+ Info ( $ "Workspace already registered — validating trust for { markerPath } ...") ;
450+ await ValidateMarkerTrustAsync ( workspacePath , markerPath ) . ConfigureAwait ( true ) ;
450451 return ;
451452 }
452453
@@ -542,54 +543,8 @@ private static Command BuildAddWorkspaceCommand()
542543 return ;
543544 }
544545
545- // Step 4: Read the marker and verify trust
546- var markerClient = McpHttpClient . FromMarkerOnly ( workspacePath ) ;
547- if ( markerClient is null )
548- {
549- Error ( "Marker file was created but could not be parsed." ) ;
550- return ;
551- }
552-
553- // Step 5: Verify HMAC-SHA256 signature
554- var lines = File . ReadAllLines ( markerPath ) ;
555- var markerFields = ParseMarkerFields ( lines ) ;
556- if ( ! VerifyMarkerSignature ( markerFields , out var sigError ) )
557- {
558- Error ( $ "Marker signature INVALID: { sigError } ") ;
559- markerClient . Dispose ( ) ;
560- return ;
561- }
562-
563- Success ( "Marker signature verified." ) ;
564-
565- // Step 6: Health nonce echo verification
566- var nonce = Guid . NewGuid ( ) . ToString ( "N" ) ;
567- try
568- {
569- var healthResult = await markerClient . GetAsync < JsonElement > (
570- $ "/health?nonce={ Uri . EscapeDataString ( nonce ) } ") . ConfigureAwait ( true ) ;
571- var echoedNonce = healthResult . TryGetProperty ( "nonce" , out var nonceProp )
572- ? nonceProp . GetString ( ) : null ;
573- if ( ! string . Equals ( echoedNonce , nonce , StringComparison . Ordinal ) )
574- {
575- Error ( $ "Health nonce mismatch: sent '{ nonce } ', got '{ echoedNonce } '.") ;
576- markerClient . Dispose ( ) ;
577- return ;
578- }
579-
580- Success ( "Health nonce verified." ) ;
581- }
582- catch ( Exception ex )
583- {
584- Error ( $ "Health check failed: { ex . Message } ") ;
585- markerClient . Dispose ( ) ;
586- return ;
587- }
588-
589- markerClient . Dispose ( ) ;
590- Success ( $ "Workspace added and trusted: { workspacePath } ") ;
591- Info ( $ "Marker: { markerPath } ") ;
592- Info ( $ "API key: { markerClient . ApiKey [ ..Math . Min ( 8 , markerClient . ApiKey . Length ) ] } ...") ;
546+ // Step 4: Validate trust on the new marker
547+ await ValidateMarkerTrustAsync ( workspacePath , markerPath ) . ConfigureAwait ( true ) ;
593548 } , s_workspaceOption , nameOption , serverOption ) ;
594549
595550 return cmd ;
@@ -640,6 +595,61 @@ private static async Task<bool> WaitForFileAsync(string filePath, TimeSpan timeo
640595 }
641596 }
642597
598+ /// <summary>
599+ /// Reads the marker file, verifies its HMAC-SHA256 signature, and performs
600+ /// a health nonce echo check. Prints success/error via <see cref="CommandHelpers"/>.
601+ /// </summary>
602+ private static async Task ValidateMarkerTrustAsync ( string workspacePath , string markerPath )
603+ {
604+ var markerClient = McpHttpClient . FromMarkerOnly ( workspacePath ) ;
605+ if ( markerClient is null )
606+ {
607+ Error ( "Marker file could not be parsed." ) ;
608+ return ;
609+ }
610+
611+ // Signature verification
612+ var lines = File . ReadAllLines ( markerPath ) ;
613+ var markerFields = ParseMarkerFields ( lines ) ;
614+ if ( ! VerifyMarkerSignature ( markerFields , out var sigError ) )
615+ {
616+ Error ( $ "Marker signature INVALID: { sigError } ") ;
617+ markerClient . Dispose ( ) ;
618+ return ;
619+ }
620+
621+ Success ( "Marker signature verified." ) ;
622+
623+ // Health nonce echo verification
624+ var nonce = Guid . NewGuid ( ) . ToString ( "N" ) ;
625+ try
626+ {
627+ var healthResult = await markerClient . GetAsync < JsonElement > (
628+ $ "/health?nonce={ Uri . EscapeDataString ( nonce ) } ") . ConfigureAwait ( true ) ;
629+ var echoedNonce = healthResult . TryGetProperty ( "nonce" , out var nonceProp )
630+ ? nonceProp . GetString ( ) : null ;
631+ if ( ! string . Equals ( echoedNonce , nonce , StringComparison . Ordinal ) )
632+ {
633+ Error ( $ "Health nonce mismatch: sent '{ nonce } ', got '{ echoedNonce } '.") ;
634+ markerClient . Dispose ( ) ;
635+ return ;
636+ }
637+
638+ Success ( "Health nonce verified." ) ;
639+ }
640+ catch ( Exception ex )
641+ {
642+ Error ( $ "Health check failed: { ex . Message } ") ;
643+ markerClient . Dispose ( ) ;
644+ return ;
645+ }
646+
647+ Success ( $ "Workspace trusted: { workspacePath } ") ;
648+ Info ( $ "Marker: { markerPath } ") ;
649+ Info ( $ "API key: { markerClient . ApiKey [ ..Math . Min ( 8 , markerClient . ApiKey . Length ) ] } ...") ;
650+ markerClient . Dispose ( ) ;
651+ }
652+
643653 /// <summary>
644654 /// Parses the flat and dotted fields from the marker YAML for signature verification.
645655 /// Returns a dictionary mapping dotted keys (e.g. "endpoints.health") to their string values.
0 commit comments