Skip to main content

Environment Config & Secrets

Every environment carries the configuration your tests need at run time. There are three distinct places a value can live, and picking the right one keeps tests portable and secrets safe. Tests only ever reference one of them — properties — so this page starts there.

Where it livesReferenced in a test asStored inReadable back?Use it for
Property{{propertyName}}Proofarc DByes (unless marked secret)Non-secret config: base URLs, page sizes, feature flags
Secret(never directly — see below)KubernetesnoValues that must never touch our database
CredentialcredentialTag (auth only)Proofarc DB (encrypted)no (masked)Login/OAuth/API-key auth for a target

The one rule that ties it together: a test reads from exactly one place — properties. A secret is pulled into a property; the test never names the secret. This gives you a single, predictable source of truth for what a test resolves.


1. Properties — the values your tests read

A property is a named value on an environment. Reference it anywhere in an API scenario or UI test with {{propertyName}}:

baseUrl: "{{apiBaseUrl}}"
steps:
- name: List users
endpointPath: "/api/users?pageSize={{pageSize}}"

Set them in the UI (Environments → expand an environment → Test Properties) or via API/MCP:

# API — the body is a raw list of {key,value}
POST /api/environments/{id}/variables
[{"key":"apiBaseUrl","value":"https://api.dev.example.com"},
{"key":"pageSize","value":"20"}]
# MCP
set_env_variables(environment="development",
variables={"apiBaseUrl":"https://api.dev.example.com","pageSize":"20"})

Properties resolve at the lowest precedence — a credential, the resolved target baseUrl, a step extraction, or a hook output with the same name all win over a property. A property can also drive the host with no target row: author base_url: "{{apiBaseUrl}}" and set that property.

A property can be marked secret (isSecret) to encrypt it at rest and mask it in the UI/logs — but for values that must never live in our database at all, use a Kubernetes secret instead.


2. Secrets — Kubernetes-backed, write-only

Secrets are stored in Kubernetes, never in the Proofarc database, and are write-only: once set, the value can never be read back through the API or UI (GitHub-style semantics). You manage them per environment; internally each environment maps to one Kubernetes Secret object keyed by a stable environment id, so two environments both named "prod" never collide.

Managing secret values requires the SUPER_ADMIN role. ADMIN/ANALYST can see the secret names (to know which references exist) but cannot set or read values.

How a test uses a secret

A test never references {{secrets.X}} directly. Instead:

  1. Set the secret (SUPER_ADMIN):
    POST /api/environments/{id}/secrets # SUPER_ADMIN
    {"key":"CLIENT_SECRET","value":"s3cr3t-value"}
  2. Reference it from a property value as {{secrets.NAME}}:
    POST /api/environments/{id}/variables
    [{"key":"clientSecret","value":"{{secrets.CLIENT_SECRET}}"}]
  3. The test uses the property{{clientSecret}} — and knows nothing about the secret.

At run time, while the environment's properties are seeded into scope, any {{secrets.NAME}} in a property value is resolved against that environment's Kubernetes secret. The resolved value is then treated as secret-derived and automatically masked in logs, results, and execution snapshots — so it never surfaces even though the test consumed it.

test → {{clientSecret}} → property value {{secrets.CLIENT_SECRET}} → Kubernetes Secret
(the only ref) (resolved while seeding properties) (write-only, per env)

Rotating and deleting

  • Rotate by setting the same name again with a new value. Rotation updates the one key and preserves every other secret on the environment.
  • Delete removes the key (and the whole Secret object once its last key is gone). After a delete, any property whose value is {{secrets.<name>}} will fail to resolve — the run fails loud rather than sending an unresolved placeholder.

Fail-loud behavior

If a property references {{secrets.X}} but the secret is unset, or the secrets store is disabled on the deployment, the run fails loudly: the property is dropped, the {{propertyName}} survives unresolved, and the standard unresolved-placeholder guard stops the step. A literal placeholder is never sent to the target.

Managing secrets

UIEnvironments → expand an environment → Secrets. You'll see the secret names, a copy-the-reference button ({{secrets.NAME}}), and — for a SUPER_ADMIN — Add / Rotate / Delete. Values are write-only; the dialog never shows an existing value.

MCP — three tools (set/delete are SUPER_ADMIN):

ToolDoes
list_environment_secrets(environment)List names + whether the store is enabled (never values)
set_environment_secret(environment, key, value)Create or rotate a secret
delete_environment_secret(environment, key, confirm=true)Delete a secret

APIGET / POST / DELETE /api/environments/{id}/secrets.

Operator note: the Kubernetes secrets store is off by default (backend.secretsStore.enabled=false). Enabling it creates a namespaced Role granting the backend get/create/update/delete on Secrets in its own namespace only. Until it's enabled, list_environment_secrets reports enabled:false and set/delete fail loud.


3. Credentials — auth for a target

Credentials are for authentication — logging a test into the target API/app. They live in the environment's encrypted credential vault and are referenced by credentialTag, not by {{...}}. See Access & Credentials and the API-testing Authentication guide.

Credentials remain the right home for username/password, OAuth client secrets used for auth, and API keys that authenticate a scan or scenario. Kubernetes secrets (§2) are for arbitrary values you want out of our database entirely and surfaced through a property.


4. Global properties — instance-wide shared values

Global properties are a standalone, instance-wide store — not tied to any project or environment. Use them for values every test on the platform shares: constants, expected messages, shared test data, locator maps.

  • UI: Configuration → Global Properties (New Property → key / value / description / secret).
  • MCP: get_global_properties, set_global_property, delete_global_property.
  • Reference them exactly like env properties: {{myKey}}. They resolve on every test surface (API scenarios, mobile YAML, cloud-provider config).
  • Groups: a global whose value is a JSON object is a group — reference leaves by path: {{checkout.submitButton}} reads the submitButton field of the checkout global. Path references resolve in mobile YAML and cloud-provider config; API scenarios currently resolve flat keys only ({{myKey}}).
  • Precedence — globals are always lowest. An environment property, credential, hook output, or step extraction with the same name overrides a global of that name. Most-specific wins; a global can never shadow environment-level config.
  • secret: true masks the value in list responses, same as env properties.
# create / update (POST is an upsert on `key`)
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
$CSS_URL/api/global-properties \
-d '{"key":"supportEmail","value":"qa@example.com","description":"shared assertion value"}'

Which do I use?

  • Non-secret config a test reads (URLs, sizes, flags) → property.
  • A value that must never live in Proofarc's database, surfaced to a test → Kubernetes secret, referenced from a property.
  • Authenticating a test/scan into a targetcredential (credentialTag).
  • The same value shared by tests across all projects/environmentsglobal property (env property of the same name overrides it where they differ).