diff --git a/.github/instructions/about-confidential-containers.instructions.md b/.github/instructions/about-confidential-containers.instructions.md new file mode 100644 index 0000000000..b377d42dd4 --- /dev/null +++ b/.github/instructions/about-confidential-containers.instructions.md @@ -0,0 +1,180 @@ +--- +description: Use this when touching internal/guest/ or internal/gcs-sidecar code, or any other code related to confidential containers (C-LCOW or C-WCOW). +--- + +# Confidential Containers (C-LCOW and C-WCOW) + +C-LCOW (Confidential Linux Containers on Windows) and C-WCOW (Confidential +Windows Containers on Windows) are containers running in an SNP UVM (a trusted +execution environment backed by AMD SEV-SNP). Inside the UVM, all sensitive +operations coming from the (untrusted) host are gated by a Rego-based security +policy. This functionality backs C-ACI, the confidential SKU of ACI. + +## Trust model + +- The UVM is launched with a "host data" field set to the SHA-256 of the + customer's security policy. When the GCS (on Linux) or gcs-sidecar (on + Windows) starts, it receives the policy from the host via an RPC (a + `ModifySettings` request for `guestresource.ResourceTypeSecurityPolicy`). + That RPC is itself untrusted — the host could send any policy — but before + the GCS constructs a `regoEnforcer` from it, it requests an SNP attestation + report from the PSP and verifies that the SHA-256 of the received policy + matches the `host_data` field baked into the report at launch. If the + hashes don't match, the policy is rejected. Until this RPC has been + processed, the enforcer in use is the `ClosedDoorSecurityPolicyEnforcer`, + so nothing meaningful can happen. +- After that point, **every** subsequent request from the host is treated as + untrusted input. The policy describes which containers may run (layer hashes, command, + env, mounts, user, capabilities, seccomp, privileged, etc.), which external + processes may exec, which fragments may be loaded, and so on. +- For C-LCOW the GCS itself performs the requested operation after enforcement + (mount filesystems, spawn processes, invoke runc). For C-WCOW the + gcs-sidecar validates and rewrites the request, then forwards it to the + Windows "inbox GCS" (a built-in C++ component) which performs the operation. + The sidecar is the only enforcement boundary — anything passed unchecked + through to the inbox GCS is effectively trusted. + +Note that the only untrusted party as far as the confidential model is concerned +is the host. The policy author, the policy itself, and the container workload +are all inside the trust boundary: + +## Rego modules + +The interpreter (see [internal/regopolicyinterpreter](internal/regopolicyinterpreter)) +loads several Rego modules into one OPA evaluation: + +- `policy` — the customer's policy, generated by tooling from the container + group spec. Declares allowed containers, external processes, fragments, and + a set of base flags (`allow_properties_access`, `allow_unencrypted_scratch`, + `allow_environment_variable_dropping`, `allow_capability_dropping`, …). + Customer policies typically just **delegate** each enforcement point to + `data.framework.`; the policy mainly supplies data. +- `framework` — [pkg/securitypolicy/framework.rego](pkg/securitypolicy/framework.rego), + bundled with GCS. Contains all the actual matching/narrowing logic for + every enforcement point, plus the error-reason rules used to build denial + messages. Has a `version` (see `version_framework`) that the policy's + `framework_version` is compared against; the framework includes backward + compatibility shims (`check_container`, `apply_defaults`, etc.) so newer + GCS can run older policies. + > **The framework must never break existing policies.** Customer + > policies are generated by tooling and bundled with images that may not + > be regenerated for a long time. Any framework change must preserve the + > exact decision for every previously-valid policy: gate new behavior on + > `policy_framework_version` / `policy_api_version`, add an + > `apply_defaults` / `check_*` shim for any new object field, and bump + > `version_framework` / `version_api` accordingly. The same rule applies + > to fragments (`fragment_framework_version`). +- `api` — [pkg/securitypolicy/api.rego](pkg/securitypolicy/api.rego). Declares + every enforcement point, the version it was introduced in, its default + result if missing, and whether `use_framework` is set (so the enforcer can + fall back to `data.framework.` when the policy itself does not + define the rule). +- **Fragments** — additional Rego modules loaded at runtime after doing + signature verification and having the issuer checked by the `load_fragment` + enforcement point. Each fragment lives in its own namespace (parsed from the + required `package ` first line; reserved namespaces `framework`, `api`, + `policy`, `metadata` are rejected — see `parseNamespace`). Fragments can + contribute additional allowed `containers`, `external_processes`, and nested + `fragments`. + +## Enforcement point lifecycle + +The call path for a host request to a policy decision: + +1. The host sends a bridge message. For C-LCOW this lands in the bridge in + [internal/guest/bridge](internal/guest/bridge); for C-WCOW it lands in + [internal/gcs-sidecar](internal/gcs-sidecar). +2. The request is unmarshalled and dispatched, typically into + [internal/guest/runtime/hcsv2/uvm.go](internal/guest/runtime/hcsv2/uvm.go) + (`Host.CreateContainer`, `Host.modifyMappedVirtualDisk`, + `Host.modifyCombinedLayers`, `Host.ExecProcess`, …) on Linux, or + equivalent sidecar handlers on Windows. +3. The handler calls into a `SecurityPolicyEnforcer` method (see + [pkg/securitypolicy/securitypolicyenforcer.go](pkg/securitypolicy/securitypolicyenforcer.go)) + such as `EnforceDeviceMountPolicy`, `EnforceOverlayMountPolicy`, + `EnforceCreateContainerPolicyV2`, `EnforceExecInContainerPolicyV2`, etc. +4. The `regoEnforcer` (see + [pkg/securitypolicy/securitypolicyenforcer_rego.go](pkg/securitypolicy/securitypolicyenforcer_rego.go)) + builds an `inputData` map for the enforcement point and calls + `policy.enforce(ctx, name, input)`, which queries + `data.policy.` (with fallback to `data.framework.` via + `applyDefaults` when the policy delegates or the rule pre-dates the + policy's `api_version`). +5. The Rego result is either `{"allowed": true, ...extra}` or `{"allowed": false}`. + On deny, the enforcer queries `data.policy.reason` to build a structured + error (`denyWithReason` / `policyDecisionToError`, truncating + `error_objects`, then `input`, then `reason` until under + `maxErrorMessageLength`). +6. Extra outputs are interpreted by the Go side: e.g. `env_list` filters the + env that survives "env dropping", `caps_list` filters capabilities, + `allow_stdio_access` decides whether the container's stdio uses the real + vsock transport or `/dev/null`, `add_module` decides whether a loaded + fragment is kept. + +## Metadata and revertable sections + +Rego is functional, but enforcement needs state (which devices are mounted, +which overlays exist, which containers have been started, which fragments +have been loaded, fragment parameter sets, …). We invented **metadata** for +this: a Rego rule can return a `metadata` array of operations +(`add`/`update`/`remove` on named maps, plus the newer "set" type) and the +interpreter applies them after a successful query. See `framework.rego` +usage of `data.metadata.devices`, `data.metadata.matches`, +`data.metadata.started`, `data.metadata.overlayTargets`, +`data.metadata.scratch_mounts`, `data.metadata.issuers`, +`data.metadata.fragment_parameters`, etc. + +Because metadata mutates real state, mount/unmount handlers wrap their work +in a **revertable section** (`StartRevertableSection` / `Commit` / `Rollback`, +see the methods on `regoEnforcer` and `commitOrRollbackPolicyRevSection` in +`hcsv2/uvm.go`). On any error the metadata is rolled back so it stays in +sync with reality. Any new side-effecting handler **must** use this pattern, +and the underlying operation **must** clean up its own partial state on +failure (the revert only undoes the Rego metadata, not files/mounts/dm +devices). If a clean revert is not possible, there should be a mechanism to +stop further operations to prevent exploits (e.g. the `mountsBroken` mechanism +for mounts). + +## Threat-model rule of thumb + +Treat **everything** coming from the host as adversarial. When changing +any in-guest code path, work through the checklist in the +[review-confidential-security](../skills/review-confidential-security/SKILL.md) +skill. + +## C-WCOW specifics + +- The enforcement boundary is [internal/gcs-sidecar](internal/gcs-sidecar), + not the inbox GCS. The sidecar unmarshals the request, runs policy + enforcement, potentially rewrites the payload (e.g. filtering env or ETW + providers), and only then forwards it. +- Anything the sidecar does **not** validate is effectively delegated to the + inbox GCS, which is not part of this repo. New resource types added to the + sidecar dispatcher must come with explicit enforcement, not a pass-through. +- The same Rego enforcer is shared with C-LCOW (`policy.osType` switches + per-input shape inside `EnforceCreateContainerPolicyV2` etc.), so changes + to framework.rego must consider both Linux and Windows code paths. + +## Where to look + +- Rego: [pkg/securitypolicy/framework.rego](pkg/securitypolicy/framework.rego), + [pkg/securitypolicy/api.rego](pkg/securitypolicy/api.rego), + [pkg/securitypolicy/policy.rego](pkg/securitypolicy/policy.rego) +- Go enforcer: [pkg/securitypolicy/securitypolicyenforcer_rego.go](pkg/securitypolicy/securitypolicyenforcer_rego.go), + [pkg/securitypolicy/securitypolicyenforcer.go](pkg/securitypolicy/securitypolicyenforcer.go) +- Interpreter: [internal/regopolicyinterpreter](internal/regopolicyinterpreter) +- C-LCOW handlers: [internal/guest/runtime/hcsv2/uvm.go](internal/guest/runtime/hcsv2/uvm.go) +- C-WCOW handlers: [internal/gcs-sidecar](internal/gcs-sidecar) +- Bridge sequential mode: [internal/guest/bridge](internal/guest/bridge) +- Tests: `regopolicy_linux_test.go`, `regopolicy_windows_test.go`, `api_test.rego` + +## Build tag + +The Rego enforcer is gated behind the `rego` Go build tag (see the +`//go:build rego` headers on `securitypolicyenforcer_rego.go` and the +test files). Non-confidential LCOW builds of GCS deliberately do not +include any Rego code. When building or testing any confidential change +you must pass `-tags rego`, e.g. `go build -tags rego ./...` or +`go test -tags rego ./pkg/securitypolicy/...`. Without it, your changes +to the Rego enforcer will not be compiled and the tests will silently +not run. diff --git a/.github/skills/add-enforcement-point/SKILL.md b/.github/skills/add-enforcement-point/SKILL.md new file mode 100644 index 0000000000..d8b2c053cd --- /dev/null +++ b/.github/skills/add-enforcement-point/SKILL.md @@ -0,0 +1,187 @@ +--- +name: add-enforcement-point +description: Step-by-step procedure for adding a new Rego enforcement point to the hcsshim confidential containers (C-LCOW / C-WCOW) policy. Use when introducing a new host-driven operation that needs to be gated by the security policy, or when extending an existing enforcement point with new inputs. +--- + +# Adding a new enforcement point + +Before starting, read +[.github/instructions/about-confidential-containers.instructions.md](../../.github/instructions/about-confidential-containers.instructions.md) +for background on the trust model, modules, metadata, and revertable sections. + +> **Backward compatibility is non-negotiable.** Customer policies are +> generated by tooling and shipped alongside container images; we do not +> control when they are regenerated. Any change to `framework.rego`, +> `api.rego`, or the container/external_process/fragment object shapes +> **must** continue to evaluate identically for every policy that was +> valid before the change. Concretely: never remove or rename existing +> rules or fields, never change the meaning of an existing input field, +> always gate new framework behavior on `policy_framework_version` / +> `policy_api_version`, and always provide an `apply_defaults` / `check_*` +> shim for new fields so older policies see a sensible default. If your +> change cannot be expressed this way, it is the wrong shape — stop and +> rethink. + +## 1. Decide the shape + +- **Name** — snake_case, matches the Go method and the Rego rule name + (`mount_device`, `create_container`, `signal_container_process`, …). +- **Inputs** — every field the policy may need to make a decision. Anything + the host controls is fair game and should be passed. Things GCS itself + derives (e.g. dynamically added devices for privileged containers, or + `/dev/sev-guest`) should be stripped before passing, otherwise the + customer policy would have to whitelist them. +- **Outputs** — `allowed` is required. Optional extras: `metadata` (state + updates), `env_list`, `caps_list`, `allow_stdio_access`, `add_module`, + any new field you need (must also be wired into the Go side). +- **Default result** — what the framework should return when the policy + pre-dates this enforcement point (`use_framework: false` case) or has no + rule defined. + +## 2. Whether to extend an existing enforcement point or create a new one + +Prefer introducing a new enforcement point over adding a new input field to +an existing one, even when the new operation can be considered a variant of an +existing one. A customer policy that pre-dates the change may have its own +hand-written rule for the existing enforcement point that does not look at the new field; +silently widening the input set can mean that their rule still evaluates to `allowed`, +even for a request it never considered and would not have intended to allow. + +You may consider extending an existing point if the new field makes the +enforcement less restrictive (i.e. add in more ways to allow an operation, +additional allowed fs types / options etc when the existing check is already +comparing the input with a set of allowed values), in a way such that anything +that would be denied by an old policy would still be denied. + +## 3. Register the enforcement point + +Edit [pkg/securitypolicy/api.rego](../../pkg/securitypolicy/api.rego) and +add an entry to `enforcement_points`. Bump the api `version` if you're +introducing a new version (and update `version_api`). Choose +`use_framework` based on intent: + +- `use_framework: true` — the policy may omit a rule for this point and + the enforcer will fall back to `data.framework.`. Use when + the framework can make a complete decision from existing policy data + (containers, fragments, flags) without requiring customers to write + anything new. +- `use_framework: false` with a permissive `default_results` (e.g. + `{"allowed": true}`) — older policies that don't know about this point + get the safe-by-default behavior. +- `use_framework: false` with `default_results: {"allowed": false}` — + required when the new feature needs additional fields in the customer's + policy (or in a fragment) to make a decision. An older policy without + those fields cannot meaningfully allow the operation, so it must deny. + Customers who want the feature must regenerate their policy with the + new tooling. + +## 4. Implement in framework.rego + +In [pkg/securitypolicy/framework.rego](../../pkg/securitypolicy/framework.rego): + +- Add `default := {"allowed": false}` (or the right default). +- Add one or more rule bodies. Use existing patterns: + - **Narrowing pattern** (`create_container`, `exec_in_container`): start + with `data.metadata.matches[input.containerID]`, repeatedly filter + with `count(...) > 0` after each criterion. Each narrowing step should + have a matching `errors[...]` rule (see below) so denials produce + useful messages. + - **Mount/unmount pattern**: check the target is not already mounted (or + is mounted), validate the path/regex, look up the expected hash/etc + against `data.metadata.devices` / `data.policy.containers[_].layers`, + return a `metadata` op (`add`/`remove`/`update`). +- For `is_linux` / `is_windows` differences provide separate rule bodies + with the relevant guard. +- If you need persistent state, use the metadata machinery rather than + trying to compute it from inputs. +- Bump `version` (and `version_framework`) if you broke compatibility, and + add a corresponding `check_*` / `apply_defaults` shim so older policies + keep working. +- Add a "delegation" for your new rule in the default / test rego + files: + - [pkg/securitypolicy/policy.rego](../../pkg/securitypolicy/policy.rego): + add ` := data.framework.`. + - [pkg/securitypolicy/open_door.rego](../../pkg/securitypolicy/open_door.rego): + add ` := {"allowed": true}`. + +## 5. Add error rules + +For every narrowing step, add `errors["human readable reason"] { input.rule == ""; }`. These are +queried via `data.policy.reason` on a deny to build the structured error +message. Without them the user sees `noReasonMessage`. + +Also extend `error_objects` if your decision produces a useful set of +candidate objects (containers, processes, fragments) that should appear in +the denial. + +## 6. Wire the Go side + +In [pkg/securitypolicy/securitypolicyenforcer.go](../../pkg/securitypolicy/securitypolicyenforcer.go), +add the method to the `SecurityPolicyEnforcer` interface. Implement it as a +no-op (or always-allow) on the non-Rego enforcers (`open_door`, +`closed_door`, JSON enforcer if still present, `standard`). + +In [pkg/securitypolicy/securitypolicyenforcer_rego.go](../../pkg/securitypolicy/securitypolicyenforcer_rego.go): + +```go +func (policy *regoEnforcer) EnforceMyThingPolicy(ctx context.Context, ...) (..., error) { + input := inputData{ + "field1": ..., + ... + } + results, err := policy.enforce(ctx, "my_thing", input) + if err != nil { + return ..., err + } + // extract any extra outputs from results + return ..., nil +} +``` + +- For OS-dependent input shape, switch on `policy.osType`. +- For sensitive fields, extend `redactSensitiveData` / + `replaceCapabilitiesWithPlaceholders` so they don't leak in error + messages. +- If your point mutates state, wrap the call site in a revertable section + (`StartRevertableSection` + `commitOrRollbackPolicyRevSection`) and make + sure the underlying operation cleans up partial state on its own failure. + +## 7. Call from the handler + +Linux: add the call in the appropriate handler in +[internal/guest/runtime/hcsv2/uvm.go](../../internal/guest/runtime/hcsv2/uvm.go) +(or wherever the host request first enters the trusted code). Validate +host-controlled fields on the Go side **first** (length, regex, path +equality, etc.) where the policy cannot meaningfully check them — e.g. +`checkValidContainerID`, `checkContainerSettings`. + +Windows: add the call in the corresponding gcs-sidecar handler under +[internal/gcs-sidecar](../../internal/gcs-sidecar). Make sure the request +is enforced **before** it is forwarded to the inbox GCS, and that any +rewriting (env filtering, etc.) happens after enforcement so the forwarded +payload reflects what was approved. + +## 8. Tests + +Add enforcer-level tests (build-tagged `rego`) in +[pkg/securitypolicy/regopolicy_linux_test.go](../../pkg/securitypolicy/regopolicy_linux_test.go) +and/or +[pkg/securitypolicy/regopolicy_windows_test.go](../../pkg/securitypolicy/regopolicy_windows_test.go) +covering: allow case, each deny case (one per `errors[...]` rule), metadata +state after success, and (where relevant) cross-interaction with fragments. +Add an entry to +[pkg/securitypolicy/api_test.rego](../../pkg/securitypolicy/api_test.rego) +so the api.rego registration is exercised. Remember to run tests with +`go test -tags rego ./pkg/securitypolicy/...` — without the tag they are +silently skipped. + +## 9. Compatibility checklist + +- Did you bump `version_api` / `version_framework` if the change is + observable to existing policies? +- Did you add `apply_defaults` / `check_*` shims for any new fields on + container / external_process / fragment objects? +- Does the no-op behavior on the non-Rego enforcers match the policy's + default (typically allow for backward compatibility)? +- For C-WCOW: does your input shape work for both Linux and Windows, or do + you need an `is_linux` / `is_windows` split? diff --git a/.github/skills/review-confidential-security/SKILL.md b/.github/skills/review-confidential-security/SKILL.md new file mode 100644 index 0000000000..dc233f916a --- /dev/null +++ b/.github/skills/review-confidential-security/SKILL.md @@ -0,0 +1,138 @@ +--- +name: review-confidential-security +description: Review a commit, PR, or local diff for confidential (C-LCOW / C-WCOW) enforcement security regressions. Use when the user asks to "audit", "security review", "check for confidentiality / enforcement bugs", or otherwise vet changes that touch GCS, gcs-sidecar, the security policy, OCI spec handling, or any host-driven request path in hcsshim. +--- + +# Reviewing a change for confidential-containers security bugs + +Before starting, read +[.github/instructions/about-confidential-containers.instructions.md](../../.github/instructions/about-confidential-containers.instructions.md) +for the trust model and the existing hardening conventions you must not +regress. Note that this skill is only about confidential enforcement. If you're +doing a general PR review, consider other bugs too, including on the host. + +## 1. Get the diff + +- If a commit/PR/range is named, fetch it (`git show `, + `git log -p `, or the GitHub PR tools). Otherwise diff the working + tree (`git diff` / `git diff main...HEAD`). +- Read the commit message(s) and PR description first — they often state + intent that the code does not. + +## 2. Classify the change + +Decide which trust boundary the change touches. Anything in this list is +**in scope** for a confidential review: + +- `internal/guest/**`, `cmd/gcs*` — C-LCOW trusted code. +- `internal/gcs-sidecar/**`, `cmd/gcs-sidecar/**` — C-WCOW trusted code. +- `pkg/securitypolicy/**` — Rego policy, framework, enforcer, interpreter + bindings. +- `internal/regopolicyinterpreter/**` — Rego interpreter glue. +- `internal/guest/bridge/**` — request dispatch and sequential-mode + enforcement. +- `internal/protocol/**`, `internal/guestresource/**`, + `internal/guestrequest/**` — shapes of host-driven messages. +- Any handler that consumes an OCI spec, mount/unmount request, exec + request, network/hostname config, fragment, or attestation input. + +Out-of-scope (host-only, or guest code that only executes on non-confidential +mode) changes do not have to be covered by this review because we assume the +host can be malicious anyway. + +## 3. What counts as a confidential enforcement bug + +A finding is only a confidential enforcement bug if a **malicious host** can +cause an outcome that the **policy author did not sanction**. The trust +model (see the instructions file) puts the policy author and the container +inside the boundary, so: + +- Bug: host bypasses or weakens a policy check, reads/modifies data in the + container in a way the user does not intend, or tricks the container into doing + something it wouldn't otherwise do. +- Not a bug: the policy can allow something dangerous; a privileged container + can escape into the rest of the UVM, etc. + +When something looks like a policy bypass, ask: *does this work without the user +needing to open doors in the security policy?* If no, it's not a confidential +bug. + +(Obviously, a non-confidential-security-policy-enforcement-related bug is still +a bug and can be severe, like unprivileged containers gaining privilege with a +malicious image / OCI spec, and worse case, escaping to the host, etc) + +## 4. Checklist — apply to every changed file in scope + +Treat **everything** coming from the host as adversarial: bridge message +fields, OCI spec fields (annotations, env, args, mounts, hooks, devices, +process.user, capabilities, seccomp, hostname, …), mount options, share +names, paths, request types, settings payloads, all of it. + +A lot of this code predates the C-ACI project, so when modifying or extending an +existing handler **do not assume the existing pattern is safe just because it's +there** — re-derive whether each host-controlled field is constrained. + +For each new or modified host-facing input field, ask: + +1. **Source of trust.** Where does this value originate? If it comes from + the bridge, an OCI spec, a `ModificationRequest.Settings`, an annotation, + or any RPC payload, treat it as adversarial. +2. **Validation.** Is it constrained? Look for: + - A Rego rule that gates the field (added to `framework.rego` / + `api.rego` / a customer's `policy.rego`). + - A Go-side validator (regex, length, path equality, `checkValidContainerID`, + `checkContainerSettings`, allowlist of mount options, …). + - For paths: is the canonical form checked, or is the value used to build a path + that escapes the intended directory? +3. **Order.** Does validation happen **before** any irreversible side + effect (mount, unmount, runc exec, file write, fragment load, network + change)? A common bug is `os.MkdirAll(hostControlledPath, ...)` before + the policy check. +4. **Opaque forwarding.** Is the change passing a struct, map, or JSON + blob through to a component outside GCS — runc, the Windows inbox GCS, + `exec`, the kernel mount syscall, an external process? If yes, every + field of that blob needs to be either validated, sanitized, or + explicitly known-safe. The runc-hooks bug (`b6907ec6c`) is the + canonical example. +5. **Enforcement coverage.** If the change adds a new bridge resource + type, request type, or modify settings case, is there a corresponding + enforcement point (or an explicit reason it's safe without one)? + Pass-through to the inbox GCS without enforcement on C-WCOW is a bug. +6. **Metadata consistency.** If the handler mutates state, is it inside a + revertable section (`StartRevertableSection` + + `commitOrRollbackPolicyRevSection`)? Does the underlying operation + clean up its own partial state on error? Does an unmount-style failure + call `setMountsBrokenIfConfidential`? +7. **Concurrency.** Is the change re-introducing parallelism on a + confidential-mode code path? Sequential dispatch (`8b3d250b1`) is load + bearing — concurrent mount + create was an exploit. +8. **Lifecycle invariants.** Does the change weaken any of these: + - Unmount overlay before unmount layers/scratch. + - No unmount of an in-use overlay (`IsOverlayInUse`). + - No `DeleteContainerState` while the container runs or its overlay is + mounted. + - `hostMounts` lock held by caller across check + mutation. +9. **Error/log content.** Does the change log or return host-controlled + data without going through `redactSensitiveData` / + `replaceCapabilitiesWithPlaceholders`? Env values especially must be + redacted. +10. **Rego changes.** + - Did `version_framework` / `version_api` get bumped if behavior + observable to existing policies changed? + - Are new container/external_process/fragment fields fronted with + `apply_defaults` / `check_*` shims so older policies still load? + - Does every narrowing step have a matching `errors[...]` rule so + denials are debuggable? +11. **C-WCOW symmetry.** For changes to shared code, did both osType + branches in the Rego enforcer get updated? Does the gcs-sidecar dispatch + cover the same cases as the GCS? + +## 5. Produce the review + +For confirmed or very likely vulnerability: +Include the file, line range, what unintended action can the host achieve, if +feasible, code snippets modifying the host-side component to demonstrate an +attack, and a concrete fix (e.g., add the Rego rule, add a validation in Go +code, move a check earlier, or ignore / reset a host-controlled field). + +Do not produce nits if they are not related to security.