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,INSERTon the raw schema. - Transformation: Requires
CREATE VIEW,CREATE TABLEon the staging schema. - Swap: Requires
DROP VIEW,CREATE VIEWon 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.