Skip to content
Merged
34 changes: 34 additions & 0 deletions .devcontainer/devcontainer-lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"features": {
"ghcr.io/devcontainers/features/azure-cli:1": {
"version": "1.3.0",
"resolved": "ghcr.io/devcontainers/features/azure-cli@sha256:d98f1066c077be0fa9d115b718f458bd803e415181b4a96f82a6f5d9f77241ac",
"integrity": "sha256:d98f1066c077be0fa9d115b718f458bd803e415181b4a96f82a6f5d9f77241ac"
},
"ghcr.io/devcontainers/features/git:1": {
"version": "1.3.5",
"resolved": "ghcr.io/devcontainers/features/git@sha256:27905dc196c01f77d6ba8709cb82eeaf330b3b108772e2f02d1cd0d826de1251",
"integrity": "sha256:27905dc196c01f77d6ba8709cb82eeaf330b3b108772e2f02d1cd0d826de1251"
},
"ghcr.io/devcontainers/features/github-cli:1": {
"version": "1.1.0",
"resolved": "ghcr.io/devcontainers/features/github-cli@sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671",
"integrity": "sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671"
},
"ghcr.io/devcontainers/features/node:1": {
"version": "1.7.1",
"resolved": "ghcr.io/devcontainers/features/node@sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6",
"integrity": "sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6"
},
"ghcr.io/devcontainers/features/powershell:1": {
"version": "1.5.1",
"resolved": "ghcr.io/devcontainers/features/powershell@sha256:df7baa89598c93bfd15808641d9ec9eb03e0ccdf52e5de4cbbce9ab2d9755d18",
"integrity": "sha256:df7baa89598c93bfd15808641d9ec9eb03e0ccdf52e5de4cbbce9ab2d9755d18"
},
"ghcr.io/devcontainers/features/python:1": {
"version": "1.8.0",
"resolved": "ghcr.io/devcontainers/features/python@sha256:fbcad6955caeecc5ad3f7886baf652e25cba5225a6c4c2287c536de2e5607511",
"integrity": "sha256:fbcad6955caeecc5ad3f7886baf652e25cba5225a6c4c2287c536de2e5607511"
}
}
}
10 changes: 9 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ Rules for comments:
* Comments that contradict current behavior are removed or updated.
* Temporal markers (phase references, dates, task IDs) are removed from code files during any edit.

Rules for markdown frontmatter:

* When editing any Markdown file whose frontmatter already contains an `ms.date` field, update that field to today's date.
* Format the date using ISO 8601 (`YYYY-MM-DD`), matching the existing `ms.date` convention.

Rules for human review checkboxes:

* Agents never check or mark complete any human review checkbox (for example, `- [ ] Reviewed and validated by a qualified human reviewer`). Only a human may convert `[ ]` to `[x]` on review checkboxes.
Expand Down Expand Up @@ -58,6 +63,7 @@ Scripts are organized by function:
* Collections (`scripts/collections/`) - Collection validation and shared helper modules.
* Extension (`scripts/extension/`) - Extension packaging and preparation.
* Linting (`scripts/linting/`) - Markdown validation, link checking, frontmatter validation, model reference validation, and PowerShell analysis.
* Devcontainer (`scripts/devcontainer/`) - Lockfile integrity validation and infrastructure change log generation.
* Security (`scripts/security/`) - Dependency pinning validation, SHA staleness checks, and action version consistency.
* Library (`scripts/lib/`) - Shared utilities such as verified downloads.
* Plugins (`scripts/plugins/`) - Plugin generation and marketplace validation.
Expand Down Expand Up @@ -216,8 +222,10 @@ Agents should use npm scripts for all validation:
* `npm run lint:py` - Python linting via ruff
* `npm run lint:models` - Model reference validation against catalog
* `npm run lint:models:refresh` - Refresh model catalog from upstream documentation
* `npm run lint:all` - Run all linters (chains `format:tables`, `lint:md`, `lint:ps`, `lint:yaml`, `lint:json`, `lint:links`, `lint:frontmatter`, `lint:adr-consistency`, `lint:collections-metadata`, `lint:marketplace`, `lint:version-consistency`, `lint:permissions`, `lint:dependency-pinning`, `lint:ps-module-pins`, `lint:py`, `validate:skills`, `lint:ai-artifacts`, `lint:models`, `eval:lint:vally`, `eval:lint:schema`, `eval:lint:text`, and `eval:lint:safety`)
* `npm run lint:all` - Run all linters (chains `format:tables`, `lint:md`, `lint:ps`, `lint:yaml`, `lint:json`, `lint:links`, `lint:frontmatter`, `lint:adr-consistency`, `lint:collections-metadata`, `lint:marketplace`, `lint:version-consistency`, `lint:permissions`, `lint:dependency-pinning`, `lint:ps-module-pins`, `lint:py`, `validate:skills`, `lint:ai-artifacts`, `lint:models`, `eval:lint:vally`, `eval:lint:schema`, `eval:lint:text`, `eval:lint:safety`, and `validate:devcontainer-lockfile`)
* `npm run validate:copyright` - Copyright header validation
* `npm run validate:devcontainer-lockfile` - Devcontainer lockfile integrity validation
* `npm run validate:devcontainer-changelog` - Devcontainer infrastructure change summary
* `npm run validate:skills` - Skill structure validation
* `npm run spell-check` - Spelling validation
* `npm run format:tables` - Markdown table formatting
Expand Down
75 changes: 14 additions & 61 deletions .github/workflows/devcontainer-change-log.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,66 +31,19 @@ jobs:
fetch-depth: 0

