Skip to content
Closed
8 changes: 4 additions & 4 deletions .devcontainer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,12 @@ A pre-configured development environment that includes all tools, extensions, an
### Languages & Runtimes

- Node.js (LTS)
- Python 3.11
- PowerShell 7.x

### CLI Tools

- Git
- GitHub CLI (`gh`)
- Azure CLI (`az`)

### Code Quality

Expand Down Expand Up @@ -93,9 +91,11 @@ gitleaks detect --source . --verbose

## Troubleshooting

**Container won't build**: Ensure Docker Desktop is running and you have sufficient disk space (5GB+).
1. **Container won't build**: Ensure Docker Desktop is running and you have sufficient disk space (5GB+).

**Extensions not loading**: Reload the window (`F1` → **Developer: Reload Window**).
2. **Extensions not loading**: Reload the window (`F1` → **Developer: Reload Window**).

3. **HTTP/TLS errors during build**: Machines with corporate firewalls performing TLS inspection will need to drop their corporate root trust certificate (PEM-formatted with `.crt` extension) file into the `.devcontainer` and rebuild the devcontainer. Also run `docker buildx use desktop-linux` to ensure you are using the default builder, which honors OS root certificate trust stores.

For more help, see [SUPPORT.md](../SUPPORT.md).

Expand Down
41 changes: 36 additions & 5 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
{
"name": "HVE Core - Markdown Editing",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
// Rename the mount to /workspace for consistency, otherwise its mounted using
// whatever folder name the user cloned the repo as
"workspaceMount": "\"source=${localWorkspaceFolder}\",target=/workspace,type=bind",
"workspaceFolder": "/workspace",
"mounts": [
// Put GitHub local user data in a volume
{
"type": "volume",
"source": "${devcontainerId}-userconfig",
"target": "/home/vscode/.config"
},
// Put node modules into volume for better performance
{
"type": "volume",
"source": "${devcontainerId}-nodemodules",
"target": "/workspace/node_modules"
}
],
"containerEnv": {
"REQUESTS_CA_BUNDLE": "/etc/ssl/certs/ca-certificates.crt", // for pip
"NODE_EXTRA_CA_CERTS": "/etc/ssl/certs/ca-certificates.crt", // for nodejs
"SSL_CERT_FILE": "/etc/ssl/certs/ca-certificates.crt" // for uv (else use --native-tls flag)
},
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "lts"
},
"ghcr.io/devcontainers/features/python:1": {
"version": "3.11"
},
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/devcontainers/features/azure-cli:1": {},
"ghcr.io/devcontainers/features/powershell:1": {}
},
"customizations": {
Expand All @@ -23,9 +42,21 @@
"bierner.markdown-mermaid",
"bpruitt-goddard.mermaid-markdown-syntax-highlighting",
"github.vscode-pull-request-github"
]
],
"settings": {
// Prevent extensions from stealing focus, see microsoft/vscode#205225
"workbench.view.showQuietly": {
"workbench.panel.output": true
},
}
}
},
// This is to ensure support for config includes is properly handled, see microsoft/vscode-remote-release/2084
"initializeCommand": {
"extractGitGlobals": "(git config -l --global --include || true) > .gitconfig.global",
"extractGitLocals": "(git config -l --local --include || true) > .gitconfig.local"
},
"postAttachCommand": "/bin/bash .devcontainer/scripts/post-attach.sh",
"onCreateCommand": "bash .devcontainer/scripts/on-create.sh",
"postCreateCommand": "bash .devcontainer/scripts/post-create.sh",
"remoteUser": "vscode"
Expand Down
29 changes: 29 additions & 0 deletions .devcontainer/scripts/post-attach.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/bash
set -euo pipefail

# devcontainers copy your local gitconfig but do not parse conditional includes.
# This re-configures the devcontainer git identities based on the prior exported
# global and local git configurations *after* parsing host includes. See also:
# https://github.com/microsoft/vscode-remote-release/issues/2084#issuecomment-2289987894
function copy_user_gitconfig() {
for conf in .gitconfig.global .gitconfig.local; do
if [ -f $conf ]; then
echo "*** Parsing ${conf##.gitconfig.} Git configuration export"
while IFS='=' read -r key value; do
case "$key" in
user.name | user.email | user.signingkey | commit.gpgsign)
echo "Set Git config ${key}=${value}"
git config --global "$key" "$value"
;;
esac
done < "$conf"
rm -f "${conf}"
fi
done
}

#
# Main execution path
#

copy_user_gitconfig
41 changes: 40 additions & 1 deletion .devcontainer/scripts/post-create.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,49 @@

set -euo pipefail

