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 five 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, or env. 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» | Reserved for restricting substitution to calls made from a specific function or module. Not yet enforced in this release -- leave it unset. Rather than silently ignore a security setting, a secret with «Caller» set refuses to substitute at all. |
| «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. |
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 (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 MyKeyis Null. - When a value is stored,
Value of MyKeyis 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).
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 formValue 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 |
Planned for a future release -- not yet available. Server-side vault storage, so that (for example) a browser user of a server-hosted model never possesses the secret in any form. | See Secret/Vault API for the planned integration contract. |
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.
- There is no first-use prompt for an unset
in-modelsecret: unset means the author hasn't supplied the value yet, and using it is an error. - Note:
in-modelstorage 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, prefermachine-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
envsecret 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
envsecret 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
envdoes not protect against.
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:
httpsis required, except for loopback addresses (127.0.0.1orlocalhost), where plainhttpis 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 thatapi.acme.com/doesn't also matchapi.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.
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
Authorizationheader) takes precedence over anything stored -- substitution never overrides text you wrote.
Prompting
When a machine-user or session 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). 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).
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 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.
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 |
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.
See Also
- What's new in Analytica 7.2?
- Secret/Vault API -- the planned contract for external vault storage
- Sink functions: ReadFromURL, DbConnection, DbQuery, DbWrite, DbTableNames, MdxQuery, OAuth2Authorize
- SetAuthorizationKey -- the older, session-only authorization-header mechanism that Secrets generalize
- AskMsgText -- masked text entry for custom credential-entry buttons
- Object menu
Enable comment auto-refresher