Prerequisites
Before you start
Use Windows PowerShell 5.1 or PowerShell 7 on your own computer. You need a VeriTrust API key created inside the organization that will own the scans.
Organization API key
Create a fresh key from API Access. New keys include gateway scan, read, and cancel permissions.
Open API Access →PowerShell
Open a normal PowerShell window. Administrator mode is not required.
Safe test content
Use content and files you are authorized to process. Start with a controlled test case.
Persistent sender + receiver console
Inline SMTP enforcement between two laptops
The live CLI keeps VeriTrust in the mail path: sender PowerShell → authenticated SMTP gateway → trusted-receiver analysis → policy decision → receiver. A passed message is relayed and saved; hold/manual-review returns SMTP 451; quarantine/block returns SMTP 550 with no downstream delivery.
Run-VeriTrust-Receiver.cmd on the receiver and Run-VeriTrust-Sender.cmd on the sender. The package also includes Validate-VeriTrust-CLI.cmd for a local Windows PowerShell parser check before launch. Tailscale and the receiver's portable Node.js/runtime dependencies are handled automatically when missing.Run the persistent sender console, pair once, then type email.eml, /send another.eml, or an absolute path repeatedly.
Authenticates the sender, captures trusted SMTP facts, submits the exact RFC822 message to VeriTrust, and applies the Gateway recommendation.
Only approved mail reaches the local receiver. Accepted .eml files are saved in the folder where the receiver console was started.
Run the receiver from a Windows account that can approve administrator elevation. The sender self-elevates only when Tailscale installation or service startup requires it.
The easiest demo setup is the same Tailscale account on both laptops. Different accounts also work when both are invited/shared into a tailnet that permits the connection.
The receiver needs a scoped Gateway API key plus the server-configured VERITRUST_EMAIL_RECEIVER_SECRET. The sender never receives either secret.
VERITRUST_EMAIL_RECEIVER_SECRET on the deployed VeriTrust API and set VERITRUST_TRUSTED_AUTHSERV_IDS=veritrust-smtp-gateway, then redeploy. Use the same receiver-secret value when the receiver CLI asks for it. Create a Gateway API key from API Access. Do not share these receiver credentials with the sender.$Bytes = New-Object byte[] 48
$Rng = [Security.Cryptography.RandomNumberGenerator]::Create()
$Rng.GetBytes($Bytes)
$ReceiverSecret = [Convert]::ToBase64String($Bytes)
$Rng.Dispose()
$ReceiverSecret
Store the generated value as VERITRUST_EMAIL_RECEIVER_SECRET in the deployed VeriTrust environment. If an existing receiver secret is already configured and known, keep using that value instead of rotating it unnecessarily.
$Receiver = Join-Path $PWD "VeriTrust-Receiver-CLI.ps1"
Invoke-WebRequest `
-Uri "https://www.veritrustlab.in/assets/powershell/VeriTrust-Receiver-CLI.ps1" `
-OutFile $Receiver `
-UseBasicParsing `
-ErrorAction Stop
Set-ExecutionPolicy Bypass -Scope Process -Force
& $Receiver
VTCLI2|... pairing code and copies it to the clipboard. Give only that pairing code to the sender. Approved messages appear live and are saved into the receiver console's starting directory.$Sender = Join-Path $PWD "VeriTrust-Sender-CLI.ps1"
Invoke-WebRequest `
-Uri "https://www.veritrustlab.in/assets/powershell/VeriTrust-Sender-CLI.ps1" `
-OutFile $Sender `
-UseBasicParsing `
-ErrorAction Stop
Set-ExecutionPolicy Bypass -Scope Process -Force
& $Sender
pair> VTCLI2|100.x.x.x|2525|veritrust-sender|...|lab.local
> email.eml
> /send phishing-test.eml
> /send C:\MailTests\invoice.eml
> /history
> /status
> /check
> /help
> /quit
/send <file.eml>/files/status/check/pair/pwd/cd <path>/last/history/clear/help/quit/status/pair/files/view last/view <file.eml>/path/reset-credentials/clear/help/quit127.0.0.1 while preserving the complete VeriTrust analysis path.Message passed VeriTrust and was relayed to the receiver.
Temporary rejection. The receiver does not receive the message.
Permanent policy rejection. The receiver does not receive the message.
Run once per session
Secure setup and authentication
Copy this complete block into PowerShell. When prompted, paste the API key and press Enter. The key appears only as asterisks.
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$BaseUrl = "https://www.veritrustlab.in"
$SecureApiKey = Read-Host "Paste your VeriTrust API key" -AsSecureString
$ApiKey = (New-Object System.Net.NetworkCredential("", $SecureApiKey)).Password
if ($ApiKey -notmatch '^vtg_(live|test)_[A-Za-z0-9_-]{20,}$') {
throw "The entered value is not a valid VeriTrust API key."
}
$AuthHeaders = @{ Authorization = "Bearer $ApiKey" }
$Authentication = Invoke-RestMethod `
-Method Get `
-Uri "$BaseUrl/api/v1/gateway/scans?limit=1" `
-Headers $AuthHeaders `
-ErrorAction Stop
if ($Authentication.ok -ne $true) {
throw "VeriTrust authentication failed."
}
Write-Host "Authenticated with VeriTrust." -ForegroundColor Green
Authenticated with VeriTrust. Keep this PowerShell window open for the examples below.Email investigation workflow
Investigate email text or an .eml file
Load the verified VeriTrust email command once per PowerShell session. It sends pasted text or the exact bytes of an original email through the Evidence Correlation Gateway and returns a clear result, risk score, recommended action, and report ID.
.eml file also preserves sender verification, sender consistency, attachment details, and the recorded delivery route.$CommandUrl = "$BaseUrl/assets/powershell/VeriTrust.EmailInvestigation.ps1"
$CommandPath = Join-Path ([IO.Path]::GetTempPath()) "VeriTrust.EmailInvestigation.ps1"
Invoke-WebRequest `
-Uri $CommandUrl `
-OutFile $CommandPath `
-UseBasicParsing `
-ErrorAction Stop
. $CommandPath
Get-Command Invoke-VeriTrustEmailInvestigation -ErrorAction Stop
Invoke-VeriTrustEmailInvestigation with command type Function. You can also download the command script, review it, and dot-source it locally. The command returns a concise report object; complete evidence remains available through .TechnicalReport. Requests use UTF-8, require an HTTPS origin, and time out after 90 seconds. Set -TimeoutSec to change that limit. When retrying the same submission, reuse an explicit -IdempotencyKey to avoid creating another investigation.if (-not (Get-Command Invoke-VeriTrustEmailInvestigation -ErrorAction SilentlyContinue)) {
$CommandPath = Join-Path ([IO.Path]::GetTempPath()) "VeriTrust.EmailInvestigation.ps1"
Invoke-WebRequest -Uri "$BaseUrl/assets/powershell/VeriTrust.EmailInvestigation.ps1" -OutFile $CommandPath -UseBasicParsing -ErrorAction Stop
. $CommandPath
}
$EmailText = @"
URGENT: Your Microsoft 365 account will be suspended today.
Confirm your password immediately using the verification link.
"@
$EmailResult = Invoke-VeriTrustEmailInvestigation `
-Subject "Action required" `
-Body $EmailText
$EmailResult | Select-Object `
Result, RiskPercent, RecommendedAction, MissingChecks, ReportId |
Format-List
if (-not (Get-Command Invoke-VeriTrustEmailInvestigation -ErrorAction SilentlyContinue)) {
$CommandPath = Join-Path ([IO.Path]::GetTempPath()) "VeriTrust.EmailInvestigation.ps1"
Invoke-WebRequest -Uri "$BaseUrl/assets/powershell/VeriTrust.EmailInvestigation.ps1" -OutFile $CommandPath -UseBasicParsing -ErrorAction Stop
. $CommandPath
}
$EmailResult = Invoke-VeriTrustEmailInvestigation -EmlPath "C:\SecurityTests\suspicious-email.eml"
$EmailResult | Select-Object `
Result, RiskPercent, RecommendedAction, InputType, MissingChecks, ReportId |
Format-List
# Show the complete forensic evidence when needed:
$EmailResult.TechnicalReport.evidence | ConvertTo-Json -Depth 30
Focused detection
Link only
Use this when the input is an HTTP or HTTPS URL without accompanying message text. Replace the example URL with the URL you are authorized to assess.
$UrlToCheck = "https://example.com/account-verification"
$IdempotencyKey = [Guid]::NewGuid().ToString()
$Headers = @{
Authorization = "Bearer $ApiKey"
"Idempotency-Key" = $IdempotencyKey
}
$Payload = @{
source = @{
kind = "powershell"
external_event_id = "link-$([Guid]::NewGuid())"
}
content = @{
text = $null
urls = @($UrlToCheck)
media = @()
}
processing_mode = "synchronous"
metadata = @{
client = "windows-powershell"
context_categories = @("web-link")
}
}
$Submission = Invoke-RestMethod `
-Method Post `
-Uri "$BaseUrl/api/v1/gateway/scans" `
-Headers $Headers `
-ContentType "application/json" `
-Body ($Payload | ConvertTo-Json -Depth 20 -Compress) `
-ErrorAction Stop
$ScanId = $Submission.scan_id
do {
$Result = Invoke-RestMethod `
-Method Get `
-Uri "$BaseUrl/api/v1/gateway/scans/$ScanId" `
-Headers $AuthHeaders `
-ErrorAction Stop
Write-Host "Status: $($Result.status)"
if (@("completed", "failed", "cancelled") -notcontains $Result.status) {
Start-Sleep -Seconds 3
}
} while (@("completed", "failed", "cancelled") -notcontains $Result.status)
$Result.decision | Format-List
$Result.evidence |
Select-Object model, status, score, verdict, confidence, reason_codes |
Format-List
example.com is intentionally non-malicious. A verification-looking path can still trigger a suspicious lexical signal, which is useful for testing but is not proof that a website is malicious.Recommended gateway flow
Combined phishing and link scan
Use this for a suspicious email containing one or more links. The gateway runs applicable models separately, correlates their evidence, then returns one policy-backed recommendation.
$MessageText = @"
Your account has been locked because of suspicious activity.
Verify your identity immediately using the supplied link.
"@
$UrlsToCheck = @("https://example.com/security-verification")
$IdempotencyKey = [Guid]::NewGuid().ToString()
$Headers = @{
Authorization = "Bearer $ApiKey"
"Idempotency-Key" = $IdempotencyKey
}
$Payload = @{
source = @{
kind = "powershell"
external_event_id = "combined-$([Guid]::NewGuid())"
}
content = @{
text = $MessageText
urls = $UrlsToCheck
media = @()
}
processing_mode = "hybrid"
metadata = @{
client = "windows-powershell"
context_categories = @("email", "credential")
}
}
$Submission = Invoke-RestMethod `
-Method Post `
-Uri "$BaseUrl/api/v1/gateway/scans" `
-Headers $Headers `
-ContentType "application/json" `
-Body ($Payload | ConvertTo-Json -Depth 20 -Compress) `
-ErrorAction Stop
$ScanId = $Submission.scan_id
do {
$Result = Invoke-RestMethod `
-Method Get `
-Uri "$BaseUrl/api/v1/gateway/scans/$ScanId" `
-Headers $AuthHeaders `
-ErrorAction Stop
$Verdict = if ($Result.decision) { $Result.decision.verdict } else { "pending" }
Write-Host "Status: $($Result.status) | Verdict: $Verdict"
if (@("completed", "failed", "cancelled") -notcontains $Result.status) {
Start-Sleep -Seconds 3
}
} while (@("completed", "failed", "cancelled") -notcontains $Result.status)
Write-Host "Recommendation: $($Result.decision.recommendation)"
Write-Host "Risk: $([Math]::Round(([double]$Result.decision.risk * 100), 2))%"
$Result.decision | Format-List
$Result.evidence |
Select-Object model, model_key, score, verdict, confidence, reason_codes |
Format-List
Audit output
Fetch reports and scan history
Every example above leaves the completed identifier in $ScanId. Use it to retrieve the unified report and save a JSON copy to the Desktop.
$ReportResponse = Invoke-RestMethod `
-Method Get `
-Uri "$BaseUrl/api/v1/gateway/reports/$ScanId" `
-Headers $AuthHeaders `
-ErrorAction Stop
$ReportPath = Join-Path `
([Environment]::GetFolderPath("Desktop")) `
"veritrust-report-$ScanId.json"
$ReportResponse |
ConvertTo-Json -Depth 50 |
Set-Content -LiteralPath $ReportPath -Encoding UTF8
Write-Host "Report saved to $ReportPath" -ForegroundColor Green
$ReportResponse.report.decision | Format-List
$History = Invoke-RestMethod `
-Method Get `
-Uri "$BaseUrl/api/v1/gateway/scans?limit=20" `
-Headers $AuthHeaders `
-ErrorAction Stop
$History.scans |
Select-Object display_id, source, status, processing_mode, degraded, created_at |
Format-Table -AutoSize
Interpretation
Understand the response
One policy outcome
risk, verdict, and recommendation summarize the correlated result across applicable artifacts.
Per-model findings
Each entry identifies its model, score, verdict, confidence, reason codes, and recorded model version.
Processing lifecycle
completed is final. Image scans can move through accepted, processing, and partially completed states first.
Human checkpoint
Manual review means the case should be examined with source context before a consequential action.
Recovery
Common errors
TAIL Tailscale reports “no matching peer”
The sender cannot see the receiver in its tailnet. For the simplest demo, log both laptops into the same Tailscale account. With different accounts, invite/share the receiver into a tailnet whose policy permits the sender. Then run tailscale status and confirm the receiver appears before retrying /check.
2525 Receiver SMTP endpoint is unreachable
Keep the receiver CLI running. It automatically disables Tailscale shields-up and recreates the Windows Firewall rule for TCP 2525 from Tailscale peers. If reachability still fails, verify the tailnet access policy allows the sender to reach the receiver and use /check again.
TRUST Trusted receiver authentication fails
The value entered on the receiver must exactly match the deployed VERITRUST_EMAIL_RECEIVER_SECRET, and VERITRUST_TRUSTED_AUTHSERV_IDS must include veritrust-smtp-gateway. Redeploy after changing server-side environment values.
CLI PowerShell parser or local script problem
Download the current CLI package above and use the included validated 2.0.1 scripts. The package includes a Windows PowerShell parser validator. Do not reuse older generated scripts from previous tests.
CMD Invoke-VeriTrustEmailInvestigation is not recognized
The command script has not been loaded in this PowerShell session. Run the “Load and verify” block above, then confirm that Get-Command Invoke-VeriTrustEmailInvestigation returns a Function. The text and .eml examples now load it automatically when it is missing.
401 This endpoint requires a valid Bearer token
The key is missing, invalid, expired, or revoked. Create a new organization API key and rerun the secure setup block. Do not type the key inside the Read-Host prompt label.
403 API key has no gateway permissions
Create a new key after gateway access is enabled. It must include gateway:scan, gateway:read, and gateway:cancel.
409 Idempotency conflict
Reuse an idempotency key only when retrying the exact same request body. Generate a new GUID when the content changes.
415 Unsupported image type
Use JPG, JPEG, PNG, WEBP, or BMP. The declared MIME type must match the stored file.
429 Gateway quota exceeded
The API key or organization has reached its current plan limit. Review usage and plan information in the dashboard.
Operational safety
Finish securely
Avoid commands such as Write-Host $ApiKey and never include the value in screenshots.
Separate keys make rotation, revocation, scopes, and usage attribution manageable.
Store the scan, request, trace, and external event identifiers with your incident record.
Remove the decrypted key from variables when the session is finished.
$ApiKey = $null
$SecureApiKey = $null
$AuthHeaders = $null
[System.GC]::Collect()
Write-Host "VeriTrust session cleared." -ForegroundColor Green