Files
attune/docs/actions/QUICKREF-parameter-delivery.md
David Culbreth 87d830f952
Some checks failed
CI / Rustfmt (push) Successful in 23s
CI / Cargo Audit & Deny (push) Successful in 30s
CI / Web Blocking Checks (push) Successful in 48s
CI / Security Blocking Checks (push) Successful in 8s
CI / Clippy (push) Failing after 1m55s
CI / Web Advisory Checks (push) Successful in 35s
CI / Security Advisory Checks (push) Successful in 37s
CI / Tests (push) Successful in 8m5s
[wip] cli capability parity
2026-03-06 16:58:50 -06:00

7.8 KiB

Parameter Delivery Quick Reference

Quick guide for choosing and implementing secure parameter passing in actions


TL;DR - Security First

DEFAULT: stdin + json (secure by default as of 2025-02-05)

KEY DESIGN: Parameters and environment variables are separate!

  • Parameters = Action data (always secure: stdin or file)
  • Environment Variables = Execution context (separate: execution.env_vars)
# ✅ DEFAULT (no need to specify) - secure for all actions
# parameter_delivery: stdin
# parameter_format: json

# For large payloads only:
parameter_delivery: file
parameter_format: yaml

Quick Decision Matrix

Your Action Has... Use This
🔑 API keys, passwords, tokens Default (stdin + json)
📦 Large config files (>1MB) file + yaml
🐚 Shell scripts Default (stdin + json or dotenv)
🐍 Python/Node.js actions Default (stdin + json)
📝 Most actions Default (stdin + json)

Two Delivery Methods

1. Standard Input (stdin)

Security: HIGH - Not in process list
When: Credentials, API keys, structured data (DEFAULT)

# This is the DEFAULT (no need to specify)
# parameter_delivery: stdin
# parameter_format: json
# Read from stdin (secrets are merged into parameters)
import sys, json
content = sys.stdin.read().strip()
params = json.loads(content) if content else {}
api_key = params['api_key']  # Secure!

2. Temporary File (file)

Security: HIGH - Restrictive permissions (0400)
When: Large payloads, complex configs

# Explicitly use file for large payloads
parameter_delivery: file
parameter_format: yaml
# Read from file
import os, yaml
param_file = os.environ['ATTUNE_PARAMETER_FILE']
with open(param_file) as f:
    params = yaml.safe_load(f)

Format Options

Format Best For Example
json (default) Python/Node.js, structured data {"key": "value"}
dotenv Simple key-value when needed KEY='value'
yaml Human-readable configs key: value

Copy-Paste Templates

Python Action (Secure with Stdin/JSON)

# action.yaml
name: my_action
ref: mypack.my_action
runner_type: python
entry_point: my_action.py
parameter_delivery: stdin
parameter_format: json

parameters:
  type: object
  properties:
    api_key:
      type: string
      secret: true
#!/usr/bin/env python3
# my_action.py
import sys
import json

def read_params():
    """Read parameters from stdin. Secrets are already merged in."""
    content = sys.stdin.read().strip()
    return json.loads(content) if content else {}

params = read_params()
api_key = params['api_key']
# Use api_key securely...

Shell Action (Secure with Stdin/JSON)

# action.yaml
name: my_script
ref: mypack.my_script
runner_type: shell
entry_point: my_script.sh
parameter_delivery: stdin
parameter_format: json
#!/bin/bash
# my_script.sh
set -e

# Read params from stdin (requires jq)
read -r PARAMS_JSON
API_KEY=$(echo "$PARAMS_JSON" | jq -r '.api_key')

# Use API_KEY securely...

Shell Action (Using Stdin with Dotenv)

name: simple_script
ref: mypack.simple_script
runner_type: shell
entry_point: simple.sh
# Can use dotenv format with stdin for simple shell scripts
parameter_delivery: stdin
parameter_format: dotenv
#!/bin/bash
# simple.sh
# Read dotenv from stdin
eval "$(cat)"
echo "$MESSAGE"

Environment Variables

