Python SDK for Piper Agent Credential Management with environment fallback.
Project description
Pyper SDK for Python (v0.3.0)
The official Python SDK for integrating your applications (agents, MCPs, scripts) with Piper, the secure credential management system designed for the AI era.
Stop asking your users to paste sensitive API keys directly into every tool! With Piper, users store their secrets once in a central, secure vault. Your application, using this SDK, can then request temporary, scoped access to those secrets only after the user has explicitly granted permission via the Piper dashboard.
This SDK simplifies the process for your agent application to:
- Establish the end-user's Piper context by automatically discovering the Piper Link application's local
instanceId. - Authenticate your agent application to the Piper system using its own credentials.
- Request access to secrets using logical variable names that you define for your agent (e.g.,
my_gmail_token,openai_api_key). The SDK normalizes these names to a canonical format (e.g.,lowercase_snake_case) for API calls. - Receive short-lived GCP Security Token Service (STS) tokens if a grant exists via Piper.
- Optionally, fall back to environment variables if Piper access is not configured, a specific grant is missing, or the Piper Link app is not running.
Core Problem Piper Solves
Modern AI agents and applications often require access to numerous sensitive user credentials. Manually managing these by asking users to paste keys into multiple applications is risky and inconvenient:
- Secret Sprawl: Keys are duplicated, increasing the attack surface.
- Difficult Revocation: Removing access from a compromised key or specific tool is hard.
- Lack of Control & Audit: Users lose track of which applications access their keys.
Piper provides a centralized, user-controlled layer. Think of it as a secure wallet or password manager specifically for your application API keys and tokens.
How it Works (Simplified Flow)
- User Stores Secret in Piper: The user adds their API key (e.g., "My Personal OpenAI Key") to their Piper dashboard once. Piper encrypts and stores the actual secret value in Google Secret Manager.
- User Installs & Links "Piper Link" App (One-Time Locally):
- The user installs the lightweight "Piper Link" application (provided separately by Piper) on their local machine.
- They perform a one-time login via Piper Link, which securely associates their local environment (represented by a unique
instanceId) with their Piper account. - The Piper Link app then runs a local service to provide this
instanceIdto SDK instances.
- Developer Registers Agent: You register your application ("MyCoolAgent") with Piper, defining the logical variable names it will use (e.g.,
openai_api_key). These are stored by Piper in a canonical format (e.g.,lowercase_snake_case). - User Grants Permission in Piper UI: The user goes to their Piper dashboard and explicitly grants "MyCoolAgent" permission to access their specific "My Personal OpenAI Key" when "MyCoolAgent" requests it using the (canonical) variable name
openai_api_key. - Agent Uses SDK
get_secret():- Your "MyCoolAgent" application initializes the
pyper-sdk'sPiperClientwith its own agentclient_idandclient_secretvalue. - When your agent calls
piper_client.get_secret("OpenAI API Key"):- The SDK normalizes "OpenAI API Key" to
openai_api_key(example). - It automatically tries to discover the
instanceIdfrom the local Piper Link app. - It sends your agent's credentials, the discovered
instanceId, and the normalizedvariable_name(openai_api_key) to the Piper backend. - Piper's backend verifies everything, resolves
instanceIdto theuser_id, and checks if that user has granted your agent access to a secret foropenai_api_key. - If authorized via Piper, the SDK receives a short-lived GCP STS token.
- If the Piper flow fails (e.g., Link app not running, no grant) and fallback is enabled, the SDK attempts to read from a configured environment variable (e.g.,
MYAPP_OPENAI_API_KEY). - The SDK returns a dictionary with the
source("piper_sts" or "environment_variable") and thevalue(STS token or raw secret from env var).
- The SDK normalizes "OpenAI API Key" to
- Your "MyCoolAgent" application initializes the
- Agent Uses the Secret/Token:
- If from Piper (
source: 'piper_sts'): Your agent uses the returned STS token (value) with Google Cloud client libraries (e.g.,google-cloud-secret-manager) to fetch the actual secret value from Google Secret Manager. For this operation, your agent temporarily acts with the permissions of Piper's internal service account. - If from an environment variable (
source: 'environment_variable'): Your agent uses the raw secret value directly.
- If from Piper (
Installation
pip install pyper-sdk==0.3.0
(Replace 0.3.0 with the latest version if necessary)
Prerequisites for Your Agent Application
Before your application can use this SDK to access user secrets via Piper, some setup is required:
-
Register Your Agent with Piper:
- Go to the Piper registration portal (e.g.,
agentpiper.com- replace with actual URL). - Register your application as an "Agent."
- You will receive a
Client IDfor your agent. - You will be given a
Client Secret Name. This is the name of the secret in Piper's own Google Secret Manager project where your agent's actualclient_secretvalue is stored (e.g.,agent-secret-YOUR_AGENT_CLIENT_ID). - Define the Variable Names your agent will use (e.g.,
openai_api_key,user_database_url). Piper will store these in a canonical format (e.g.,lowercase_snake_case). These are the names you will pass tosdk.get_secret().
- Go to the Piper registration portal (e.g.,
-
GCP IAM Permissions for Your Agent to Fetch its Own Secret:
- Your agent application's runtime identity (e.g., a service account if running on GCP, or your Application Default Credentials for local development) must have IAM permission to fetch its own
client_secretvalue from Piper's Secret Manager. - This typically involves granting the
secretmanager.secretAccessorrole to your agent's identity on the specific secret resource:projects/PIPER_SYSTEM_PROJECT_ID/secrets/YOUR_AGENT_CLIENT_SECRET_NAME/versions/latest. - Consult Piper documentation for the exact
PIPER_SYSTEM_PROJECT_ID.
- Your agent application's runtime identity (e.g., a service account if running on GCP, or your Application Default Credentials for local development) must have IAM permission to fetch its own
-
Instruct Your End-Users:
- Users must have a Piper account.
- Users need to download, install, and run the Piper Link application (instructions provided separately by Piper) and link their Piper account through it once on their local machine. This establishes their local context.
- Users must go to their Piper dashboard to grant your specific agent permission to access their specific secrets, mapping them to the variable names your agent uses.
SDK Usage Example
import os
import logging
from pyper_sdk.client import PiperClient, PiperConfigError, PiperAuthError, PiperLinkNeededError
from google.cloud import secretmanager # Required if using STS tokens to fetch from GCP SM
from google.oauth2 import credentials # For using STS tokens with GCP libraries
--- Agent Configuration ---
# These should be securely managed by your application and set as environment variables
MY_AGENT_CLIENT_ID = os.environ.get("MY_AGENT_PIPER_CLIENT_ID")
MY_AGENT_CLIENT_SECRET_NAME = os.environ.get("MY_AGENT_PIPER_CLIENT_SECRET_NAME") # Name in Piper's SM
# This is Piper's main GCP Project ID - obtain from Piper documentation
PIPER_SYSTEM_GCP_PROJECT_ID = os.environ.get("PIPER_SYSTEM_PROJECT_ID", "444535882337")
--- Logging Setup ---
logging.basicConfig(level=logging.INFO)
logging.getLogger('PiperSDK').setLevel(logging.DEBUG) # DEBUG for verbose SDK output
logging.getLogger('urllib3').setLevel(logging.WARNING) # Quieten noisy library
def fetch_this_agent_client_secret_value(piper_gcp_project: str, sm_secret_name_for_this_agent: str) -> str:
"""
Fetches this agent's own client_secret value from Piper's Secret Manager.
Your agent's runtime identity needs 'secretmanager.secretAccessor' permission.
"""
try:
sm_client = secretmanager.SecretManagerServiceClient() # Uses Application Default Credentials
full_secret_path = sm_client.secret_version_path(piper_gcp_project, sm_secret_name_for_this_agent, "latest")
logging.debug(f"Fetching agent's client_secret from: {full_secret_path}")
response = sm_client.access_secret_version(request={"name": full_secret_path})
secret_value = response.payload.data.decode("UTF-8")
if not secret_value:
raise PiperConfigError(f"Fetched agent client_secret '{sm_secret_name_for_this_agent}' is empty.")
return secret_value
except Exception as e:
logging.error(f"FATAL: Could not fetch this agent's client_secret ('{sm_secret_name_for_this_agent}') from Piper's SM: {e}", exc_info=True)
raise PiperConfigError(f"Failed to fetch own client_secret '{sm_secret_name_for_this_agent}'. Ensure agent identity has permission in project '{piper_gcp_project}'.") from e
def get_actual_secret_from_sm_using_sts(sts_token_value: str, user_secret_name_in_piper_sm: str, piper_gcp_project: str) -> str:
"""
Uses a Piper-issued STS token to fetch the actual user secret from Piper's SM.
The 'user_secret_name_in_piper_sm' is the actual name of the user's secret
in Secret Manager (this is the 'piper_credential_id' returned by get_secret()).
"""
try:
temp_gcp_creds = credentials.Credentials(token=sts_token_value)
sm_client = secretmanager.SecretManagerServiceClient(credentials=temp_gcp_creds)
# The piper_credential_id *is* the name of the secret in Piper's Secret Manager
secret_version_path = sm_client.secret_version_path(piper_gcp_project, user_secret_name_in_piper_sm, "latest")
logging.info(f"Fetching actual secret value from SM: {secret_version_path} using STS token.")
response = sm_client.access_secret_version(name=secret_version_path)
actual_secret = response.payload.data.decode('UTF-8')
logging.info(f"Successfully fetched actual secret for {user_secret_name_in_piper_sm}.")
return actual_secret
except Exception as e:
logging.error(f"Failed to fetch actual secret {user_secret_name_in_piper_sm} using STS token: {e}", exc_info=True)
raise PiperError(f"Could not retrieve actual secret '{user_secret_name_in_piper_sm}' from SM using STS token.") from e
if __name__ == "__main__":
if not MY_AGENT_CLIENT_ID or not MY_AGENT_CLIENT_SECRET_NAME:
print("FATAL: MY_AGENT_PIPER_CLIENT_ID and MY_AGENT_PIPER_CLIENT_SECRET_NAME environment variables must be set.")
exit(1)
try:
my_agent_secret_value = fetch_this_agent_client_secret_value(
PIPER_SYSTEM_GCP_PROJECT_ID,
MY_AGENT_CLIENT_SECRET_NAME
)
print(f"Successfully fetched this agent's client_secret (ending ...{my_agent_secret_value[-4:]}).")
piper_client = PiperClient(
client_id=MY_AGENT_CLIENT_ID,
client_secret=my_agent_secret_value,
# _piper_system_project_id=PIPER_SYSTEM_GCP_PROJECT_ID, # Only if overriding SDK defaults for URL construction
# enable_env_fallback=True, # Default is True
# env_variable_prefix="MYAPP_",
# env_variable_map={"My Custom OpenAI Variable": "MY_EXACT_OAI_ENV_VAR"}
)
print(f"PiperClient initialized. SDK will attempt Piper Link discovery.")
--- Example 1: Get an OpenAI Key via Piper ---
# Assumes:
# 1. User has run Piper Link app.
# 2. User has granted this agent access to variable "openai_api_key" in Piper UI.
# 3. Environment variable (e.g., MYAPP_OPENAI_API_KEY) is NOT set, to force Piper attempt.
openai_variable = "openai_api_key" # This is the canonical name your agent defined
print(f"\nAttempting to get secret for Piper Variable: '{openai_variable}'")
secret_info = piper_client.get_secret(openai_variable) # SDK normalizes "OpenAI API Key" if dev typed that
print(f" Source: {secret_info['source']}")
actual_openai_key = None
if secret_info['source'] == 'piper_sts':
print(f" STS Token (last 6): ...{secret_info['value'][-6:]}")
# The 'piper_credential_id' IS the name of the secret in Piper's Secret Manager
actual_openai_key = get_actual_secret_from_sm_using_sts(
secret_info['value'], # This is the STS token
secret_info['piper_credential_id'], # This is the SM Secret ID
PIPER_SYSTEM_GCP_PROJECT_ID
)
elif secret_info['source'] == 'environment_variable':
print(f" Env Var Name: {secret_info['env_var_name']}")
actual_openai_key = secret_info['value'] # This is the raw secret
if actual_openai_key:
print(f" Retrieved Actual OpenAI Key (last 6 chars): ...{actual_openai_key[-6:]}")
# Now your agent can use actual_openai_key with the OpenAI library
else:
print(f" Could not retrieve OpenAI key value.")
except PiperLinkNeededError:
print("ERROR: Piper Link is not set up. Please instruct the user to run the Piper Link application to link their Piper account.")
except PiperConfigError as e:
print(f"ERROR: SDK or Agent Configuration Error: {e}")
except PiperAuthError as e:
print(f"ERROR: Piper Authentication/Authorization Error: {e}")
except Exception as e:
print(f"An unexpected fatal error occurred in the application: {e}", exc_info=True)
Error Handling
The primary method piper_client.get_secret() can raise several exceptions:
ValueError: Ifvariable_namepassed toget_secret()is invalid.PiperLinkNeededError: If the Piper Link application is not running or configured, and Piper access is attempted (and fallback is disabled or also fails). Your application should catch this and guide the user to set up Piper Link.PiperAuthError: If there's an issue with Piper API calls:error_code='mapping_not_found': The user has not granted your agent access to this variable in their Piper dashboard.error_code='permission_denied': The grant exists, but some other permission check failed (e.g., Firestore grant status not "ACTIVE" for the underlying credential).- Other codes for
invalid_client(your agent's ownclient_id/secretissue),invalid_token, etc. - The exception object contains
status_code,error_code, anderror_detailsfrom the API.
PiperConfigError: If Piper access fails for other configuration reasons (e.g., could not fetch agent's own client secret), AND environment variable fallback is disabled or also fails. The message will indicate both failures if applicable.
Always wrap calls to get_secret() in try...except blocks to handle these cases gracefully in your application.
Environment Variable Fallback
The PiperClient's get_secret() method supports falling back to environment variables if the Piper flow cannot retrieve the secret. This allows for flexibility and gradual adoption.
enable_env_fallback: bool(inPiperClient.__init__, defaults toTrue): IfTrue,get_secret()will attempt to read from an environment variable if the Piper flow fails (e.g.,PiperLinkNeededErroris raised internally, or Piper returnsmapping_not_found,permission_denied).- How Fallback Variable Names are Determined:
fallback_env_var_nameparameter inget_secret(): If you provide this, it uses that exact environment variable name.env_variable_map: Dict[str, str](inPiperClient.__init__): Iffallback_env_var_nameis not given, the SDK checks this map using the originalvariable_name(e.g.,"My OpenAI Key") as the key. Example:env_variable_map={"My OpenAI Key": "MY_APPS_ACTUAL_OAI_ENV_VAR"}.env_variable_prefix: str(inPiperClient.__init__): If no map entry is found, the SDK constructs a name:- Takes the original
variable_name(e.g.,"My OpenAI Key"). - Converts it to UPPERCASE_SNAKE_CASE (e.g.,
"MY_OPENAI_KEY"). - Prepends your
env_variable_prefix(e.g., if prefix is"AGENTX_", it looks forAGENTX_MY_OPENAI_KEY). An empty prefix is common.
- Takes the original
- The
get_secret()method's return dictionary will havesource: "environment_variable"andenv_var_nameindicating the variable found, if fallback was used. Thevaluewill be the raw secret string from the environment.
Security Considerations
- Agent
client_secret: Your application is responsible for securely fetching and handling its ownclient_secretvalue (obtained from Piper's Secret Manager). - Piper Link App: Users need to trust and correctly install the Piper Link application for the
instanceIddiscovery to work securely. - STS Token Usage: The STS tokens vended by Piper are short-lived and grant temporary impersonation of Piper's internal service account (
piper-functions-sa) to access specific secrets in Google Secret Manager. Handle these STS tokens as sensitive credentials.
License
MIT License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyper_sdk-0.3.1.tar.gz.
File metadata
- Download URL: pyper_sdk-0.3.1.tar.gz
- Upload date:
- Size: 20.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
070f7293557ebda68a33e695e06776131a0a4e915ae2a0e2a6a56f452ce3d1fc
|
|
| MD5 |
6fad36a83cf8b38c8b94f3f677155461
|
|
| BLAKE2b-256 |
9b312c55d3d4e98ae3e1b1c23c8358d9eb68564f8b26b18d9dfe736cdfaa2c89
|
File details
Details for the file pyper_sdk-0.3.1-py3-none-any.whl.
File metadata
- Download URL: pyper_sdk-0.3.1-py3-none-any.whl
- Upload date:
- Size: 15.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b15f547a35e5129306e9133b1719a661f9b1abc1cf43b4eea0560183543dcfd5
|
|
| MD5 |
578e2a8d567f29dfd067a55896cd43c1
|
|
| BLAKE2b-256 |
abc46f74ff8d5872c4dcec615226c2748bf7b8cd138282b47b779b2d60ce22fc
|