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"
}
}
}
3 changes: 3 additions & 0 deletions .github/workflows/devcontainer-change-log.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ jobs:
.devcontainer/devcontainer.json)
echo "| \`$file\` | Config | High |"
;;
.devcontainer/devcontainer-lock.json)
echo "| \`$file\` | Lockfile | Medium |"
;;
.github/workflows/copilot-setup-steps.yml)
echo "| \`$file\` | Setup Steps | Medium |"
;;
Expand Down
80 changes: 80 additions & 0 deletions .github/workflows/devcontainer-lockfile-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
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: bash
run: |
set -euo pipefail

LOCK=".devcontainer/devcontainer-lock.json"
CONFIG=".devcontainer/devcontainer.json"
ERRORS=0

# 1. Lockfile must exist
if [[ ! -f "$LOCK" ]]; then
echo "::error file=$CONFIG::devcontainer-lock.json is missing — rebuild the dev container to generate it"
exit 1
fi

# 2. Every feature must have resolved + integrity with sha256
MISSING_SHA=$(jq -r '.features | to_entries[]
| select(.value.resolved == null or .value.integrity == null
or (.value.integrity | startswith("sha256:") | not))
| .key' "$LOCK")
if [[ -n "$MISSING_SHA" ]]; then
while IFS= read -r key; do
echo "::error file=$LOCK::Feature missing SHA-256 integrity: $key"
done <<< "$MISSING_SHA"
ERRORS=$((ERRORS + 1))
fi

# 3. Feature keys in lockfile must cover devcontainer.json features
CONFIG_KEYS=$(jq -r '.features | keys[] | ascii_downcase' "$CONFIG" | sort)
LOCK_KEYS=$(jq -r '.features | keys[] | ascii_downcase' "$LOCK" | sort)
MISSING_KEYS=$(comm -23 <(echo "$CONFIG_KEYS") <(echo "$LOCK_KEYS"))
if [[ -n "$MISSING_KEYS" ]]; then
while IFS= read -r key; do
echo "::error file=$LOCK::Lockfile missing entry for feature: $key"
done <<< "$MISSING_KEYS"
ERRORS=$((ERRORS + 1))
fi

if [[ "$ERRORS" -gt 0 ]]; then
echo "LOCKFILE_FAILED=true" >> "$GITHUB_ENV"
else
echo "✓ Lockfile covers all features with SHA-256 integrity"
fi
continue-on-error: ${{ inputs.soft-fail }}

- name: Check results
if: ${{ !inputs.soft-fail }}
shell: bash
run: |
if [[ "${LOCKFILE_FAILED:-}" == "true" ]]; then
echo "::error::Devcontainer lockfile integrity check failed"
exit 1
fi
8 changes: 8 additions & 0 deletions .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,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
14 changes: 13 additions & 1 deletion docs/customization/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Environment Customization
description: Configure DevContainers, VS Code settings, MCP servers, and coding agent environments for your team
author: Microsoft
ms.date: 2026-02-24
ms.date: 2026-06-08
ms.topic: how-to
keywords:
- devcontainer
Expand Down 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