System Variables (always set):

  • ATTUNE_EXECUTION_ID - Execution ID
  • ATTUNE_ACTION_REF - Action reference
  • ATTUNE_PARAMETER_DELIVERY - Method used (stdin/file, default: stdin)
  • ATTUNE_PARAMETER_FORMAT - Format used (json/dotenv/yaml, default: json)
  • ATTUNE_PARAMETER_FILE - Path to temp file (file delivery only)

Custom Variables (from execution.env_vars):

  • Set any custom environment variables via execution.env_vars when creating execution
  • These are separate from parameters
  • Use for execution context, configuration, non-sensitive metadata

Common Patterns

Detect Delivery Method

import os

delivery = os.environ.get('ATTUNE_PARAMETER_DELIVERY', 'env')
if delivery == 'stdin':
    params = read_from_stdin()
elif delivery == 'file':
    params = read_from_file()
else:
    params = read_from_env()

Mark Sensitive Parameters

parameters:
  type: object
  properties:
    api_key:
      type: string
      secret: true        # Mark as sensitive
    password:
      type: string
      secret: true
    public_url:
      type: string          # Not marked - not sensitive

Validate Required Parameters

params = read_params()
if not params.get('api_key'):
    print(json.dumps({"error": "api_key required"}))
    sys.exit(1)

Security Checklist

  • Identified all sensitive parameters
  • Marked sensitive params with secret: true
  • Set parameter_delivery: stdin or file (not env)
  • Set appropriate parameter_format
  • Updated action script to read from stdin/file
  • Tested that secrets don't appear in ps aux
  • Don't log sensitive parameters
  • Handle missing parameters gracefully

Testing

# Run action and check process list
./attune execution start mypack.my_action --params '{"api_key":"secret123"}' &

# In another terminal
ps aux | grep attune-worker
# Should NOT see "secret123" in output!

Key Design Change (2025-02-05)

Parameters and Environment Variables Are Separate

Parameters (always secure):

  • Passed via stdin (default) or file (large payloads)
  • Never passed as environment variables
  • Read from stdin or parameter file
# Read parameters from stdin (secrets are merged in)
import sys, json
content = sys.stdin.read().strip()
params = json.loads(content) if content else {}
api_key = params['api_key']  # Secure!

Environment Variables (execution context):

  • Set via execution.env_vars when creating execution
  • Separate from parameters
  • Read from environment
# Read environment variables (context, not parameters)
import os
log_level = os.environ.get('LOG_LEVEL', 'info')

Don't Do This

# ❌ Don't log sensitive parameters
logger.debug(f"Params: {params}")  # May contain secrets!

# ❌ Don't confuse parameters with env vars
# Parameters come from stdin/file, not environment

# ❌ Don't forget to mark secrets
# api_key:
#   type: string
#   # Missing: secret: true

# ❌ Don't put sensitive data in execution.env_vars
# Use parameters for sensitive data, env_vars for context

Do This Instead

# ✅ Log only non-sensitive data
logger.info(f"Calling endpoint: {params['endpoint']}")

# ✅ Use stdin for parameters (the default!)
# parameter_delivery: stdin  # No need to specify

# ✅ Mark all secrets
# api_key:
#   type: string
#   secret: true

# ✅ Use env_vars for execution context
# Set when creating execution:
# {"env_vars": {"LOG_LEVEL": "debug"}}

Help & Support

Full Documentation: docs/actions/parameter-delivery.md

Examples: See packs/core/actions/http_request.yaml

Questions:

  • Parameters: Check ATTUNE_PARAMETER_DELIVERY env var
  • Env vars: Set via execution.env_vars when creating execution

Summary

  1. Default is stdin + json - secure by default! 🎉
  2. Parameters and environment variables are separate concepts
  3. Parameters are always secure (stdin or file, never env)
  4. Mark sensitive parameters with secret: true
  5. Use execution.env_vars for execution context, not parameters
  6. Test that secrets aren't in process list

Remember: Parameters are secure by design - they're never in environment variables! 🔒