Secret

Release:

 • 4.6 •  5.0 •  5.1 •  5.2 •  5.3 •  5.4 •  6.0 •  6.1 •  6.2 •  6.3 •  6.4 •  6.5 •   •  6.6 •  7.0 •  7.1 •  7.2

new to Analytica 7.2

A Secret is an object that lets your model use a credential -- a database password, an API key, a bearer token -- without the credential itself ever appearing in the model, in any computed result, or in any saved file.

The key idea: a secret is a reference, never a value. Expressions in your model never touch the actual credential. Instead, the Secret's identifier evaluates to a placeholder text such as {{secret:AcmeKey}}, which you compose into a connection string or URL like any other text. The actual value is substituted for the placeholder inside a small set of built-in functions (the allowed sinks), in compiled code, at the last moment before the text leaves Analytica for the outside world -- and only when the call's destination matches the Secret's declared policy.

For example, with a Secret named AcmeKey, a database query is written as:

DbQuery(f"DSN=AcmeWarehouse;UID=svc_reports;PWD={AcmeKey}", sql)

This definition is fully readable -- no cloaking, no locked module. Its evaluated text, which anyone can probe, show, or export, contains only the literal characters DSN=AcmeWarehouse;UID=svc_reports;PWD={{secret:AcmeKey}}. Only DbQuery (because this secret's policy lists it) ever swaps in the real password, inside its own implementation, after the text has left the Analytica language -- and only when the connection string is aimed at the destination the secret is pinned to.

Because the plaintext never enters the language, there is nothing to leak: results, the Value attribute, error messages, typescript, COM, Python, autosaves and .ana files all carry only the placeholder. And because the policy binds the secret to where it may go, model code cannot smuggle it out -- a ReadFromURL call aimed at some other server is refused before anything is substituted.

Creating a Secret

Creating Secret objects requires Analytica Developer (they can also be created in ADE and on the Analytica Cloud Platform server). Models that contain secrets load and run in every edition.

With a diagram in focus and in edit mode, select Object menu / New / Secret. The new node appears as a gold shield shape. There is deliberately no toolbar button for Secrets. Double-clicking the node opens its Object window.

You can also create one from typescript (Secret MyKey) or from an expression in a side-effect context (CreateNewObject('Secret')).

In editions that cannot create secrets, the menu item is absent and the typescript and CreateNewObject routes report that creating Secret objects requires a higher edition -- but a model that already contains secrets still loads and runs normally.

Attributes

A Secret's Object window shows the usual Class, Identifier, Title and Description rows, plus a Value row (below) and six Secret-specific attributes. The Secret-specific attributes have class-qualified names (Secret::Storage, etc.) so they don't collide with identifiers in your model; you read and assign them with the usual attribute syntax, e.g. Secret::Storage of MyKey.

Attribute Meaning
«Secret::Storage» Where the value lives: machine-user (the default when unset), session, in-model, env, vault-user, or vault-shared. See Storage kinds.
«Secret::Sinks» A comma-separated list of the built-in functions allowed to substitute this secret, e.g. ReadFromURL or DbConnection, DbQuery, DbWrite. A secret whose «Sinks» is unset cannot be substituted anywhere.
«Secret::Destination» Where the substituted text may be sent. For web sinks: a comma-separated list of allowed URL prefixes (e.g. https://api.acme.com/). For database sinks: key=value clauses that the connection string must contain (e.g. Server=db.acme.com or DSN=Acme). See How substitution works. A secret whose «Destination» is unset is refused at every sink.
«Secret::Caller» Optional: pins substitution to a calling context -- a user-defined function or a module. When set, the secret substitutes only while that function (or something inside that module) is being evaluated; any other use is refused. See How substitution works.
«Secret::Prompt» The question text shown by the masked value-entry dialog (both the Set... button and the first-use prompt). A generic prompt is used when unset.
«Secret::Vault» For the vault storage kinds: the alias of the configured vault that holds the value. May be omitted when exactly one vault is configured. See vault details.

Title and Description behave as on any object. Along with «Secret::Prompt», they remain editable at all times -- only the four policy attributes (Storage, Sinks, Destination, Caller) freeze while a value is set, and only for the storage kinds that hold a stored value (see below).

The Value attribute

For a Secret, the Value attribute is the stored-value slot -- and reading it never yields the credential:

  • When no value is stored, Value of MyKey is Null.
  • When a value is stored, Value of MyKey is the constant text «opaque secret».
  • So IsNull(Value of MyKey) is the "is it set?" test.

(Note the deliberate divergence from other classes: evaluating the identifier MyKey yields the placeholder text, not the Value attribute.)

Setting the value from the Object window

The Value row in the Object window (and in the attribute panel) shows Null with a Set... button when unset, or «opaque secret» with a Change... button when set. The button opens a modal dialog whose question is your «Secret::Prompt» and whose input is masked (●●●). Submitting an empty field clears the value; Cancel changes nothing. For machine-user storage the dialog adds a Remember on this computer checkbox (checked by default; unchecked keeps the entry for this session only). For in-model storage the button works in edit mode only (details).

Setting, changing, or clearing the value is not undoable -- there is no plaintext anywhere for Undo to restore. Metadata edits (Title, Description, the policy attributes) undo normally.

Setting the value from an expression

Assignments to Value follow Analytica's usual side-effect rules, with one deliberate carve-out:

  • Value of MyKey := "the value" -- stores a value. Allowed in side-effect contexts: button OnClick scripts, typescript commands, OnLoad-style event attributes.
  • Value of MyKey := Null -- clears it (side-effect contexts only). From typescript you can also use the attribute-removal form Value of MyKey: (nothing after the colon).
  • During ordinary evaluation (e.g. in a variable's definition), the assignment is allowed only if no value is set yet -- the set-if-unset carve-out. An evaluation can complete a missing credential, but can never replace or clear one; attempting to gives the error: "The value of secret MyKey is already set. It can be replaced or cleared from a button script or typescript command, but not while a variable is being evaluated."

The carve-out is what lets you build first-use credential entry directly in Analytica code:

If IsNull(Value of MyKey) Then Value of MyKey := AskMsgText(Secret::Prompt of MyKey, password: True)

In every context, the assignment expression's own result is the opaque reading («opaque secret» or Null) -- never the assigned text -- so the idiom above can't accidentally cache the plaintext into a variable, and MsgBox(Value of s := 'x') shows «opaque secret».

The value you enter passes through the language exactly once, at entry (e.g. from AskMsgText(..., password: True)). Showing it to yourself as you type it is not a leak.

Storage kinds

«Secret::Storage» Where the value lives Typical use
machine-user (default) Windows Credential Manager, encrypted per Windows user by the operating system. Survives restarts. "Company database; each user has their own login." The model ships with the declaration only; each user is prompted once per computer, with Remember on this computer.
session Process memory only; gone when Analytica closes. Short-lived tokens; credentials you'd rather re-enter each session.
in-model Sealed into the .ana model file itself, encrypted with AES-256-GCM. A service credential the author distributes with the model: everyone who opens the model can use it (through its pinned sinks and destination); no one can read it out.
env An environment variable named by the Secret's own identifier, read fresh at each substitution. Analytica stores nothing. CI pipelines and servers, where the operator provisions credentials through the environment.
vault-user, vault-shared An external vault -- a web service your deployment configures, reached over the Secret/Vault API HTTP contract. vault-user values are per-user; vault-shared values are deployment-wide. Server deployments, so that (for example) a browser user of a server-hosted model never possesses the secret in any form. See vault details.

machine-user details

Stored credentials are visible -- and deletable -- by you in the Windows Credential Manager control panel, under generic credentials named Analytica/secret/.... This transparency is a feature: nothing is squirreled away where you can't audit or remove it. (The entries are keyed by an internal per-secret GUID rather than by name, so the list is not human-readable by name; match entries against a model when needed by the GUID saved in its file.)

Because the stored value is keyed to the individual Secret object, renaming a secret or moving it to another module keeps its value reachable -- and a newly created secret can never inherit a value from some earlier secret that happened to have the same name. A secret in a linked library is the same object in every model that links the library, so its credential is entered once per machine for the library, not once per model.

If you leave Remember on this computer unchecked when prompted, the value is kept for the current session only.

in-model details

Set an in-model secret the same way as any other (the Set... button, or Value of S := ... from a side-effect context). The encrypted value is saved inside the model file and travels with it: anyone who opens the model can use the secret through its pinned sinks and destination, but no one -- including the author -- can read the value back out.

Things to know:

  • The encrypted value is bound to the secret's policy. Editing any policy field -- or hand-editing the model file -- leaves the secret unset; re-enter the value under the new policy. (This is the freeze rule doing its job: nobody, including a file editor, can re-aim an already-stored value.)
  • Renaming the secret or moving it between modules preserves the value.
  • Because the sealed value is part of the model file, setting or changing it is an authoring act: the Set.../Change... button works in edit mode only, and is disabled in browse mode. The per-user kinds have no such restriction -- entering your own machine-user or session credential works in browse mode, in every edition. Typescript and button-script assignment (Value of S := ...) are unaffected by mode.
  • There is no first-use prompt for an unset in-model secret: unset means the author hasn't supplied the value yet, and using it is an error.
  • Note: in-model storage protects the credential at rest and against everything model code can do -- but not against a determined user attacking the application itself with a debugger or memory tools, since the application must be able to decrypt the value to use it. Historically such an attack is quite difficult to pull off, but with the emergence of extremely capable LLMs like Mythos and Astra, such attacks might become plausible in the near future. Treat it as strong obfuscation -- a categorical upgrade over pasting a key into a model file, not absolute secrecy. When each user can hold their own credential, prefer machine-user.

env details

An env secret reads the environment variable named by the secret's own identifier: a secret whose identifier is ACME_API_KEY with Storage: env reads the environment variable ACME_API_KEY. The variable is read lazily, at each substitution, inside the substitution code -- the value never enters the Analytica language. This is the sanctioned replacement for reading credentials with GetProcessInfo("env:..."), which returns the raw value into your expression where anything can copy it. GetProcessInfo("env:...") is generally disabled, or allowed only for specially configured (non-sensitive) env vars when running on a server like with ACP or ADE apps ; an env secret's value gets the full policy gate (sinks + destination) instead.

  • You cannot set or clear an env secret from Analytica -- Value of S := ... is an error, because the value lives outside the model. Set the variable in the environment that launches Analytica (or the server process).
  • IsNull(Value of S) reports whether the variable currently exists.
  • Substituting an env secret whose variable doesn't exist is an error (no prompt).
  • Intended for CI and server use, where the operator controls both the environment and which models run. See the honesty table below for what env does not protect against.

vault details

The vault storage kinds keep the value in an external vault -- a web service that your deployment configures, reached over the Secret/Vault API HTTP contract (https required, except plain http to loopback addresses). The «Secret::Vault» attribute names which configured vault to use (by its alias); it may be omitted when exactly one vault is configured.

  • vault-user -- each person gets their own value: the vault key includes the user identity (the logged-in user on the Analytica Cloud Platform; the Windows user name on the desktop). Using an unset vault-user secret raises the same masked first-use prompt as machine-user, but with no Remember checkbox -- the vault is the store, and the entry is stored back to it.
  • vault-shared -- one value for the whole deployment. An unset vault-shared secret is an operator error, never a user prompt: "Secret «S» (vault-shared) has no value in vault '«v»'. A deployment operator must provision it; shared secrets never prompt the user."
  • Any vault or transport failure (bad status, TLS failure, timeout) makes the secret cleanly unavailable, with an error naming the vault. Analytica never falls back to a different store.
  • The policy attributes of a vault secret are never frozen: the policy fingerprint is part of the vault path, so an edited policy simply finds no value until one is entered under it (see the freeze rule).

Registering a vault. A deployment registers vaults at startup:

  • In Analytica.ini:
    SysLib_Internal::AddSecretVault("https://secrets.acme.com/analytica/v1", "%ACME_VAULT_TOKEN%", name: "acme").
  • On an Analytica Cloud Platform server, lines in the server configuration (the SecretVault line first):
    SecretVault acme = «url»
    SecretVaultAuth acme = %TOKEN%
    # and optionally
    SecretVaultSign acme = %KEY%

The «auth» and «sign» arguments accept an environment-variable name between percent signs, resolved at request time, so the token itself stays out of the file. Startup-registered vaults live for the process and are writable (values entered at a prompt are stored back). A model can also call ::SysLib_Internal::AddSecretVault itself, from typescript or a button script: such a vault lasts only while that model is open and is read-only -- Analytica will read values from it but never write to it, so a model-supplied vault cannot capture what users type into credential prompts. See Secret/Vault API for the full contract, including signed identity assertions.

Using a secret in an expression

The recommended idiom is identifier interpolation in an f-string (or any text operation -- the placeholder is ordinary text):

ReadFromURL(f"https://api.acme.com/v1/report", httpHeaders: f"Authorization: Bearer {AcmeKey}")

You can hand-type the literal placeholder {{secret:AcmeKey}} into a text -- policy is enforced at substitution time and doesn't care how the text was built -- but the identifier form is better: renaming the secret updates identifier references automatically, while text literals are not rewritten. A hand-typed bare name must also be unambiguous among all the secrets in your loaded model.

A secret declared inside a namespace-scoped library gets a qualified placeholder, e.g. {{secret:AcmeLib::CrmToken}}; the identifier reference produces the correct qualified form for you.

How substitution works

Substitution happens in one place: inside an allow-listed sink function, just before the text leaves Analytica (to the ODBC driver, to the web stack). For each placeholder, the sink must be listed in the secret's «Sinks» and the call's destination must satisfy the secret's «Destination» -- otherwise the call fails with an error naming the secret, and nothing is sent. Both restrictions are checked on every call; there is no way to "log in" a secret for general use.

The built-in functions that can act as sinks in Analytica 7.2:

Sink Placeholders substituted in «Destination» is matched against
ReadFromURL «url», «httpHeaders», «httpContent» the URL
DbConnection, DbQuery, DbWrite, DbTableNames the connection string the connection string
MdxQuery «connectionString» and «password» the connection string
OAuth2Authorize «client_secret» the «token_url» (token endpoint)

Functions that write to disk (such as WriteTextFile) are deliberately never sinks.

URL destinations («Destination» for ReadFromURL and OAuth2Authorize): a comma-separated list of allowed URL prefixes. The URL must extend one of them. Additional rules:

  • https is required, except for loopback addresses (127.0.0.1 or localhost), where plain http is accepted.
  • A prefix written without a scheme (e.g. api.acme.com/) matches the URL after its scheme -- meaning "this host under any permitted scheme".
  • Include a trailing / in a host-only prefix, so that api.acme.com/ doesn't also match api.acme.com.evil.example/.

Connection-string destinations (the database sinks): a comma-separated list of key=value clauses, all of which must appear in the connection string (keys are case-insensitive; {}-braced values are recognized per ODBC grammar). Pin the clause that identifies the server, e.g. Server=db.acme.com or DSN=Acme, so the password can only ever accompany a connection to that database.

Caller pinning («Secret::Caller», optional): when set, substitution additionally requires that the pinned user-defined function (or something inside the pinned module) is being evaluated at the moment of the call. Any other use is refused with an error naming the pin: "Secret «S» is pinned to «F» (its Secret::Caller attribute): it can be used only while «F» is being evaluated." A pin that names nothing refuses substitution. To close the impersonation loophole, the policy fingerprint captures the pinned function's definition content (or the module's stationary identity) at the moment the value is set -- so editing the pinned function's definition, or renaming it and creating a new function with the same name, leaves the secret unset; re-enter the value under the new state. Honesty: a caller pin is only as strong as the immutability of the pinned code -- «Destination» remains the primary control.

Other properties of substitution:

  • Refusal is an error, before anything is sent. The error names the secret; for web sinks it echoes the URL with the placeholder still visible -- error messages never contain post-substitution text.
  • Single-pass: substituted values are never re-scanned for placeholders, so a credential whose actual value happens to contain {{secret:...}} is passed through verbatim.
  • An explicitly supplied credential in the call (e.g. your own Authorization header) takes precedence over anything stored -- substitution never overrides text you wrote.

Prompting

When a machine-user, session, or vault-user secret is used in a sink and no value is stored, Analytica raises a masked entry dialog: the question is your «Secret::Prompt», the caption is the secret's name, and for machine-user a Remember on this computer checkbox appears (checked by default). A vault-user entry has no checkbox -- the vault is the store, and what you enter is stored back to it. Entering a value lets the call proceed; Cancel makes the evaluation fail with an error saying the secret has no value.

There is no first-use prompt for in-model or env secrets -- an unset value of those kinds is an error (for in-model, the author hasn't finished authoring; for env, the environment isn't provisioned). An unset vault-shared secret likewise never prompts: it is an operator error (see vault details).

In server or ADE contexts with no interactive user, the same situation is a clean "not set" error rather than a prompt. On the Analytica Cloud Platform, the masked prompt appears in the web client.

For custom credential-entry UX (your own button or first-use logic), use the set-if-unset idiom with AskMsgText(..., password: True).

Policy is frozen while a value is set

Once a secret of machine-user, session, or in-model storage has a value, its four policy attributes -- Secret::Storage, Secret::Sinks, Secret::Destination, and Secret::Caller -- become read-only: the Object window greys them, and typescript or expression edits are refused. Title, Description, and «Secret::Prompt» stay editable: none of them affects where an existing value can go, and a changed prompt only affects what a user types next, under the unchanged pinned policy.

To change the policy: clear the value first (the Change... button with an empty entry, or Value of S := Null from a button script or typescript), edit the policy, then re-enter the value under the new policy.

This is a security invariant, not a UI convenience. A stored value is bound to the exact policy it was entered under -- structurally, not by convention -- so no edit path, including hand-editing the .ana file in a text editor, can loosen or re-aim a secret that already has a value. The attack this kills: re-pointing a stored credential's «Destination» at an attacker's server and letting substitution deliver the plaintext. After any policy edit, the secret is simply unset, and someone who legitimately has the credential re-enters it.

For env and the vault storage kinds the policy attributes are not frozen -- Analytica holds no stored value under the old policy to protect. For the vault kinds the same binding invariant is enforced by the policy fingerprint in the vault path instead: an edited declaration asks the vault for a key that was never written, and finds nothing until a value is entered (or provisioned) under the new policy.

Dependencies and arrows

A Secret participates in dependency tracking like any input:

  • An identifier reference (f"...{MyKey}...") or a Value read (IsNull(Value of MyKey)) makes the referencing variable depend on the secret. The diagram draws an arrow from the shield to the reader, and changing the secret's value invalidates its dependents -- change the credential and its consumers recompute on next demand.
  • An assignment (Value of MyKey := ...) is not a dependency; instead the diagram draws a reverse arrow into the shield, showing where the secret can get set, without invalidating the assigning code when the value changes.
  • A hand-typed literal {{secret:...}} in a text creates no dependency and no arrow -- one more reason to prefer the identifier idiom.

What a Secret protects -- and what it doesn't

No security feature is absolute, and overclaiming is how security features rot. Here is what each storage kind actually defends against:

Threat machine-user in-model session env
A user of your model tries to read the credential -- definitions, results, the Value attribute, typescript, COM, Python ✔ only the placeholder is ever visible ✔ same ✔ same ✔ same
Model code tries to smuggle the value out -- WriteTextFile, or a ReadFromURL aimed at an attacker's server ✔ substitution happens only in the pinned sinks toward the pinned destination ✔ same ✔ same ✔ same
The credential lands in .ana files, autosaves, version control, or email ✔ never written to the model file ✔ present only as an AES-256-GCM encrypted blob ✔ never written ✔ never written
Someone edits the model -- even with a text editor -- to re-aim a secret whose value is already stored ✔ any policy edit leaves the secret unset; the old value is unreachable ✔ same (the encrypted blob is bound to its policy) ✔ same ✘ nothing is stored, so nothing is invalidated: the edited model reads the same variable under its own new policy. Run only trusted models where the variable is set
A different model tries to use your stored value ✔ values are keyed to the individual Secret object -- another model's secret, even with the same name, can never find them n/a -- the value travels with the model file ✔ same keying ✘ any model running where the variable is set can declare its own env secret naming it
A determined user runs the model under a debugger or inspects process memory ✔ nothing to find beyond credentials that user entered themselves ✘ the application can decrypt it, so a determined user can too -- strong obfuscation, not absolute secrecy ✔ only values entered in this session on this machine are present ✘ environment variables are readable by any process the user runs
The destination server itself logs the credential; DNS or TLS attacks ✘ out of scope ✘ out of scope ✘ out of scope ✘ out of scope

The vault kinds behave like machine-user in this table -- per-user (or deployment-wide) storage outside the model -- with the value held on the vault server rather than on the user's computer; in a web deployment the browser user never possesses it in any form.

Two points worth stating plainly:

  • Secret protection is not data protection. A secret cannot be sent anywhere except its pinned destination -- but the data the model legitimately retrieves with it (query results, API responses) are ordinary values. A modified copy of your model could use a stored credential against the legitimate server and then mishandle the results. Review models from others before running them, exactly as you would today.
  • Non-goals: protecting model IP (that remains the job of definition hiding/obfuscation, unchanged); defending against an attacker who already controls the user's Windows account; resistance to memory forensics (memory holding plaintext is scrubbed on a best-effort basis).

Model files and older releases

In a saved model, a secret appears as an ordinary object declaration (spelled ::Secret, with attribute lines like ::Secret::Storage:). The value appears nowhere in the file -- search for it -- except for an in-model secret's encrypted blob.

A model that contains secrets also saves a guard line so that opening it in Analytica 7.1 or earlier produces the deliberate error "This model requires Analytica release 7.2 or later" (with the usual option to abort the load), rather than a cascade of syntax errors. A model with no secrets saves byte-identically to previous releases.

On reload, machine-user values are found again automatically; session values are gone by design; in-model values travel in the file; env values come from the environment; vault-stored values are fetched from the configured vault.

See Also

Comments


You are not allowed to post comments.