Skip to content

Commit a390e26

Browse files
docs(security): add security assurance case and threat model for OSSF Silver (#259)
## Summary Adds comprehensive security threat model documentation for OSSF Silver badge compliance, aligned with GitHub's defensive architecture principles. Closes #244 ## Changes This PR adds **5 files**: ### Security Documentation - **`docs/security/threat-model.md`** - Comprehensive threat model with 36 threats (STRIDE + AI-specific + RAI), 18+ controls, and MCP server trust analysis - **`docs/security/README.md`** - Security documentation index - **`SECURITY.md`** - Added link to new security documentation ### Bug Fixes - **`scripts/linting/Validate-MarkdownFrontmatter.ps1`** - Fixed repo-relative path detection for template exclusion; updated comment wording for clarity - **`.github/workflows/pester-tests.yml`** - Updated Pester from 5.6.1 to 5.7.1 to resolve PSGallery availability issues ## Threat Model Highlights - **36 identified threats** across STRIDE categories, AI/ML-specific risks, and Responsible AI concerns - **18+ security controls** mapped to threats with implementation guidance - **MCP server trust boundaries** - Analysis of ADO, GitHub, Microsoft Docs, and Context7 integrations - **Data flow diagrams** - Mermaid-based visualization of system boundaries ## Testing - ✅ All linting passes (`npm run lint`) - ✅ All 338 Pester tests pass (`npm run test:ps`) - ✅ Frontmatter validation passes with template exclusion fix
1 parent d8337b8 commit a390e26

File tree

8 files changed

+989
-8
lines changed

8 files changed

+989
-8
lines changed

.github/workflows/frontmatter-validation.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ jobs:
4545
run: |
4646
New-Item -ItemType Directory -Force -Path logs | Out-Null
4747
48+
- name: Install PowerShell-Yaml
49+
shell: pwsh
50+
run: |
51+
Install-Module -Name PowerShell-Yaml -Force -Scope CurrentUser
52+
4853
- name: Run Frontmatter Validation
4954
shell: pwsh
5055
run: |

.github/workflows/pester-tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ jobs:
4141
- name: Install Pester
4242
shell: pwsh
4343
run: |
44-
Install-Module -Name Pester -RequiredVersion 5.6.1 -Force -Scope CurrentUser
45-
Import-Module Pester -RequiredVersion 5.6.1
44+
Install-Module -Name Pester -RequiredVersion 5.7.1 -Force -Scope CurrentUser
45+
Import-Module Pester -RequiredVersion 5.7.1
4646
4747
- name: Install PowerShell-Yaml
4848
shell: pwsh

SECURITY.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https:
5353

5454
<!-- END MICROSOFT SECURITY.MD BLOCK -->
5555

56+
## Security Documentation
57+
58+
For comprehensive security documentation including threat models and security controls, see [Security Documentation](docs/security/README.md).
59+
5660
## Verifying Release Integrity
5761

5862
HVE Core releases are cryptographically signed using GitHub Artifact Attestations. This establishes provenance and allows you to verify that release artifacts were built from this repository's official CI/CD pipeline.

docs/security/README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
title: Security Documentation
3+
description: Index of security documentation including threat model and assurance case for HVE Core
4+
author: Microsoft
5+
ms.date: 2026-01-23
6+
ms.topic: overview
7+
keywords:
8+
- security
9+
- documentation
10+
- index
11+
estimated_reading_time: 2
12+
---
13+
14+
## Overview
15+
16+
This directory contains security documentation for HVE Core, demonstrating defense-in-depth security practices.
17+
18+
## Documents
19+
20+
| Document | Description |
21+
|----------------------------------|--------------------------------------------------------|
22+
| [Threat Model](threat-model.md) | Comprehensive threat model and security assurance case |
23+
| [SECURITY.md](../../SECURITY.md) | Vulnerability disclosure and reporting process |
24+
25+
## Security Posture
26+
27+
HVE Core is an enterprise prompt engineering framework that:
28+
29+
- Contains no runtime services or user data storage
30+
- Operates as development-time tooling consumed by GitHub Copilot
31+
- Relies on defense-in-depth with 18+ automated security controls
32+
33+
The [threat model](threat-model.md) documents:
34+
35+
- 36 threats across STRIDE, AI-specific, and Responsible AI categories
36+
- Security controls mapped to each threat
37+
- MCP server trust analysis
38+
- Quantitative security metrics
39+
- GSN-style assurance argument
40+
41+
## Related Resources
42+
43+
- [Branch Protection](../contributing/branch-protection.md): Repository protection configuration
44+
- [MCP Configuration](../getting-started/mcp-configuration.md): MCP server setup and trust guidance
45+
- [GOVERNANCE.md](../../GOVERNANCE.md): Project governance and maintainer roles
46+
47+
---
48+
49+
🤖 *Crafted with precision by ✨Copilot following brilliant human instruction, then carefully refined by our team of discerning human reviewers.*

docs/security/threat-model.md

Lines changed: 893 additions & 0 deletions
Large diffs are not rendered by default.

scripts/linting/Modules/FrontmatterValidation.psm1

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -617,9 +617,15 @@ function Test-CommonFields {
617617
$issues = [System.Collections.Generic.List[ValidationIssue]]::new()
618618

619619
# Validate keywords array
620+
# ConvertFrom-Yaml returns sequences as List[object], not native PowerShell arrays
620621
if ($Frontmatter.ContainsKey('keywords')) {
621622
$keywords = $Frontmatter['keywords']
622-
if ($keywords -isnot [array] -and $keywords -notmatch ',') {
623+
$isCollection = $keywords -is [array] -or
624+
$keywords -is [System.Collections.IList] -or
625+
($keywords -is [System.Collections.IEnumerable] -and
626+
$keywords -isnot [string] -and
627+
$keywords -isnot [hashtable])
628+
if (-not $isCollection -and $keywords -notmatch ',') {
623629
$issues.Add([ValidationIssue]::new('Warning', 'keywords', 'Keywords should be an array', $RelativePath))
624630
}
625631
}
@@ -749,10 +755,13 @@ function Get-FileTypeInfo {
749755
($File.Name -in @('CODE_OF_CONDUCT.md', 'CONTRIBUTING.md', 'SECURITY.md', 'SUPPORT.md', 'README.md'))
750756
$info.IsDevContainer = $File.DirectoryName -like "*.devcontainer*" -and $File.Name -eq 'README.md'
751757
$info.IsVSCodeReadme = $File.DirectoryName -like "*.vscode*" -and $File.Name -eq 'README.md'
752-
# Exclude .copilot-tracking and templates from docs validation
758+
# Exclude .copilot-tracking (gitignored workflow artifacts) and markdown templates from docs validation
753759
$isCopilotTracking = $File.DirectoryName -like "*.copilot-tracking*"
754760
$isTemplate = $File.Name -like "*TEMPLATE*"
755-
$info.IsDocsFile = $File.DirectoryName -like "*docs*" -and -not $info.IsGitHub -and -not $isCopilotTracking -and -not $isTemplate
761+
# Use repo-relative path to avoid misclassifying files when repo is under a parent containing "docs"
762+
$relativePath = [System.IO.Path]::GetRelativePath($RepoRoot, $File.FullName)
763+
$relativePathNormalized = $relativePath -replace '\\', '/'
764+
$info.IsDocsFile = ($relativePathNormalized -match '(^|/)docs(/|$)') -and -not $info.IsGitHub -and -not $isCopilotTracking -and -not $isTemplate
756765

757766
return $info
758767
}

scripts/linting/Validate-MarkdownFrontmatter.ps1

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
#requires -Version 7.0
1414

1515
using namespace System.Collections.Generic
16+
# Import FrontmatterValidation module with 'using' to make PowerShell class types
17+
# (FileTypeInfo, ValidationIssue, etc.) available at parse time for [OutputType] attributes
18+
using module .\Modules\FrontmatterValidation.psm1
1619

1720
param(
1821
[Parameter(Mandatory = $false)]
@@ -41,8 +44,8 @@ param(
4144
)
4245

4346
# Import helper modules
47+
# Note: FrontmatterValidation.psm1 is imported via 'using module' at top of script for class type availability
4448
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'Modules/LintingHelpers.psm1') -Force
45-
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'Modules/FrontmatterValidation.psm1') -Force
4649

4750
#region Type Definitions
4851

@@ -429,7 +432,7 @@ function Test-JsonSchemaValidation {
429432
}
430433
}
431434

432-
# Get-FileTypeInfo moved to FrontmatterValidation.psm1
435+
# Get-FileTypeInfo is provided by FrontmatterValidation.psm1 via 'using module' directive
433436

434437
function Test-FrontmatterValidation {
435438
<#
@@ -731,12 +734,26 @@ if ($MyInvocation.InvocationName -ne '.') {
731734
$result = Test-FrontmatterValidation -Paths $Paths -ExcludePaths $ExcludePaths -WarningsAsErrors:$WarningsAsErrors -EnableSchemaValidation:$EnableSchemaValidation -SkipFooterValidation:$SkipFooterValidation
732735
}
733736

737+
# Normalize result: if pipeline output produced an array, extract the ValidationSummary object
738+
# PowerShell functions can inadvertently output multiple objects; take the last (the return value)
739+
if ($result -is [System.Array]) {
740+
$result = $result | Where-Object { $null -ne $_ -and $_.GetType().GetMethod('GetExitCode') } | Select-Object -Last 1
741+
}
742+
734743
# In ChangedFilesOnly mode with no changed files, TotalFiles=0 is a successful no-op
735-
if ($ChangedFilesOnly -and $result.TotalFiles -eq 0) {
744+
if ($ChangedFilesOnly -and $null -ne $result -and $result.TotalFiles -eq 0) {
736745
Write-Host "✅ No changed markdown files to validate - success!" -ForegroundColor Green
737746
exit 0
738747
}
739748

749+
# Validate result object before calling GetExitCode to prevent method invocation errors
750+
# PowerShell class methods are compiled to .NET type metadata, not stored in PSObject.Methods (ETS only)
751+
if ($null -eq $result -or $null -eq $result.GetType().GetMethod('GetExitCode')) {
752+
$resultTypeName = if ($null -eq $result) { '<null>' } else { $result.GetType().FullName }
753+
Write-Host "Validation did not produce a usable result object (type: $resultTypeName). Exiting with code 1."
754+
exit 1
755+
}
756+
740757
$exitCode = $result.GetExitCode($WarningsAsErrors)
741758
if ($exitCode -ne 0) {
742759
exit $exitCode

scripts/tests/linting/Validate-MarkdownFrontmatter.Tests.ps1

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
#Requires -Modules Pester
2+
# Import module with 'using' to make PowerShell class types (FileTypeInfo, ValidationSummary, etc.) available at parse time
3+
using module ..\..\linting\Modules\FrontmatterValidation.psm1
24

35
BeforeAll {
6+
# Dot-source the main script
47
$scriptPath = Join-Path $PSScriptRoot '../../linting/Validate-MarkdownFrontmatter.ps1'
58
. $scriptPath
9+
610
$mockPath = Join-Path $PSScriptRoot '../Mocks/GitMocks.psm1'
711
Import-Module $mockPath -Force
812
$script:SchemaDir = Join-Path $PSScriptRoot '../../linting/schemas'

0 commit comments

Comments
 (0)