Building a Secure Copilot Development Environment

A Defence-in-Depth Approach to AI-Assisted Development

GitHub Dev Days 2026 | Secure Copilot Development Environment

About Me

Mark Gottselig

Principal Developer, MNP

https://github.com/Greven145

GitHub Dev Days 2026 | Secure Copilot Development Environment

Agenda

  • Problem Statement - Why AI without guardrails is risky
  • Defence-in-Depth Architecture - three independent layers, not one
  • Layer 1: Devcontainer & host hardening - isolation, capability restrictions, supply-chain script blocking, WSL/host coverage
  • Layer 2: AI configuration - Copilot CLI permissions/hooks/sandboxing, copilot-instructions.md, MCP servers, org governance
  • Layer 3: Pre-commit controls - hygiene, secret scanning, GitHub Actions security
  • What It Takes - Time, Scrutiny, Memory (what we learned running this in production)
  • Demo - The sample repo walkthrough
GitHub Dev Days 2026 | Secure Copilot Development Environment

The Problem: AI Without Guardrails

Real risks many have encountered:

  • Secrets leakage - API keys, credentials committed to git history
  • Hallucination - Non-existent APIs and packages referenced in generated code
  • Supply chain - Attackers publish malicous packages, poison dependency resolution
  • Misconfiguration - OAuth flows, CORS rules, secrets exposed in config
  • Process bypass - Code review skipped, quality gates disabled

These are process risks, and can affect a person or AI equally.

GitHub Dev Days 2026 | Secure Copilot Development Environment

Defence-in-Depth Architecture

GitHub Dev Days 2026 | Secure Copilot Development Environment

Before Layer 1: How a Dev Container Actually Works


VS Code stays on the host. Everything else - Copilot, extensions, terminal, debugger, file system access - runs inside the container.

Every layer that follows is about controlling what that container is allowed to do.

GitHub Dev Days 2026 | Secure Copilot Development Environment

Layer 1: Devcontainer Security Hardening

Base image: mcr.microsoft.com/devcontainers/dotnet:<pinned-sha>

Security posture:

--init                            # Proper PID 1 / signal handling
--cap-drop=ALL                    # Drop all Linux capabilities
--security-opt=no-new-privileges  # Prevent privilege escalation
--env-file .devcontainer/.env     # Runtime secret injection only
GitHub Dev Days 2026 | Secure Copilot Development Environment

Layer 1: Devcontainer Security Hardening

Runtime Isolation - Preferred hierarchy for containers

  • No Docker at all (preferred)
    • Smallest local attack surface and least privilege by default
  • Sysbox (second choice)
    • Stronger isolation model for nested container workflows
    • Better boundary than host-socket patterns for most teams
  • Docker-in-Docker (fallback)
    • Isolated daemon (no host socket mount)
    • Accept startup/runtime overhead in exchange for safer isolation
GitHub Dev Days 2026 | Secure Copilot Development Environment

Layer 1: Devcontainer Security Hardening

Memory & CPU

  • Prevents resource exhaustion attacks
  • remoteUser: vscode + shared caches keep behavior predictable
  • Telemetry opt-outs reduce unnecessary data emission (DO_NOT_TRACK, DOTNET_CLI_TELEMETRY_OPTOUT)
GitHub Dev Days 2026 | Secure Copilot Development Environment

Layer 1: Persistent Volumes for Developer State

Named volumes (survive container rebuilds):

~/.ssh                     # SSH keys for git/Azure
~/.azure                   # Azure CLI auth state
~/.config/gh               # GitHub CLI config + PATs
~/.copilot                 # Copilot CLI state (`COPILOT_HOME`)
~/.vscode-server/data/User # VS Code user settings/extensions
~/.nuget                   # NuGet credentials cache
~/.npm-cache               # npm package cache
~/.memory                  # AI session memory (knowledge graph)

Scope the credentials used to the minimum necessary for your work, and use short-lived credentials. If you are a super-admin, so potentially is your coding agent.

GitHub Dev Days 2026 | Secure Copilot Development Environment

Layer 1: Stop Auto-Run of Package Install Scripts