- name: Write infrastructure change summary
env:
GIT_SHA: ${{ github.sha }}
REF_NAME: ${{ github.ref_name }}
EVENT_NAME: ${{ github.event_name }}
BEFORE_SHA: ${{ github.event.before }}
shell: pwsh
run: |
set -euo pipefail

{
echo "## Devcontainer Infrastructure Changes"
echo ""
echo "| Property | Value |"
echo "|----------|-------|"
echo "| Commit | \`${GIT_SHA}\` |"
echo "| Branch | \`${REF_NAME}\` |"
echo "| Trigger | \`${EVENT_NAME}\` |"
echo ""

if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
echo "_Triggered via workflow_dispatch. No push range available for automatic diff._"
elif [ "$BEFORE_SHA" = "0000000000000000000000000000000000000000" ]; then
echo "_Initial push to branch — no prior commit range available._"
else
if ! CHANGED=$(git diff --name-only "$BEFORE_SHA" "$GIT_SHA" -- '.devcontainer/' '.github/workflows/copilot-setup-steps.yml' 2>&1); then
echo "_Could not compute diff: \`$BEFORE_SHA\` may not be reachable (force push?)._"
elif [ -z "$CHANGED" ]; then
echo "_No devcontainer infrastructure files changed in this push._"
else
echo "| File | Category | Pre-build Impact |"
echo "|------|----------|-----------------|"
while IFS= read -r file; do
[ -z "$file" ] && continue
case "$file" in
.devcontainer/scripts/on-create.sh)
echo "| \`$file\` | Lifecycle Scripts | High |"
;;
.devcontainer/scripts/post-create.sh)
echo "| \`$file\` | Lifecycle Scripts | Low |"
;;
.devcontainer/Dockerfile*|.devcontainer/*.dockerfile)
echo "| \`$file\` | Base Image | High |"
;;
.devcontainer/features/*)
echo "| \`$file\` | Features | Medium |"
;;
.devcontainer/devcontainer.json)
echo "| \`$file\` | Config | High |"
;;
.github/workflows/copilot-setup-steps.yml)
echo "| \`$file\` | Setup Steps | Medium |"
;;
.devcontainer/*)
echo "| \`$file\` | Config | Medium |"
;;
*)
echo "| \`$file\` | Other | Unknown |"
;;
esac
done <<< "$CHANGED"
fi
fi
} >> "$GITHUB_STEP_SUMMARY"
$params = @{
EventName = '${{ github.event_name }}'
}
if ('${{ github.sha }}') {
$params['CommitSha'] = '${{ github.sha }}'
}
if ('${{ github.ref_name }}') {
$params['BranchName'] = '${{ github.ref_name }}'
}
if ('${{ github.event.before }}') {
$params['BeforeSha'] = '${{ github.event.before }}'
}
& scripts/devcontainer/Write-DevcontainerChangeLog.ps1 @params

37 changes: 37 additions & 0 deletions .github/workflows/devcontainer-lockfile-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Devcontainer Lockfile Integrity

on:
workflow_call:
inputs:
soft-fail:
description: 'Whether to continue on lockfile integrity violations'
required: false
type: boolean
default: false

permissions:
contents: read

jobs:
check-lockfile:
name: Validate Devcontainer Lockfile
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Validate lockfile integrity
Comment thread
WilliamBerryiii marked this conversation as resolved.
id: validate
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path logs | Out-Null
$params = @{}
if ('${{ inputs.soft-fail }}' -ne 'true') {
$params['FailOnViolation'] = $true
}
& scripts/devcontainer/Test-DevcontainerLockfile.ps1 @params
exit $LASTEXITCODE
8 changes: 8 additions & 0 deletions .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,14 @@ jobs:
upload-sarif: true
upload-artifact: false

devcontainer-lockfile-check:
name: Devcontainer Lockfile Integrity
uses: ./.github/workflows/devcontainer-lockfile-check.yml
permissions:
contents: read
with:
soft-fail: false

workflow-permissions-check:
name: Workflow Permissions Check
uses: ./.github/workflows/workflow-permissions-scan.yml
Expand Down
42 changes: 23 additions & 19 deletions docs/architecture/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Build Workflows
description: GitHub Actions CI/CD pipeline architecture for validation, security, and release automation
sidebar_position: 3
author: WilliamBerryiii
ms.date: 2026-05-20
ms.date: 2026-06-08
ms.topic: overview
---

Expand Down Expand Up @@ -55,6 +55,7 @@ flowchart TD
| `release-marketplace-prerelease.yml` | Manual | VS Code extension pre-release publishing |
| `copilot-setup-steps.yml` | Manual | Coding agent environment setup |
| `devcontainer-change-log.yml` | Push to main/develop | Logs devcontainer infrastructure file changes to the step summary |
| `devcontainer-lockfile-check.yml` | Reusable | Validates devcontainer lockfile integrity and SHA-256 pinning |
| `release-prerelease.yml` | PR closed | Pre-release tag and publish on merge to main |
| `release-prerelease-pr.yml` | Push to main | Pre-release companion PR management |
| `scorecard.yml` | Schedule, push | OpenSSF Scorecard security analysis |
Expand Down Expand Up @@ -100,6 +101,7 @@ Individual validation workflows called by orchestration workflows:
| `docusaurus-tests.yml` | Docusaurus test suite | N/A (npm test) |
| `model-validation.yml` | Model reference validation | `npm run lint:models` |
| `ai-artifact-validation.yml` | AI artifact structure validation | `npm run lint:ai-artifacts` |
| `devcontainer-lockfile-check.yml` | Devcontainer lockfile integrity | N/A (bash + jq direct) |
| `action-version-consistency-scan.yml` | Action version consistency | `npm run lint:version-consistency` |

Workflows marked with `*` are dual-purpose: they accept `workflow_call` for reuse by orchestration workflows and also run independently via their own triggers.
Expand Down Expand Up @@ -130,6 +132,7 @@ flowchart LR

subgraph "Security"
DPC[dependency-pinning-check]
DCL[devcontainer-lockfile-check]
NA[npm-audit]
CQL[codeql]
GLS[gitleaks-scan]
Expand All @@ -138,24 +141,25 @@ flowchart LR

### Jobs

| Job | Reusable Workflow | Validates |
|--------------------------|-------------------------------|--------------------------------|
| spell-check | `spell-check.yml` | Spelling across all files |
| markdown-lint | `markdown-lint.yml` | Markdown formatting rules |
| table-format | `table-format.yml` | Markdown table structure |
| psscriptanalyzer | `ps-script-analyzer.yml` | PowerShell code quality |
| yaml-lint | `yaml-lint.yml` | YAML syntax |
| pester-tests | `pester-tests.yml` | PowerShell unit tests |
| frontmatter-validation | `frontmatter-validation.yml` | AI artifact metadata |
| skill-validation | `skill-validation.yml` | Skill directory structure |
| link-lang-check | `link-lang-check.yml` | Link accessibility |
| markdown-link-check | `markdown-link-check.yml` | Broken links |
| dependency-pinning-check | `dependency-pinning-scan.yml` | Dependency pinning |
| npm-audit | Inline | npm dependency vulnerabilities |
| codeql | `codeql-analysis.yml` | Code security patterns |
| copyright-headers | `copyright-headers.yml` | Copyright header compliance |
| plugin-validation | `plugin-validation.yml` | Plugin and collection metadata |
| gitleaks-scan | `gitleaks-scan.yml` | Secret detection |
| Job | Reusable Workflow | Validates |
|-----------------------------|-----------------------------------|---------------------------------|
| spell-check | `spell-check.yml` | Spelling across all files |
| markdown-lint | `markdown-lint.yml` | Markdown formatting rules |
| table-format | `table-format.yml` | Markdown table structure |
| psscriptanalyzer | `ps-script-analyzer.yml` | PowerShell code quality |
| yaml-lint | `yaml-lint.yml` | YAML syntax |
| pester-tests | `pester-tests.yml` | PowerShell unit tests |
| frontmatter-validation | `frontmatter-validation.yml` | AI artifact metadata |
| skill-validation | `skill-validation.yml` | Skill directory structure |
| link-lang-check | `link-lang-check.yml` | Link accessibility |
| markdown-link-check | `markdown-link-check.yml` | Broken links |
| dependency-pinning-check | `dependency-pinning-scan.yml` | Dependency pinning |
| devcontainer-lockfile-check | `devcontainer-lockfile-check.yml` | Devcontainer lockfile integrity |
| npm-audit | Inline | npm dependency vulnerabilities |
| codeql | `codeql-analysis.yml` | Code security patterns |
| copyright-headers | `copyright-headers.yml` | Copyright header compliance |
| plugin-validation | `plugin-validation.yml` | Plugin and collection metadata |
| gitleaks-scan | `gitleaks-scan.yml` | Secret detection |

All jobs run in parallel with no dependencies, enabling fast feedback (typically under 3 minutes).

Expand Down
12 changes: 12 additions & 0 deletions docs/customization/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@ to add Terraform:
}
```

### Lockfile

When the dev container builds, it generates a `devcontainer-lock.json` file in
the same directory as `devcontainer.json`. This lockfile pins each feature to an
exact version and OCI SHA-256 digest, providing reproducible builds and
supply-chain integrity verification. The lockfile is committed to source control
and validated by CI.

After modifying features in `devcontainer.json`, rebuild the dev container to
regenerate `devcontainer-lock.json` and commit both files together. PR validation
fails if the lockfile is missing or out of sync with `devcontainer.json`.

### Adding VS Code Extensions

Include team-specific extensions in the `customizations.vscode.extensions`
Expand Down
36 changes: 35 additions & 1 deletion docs/security/dependency-pinning.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Dependency Pinning
description: How HVE Core enforces dependency pinning across GitHub Actions, npm, pip, and shell downloads with automated CI validation
sidebar_position: 3
author: Microsoft
ms.date: 2026-03-02
ms.date: 2026-06-08
ms.topic: concept
keywords:
- dependency pinning
Expand Down Expand Up @@ -79,6 +79,40 @@ GitHub Actions references must use full 40-character commit SHAs because action

The scanner validates that the SHA is a real 40-character hexadecimal string and optionally checks staleness against the GitHub API.

## DevContainer Features: Lockfile Integrity

DevContainer features declared in `.devcontainer/devcontainer.json` are pinned through a lockfile (`devcontainer-lock.json`) that records the exact version, OCI digest, and SHA-256 integrity hash for each feature. This follows the [devcontainer lockfile spec](https://github.com/devcontainers/spec/blob/main/docs/specs/devcontainer-lockfile.md), modeled after `package-lock.json`.

### What Is Validated

The `devcontainer-lockfile-check.yml` workflow enforces three checks during PR validation:

| Check | Failure Condition |
|--------------------|----------------------------------------------------------------------------|
| Lockfile existence | `devcontainer-lock.json` is absent from the repository |
| SHA-256 integrity | A feature entry is missing `resolved` or `integrity` with `sha256:` prefix |
| Feature coverage | A feature in `devcontainer.json` has no corresponding lockfile entry |

### Lockfile Format

Each feature entry records the resolved OCI reference and integrity hash:

```json
{
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "1.7.1",
"resolved": "ghcr.io/devcontainers/features/node@sha256:8c0de46...",
"integrity": "sha256:8c0de46..."
}
}
}
```

### Regenerating the Lockfile

Rebuild the dev container to regenerate the lockfile. In VS Code, use the **Dev Containers: Rebuild Container** command. Commit the updated `devcontainer-lock.json` alongside any changes to `devcontainer.json`.

## pip: Exact-Version Pinning

Python dependencies must use the `==` operator for exact version pinning. The scanner excludes virtual environment directories (`.venv`, `venv`, `.tox`, `.nox`, `__pypackages__`) to avoid false positives from installed package metadata.
Expand Down
Loading
Loading