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 .m file

In the source, in version control, in every copy

Nothing real; only throw-away placeholders in examples

MATLAB vault (setSecret, getSecret)

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

.env file and loadenv

A plain text file you control, outside the repository

Settings shared with a team through a protected location, different sets per workflow

Environment variable (setenv, getenv)

The process environment

The common ground: the value is set by the vault, by loadenv or by the operating system or deployment platform, and code only calls getenv

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 dictionary of 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

setSecret(secretname)

Opens the Secret Prompt dialog box, where you type or paste the value, and saves it as the secret secretname. setSecret(secretname,Overwrite=tf) replaces the value of an existing secret (default false, which is an error if the secret exists); the metadata is unchanged.

R2024a

getSecret(secretname)

Returns the value as a string scalar.

R2024a

isSecret(secretname)

Tests whether the secret exists.

R2024a

renameSecret, removeSecret

Rename a secret, or remove it. Removing is irreversible.

R2024a

setSecretMetadata, getSecretMetadata, removeSecretMetadata

Write, read and delete the metadata dictionary of a secret.

R2024a

listSecrets

Displays the names and the metadata sizes of all secrets. It never shows values.

R2024a

importSecrets(filename)

Imports secrets from a .env file into the vault in one step (below).

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=Value options: OverwriteEnvironmentVariable (default true; false keeps an existing variable of the same name, which lets the real environment of a server override a developer file), ExpandVariables (default true), Encoding (default "UTF-8") and FileType ("auto" or "env", to read a file such as settings.txt as a .env file).

  • With variable expansion on, ${VAR} is replaced by the value of VAR found first in the file and then in the environment, and ${VAR:-Default} supplies a default; if none applies the value is empty.

  • filename can be a file in the current folder or on the path, a relative or full path, an http:// or https:// URL, or a remote location (s3://, wasb://, wasbs://, hdfs://). Prefer a local file with restricted permissions over a URL: a secret fetched over plain http:// 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 .env file 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 .env file that contains your credentials.

  • Do not check .env files into source code repositories. For Git the documentation suggests excluding the .env extension in a global ignore file (.gitignore_global) so that the rule covers every repository; a .env line in the repository’s own .gitignore also 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 loadenv page warns that they may read a plain text .env file directly without calling loadenv. 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.

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 save of 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 and diary files.

  • Keep the value short-lived: fetch it with getSecret where 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.example with placeholder values, and ignore heatflow.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 (pcode)

Converts .m files to a content-obscured, execute-only .p format

MATLAB only

matlab.lang.obfuscateNames (R2024b)

Replaces names of local variables, local functions and nested functions with generic names before pcode

MATLAB only

Standalone executable (mcc)

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

mex on C or C++, or MATLAB Coder on MATLAB code, producing platform-specific binary files

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 myfunc creates myfunc.p in the current folder, and a call to myfunc then runs the .p file; which myfunc shows the .p path. A P-code file takes precedence over the .m file of the same name for execution, even after the .m file is modified. A forgotten .p file 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 .p files in the same folders as the inputs.

  • A wildcard such as spr*.m is accepted, and files without an .m extension are ignored. Live scripts and live functions (.mlx) are not supported; convert them to plain .m first (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 -R2022a algorithm (added in R2022a) is more complex; the default is the algorithm redesigned in R2007b. Files made with -R2022a do 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.requiredFilesAndProducts lists the files a program depends on, so the whole dependency set can be converted, not just the entry point.

  • Because the .p file wins over the .m file, distributing both gives users the source you meant to hide. Ship the .p files without the .m sources (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: -R2022a files need R2022a or later.

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:

  • inputFile must be a function or class definition file and both paths must be local .m files. If inputFile and outputFile are 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 of load.

  • Only direct occurrences of names are renamed (such as x in x = 1, if x < 5 and f(x+y)). Occurrences in strings, character vectors and command-syntax arguments are not, so eval("f(x+y)") and clear x can 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 (default true: a name that also appears alone as a command-syntax argument or a literal is preserved), PreserveArguments (default true; with false only positional arguments are obfuscated, and arguments of local, nested and anonymous functions are always obfuscated) and Renaming ("random", the default, deterministic pseudo-random names, or "natural" for id1, id2, …​).

  • Do not write code that relies on specific obfuscated names; use PreserveNames to exclude names instead, because the generated names may change in a future release.

  • The page’s advice: use name obfuscation together with pcode -R2022a and mcc -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 .env file that is outside the repository and ignored by Git. The code calls getenv or getSecret in one small function and nowhere else.

  • Repository: commit heatflow.env.example with 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, run pcode(...,"-R2022a"), test the .p files with the same test suite, and ship only the .p files 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.