npm / pnpm / yarn

ignore-scripts=true   # .npmrc, baked into the devcontainer image
  • Blocks install hooks; becomes the default in npm v12, pnpm 10, Yarn 4.14 (2026). Only allowlist via npm approve-scripts for exceptions, don't re-enable globally

NuGet / MSBuild

  • .targets/.props auto-import - can run code on restore/build
  • Mitigate: NuGetAudit=true + source mapping + PR review of new files

Code can run the moment a dependency is added. Treat as the default, not the exception.

GitHub Dev Days 2026 | Secure Copilot Development Environment

Layer 1: Host-Level Defense - Beyond Docker

Container hardening stops at the sandbox edge. WSL2 and the host need their own coverage.

Microsoft Defender for Endpoint - WSL plug-in

  • Brings WSL file/process/network activity into the MDE timeline (eBPF telemetry)

Network proxies

  • MDE's WSL plug-in inherits the host's existing EDR telemetry proxy automatically
  • Route devcontainer/AI-agent egress through that same corporate proxy for visibility

Don't exclude WSL from real-time AV for performance - that reopens the blind spot.

GitHub Dev Days 2026 | Secure Copilot Development Environment

Layer 2: Lock the Agent Down First

copilot-instructions.md shapes what Copilot suggests, not what it's allowed to run.

  • Runtime permissions are the only real security boundary, not instructions
  • Deny by default, allowlist the minimum, sandbox the rest

Get the blast radius right first. Then tune behavior.

GitHub Dev Days 2026 | Secure Copilot Development Environment

Layer 2: Copilot CLI Hardening Rules

  • Allow specific subcommands - shell(git push), not bare shell(git)
  • Block destructive + network commands - rm -rf, git push, curl, wget, chmod
  • Sandbox the runtime - the one control that survives every other bypass
GitHub Dev Days 2026 | Secure Copilot Development Environment

Layer 2: Copilot CLI's Permission Surface

No single config file - five mechanisms, layered:

  1. Launch flags - --allow-tool/--deny-tool (per session, not persisted)
  2. Interactive prompts - Yes / Yes-for-session / No, at the moment of the call
  3. permissions-config.json - auto-saved approvals, per project (~/.copilot/)
  4. settings.json (personal) - trusted_folders, allowed_urls, denied_urls
  5. preToolUse hooks - the only mechanism that hard-blocks, deterministically
GitHub Dev Days 2026 | Secure Copilot Development Environment

Layer 2: Hard Denies via Launch Flags

copilot --deny-tool='shell(rm)' --deny-tool='shell(curl)' \
  --deny-tool='shell(env)' --allow-tool='shell(git status)' \
  --disallow-temp-dir
  • Deny always beats allow - even under --allow-all
  • Never alias --yolo / --allow-all

If you don't want to type all those commands each time, add an alias or script to your devcontainer

GitHub Dev Days 2026 | Secure Copilot Development Environment

Layer 2: settings.json - the Keys That Matter

// ~/.copilot/settings.json (personal)
{ "trusted_folders": [], "denied_urls": ["*"] }

// .github-private/managed-settings.json (enterprise admin only)
{ "permissions": { "disableBypassPermissionsMode": "disable" } }
  • Empty trusted_folders / denied_urls - approve per session, not globally
  • disableBypassPermissionsMode - enterprise-managed, blocks --yolo org-wide
GitHub Dev Days 2026 | Secure Copilot Development Environment

Layer 2: preToolUse Hook - a Deterministic Backstop

# hooks/deny-risky.sh (preToolUse, .github/hooks/policy.json)
CMD="$(jq -r .toolArgs.command <<<"$1")"
case "$CMD" in
  *"rm -rf"*|*"| sh"*|*"git push -f"*)
    echo '{"permissionDecision":"deny",
           "permissionDecisionReason":"blocked"}' ;;
esac
  • Only mechanism that hard-blocks a tool call before it runs
  • Known gap: not enforced on sub-agents spawned via the task tool
GitHub Dev Days 2026 | Secure Copilot Development Environment