main() {
# Volume ownership is not set automatically due to a bug:
# https://github.com/microsoft/vscode-remote-release/issues/9931
#
# IMPORTANT: workaround requires Docker base image to have password-less sudo.
function fix_volume_ownership() {
volume_path="$1"

if [ ! -d "$volume_path" ]; then
echo "ERROR: the volume path provided '$volume_path' does not exist."
exit 1
fi

echo "Setting volume ownership for $volume_path"
sudo chown "$USER:$USER" "$volume_path"
}

function fix_volume_ownerships() {
echo "Applying volume ownership workaround (see microsoft/vscode-remote-release#9931)..."
fix_volume_ownership "/home/${USER}/.config"
fix_volume_ownership "/workspace/node_modules"
}

function npm_install() {
echo "Installing NPM dependencies..."
npm install
echo "NPM dependencies installed successfully"
}

function update_ca_certs() {
# Adds a root CA to the system certificate store. Useful if developer machines
# have MITM TLS inspection happening, e.g. with ZScaler.
echo "Updating container system CA certificates..."
if compgen -G ".devcontainer/*.crt" > /dev/null; then
sudo cp .devcontainer/*.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
fi
echo "Container's system CA certificates updated successfully"
Comment on lines +37 to +44
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The update_ca_certs function trusts any .crt file present in .devcontainer/ by copying it into the system CA store. This enables an attacker to commit a malicious root CA to the repo, causing the container to trust attacker-controlled TLS endpoints (e.g., for npm, pip, git), enabling MITM and credential/token theft. Restrict trusted CAs to a pre-approved, immutable source (e.g., mount a secure host-managed CA volume or require explicit opt-in via a verified checksum), and validate certificates before installation (e.g., whitelist filenames and verify SHA256).

Suggested change
# Adds a root CA to the system certificate store. Useful if developer machines
# have MITM TLS inspection happening, e.g. with ZScaler.
echo "Updating container system CA certificates..."
if compgen -G ".devcontainer/*.crt" > /dev/null; then
sudo cp .devcontainer/*.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
fi
echo "Container's system CA certificates updated successfully"
# Adds a root CA to the system certificate store, but only if it matches a pre-approved whitelist.
# This prevents malicious CAs from being trusted.
echo "Updating container system CA certificates..."
# Define whitelist: filename:sha256sum (add entries as needed)
declare -A CA_CERT_WHITELIST=(
# Example entry (replace with your actual cert and hash):
# ["my-org-root.crt"]="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
)
local found_valid=0
for certfile in .devcontainer/*.crt; do
if [[ ! -f "${certfile}" ]]; then
continue
fi
certname="$(basename "${certfile}")"
expected_hash="${CA_CERT_WHITELIST[${certname}]:-}"
if [[ -z "${expected_hash}" ]]; then
echo "WARNING: Skipping unapproved CA certificate: ${certname}"
continue
fi
actual_hash="$(sha256sum "${certfile}" | awk '{print $1}')"
if [[ "${actual_hash}" != "${expected_hash}" ]]; then
echo "WARNING: Hash mismatch for ${certname}, skipping."
continue
fi
echo "Installing approved CA certificate: ${certname}"
sudo cp "${certfile}" /usr/local/share/ca-certificates/
found_valid=1
done
if (( found_valid )); then
sudo update-ca-certificates
echo "Container's system CA certificates updated successfully"
else
echo "No valid CA certificates installed."
fi

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WilliamBerryiii thoughts? I'm inclined to consider this a non-risk, if an attacker has access to the repo they can do far worse damage by updating any of the initialization scripts for the dev container.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would love perspective from @agreaves-ms on this ... I'm on the fence here (mainly because I'm on windows and mucking with the cert store can be a PITA if things go sideways).

}

main() {
fix_volume_ownerships
npm_install
update_ca_certs
}

main "$@"
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
**/.git/
**/node_modules/
12 changes: 11 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
# Set the default behavior, in case core.autocrlf has not been set.
* text=auto

# Declare files that will always have LF line endings on checkout.
# Declare files that must have specific line endings on checkout.
## Windows scripts - must be CRLF
*.ps text eol=crlf
*.ps1 text eol=crlf
*.bat text eol=crlf
*.cmd text eol=crlf
*.bat text eol=crlf

## Linux scripts - must be LF
*.sh text eol=lf
*.Dockerfile text eol=lf
Dockerfile text eol=lf

# Denote all files that are truly binary and should not be modified.
*.docx binary
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -442,3 +442,7 @@ pr-reference.xml
.mcp/*-local.json
.mcp/*.local.json
.mcp/.env

# devcontainer rebuild
/.gitconfig.global
/.gitconfig.local
Loading