Skip to main content

Security & Secrets

msh is designed with security-first principles, ensuring that sensitive credentials are never exposed in logs, command-line arguments, or version control.

Secret Injection​

msh relies entirely on Environment Variables for secret management. We follow the dlt convention for ingestion and standard dbt env vars for transformation.

How it Works​

When you define a credential in your .msh file using the ${VAR_NAME} syntax, msh does not substitute the value during compilation. Instead, it passes the reference to the execution engine.

# Safe
credentials: ${STRIPE_API_KEY}

The actual value is resolved only at runtime by the underlying engine (dlt or dbt), ensuring the static artifacts (compiled SQL/YAML) never contain secrets.

Subprocess Safety​

msh orchestrates tools like dbt and dlt by spawning subprocesses.

  • No CLI Arguments: Secrets are never passed as command-line arguments. This prevents them from appearing in process listings (ps aux) or shell history.
  • Environment Inheritance: Secrets are passed strictly via the process environment (os.environ).

Example: dbt Execution​

When msh runs dbt, it constructs the environment dictionary explicitly:

# Internal msh logic
env = os.environ.copy()
env['POSTGRES_PASSWORD'] = os.getenv('POSTGRES_PASSWORD')

subprocess.run(['dbt', 'run'], env=env)

Docker & CI/CD Best Practices​

When deploying msh in containerized environments (Kubernetes, GitHub Actions), follow these practices:

GitHub Actions​

Inject secrets into the step environment, not as build args.

- name: Run msh pipeline
run: msh run --env prod
env:
# Source Credentials
STRIPE_API_KEY: ${{ secrets.STRIPE_API_KEY }}

# Destination Credentials
DESTINATION__SNOWFLAKE__PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }}

Docker​

Do not bake secrets into your Docker image.

BAD:

ENV STRIPE_KEY=sk_live_...

GOOD: Pass them at runtime:

docker run -e STRIPE_KEY=$STRIPE_KEY my-msh-image

Role-Based Access Control (RBAC)​

msh operates with the permissions of the user/role provided in the connection string.

  • Ingestion: Requires CREATE TABLE, INSERT on the raw schema.
  • Transformation: Requires CREATE VIEW, CREATE TABLE on the staging schema.
  • Swap: Requires DROP VIEW, CREATE VIEW on the public schema.

We recommend creating a dedicated service account (e.g., msh_user) with least-privilege access scoped to the specific schemas it manages.