Layer 2: What "Sandboxing" Actually Means

  • /sandbox enable runs Copilot's shell commands inside an OS-enforced sandbox - not another flag/prompt check
  • Built on Microsoft MXC: a kernel-enforced JSON policy controlling filesystem, network, clipboard, and display access
  • Experimental feature that needs to be enabled

Enforcement moves from the CLI's heuristics to the OS kernel.

GitHub Dev Days 2026 | Secure Copilot Development Environment

Layer 2: .github/copilot-instructions.md

Anti-hallucination guard example:

CRITICAL: Before referencing any external package, method, or API not already in 
the project manifest, verify it exists in NuGet.org, npm registry, or docs. If 
unverifiable, flag with // VERIFY: does this exist?

Secure-coding guard example:

CRITICAL: Never hardcode secrets, API keys, or credentials - use environment 
variables or the project's secret manager. Flag any use of eval(),
shell=True/os.system, string-concatenated SQL, or disabled TLS/certificate 
verification with // SECURITY: review required.

.github/copilot-instructions.md is infrastructure.

GitHub Dev Days 2026 | Secure Copilot Development Environment

Layer 2: MCP Servers - Minimum Tool Configuration

Example: Remote GitHub MCP server

{ // Default: read-only
  "type": "http",
  "url": "https://api.githubcopilot.com/mcp/readonly",
  "headers": { "X-MCP-Toolsets": "repos,issues" }
}

{ // Only when write access is justified: drop /readonly
  "type": "http",
  "url": "https://api.githubcopilot.com/mcp",
  "headers": { "X-MCP-Toolsets": "pull_requests" }
}
GitHub Dev Days 2026 | Secure Copilot Development Environment

Layer 2: Org Governance - MCP Allowlists

MCP enforcement (Copilot Business/Enterprise)

  • Allow all (default) - registry servers are recommendations only
  • Registry only - blocks any server not in the internal registry at runtime
  • Enforcement is server-ID/name-based today - can be bypassed by editing config files
  • GitHub's own guidance: disable MCP org-wide until stricter enforcement ships

Registry-only MCP is the org-level half of "deny by default."

GitHub Dev Days 2026 | Secure Copilot Development Environment

Layer 2: Org Governance - Audit Trails

Audit trail

  • preToolUse/userPromptSubmitted hooks double as audit logging - redact ghp_/gho_/ghu_/ghs_ tokens and Bearer/--token values before writing audit.jsonl
  • OpenTelemetry export exists but is off by default - turn it on
  • Review ~/.copilot/logs/ after autonomous runs

Redacted audit logs - so a bypass is at least detectable, even when it isn't preventable.

GitHub Dev Days 2026 | Secure Copilot Development Environment

Layer 3: Catching Issues on the Way Out

Layers 1 and 2 constrain what Copilot can do while it's running. Layer 3 checks what it actually produced, before it leaves your machine.

  • Runs locally, on every git commit - before push, before CI, before a reviewer ever sees it
  • Same checks for human- and AI-written code - it doesn't matter who typed it
  • Cheapest place to catch a mistake: your laptop, not a failed pipeline or a PR comment

If a secret or a vulnerable workflow change gets this far, it never left the room.

GitHub Dev Days 2026 | Secure Copilot Development Environment

Layer 3: Pre-Commit Controls - Universal Hygiene

Runs on every git commit

- trailing-whitespace
- end-of-file-fixer
- check-yaml, check-json
- check-merge-conflict
- check-added-large-files (1000 KB threshold)
- detect-private-key
- mixed-line-ending (enforce LF)
- check-case-conflict (Windows/Linux compatibility)

Blocks commit if any check fails.

GitHub Dev Days 2026 | Secure Copilot Development Environment

Layer 3: Pre-Commit Controls - Secret Scanning

detect-secrets (Yelp)

  • Regex patterns for known token types (GitHub, AWS, Azure)
  • Baseline suppression for false positives

gitleaks (TZRice)

  • More comprehensive regex ruleset than detect-secrets, Catches patterns detect-secrets misses
  • Specifically designed for git history scanning

Run as pre-commit hook. Non-blocking baseline mode: only new detections block.

