Security and API Keys¶
API keys are bearer credentials. Anyone who obtains a key can use the provider permissions assigned to it. Keep secrets out of source control, TOML, command history, logs, diagnostics, and generated documentation.
Survey Scribe can resolve credentials into SurveyScribeConfig. Routing consumes
an injected StructuredProvider; the routing core does not read credentials or
import a provider SDK. Use the same controls when an application constructs the
optional provider adapter.
Survey Scribe has no telemetry client. Local normalization and native XLSForm conversion do not send source content. Provider-backed extraction sends normalized content to the endpoint that you configure, subject to that provider's retention and logging policy. See Privacy and Local-First Boundaries.
Recommended order¶
Use these options in decreasing order of preference:
- A managed identity or short-lived token callback when the provider supports it.
- A secret manager injected into an environment variable at process start.
- A local
.envfile that is excluded from version control for development only. - Direct construction from a secret already held in memory.
Never put a literal key in Python, notebooks, TOML, YAML, shell scripts, test fixtures, or command arguments.
Environment variables¶
Set the provider-standard variable for the current shell.
export OPENAI_API_KEY="<your-key>"
$Env:OPENAI_API_KEY = "<your-key>"
set OPENAI_API_KEY=YOUR_KEY_VALUE
Then explicitly allow environment resolution:
from survey_scribe.config import SurveyScribeConfig
config = SurveyScribeConfig.resolve(
constructor={"provider": "openai", "model": "gpt-model-name"},
resolve_environment=True,
)
assert config.api_key is not None
Use SURVEY_SCRIBE_API_KEY when you need one generic variable across supported
providers. It takes priority over a provider-specific key.
Local .env files¶
Survey Scribe does not read .env files automatically. This prevents unexpected
filesystem access by SDK code. For local development, load one explicitly with
python-dotenv:
python -m pip install python-dotenv
Create .env with restrictive filesystem permissions:
OPENAI_API_KEY=replace-with-your-secret
SURVEY_SCRIBE_MODEL=gpt-model-name
Exclude it from Git and provide only a placeholder template:
.env
.env.*
!.env.example
OPENAI_API_KEY=
SURVEY_SCRIBE_MODEL=
Load the file without replacing secrets already supplied by the environment:
from dotenv import load_dotenv
from survey_scribe.config import SurveyScribeConfig
load_dotenv(override=False)
config = SurveyScribeConfig.resolve(
constructor={"provider": "openai"},
resolve_environment=True,
)
Do not use a committed .env file for CI or production.
Direct configuration construction¶
Direct construction is safe only when the secret comes from a protected runtime
source. Wrap the value in Pydantic SecretStr so accidental representation does
not show the clear text:
import os
from pydantic import SecretStr
from survey_scribe.config import SurveyScribeConfig
config = SurveyScribeConfig(
provider="openai",
model="gpt-model-name",
api_key=SecretStr(os.environ["OPENAI_API_KEY"]),
)
Do not do this:
config = SurveyScribeConfig(api_key="<hard-coded-secret>") # Never do this.
Only one of api_key, bearer_token, or token_callback can be configured.
Structured provider boundary¶
Applications can construct an adapter from the openai extra or provide another
implementation of StructuredProvider. Instructor is internal to the packaged
OpenAI-compatible adapter. Direct OpenAI or LangChain objects are not routing
core inputs; adapt them to StructuredProvider so schema inspection, normalized
metadata, retry counts, truncation, and shared concurrency remain enforceable.
Do not first place a key in an ordinary application dictionary that might be logged or serialized. Do not add it to a URL query string, graph, sidecar, manifest, or evaluator fixture.
Live tests versus production¶
An optional protected live model-quality test does not configure production and must not become an interactive approval on every production request. Production administrators own provider selection, gateway quota, secret storage, source authorization, logging, and retention policy. The package enforces configured per-request, schema, source-binding, and concurrency bounds; it does not claim control over gateway-side retention.
Token callbacks¶
A synchronous, no-argument callback can provide short-lived bearer tokens:
from survey_scribe.config import SurveyScribeConfig
def acquire_token() -> str:
return credential_provider.get_token()
config = SurveyScribeConfig(
provider="azure",
model="survey-extractor",
token_callback=acquire_token,
)
The callback is excluded from configuration serialization. Keep the credential provider itself outside the serialized application state.
Gateway metadata and auxiliary secrets¶
Direct AzureOpenAIProvider construction separates static request metadata from
auxiliary secret headers. Put only non-secret values in metadata_headers. Put
auxiliary credentials in sensitive_headers_callback, which is invoked
immediately before each package-owned request attempt. Returned values are
copied into an attempt-local mapping, are not retained on the provider, and are
never added to serialized configuration.
The sensitive callback must be synchronous, bounded, and non-blocking. Read from
a host-managed in-memory cache, environment variable, or local secret provider;
do not make a network request from the callback. Applications own credential
acquisition, rotation, and access controls. Survey Scribe validates header names,
values, collisions, reserved transport fields, and required_headers before the
completion transport runs.
Gateway headers do not replace the primary Azure credential. Configure exactly one API key or token callback, and keep that credential in the SDK-owned field. See the generic adapter-only example.
GitHub Actions secrets¶
Current Survey Scribe repository tests are credential-free. A downstream application can create a separate protected provider job. The command below is a placeholder for that downstream repository, not a Survey Scribe test path:
- name: Run provider integration tests
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: python -m pytest downstream_tests/protected_provider
Use protected GitHub environments for production credentials. Documentation builds do not need provider credentials and must not receive them.
TOML and URL controls¶
survey-scribe.toml is for non-secret settings. The loader rejects api_key,
bearer_token, token_callback, and other credential-like keys at any nesting
level.
base_url rejects user information, fragments, and sensitive query parameters
such as api_key, token, client_secret, password, signature, and sig.
Use the provider SDK authentication field or request header instead.
Logging and diagnostics¶
Credential fields are excluded from model_dump(), JSON serialization, and
repr(config). Package redaction helpers remove common authorization headers,
assigned secrets, URL credentials, query secrets, and known sensitive values.
These controls reduce disclosure risk but cannot identify every secret format.
- Never log
os.environ, request headers, provider request objects, or raw exceptions. - Pass known secret values to redaction helpers before recording third-party errors.
- Review sidecar diagnostics before sharing them outside the approved environment.
- Treat questionnaire text and SVIS output as potentially restricted data.
The main SVIS artifact intentionally contains question_text. Redaction of
diagnostics does not remove questionnaire content from the main output.
Rotation and incident response¶
- Revoke a disclosed key at the provider immediately.
- Create a replacement with the minimum permissions and quota required.
- Update the secret manager or CI secret without committing the value.
- Restart affected workloads so that they read the new credential.
- Inspect provider audit logs and usage for unauthorized activity.
- Remove the secret from Git history if it was committed; deleting the current file alone is not sufficient.
Use separate keys for development, CI, and production. Add spend or rate limits where the provider supports them, and rotate long-lived keys on a defined schedule.