You are here:

Securing Snowflake CoCo CLI

Securing Snowflake CoCo CLI

Snowflake’s CoCo CLI is a powerful agentic coding tool, but like any tool that can read files, execute shell commands, and access your Snowflake environment, it comes with a meaningful attack surface. This post walks through the risks of running CoCo unsecured, why Podman’s sandbox falls short for many teams, and how to containerize CoCo with Docker in a way that’s genuinely hardened.

What Is CoCo CLI?

CoCo CLI is Snowflake’s command-line AI coding assistant, backed by Cortex-hosted models. It can read and write files, execute shell commands, and interact with your Snowflake environment. This makes it significantly more capable (and more dangerous) than a simple chat interface.

Out of the box, CoCo supports a range of tool capabilities:

Tool Description
EXECUTE_COMMAND Run bash/shell commands
FILE_READ Read file contents
FILE_WRITE Create and modify files
FILE_EDIT Edit existing files
WEB_ACCESS Web search and fetch operations

These tools operate under a tiered risk model that governs whether the CLI prompts for confirmation:

Risk LevelExample CommandsDefault Behavior
SAFE ls, cat, echo, grep Auto-approved
LOW touch file.txt (new file creation) Usually auto-approved
MEDIUM nano file.txt, moderate bash Prompts in Confirm mode
HIGH rm, curl, wget, sudo Always prompts
CRITICAL rm -rf, destructive operationsExtra confirmation required

This classification system is helpful, but it’s only as trustworthy as the environment it runs in. If the agent is compromised or if you’ve configured it to run non-interactively in a pipeline (which most people will want to) those guardrails disappear.

Risks of Running CoCo Unsecured

Before diving into hardening steps, it’s worth being concrete about what can go wrong:

Credential exfiltration. CoCo runs with the permissions of whatever process launched it. If your shell environment contains SNOWFLAKE_PASSWORD, AWS credentials, or SSH keys in ~/.ssh, the agent can read and exfiltrate them — either through a prompt injection in a file it reads or through a supply-chain compromise in a Cortex skill.

Unrestricted filesystem access. Running as your local user, the agent can read any file you can read. That includes ~/.aws/credentials, .env files with database passwords, private keys, and browser session data.

Network access without limits. The WEB_ACCESS tool and curl/wget via EXECUTE_COMMAND can exfiltrate data or pull down malicious payloads. Without network egress controls, there’s no way to contain the blast radius.

Privilege escalation. If the agent discovers sudo without a password (common in developer environments), a MEDIUM-risk operation can quickly become a CRITICAL one.

Overprivileged Snowflake role. If the service account used by CoCo has broad Snowflake privileges, a compromised session can read sensitive tables, clone data, or alter schemas

CoCo's Built-In Podman Sandbox

Snowflake ships CoCo with optional Podman-based sandboxing. Podman is a daemonless container runtime that supports rootless containers. Containers run as your user, not as root, which reduces the risk of a container’s escape resulting in host compromise.

The Podman sandbox is a meaningful improvement over running CoCo naked on your machine. It isolates the filesystem and provides a boundary around command execution.

However, Podman has real limitations in practice:

    • Adoption is still limited. Most teams are running Docker in their CI pipelines, developer environments, and security scanning toolchains. Introducing Podman as a one-off dependency for CoCo creates operational friction and may not integrate with your existing image scanning, SIEM logging, or container policy tooling.
    • Corporate environments often block it. Rootless Podman has specific kernel requirements (user.max_user_namespaces, cgroup v2) that are not always available on managed developer workstations.
    • It doesn’t solve the upstream problems. Podman sandboxes the execution, but it doesn’t address credential hygiene, Snowflake RBAC, network egress controls, or image provenance.

For teams that are already Docker focused, building a hardened Docker image for CoCo is often the more practical and more complete solution.

Dockerizing CoCo Securely

The following approach layers multiple independent defenses. No single control is sufficient on its own; defense in depth is the goal. The Dockerfile snippets throughout this section are drawn from a working reference implementation.

1. Install the CLI Non-Interactively and Skip Podman

The CoCo install script is interactive by default. In a Docker build context it will hang or fail unless you suppress all prompts. Three environment variables control this:

    • NON_INTERACTIVE=1 — skips all yes/no prompts
    • SKIP_PODMAN=1 — skips the Podman install prompt (we’re using Docker instead)
    • SKIP_PATH_PROMPT=1 — skips the shell PATH update prompt

The script also auto-detects /.dockerenv and sets these automatically but setting them explicitly is safer. It guarantees non-interactive behavior regardless of your build environment:


dockerfile 
FROM ubuntu:24.04 
RUN apt-get update && apt-get install -y --no-install-recommends \ 
ca-certificates curl python3 python3-pip tar \ 
&& rm -rf /var/lib/apt/lists/* 
# Install Snowflake CLI (required prerequisite) 
RUN SNOW_CLI_VERSION="3.15.0" \ 
&& SNOW_CLI_DEB="snowflake-cli-${SNOW_CLI_VERSION}.x86_64.deb" \ 
&& curl -fsSL \ 
"https://sfc-repo.snowflakecomputing.com/snowflake-cli/linux_x86_64/${SNOW_CLI_VERSION}/${SNOW_CLI_DEB}" \ 
-o "/tmp/${SNOW_CLI_DEB}" \ 
&& dpkg -i "/tmp/${SNOW_CLI_DEB}" \ 
&& rm "/tmp/${SNOW_CLI_DEB}" 
# Install CoCo CLI — suppress all interactive prompts 
ENV HOME=/tmp 
RUN NON_INTERACTIVE=1 SKIP_PODMAN=1 SKIP_PATH_PROMPT=1 \ 
sh -c "$(curl -LsS https://ai.snowflake.com/static/cc-scripts/install.sh)" 
ENV PATH="/tmp/.local/bin:$PATH" 

Note that HOME is set to /tmp at build time. The install script places the cortex binary at $HOME/.local/bin, so setting HOME=/tmp at both build and runtime ensures the binary ends up at a predictable, consistent path.

2. Authenticate via Short-Lived PAT Tokens

Never bake long-lived credentials into your Docker image or mount your local ~/.snowflake directory into the container. Instead, generate a short-lived Personal Access Token (PAT) and pass it as an environment variable at runtime. Credentials are injected only when the container runs — nothing sensitive is baked into image layers:

bash 
docker run --rm \ 
-e SNOWFLAKE_ACCOUNT="myorg-myaccount" \ 
-e SNOWFLAKE_USER="cortex_svc_user" \ 
-e SNOWFLAKE_PAT="${SNOWFLAKE_PAT}" \ 
cortex-code:hardened 

PATs with short TTLs dramatically reduce the window of exposure if a token is leaked. Rotate them programmatically rather than using static credentials.

3. Redirect SNOWFLAKE_HOME to a Writable, Ephemeral Directory

By default, CoCo writes its config, session tokens, and logs to ~/.snowflake. In a hardened container, you want these to land somewhere controlled and ephemeral — not in a mounted volume or a directory that survives between runs. Setting SNOWFLAKE_HOME=/tmp/.snowflake accomplishes both goals: /tmp is always writable by any UID, and it’s discarded when the container exits. Sessions never persist across runs:

dockerfile 
ENV SNOWFLAKE_HOME=/tmp/.snowflake  

Pair this with HOME=/tmp at runtime to keep all CoCo state self-contained within /tmp.

4. Pre-Accept First-Run Dialogs in the Image

CoCo presents several interactive prompts on first launch — a trust/onboarding dialog, a telemetry notice, and user preference questions. In a container context these prompts will block execution. The fix is to pre-seed the config files that the CLI checks before showing each prompt.

settings.json controls for telemetry acceptance and user preferences. Copy it into the image at its expected runtime paths:


dockerfile 
# Pre-accept first-run dialogs 
COPY settings.json /tmp/.snowflake/cortex/settings.json 
RUN chmod 644 /tmp/.snowflake/cortex/settings.json  

This is more reliable than trying to pass flags at runtime, and it eliminates the prompts entirely rather than hoping the CLI respects a particular flag in every execution path.

5. Enforce a Managed Security Policy

Snowflake supports a managed-settings.json file that an organization (or in this case, the image maintainer) can use to enforce security policy regardless of what the user configures at runtime. When this file exists at the Linux system path /etc/cortex/managed-settings.json, the CLI reads it before applying any user settings. Since this file is owned by root and placed outside the user’s home directory, a process running as UID 1000 cannot modify or override it:

dockerfile 
RUN mkdir -p /etc/cortex 
COPY managed-settings.json /etc/cortex/managed-settings.json 
RUN chmod 644 /etc/cortex/managed-settings.json 

Use this to lock down tool permissions, disable WEB_ACCESS or EXECUTE_COMMAND for environments that don’t need them, or enforce confirmation mode regardless of user preference. This is the right place to implement your organization’s tool allowlist rather than relying on users to configure it themselves.

6. Pre-Scan Volumes for Secrets Before Mounting

Mounting a local directory into a container is convenient — and dangerous. Developers routinely store .env files, private keys, and cloud credentials in project directories, often without realizing it. When you mount ~/projects/my-app into the container, you’re also mounting every secret that lives there.

Install detect-secrets in the image so it’s available as a pre-flight check in your entrypoint script:


dockerfile 
RUN pip3 install --break-system-packages detect-secrets==1.5.0 


Then in your docker-entrypoint.sh, scan any mounted directory before CoCo starts:

bash 
#!/bin/bash 
set -e 
if [ -d "/work" ]; then 
echo "Scanning /work for secrets..." 
detect-secrets scan /work > /tmp/.secrets.baseline 
if detect-secrets audit /tmp/.secrets.baseline --only-allowlisted 2>&1 | grep -q "potential secret"; then 
echo "ERROR: Secrets detected in mounted volume. Aborting." >&2 
exit 1 
fi 
fi 
exec "$@" 

Mounting an image in a directory with secrets is an easy mistake to make (one I even make from time to time), so having this gaurdrail is critical.

7. Use .dockerignore for Sensitive Data

A .dockerignore file prevents files from being sent to the Docker build context entirely, which means they can’t accidentally end up in your image layers — even in intermediate layers that tools like docker history can expose. Treat it as a security boundary, not just a build performance optimization:

.env
.env.*
*.pem
*.key
*.p12
.aws
.ssh
.secrets.baseline
.git

8. Drop Linux Capabilities and Block Privilege Escalation

Linux capabilities are the fine-grained components of root privilege. Use –cap-drop ALL to start with zero capabilities and add back only what’s strictly necessary. Pair it with –security-opt no-new-privileges to prevent any process inside the container from gaining additional privileges through setuid binaries:

bash 
docker run --rm \ 
--cap-drop ALL \ 
--security-opt no-new-privileges \ 
-e SNOWFLAKE_ACCOUNT="myorg-myaccount" \ 
-e SNOWFLAKE_USER="cortex_svc_user" \ 
-e SNOWFLAKE_PAT="${SNOWFLAKE_PAT}" \ 
cortex-code:hardened 

These two flags are among the highest-leverage runtime hardening steps available and have essentially no downside for an interactive coding tool.

9. Create a Snowflake Service Account Following Least Privilege

The Snowflake role associated with your CoCo sessions should have exactly the permissions needed — nothing more. In practice, this means a dedicated service account with a custom role:


sql 
CREATE ROLE cortex_code_role; 
GRANT USAGE ON WAREHOUSE cortex_wh TO ROLE cortex_code_role; 
GRANT USAGE ON DATABASE my_db TO ROLE cortex_code_role; 
GRANT USAGE ON SCHEMA my_db.my_schema TO ROLE cortex_code_role; 
GRANT SELECT ON ALL TABLES IN SCHEMA my_db.my_schema TO ROLE cortex_code_role; 
CREATE USER cortex_svc_user 
DEFAULT_ROLE = cortex_code_role 
DEFAULT_WAREHOUSE = cortex_wh; 
GRANT ROLE cortex_code_role TO USER cortex_svc_user; 

No ACCOUNTADMIN, SYSADMIN, or SECURITYADMIN. Add a network policy that restricts this account to expected source IP ranges. Periodically audit usage with Snowflake’s access history views and trim anything unused.

10. Scan Built Images with a Security Scanner

Before running or distributing your image, scan it for known CVEs. Tools like Trivy integrate cleanly into CI pipelines and can fail a build automatically on HIGH or CRITICAL findings:

bash 
# Scan and fail on HIGH/CRITICAL findings 
trivy image --exit-code 1 --severity HIGH,CRITICAL cortex-code:hardened 

Run this on every build, not just the initial one. Base image vulnerabilities are discovered continuously — ubuntu:24.04 is a moving target, and a clean image today may not be clean next month. Pin your base image digest and rebuild on a schedule.

Putting It All Together: The Reference Dockerfile

Here’s the complete Dockerfile that implements all of the above:


dockerfile 
FROM ubuntu:24.04 
# ── System dependencies ──────────────────────────────────────────────────────── 
RUN apt-get update && apt-get install -y --no-install-recommends \ 
ca-certificates curl python3 python3-pip tar \ 
&& rm -rf /var/lib/apt/lists/* 
# detect-secrets available in the entrypoint for pre-flight scanning 
RUN pip3 install --break-system-packages detect-secrets==1.5.0 
# ── Snowflake CLI (required prerequisite for CoCo) ───────────────────── 
RUN SNOW_CLI_VERSION="3.15.0" \ 
&& SNOW_CLI_DEB="snowflake-cli-${SNOW_CLI_VERSION}.x86_64.deb" \ 
&& curl -fsSL \ 
"https://sfc-repo.snowflakecomputing.com/snowflake-cli/linux_x86_64/${SNOW_CLI_VERSION}/${SNOW_CLI_DEB}" \ 
-o "/tmp/${SNOW_CLI_DEB}" \ 
&& dpkg -i "/tmp/${SNOW_CLI_DEB}" \ 
&& rm "/tmp/${SNOW_CLI_DEB}" 
# ── CoCo CLI ──────────────────────────────────────────────────────────── 
# HOME=/tmp so the binary lands in /tmp/.local/bin — same path used at runtime. 
# NON_INTERACTIVE/SKIP_PODMAN/SKIP_PATH_PROMPT suppress all prompts during build. 
ENV HOME=/tmp 
RUN NON_INTERACTIVE=1 SKIP_PODMAN=1 SKIP_PATH_PROMPT=1 \ 
sh -c "$(curl -LsS https://ai.snowflake.com/static/cc-scripts/install.sh)" 
# ── Pre-accept first-run dialogs ─────────────────────────────────────────────── 
COPY claude.json /tmp/.claude.json 
COPY settings.json /tmp/.snowflake/cortex/settings.json 
RUN chmod 644 /tmp/.claude.json /tmp/.snowflake/cortex/settings.json 
# ── Managed security policy (root-owned, users cannot override) ─────────────── 
RUN mkdir -p /etc/cortex 
COPY managed-settings.json /etc/cortex/managed-settings.json 
RUN chmod 644 /etc/cortex/managed-settings.json 
# ── Entrypoint ───────────────────────────────────────────────────────────────── 
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh 
RUN chmod 755 /usr/local/bin/docker-entrypoint.sh 
# ── Runtime environment ──────────────────────────────────────────────────────── 
ENV PATH="/tmp/.local/bin:$PATH" 
ENV SNOWFLAKE_HOME=/tmp/.snowflake 
WORKDIR /work 
ENTRYPOINT ["docker-entrypoint.sh"] 
CMD ["cortex"] 


Run it with:


bash 
docker run --rm \ 
--cap-drop ALL \ 
--security-opt no-new-privileges \ 
-e SNOWFLAKE_ACCOUNT="myorg-myaccount" \ 
-e SNOWFLAKE_USER="cortex_svc_user" \ 
-e SNOWFLAKE_PAT="${SNOWFLAKE_PAT}" \ 
-v "$(pwd)":/work \ 
cortex-code:hardened  

A few things worth noting about this setup: WORKDIR /work is the directory CoCo will operate in — it’s populated at runtime via the volume mount, not baked into the image. All CoCo state (config, sessions, logs) lives in /tmp and is discarded when the container exits. Credentials never touch the image — they exist only for the lifetime of the container.

Sandboxing vs. Running Locally: The Real Tradeoff

Running CoCo in a hardened Docker container is not zero-cost. It adds build and startup time, introduces image maintenance overhead, and requires your team to understand what’s in the container. For quick local experimentation, that overhead may feel excessive.

The moment CoCo is used on anything beyond a throwaway environment – a machine that touches production credentials, a developer laptop with SSH keys to shared infrastructure, a CI pipeline with cloud provider access security must be prioritized. The container boundary, the non-root user, the capability drops, and the credential isolation together create a meaningful gap between “the agent did something bad” and “the agent compromised your entire environment.”

The sandboxed Podman approach Snowflake ships is a good first step. The Docker approach described here goes further by addressing the upstream risks, credential hygiene, Snowflake RBAC, image provenance, network policy, and tool surface that sandboxing alone doesn’t solve.

Treat CoCo like you’d treat any other process that has shell access and network egress in your environment: with appropriate skepticism, explicit permissions, and layers of defense.

Author

Avatar photo
Email:

Share on:

Recent Insights

7Rivers CTA
Button

You might also be interested in...

How to Build AI-Infused Applications on Snowflake’s Data Cloud

You Built the Model. So Why Is It Still Stuck in a Notebook? We have seen this before, your

The Data Leader’s Role in Enterprise AI: What the 72% Getting It Wrong Have in Common

Imagine this: you’ve invested $400,000 into an AI initiative, and your CEO wants to know the results. You’ve promised

From Action to Autonomy: Why Trust Is the Real Gate at Snowflake Summit 2026

Enterprise data work has moved quickly from insight, to action, to autonomy. Snowflake Summit 2026 made the next move

Ready to Lead the Future with AI?

No matter where you are in your AI and data journey, 7Rivers is here to guide you.