GitHub Dev Days 2026 | Secure Copilot Development Environment

Layer 3: Pre-Commit Controls - GitHub Actions Security Scanning

zizmor (1.24.1)

  • Detects workflows with pull_request_target + unpinned steps
  • Catches expression injection vulnerabilities
  • Flags insecure patterns like ${{ github.event.issue.title }} in shell

Run as pre-commit hooks on .github/workflows/

GitHub Dev Days 2026 | Secure Copilot Development Environment

What you have to pay for security.

Time. Scrutiny. Memory.

The controls are the easy artifact to produce. The discipline to operate them is the actual deliverable.

GitHub Dev Days 2026 | Secure Copilot Development Environment

What It Takes: Time

The timeline:

  • First: Devcontainer + configuration + pre-commit setup (seems easy, is hard)
  • Next: copilot-instructions.md + MCP discovery (defining Copilot's rules is iterative)
  • Lastly: Refinement + lessons learned

Budget accordingly. Don't try to do it in a sprint.

GitHub Dev Days 2026 | Secure Copilot Development Environment

What It Takes: Scrutiny

None of this self-enforces:

  • Permissions, hooks, and sandboxing stop dangerous actions - they say nothing about bad code
  • Pre-commit and CI catch known patterns - they don't catch a subtly wrong business rule or a misused API
  • Someone still has to actually read every diff Copilot produces

Scrutinize AI-authored PRs at least as closely as human ones - more, until trust is earned.

GitHub Dev Days 2026 | Secure Copilot Development Environment

What It Takes: Memory

Scrutiny only compounds if it's written down:

  • Every incident is a choice: fix it once, or turn it into a rule that prevents the next one
  • copilot-instructions.md and pre-commit baselines are where those rules live - durable, reviewable, machine-enforced
  • Without that, every new hire (and every new agent session) relearns the same lesson the hard way

Your instructions file and your baselines are the team's memory. Treat them like it.

GitHub Dev Days 2026 | Secure Copilot Development Environment

Demo: The Sample Repo Walkthrough

We've built Greven145/secure-copilot-devenv - a complete reference implementation.

What's inside:

  • .devcontainer/ - Full security-hardened container (DinD, cap-drop, pinned versions)
  • .pre-commit-config.yaml - All hooks (gitleaks, zizmor, actionlint, detect-secrets)
  • .github/copilot-instructions.md - AI constraints + anti-hallucination guard
  • src/ + tests/ - Minimal .NET MCP server (enough for hooks to have real work)
  • .github/workflows/ - CI + Dependabot
  • Directory.Build.props - NuGetAudit=true + source mapping
GitHub Dev Days 2026 | Secure Copilot Development Environment

Takeaways

  1. Defence-in-depth is non-negotiable. Each layer is independent. Each layer catches different risks.
  2. AI config is infrastructure. .github/copilot-instructions.md is not documentation - it's executable rules that evolve over time.
  3. Local feedback is faster than CI. Pre-commit hooks run before you push. Catch issues early.
  4. Security is iterative and layered. Every incident = add a rule to prevent next time. Your copilot instructions and pre-commit hooks will grow.
  5. Budget closer to 10 weeks, not 2 days. Building this right takes time. The payoff is: AI assistance without compromising security.
GitHub Dev Days 2026 | Secure Copilot Development Environment

Q&A

Questions?

or

Grab the sample repo, spin up the devcontainer, and let's debug it together.

GitHub Dev Days 2026 | Secure Copilot Development Environment

Thank You!

Scan to view this presentation

QR code linking to this presentation

greven145.github.io/github-devdays-presentation

Scan for the sample repo

QR code linking to the sample repo

github.com/Greven145/secure-copilot-devenv

GitHub Dev Days 2026 | Secure Copilot Development Environment

- Builds on 6 months of production experience running AI-assisted dev at scale - We've learned what works, what breaks, what can't be skipped - Payoff: a checklist to apply these patterns to your own projects

- Thesis: these risks aren't inherent to AI - right controls = AI *and* security - Hallucination example: Copilot suggested a .NET API that didn't exist, broke the build - Secret leakage: detect-secrets alone isn't enough - need gitleaks too (complementary regex) - Supply chain: lookalike packages / poisoned dependency resolution → malicious installs, postinstall execution - Key line: we didn't build these because we distrust AI - we trust the process, and the process must survive both human and machine error

- Layered defense: if one layer fails, others still protect you - Not belt-and-suspenders paranoia - it's insurance - Example: dev forgets pre-commit → CI's secret scan still catches it - Example: devcontainer escapes capability restrictions → AI config + pre-commit are independent backstops - Track record: 6 months production use, zero confirmed security incidents

- Source: code.visualstudio.com/docs/devcontainers/containers - Ground the audience here before Layer 1 - rest of hardening only makes sense once this split is clear - VS Code UI/theme extensions never leave local OS - exposed port connects to "VS Code Server" inside container - Workspace extensions, terminal, app, debugger, file system all run inside container boundary (source via volume mount, not copy) - Copilot (CLI, Chat, or editor agent) inherits the container's capabilities - NOT the host's - Why this matters: harden this box (cap-drop, no-new-privileges, avoid DinD) and you've bounded everything Copilot can touch

- Mirrors the reference config: minimal Ubuntu base, run with init, drop all capabilities, enforce no-new-privileges - Secrets injected at runtime via env-file (Key Vault in production) - never committed to devcontainer.json - Point: least privilege + secret hygiene by default, not optional team discipline

- Ordering is intentional: avoid local container daemons unless you need them - Container-in-container required? Prefer stronger sandboxing (Sysbox) first - DinD is the practical fallback (e.g. Aspire workflows) - DOOD (host socket mount) is out-of-policy - collapses host/container boundary, weakens every other control in this layer

- Resource control paired with execution predictability: non-root remote user, shared cache volumes, explicit telemetry opt-outs - Reduces noisy-neighbor behavior, avoids accidental root-owned artifacts - Limits non-essential outbound metadata while preserving developer ergonomics

- Reference config separates host bind mounts (read-only where possible) from named volumes that persist auth state/caches across rebuilds - Security/usability trade: preserve minimum state needed so devs don't resort to insecure workarounds - Still keeps secrets out of git, limits writable host surface area

- Devcontainer-level complement to the supply-chain risk from the Problem slide - Lifecycle scripts (npm postinstall, NuGet MSBuild targets) = code exec the moment install/restore runs - no CVE required - Bake ignore-scripts=true into devcontainer's .npmrc - applies by default to every install - Ecosystem catching up: npm v12 (mid-2026), pnpm 10, Yarn 4.14 flip default off - response to "Miasma" worm (postinstall scripts) - NuGet vector less known: PackageReference closed install.ps1, but build/.targets + .props still auto-import and can carry MSBuild tasks - Real exploit: "IAmReboot" NuGet/MSBuild abuse campaign - Mitigation: NuGetAudit + source mapping + PR review of new targets/props files

- Closes a gap reviewers often raise: devcontainer hardening only covers inside the sandbox - Host machine (esp. Windows + WSL2, most common local setup here) needs equivalent coverage - WSL2 was a blind spot for MDE until recently - plug-in (via Windows Update) brings WSL file/process/network telemetry into the same timeline, eBPF-based - Pair with endpoint mgmt: Intune settings catalog (or org's EPP/MDM) to govern allowed WSL distros/versions - Host firewall rules auto-apply to WSL via mirrored networking (Windows 11 22H2 + WSL 2.0.9+) - Route devcontainer/AI-agent egress through the same proxy/egress filter - don't let a compromised MCP server or bad package skip network controls just by being inside WSL - Common failure mode: teams exclude WSL vhdx from real-time AV for performance - reopens the exact blind spot the MDE plug-in closes

- Sets up why this section opens with Copilot CLI runtime permissions before copilot-instructions.md and MCP - Least privilege is the operating model for Copilot's runtime, not just its instructions file

- From settings-config.md's Copilot CLI tool-pattern syntax: shell(COMMAND) takes first-level subcommand for git/gh - shell(git:*) matches git push/git pull but NOT gitea - Bare shell(git) quirk = issue #1736 - the Copilot-specific case for "don't rely on broad allows" - Use precise subcommand allows, not a wildcard on the binary - Sandboxing = single highest-value control because it survives a permission-system bypass

- Copilot CLI has NO first-class permissions block (allow/deny/ask arrays) in committed JSON - Depends instead on launch flags, auto-saved permissions-config.json, and hooks - plan hardening around that gap - Flags/prompts = ephemeral, per-session - permissions-config.json / settings.json = persist but still heuristic/advisory - preToolUse hooks = only layer that's deterministic, enforced before the tool runs - Why Layer 2 builds toward hooks + sandboxing as the real backstops, not the settings file

- shell(env) deny = PromptArmor-recommended mitigation for the env-bypass incident (covered two slides later) - Deny-beats-allow and never-alias-yolo both come straight from settings-config.md Stage 1 recommendations

- disableBypassPermissionsMode is real/current (GitHub changelog, June 17 2026) - enterprise admin control, NOT a personal setting - Lives in org's managed-settings.json (.github-private repo; legacy path .github/copilot/settings.json also works) - GitHub pulls it automatically for Copilot Business/Enterprise users - Blocks --yolo/--allow-all, /yolo command, all combined-bypass runtime paths - Gap to call out if asked: does NOT block individual flags like --allow-all-tools or --allow-all-paths - Don't imply a developer can set this in their own settings.json

- Hook = deterministic enforcement layer behind the heuristic flags/prompts - Verified against GitHub Docs (copilot-cli/customize-copilot/use-hooks, copilot-sdk/hooks/pre-tool-use) - Registers under hooks.preToolUse in .github/hooks/*.json with a bash/powershell script path - Script receives tool call (toolArgs.command for shell calls), returns {"permissionDecision":"deny",...} to block before it runs - Live example: deny download-and-execute pipelines (curl | sh) and force-pushes, same as GitHub's own tutorial hook - Known gap: NOT enforced on sub-agents via the task tool - tracked, documented limitation (settings-config.md cites issue #2392) - Call this out so team doesn't treat hooks as a complete boundary

- Verified via GitHub Docs (cloud-and-local-sandboxes/configuring-local-sandbox-settings) + June 2 2026 changelog ("sandboxes for Copilot now in public preview") - MXC = Microsoft Execution Containers - OS-level isolation primitive Microsoft ships across Windows/WSL for agentic workloads generally - /sandbox opens config UI with General/Filesystem/Network tabs to scope what the sandboxed process can touch - Copilot-specific, per-session control that sits *inside* whatever container Layer 1 already provides - Scopes what Copilot itself can do, not just where the dev environment runs - Status: public preview as of June 2026, subject to change

- copilot-instructions.md turns team conventions into machine-readable constraints - Two failure modes to focus on: - Hallucination: require verification before referencing packages/APIs not already in the manifest - Insecure-by-default codegen: require self-flagging risky patterns (hardcoded secrets, shell injection, string-built SQL, disabled TLS) instead of silently emitting them - Both examples turn a soft preference into a repeatable, reviewable instruction - Treat this file like policy-as-code - refine whenever you discover a new failure pattern

- The actual lever that matters: tool scoping, not "good MCP examples" - Concrete example: remote GitHub MCP server (api.githubcopilot.com/mcp/, see github/github-mcp-server docs/remote-server.md) - Path segments compose the access grant: / = default toolset, /readonly = force read-only, /x/{toolset} = exactly one toolset, /x/{toolset}/readonly = combine both - Insiders Mode section: /insiders and /readonly/insiders = same pattern for opt-in early-access tools - Equivalent X-MCP-Toolsets / X-MCP-Readonly / X-MCP-Insiders headers for clients that can't encode in URL - Recommend /x/{toolset}/readonly as default for most sessions (research, triage, review prep) - only swap to write-enabled when needed - Same least-privilege principle as Layer 1 capability drops, applied at the MCP layer - access level declared in the connection itself, not buried in env vars/flags

- From settings-config.md's "Enterprise/org governance" section - "Registry only" is enforcement, but walkable by editing local config - deterrent/detection control, not a hard boundary - GitHub's own advice for highest security bar: disable MCP org-wide until that gap closes, rather than trust the registry to hold

- Audit logging isn't automatic - same hooks that block dangerous calls double as the ones you wire up to log every call - Bake in secret redaction (toolArgs can contain tokens) - OTel export has to be deliberately turned on - off by default - If nobody pipes these to centralized logging, "we have audit logs" is theoretical - Pairs with MCP allowlist slide: registry-only enforcement is bypassable, so audit trail is what catches the bypass after the fact

- Section transition: Layer 1 = perimeter (devcontainer/host), Layer 2 = in-flight behavior (permissions, hooks, sandboxing) - both constrain the agent while it acts - Layer 3 is different in kind: not stopping Copilot from doing something - it's the safety net inspecting the diff itself, regardless of who/what produced it - Say out loud: pre-commit doesn't care the code came from AI - a hardcoded secret or unpinned Action is just as bad from a human - Why it's worth investing in even for teams not using AI yet - Cheapest place in the pipeline to catch something: instant local feedback beats a red CI run or a review comment 3 days later

- "Boring" checks that prevent common accidents: - trailing spaces → noisy diffs - missing newlines at EOF - YAML/JSON syntax errors caught before CI - incomplete merge conflict resolution - accidental large file commits - obvious PEM headers (RSA keys, etc.) - line ending inconsistencies that break scripts - case sensitivity issues on cross-platform teams - Not glamorous, but every one of these has caused production incidents before

- Why both? detect-secrets = tuned for high-entropy patterns; gitleaks = deeper knowledge of specific token formats (GitHub PATs, Slack webhooks, Azure SAS URLs) - they catch different things - Real incident 1: detect-secrets missed a GitHub token embedded in a larger string - gitleaks caught it via GitHub-token-specific regex - Real incident 2: high-entropy hex string not flagged (matched a UUID enumeration in the baseline) - gitleaks caught it via AWS access key format pattern - Conclusion: you need both

- GitHub Actions are a common attack vector - pull_request_target runs with write permissions on the base branch - extremely dangerous combined with untrusted code - Expression injection: user input interpolated directly into a shell command without escaping - Example: `run: echo ${{ github.event.issue.title }}` where title = `; rm -rf /` - These tools catch these patterns automatically

- Section marker: closes the three-layer architecture, pivots from "here's what to build" to "here's what it takes to live with it" - Keep this slide spoken, not read - three words doing the work of the next three slides - Time = the 7-week build-out wasn't optional overhead, it's the floor - Scrutiny = none of this self-enforces; a human must keep reading Copilot's output closely enough to catch what it misses - Memory = scrutiny only compounds if written down - that's what copilot-instructions.md is for

- Most teams will be over-optimistic about timeline - say this explicitly - The technical work isn't the hard part - Hard part: discovering what your rules should be, curating baselines (detect-secrets false positives need manual suppression), integrating all the pieces, debugging in CI, refining Copilot constraints through real use - Plan for it

- Layers 1-3 constrain behavior or catch known-bad patterns - none substitute for a human asking "is this right?" - Devcontainer can't tell you the agent misunderstood a requirement; a secret scanner can't tell you a SQL query is subtly wrong - Scrutiny doesn't show up in any config file - it's a team habit - Most likely to erode under deadline pressure - exactly when you need it most

- Payoff of treating copilot-instructions.md as infrastructure (Layer 2), not documentation - It's where institutional knowledge persists across people and sessions - Copilot has no memory between sessions by default - Same logic for detect-secrets/gitleaks baselines: each suppression/new pattern is a small permanent improvement the next person benefits from - Skip this and you're stuck re-litigating the same incident every few months

- Repo is intentionally minimal - not a tutorial project with lots of code - It's scaffolding that demonstrates: where secrets scanning goes, how to structure copilot-instructions.md, how pre-commit hooks wire into CI - Minimal .NET code is deliberate - value isn't in the app code, it's in the *surrounding infrastructure*