Secrets and Protecting Code
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
Written against MATLAB R2026b. The vault functions need a graphical Secret Prompt dialog box and the P-code, obfuscation and compiler functions need MATLAB (the last one a toolbox), so almost nothing here can run in GNU Octave. The one session that was run, a small helper that reads a secret from an environment variable, was executed in GNU Octave 11.3.0 and shows Octave’s output. Everything else is checked against the R2026b documentation and is "not run here". All credentials in the examples are obviously fake placeholders such as YOUR_API_KEY_HERE; never paste a real key into a document, a test or a script that you will commit.
The page covers two different problems that are often mixed up:
-
Keeping secrets out of code: the program needs a password or a token, but the source file must not contain it, because source files get copied, committed, emailed and read by people and tools who should not have the credential.
-
Protecting code: the program itself is the asset, and you distribute it to people who should be able to run it without being able to read it easily.
Keeping sensitive information out of code
The documentation page Keep Sensitive Information Out of Code defines sensitive information as anything that gives access to your workflow: passwords, certificates, credentials and OAuth tokens, and configuration data for SFTP and FTP servers, password-protected files and archives, cloud providers such as Amazon S3 and Azure Blob Storage, API keys, SSH keys and encryption keys, and databases and applications. Keeping it out of the code rather than hard coding it makes the workflow more secure and the code safer to share. MathWorks lists two ways to do it: the MATLAB vault and a .env file. A third, older way, plain environment variables, is what both end up feeding.
| Approach | Where the value lives | Good for |
|---|---|---|
Hard coded in the |
In the source, in version control, in every copy |
Nothing real; only throw-away placeholders in examples |
MATLAB vault ( |
Encrypted (AES-256) in your vault, accessible only from your operating system account on your machine (MATLAB Online: your MathWorks Account) |
A person’s own credentials on their own machine |
|
A plain text file you control, outside the repository |
Settings shared with a team through a protected location, different sets per workflow |
Environment variable ( |
The process environment |
The common ground: the value is set by the vault, by |
The MATLAB vault
Your MATLAB vault and the secrets in it persist across MATLAB sessions. A secret has three parts:
-
Name: a unique, case-sensitive text identifier. The name is stored unencrypted, as a string scalar. Since R2025a the name may contain a forward slash, which allows names such as
"heatflow/WeatherKey". -
Value: a text value, stored encrypted with AES-256 and returned as a string scalar.
-
Metadata: an optional
dictionaryof extra information, stored unencrypted (see Dictionaries and containers.Map). Never put the sensitive part in the metadata.
On an installed MATLAB the vault is accessible only from the exact combination of your operating system account and your local machine, and the vault and its secrets do not leave the machine. With MATLAB Online the vault is accessible with your MathWorks Account and is stored on a secure database on MathWorks servers; the documentation recommends two-step verification for the account in that case.
Functions
| Function | What it does | Since |
|---|---|---|
|
Opens the Secret Prompt dialog box, where you type or paste the value, and saves it as the secret |
R2024a |
|
Returns the value as a string scalar. |
R2024a |
|
Tests whether the secret exists. |
R2024a |
|
Rename a secret, or remove it. Removing is irreversible. |
R2024a |
|
Write, read and delete the metadata dictionary of a secret. |
R2024a |
|
Displays the names and the metadata sizes of all secrets. It never shows values. |
R2024a |
|
Imports secrets from a |
R2026a |
The "Since" column for setSecret and importSecrets is from their reference pages; for the other vault functions, which are documented together on the Keep Sensitive Information Out of Code page, R2024a is assumed from the vault’s introduction (check each reference page before relying on an exact release). setSecret requires a graphical user interface, so it cannot be used in a headless job. Populate the vault interactively (or with importSecrets) on the machine that runs the job, and have the job call only getSecret.
Using a secret
The documentation uses an SFTP password and an Amazon S3 key. The same pattern for the heatflow weather service is below. The name "HeatflowWeatherKey" is a label; the value is typed into the dialog box and appears nowhere in the script.
% one time, interactively; opens the Secret Prompt dialog box
setSecret("HeatflowWeatherKey")
listSecrets % names and metadata sizes only, never values
isSecret("HeatflowWeatherKey")
% in the script that needs it
key = getSecret("HeatflowWeatherKey");
opts = weboptions(HeaderFields = ["X-Api-Key" key]);
data = webread("https://weather.example.com/api/temperature", opts);
Not run here: setSecret needs the dialog box and the service address is a placeholder. The webread and weboptions functions are described on JSON, XML and Web Access. setSecret("SFTPpassword") followed by sftp("sftp.example.net","adam",Password=getSecret("SFTPpassword")) is the example on the reference page of setSecret.
Do not print the value afterwards: avoid key without a semicolon, disp(key), logging it, or putting it in an error message, because the Command Window, the diary and log files then hold the secret. When a function needs the secret as an environment variable (for example, remote data access reads AWS_ACCESS_KEY), copy it into the environment only for the process, as the documentation does:
setenv("AWS_ACCESS_KEY", getSecret("AwsAccessKey"));
t = readtable("s3://bucketname/exampleFolder/ExampleFile");
Not run here (it needs an S3 bucket). Metadata is a convenient place for a description, an owner or a creation date:
m = dictionary(["Description" "Owner"], [{"Weather service key"} {"heatflow team"}]);
setSecretMetadata("HeatflowWeatherKey", m);
getSecretMetadata("HeatflowWeatherKey")
removeSecret("HeatflowWeatherKey"); % irreversible
Not run here, and not a place for anything sensitive: the metadata is stored unencrypted.
.env files and loadenv
A .env ("dotenv") file is a plain text file of one KEY=value pair per line. loadenv (introduced in R2023a) reads it and sets each pair as an environment variable of the MATLAB process, so later code reads it with getenv. It is easy to move, share and manage access to, and different workflows can use different files.
# heatflow.env : placeholders only, never real values
HEATFLOW_API_KEY="YOUR_API_KEY_HERE"
HEATFLOW_BASE_URL="https://weather.example.com/api"
HEATFLOW_TIMEOUT=30
loadenv("heatflow.env") % sets the variables in this MATLAB session
key = getenv("HEATFLOW_API_KEY");
D = loadenv("heatflow.env"); % with an output: returns a dictionary and
D("HEATFLOW_BASE_URL") % does not change the environment
Not run here (loadenv is not in Octave). The syntaxes are loadenv(filename), loadenv(filename,Name=Value) and D = loadenv(___), where D is a dictionary of strings. The parsing rules and options on the reference page:
-
One key-value pair per line; empty lines are skipped and everything after
#is a comment. -
Key names may contain only letters, digits, underscore, dot and hyphen. Text in quotes is kept as written, white space outside quotes is trimmed, new lines are expanded in double-quoted values, an empty value is an empty string, and for duplicate keys the last valid value wins.
-
Name=Valueoptions:OverwriteEnvironmentVariable(defaulttrue;falsekeeps an existing variable of the same name, which lets the real environment of a server override a developer file),ExpandVariables(defaulttrue),Encoding(default"UTF-8") andFileType("auto"or"env", to read a file such assettings.txtas a.envfile). -
With variable expansion on,
${VAR}is replaced by the value ofVARfound first in the file and then in the environment, and${VAR:-Default}supplies a default; if none applies the value is empty. -
filenamecan be a file in the current folder or on the path, a relative or full path, anhttp://orhttps://URL, or a remote location (s3://,wasb://,wasbs://,hdfs://). Prefer a local file with restricted permissions over a URL: a secret fetched over plainhttp://is exposed on the network.
Rules for .env files
The reference pages repeat the same security considerations, and they apply equally to any file that holds a secret:
-
A
.envfile is plain text, so its location and permissions must match the security level of the workflow. A local machine is often the most secure place. -
Do not deploy a
.envfile that contains your credentials. -
Do not check
.envfiles into source code repositories. For Git the documentation suggests excluding the.envextension in a global ignore file (.gitignore_global) so that the rule covers every repository; a.envline in the repository’s own.gitignorealso works. Add the rule before the first commit: a secret that was ever committed stays in the history and has to be revoked and replaced, not merely deleted. -
If you use AI coding agents or tools that can execute MATLAB code, the
loadenvpage warns that they may read a plain text.envfile directly without callingloadenv. For a more secure workflow it recommends storing the secrets in the vault instead.
A .env file next to the code is thus the weakest of the options: it is a clear text copy of the secret in your working folder. Keep it outside the repository, give it restrictive permissions, and prefer the vault for a credential that belongs to one person.
Source: MATLAB documentation: loadenv
Moving a .env file into the vault: importSecrets
importSecrets(filename) (new in R2026a) reads the same key=value format and saves each pair as a secret in the vault instead of setting an environment variable. The parsing rules are those of loadenv. Options are Overwrite (default false: it is an error if the file contains a name that already exists in the vault), FileType, Encoding and ExpandVariables.
importSecrets("heatflow.env"); % error if a name is already in the vault
importSecrets("heatflow.env", Overwrite=true); % replace existing values
listSecrets % check the names; values are not shown
delete("heatflow.env") % the plain text copy is no longer needed
Not run here (R2026a feature, and the vault is not available in Octave). The last line is the point of the exercise: once the values are in the vault, the plain text file is a liability and should be deleted from disk. After the import, read the values with getSecret("HEATFLOW_API_KEY"); the secret names are the keys of the file.
Environment variables
setenv(name,value) sets a variable of the MATLAB process, getenv(name) reads it (as a string or character vector, empty if it is not set) and isenv(name) and unsetenv(name) test and remove it. Variables set this way last for the MATLAB session and are inherited by processes it starts (such as those run with system). MATLAB can also read the environment it was started from, so on a server or in a continuous integration job the platform’s own secret store can inject the value before MATLAB starts, and the code never needs to know where it came from (see Continuous Integration).
A small helper makes a missing credential fail early with a clear message instead of a confusing authorization error later. The message deliberately names the variable and never prints its value. The file is a script with the helper as a local function at the end, the layout MATLAB needs (a file that begins with function is a function file, not a script).
setenv("HEATFLOW_API_KEY", "YOUR_API_KEY_HERE");
k = requireEnv("HEATFLOW_API_KEY")
setenv("HEATFLOW_API_KEY", "");
try
requireEnv("HEATFLOW_API_KEY");
catch err
disp(err.identifier)
disp(err.message)
end
function key = requireEnv(name)
key = getenv(name);
if isempty(key)
error("secrets:missing", "Environment variable %s is not set", name);
end
end
Octave needs a function to be defined before a script calls it, so the run used the same code with the function moved to the top, after a 1; line (see Scripts and Code Sections). Octave 11.3.0 output (MATLAB displays the string with quotes):
k = YOUR_API_KEY_HERE
secrets:missing
Environment variable HEATFLOW_API_KEY is not set
The identifier secrets:missing is a name chosen by the example. Argument errors and identifiers in general are on Errors, Warnings and Exceptions.
Habits that keep secrets out of trouble
-
Never hard code a real secret, not even "temporarily". Use a placeholder in documentation, examples and tests.
-
Do not save secrets into data files. A
saveof the whole workspace writes a variable that holds a key into a MAT-file; save only the named variables you mean to (see Workspace Variables and MAT-Files). The same goes for live script output, published reports anddiaryfiles. -
Keep the value short-lived: fetch it with
getSecretwhere it is used, pass it to the call, and clear the variable (clear key) afterwards. Do not store it in a global or a persistent variable, a class property or a structure that gets saved or displayed. -
Check in a template, not the real file. Commit
heatflow.env.examplewith placeholder values, and ignoreheatflow.env. -
Rotate on exposure. A secret that reached a repository, a log or a chat is compromised. Revoke it at the service, create a new one and replace the vault entry (
setSecret(name,Overwrite=true)). -
Use the Code Analyzer and review. The analyzer does not detect credentials, so reviews and secret-scanning tools in the repository host are the safety net (see Code Analyzer and Code Quality).
Protecting source code you distribute
An .m file is plain text, and the documentation page Security Considerations to Protect Your Source Code says plainly that the contents of MATLAB source files are easily accessed, revealing design and implementation details. If you do not want to distribute proprietary application code in that form, it lists several options, which can be combined:
| Option | What it does | Needs |
|---|---|---|
P-code ( |
Converts |
MATLAB only |
|
Replaces names of local variables, local functions and nested functions with generic names before |
MATLAB only |
Standalone executable ( |
Bundles the code in an archive; each code file is encrypted with AES-256 and the user needs no MATLAB |
MATLAB Compiler (or Simulink Compiler) and a supported C or C++ compiler; see Toolboxes and Add-On Products |
Native code |
|
A C/C++ compiler, MATLAB Coder; binary obfuscation tools outside MathWorks can add a layer |
Remote protected server |
The application runs on MATLAB Web App Server or MATLAB Production Server with restricted access, and users call it through a secure interface |
Server products |
Secure operating system services |
System hardening, for example a shielded virtual machine |
Operating system and infrastructure |
For Simulink models the same page lists model protection. Because no single method is complete, the documentation advises combining several approaches to protect sensitive code or data.
P-code with pcode
pcode obfuscates the code in a .m file, or in all script and function files of a folder, and writes .p files.
pcode(item) % item is a file, a function on the path or a folder
pcode(item,"-R2022a") % more complex obfuscation; runs only in R2022a and later
pcode(item,"-R2007b") % the default, legacy algorithm
pcode(item1,item2,...,itemN)
pcode(___,"-inplace") % write the .p files beside the inputs
The behavior on the reference page:
-
pcode myfunccreatesmyfunc.pin the current folder, and a call tomyfuncthen runs the.pfile;which myfuncshows the.ppath. A P-code file takes precedence over the.mfile of the same name for execution, even after the.mfile is modified. A forgotten.pfile is a classic source of "my edits have no effect" (see Function Precedence and Name Resolution for the precedence rules, and delete the stale.p). -
For a folder, all script and function files in it are obfuscated and the results are saved in the current folder, unless
"-inplace"is given, which writes the.pfiles in the same folders as the inputs. -
A wildcard such as
spr*.mis accepted, and files without an.mextension are ignored. Live scripts and live functions (.mlx) are not supported; convert them to plain.mfirst (see Live Scripts and the Live Editor). -
Files in a class folder or a namespace (
@myClass,+pkg) get the same structure in the output folder (see Namespaces and Class Folders). -
The
-R2022aalgorithm (added in R2022a) is more complex; the default is the algorithm redesigned in R2007b. Files made with-R2022ado not run in earlier releases, so choose it only when every user has R2022a or later. -
A P-code file cannot be opened in the Editor or the Live Editor.
matlab.codetools.requiredFilesAndProductslists the files a program depends on, so the whole dependency set can be converted, not just the entry point. -
Because the
.pfile wins over the.mfile, distributing both gives users the source you meant to hide. Ship the.pfiles without the.msources (help text is covered on Help Text and Documenting Code).
What P-code does and does not protect
-
It does hide the source from a casual reader and from the Editor, and let you give users working functions without the text.
-
It does not encrypt in the cryptographic sense: the reference page calls the result an "obfuscated" format, the overview calls it "content-obscured", and both advise combining several approaches. Treat it as a deterrent, not as a vault.
-
It cannot protect a secret stored inside it. A password or key hard coded in a function remains in the program that has to use it, so a determined user can recover it. Secrets belong in the vault or the environment, never in P-code.
-
It does not hide behavior. P-code is run by the same MATLAB as any other code, so what it does can still be observed from outside by calling it with test inputs.
-
It is not portable across everything:
-R2022afiles need R2022a or later.
Source: MATLAB documentation: pcode
matlab.lang.obfuscateNames
Introduced in R2024b, matlab.lang.obfuscateNames(inputFile,outputFile) reads a function or class definition file and writes nearly identical source to outputFile, with the names of local variables, local functions and nested functions replaced by generic names such as id242487092. It is meant as an extra layer for advanced users who already use pcode -R2022a, not as a protection by itself, because it keeps comments (which makes debugging easier) and the source is still readable text.
% write an obfuscated copy of one file into another folder
matlab.lang.obfuscateNames("SourceCode/myfunc.m", "ObfuscatedCode/myfunc.m")
% or give a folder; the file keeps its name
matlab.lang.obfuscateNames("SourceCode/myfunc.m", "ObfuscatedCode")
% obfuscate arguments too, and use sequential names (id1, id2, ...)
matlab.lang.obfuscateNames("SourceCode/myfunc.m", "ObfuscatedCode", ...
PreserveArguments = false, Renaming = "natural")
% then convert the obfuscated copy
pcode("ObfuscatedCode/myfunc.m", "-R2022a")
Not run here (R2024b function). The documentation example turns sumVar = inputArg1 + inputArg2 into a line that uses a generated id... name while the function’s own name and, by default, its arguments stay as written.
Details from the reference page:
-
inputFilemust be a function or class definition file and both paths must be local.mfiles. IfinputFileandoutputFileare the same file it is overwritten without a warning; a warning is issued if the output file name does not match the function or class being defined. Output is encoded as UTF-8. -
Never obfuscated:
ans,varargin,varargout, and names used as literal arguments ofload. -
Only direct occurrences of names are renamed (such as
xinx = 1,if x < 5andf(x+y)). Occurrences in strings, character vectors and command-syntax arguments are not, soeval("f(x+y)")andclear xcan be broken by the renaming. The page therefore warns that obfuscated code may behave differently from the original, and recommends thorough testing with the MATLAB testing frameworks (see eval and Its Alternatives for why such code is hard to analyze, and Unit Testing with matlab.unittest). -
Name-value options:
PreserveNames(names to leave alone),PreserveNamesFromLiterals(defaulttrue: a name that also appears alone as a command-syntax argument or a literal is preserved),PreserveArguments(defaulttrue; withfalseonly positional arguments are obfuscated, and arguments of local, nested and anonymous functions are always obfuscated) andRenaming("random", the default, deterministic pseudo-random names, or"natural"forid1,id2, …). -
Do not write code that relies on specific obfuscated names; use
PreserveNamesto exclude names instead, because the generated names may change in a future release. -
The page’s advice: use name obfuscation together with
pcode -R2022aandmcc -s, and do not distribute the obfuscated source directly; distribute it as a P-code file, ideally inside a deployable archive made by MATLAB Compiler.
Standalone executables and native code
With MATLAB Compiler, mcc bundles the program and its files into an archive in which every MATLAB code file (plain text or P-code) is encrypted with AES-256, and the end user needs no MATLAB. By default file names and the folder layout are not hidden, and other file types (MAT, FIG, MEX) are not encrypted. The -s option of mcc obscures the file names and structure and also encrypts those other types; the files stay encrypted on disk and are decrypted in memory at run time. Other documented options are -j (convert all .m files to P-files when packaging) and -k (provide your own encryption key). These are MATLAB Compiler features and are outside this page; see Toolboxes and Add-On Products.
Converting code to native code (mex, MATLAB Coder) produces binaries that are harder to read than P-code, at the price of a build step for each platform. The documentation notes that binary obfuscation with tools outside MathWorks can add another layer.
In every case, the secret stays out of the code: an encrypted archive that holds a hard coded key still gives the key to anyone who runs the program and watches what it sends.
A practical recipe for the heatflow package
-
Secrets: a personal credential for the weather service goes in the vault (
setSecret); a shared one for a team arrives as an environment variable from the deployment platform or, on a developer machine, from a.envfile that is outside the repository and ignored by Git. The code callsgetenvorgetSecretin one small function and nowhere else. -
Repository: commit
heatflow.env.examplewith placeholders and a note on where to get the real values; do not commit the real file. Review diffs for accidental values before committing. -
Distribution to colleagues who may read the source: ship the toolbox (see Packages and the MATLAB Package Manager) as source, with a license.
-
Distribution to users who should only run it: list the dependencies with
matlab.codetools.requiredFilesAndProducts, optionally obfuscate names, runpcode(...,"-R2022a"), test the.pfiles with the same test suite, and ship only the.pfiles plus the help files. If stronger protection is needed, use MATLAB Compiler with-s, or run the code on a server you control.
Where older books differ
Books on MATLAB written before the vault and loadenv often keep passwords in a script, in a MAT-file or in a .m file that sets variables, and describe P-code only in the context of speed or hiding code. The current documentation recommends keeping sensitive information out of code, with the vault (R2024a) and .env files with loadenv (R2023a); it states that .env files must not be checked into repositories or deployed; and it recommends the more complex -R2022a obfuscation, name obfuscation (R2024b) and compiled deployment for protecting source. importSecrets is new in R2026a.