SkillTotal

Is wandb safe?

wandb is an AI python_package analyzed by SkillTotal's deterministic static scanner. The scan found no malicious indicators, though 10 risky constructs are reported for review. It can: dynamic code execution, filesystem read, filesystem write, network egress and shell execution — capabilities are what the code can do, not a verdict on intent. Risk score 100/100 (critical).

wandb 0.28.0

python_package · pypi:wandb
CRITICAL
100
/ 100 risk score
Snapshot · scanned Jul 3, 2026 · wandb@0.28.0 · engine 0.24.0 / ruleset 25
High-risk capabilities - review before installing
Notable — review in context (capabilities are not malware):
  • Sensitive-data access combined with network egress
  • Python shell/command execution
  • Python dynamic code execution

No malicious indicators found by static analysis.

Automated static-analysis result. It can contain false positives and false negatives, and is not a claim about the intent of wandb's authors. Report a false positive.

Capabilities — what this component can do (not a risk score):
dynamic code executionfilesystem readfilesystem writenetwork egressshell execution

Findings (10)

CRITICALSensitive-data access combined with network egressST-COMBO-EXFIL

The component both reads secret locations (keys, tokens) and can send data over the network.

if config_file is not None or os.path.exists(os.path.expanduser("~/.kube/config")):
userCredsFilename    = "application_default_credentials.json"
from urllib.parse import quote
entity, project = (quote(tags[k]) for k in ("entity", "project"))  # type: ignore[index]

Why it matters: That combination is the classic path for quietly stealing credentials off your machine.

Fix: Verify that secrets read from disk are never transmitted off-host without explicit, auditable user consent.

HIGHPossible command injection (shell + dynamic command)ST-CMDI-PY

The code builds an OS command out of values that can change at runtime, then runs it through a shell.

self.process = subprocess.Popen(command, shell=True)

Why it matters: If any of those values come from untrusted input, an attacker can run their own commands on the machine.

Fix: Pass arguments as a list without shell=True (e.g. subprocess.run(['git', 'checkout', branch])); never build a shell string from external input. If a shell is unavoidable, quote with shlex.quote.

HIGHPython dynamic code executionST-DYN-PY

The code turns strings into live code at runtime (eval / new Function / exec).

self.model = copy.deepcopy(model).eval().to(self.device)

Why it matters: If those strings aren't fixed and trusted, they become a way to run arbitrary code.

Fix: Avoid evaluating dynamically constructed code; if unavoidable, ensure the input is a trusted constant and never derived from external data.

HIGHEmbedded secret / credentialST-SECRET-EMBEDDED

A hardcoded credential (API key, token, or private key) is shipped in the code.

CertAlgoSKECDSA256v01  = "sk-e…[redacted, 31 chars]@openssh.com"
CertAlgoSKED25519v01   = "sk-s…[redacted, 23 chars]@openssh.com"

Why it matters: Anyone who gets the package gets the secret — rotate it and load secrets at runtime instead.

Fix: Remove the secret from the code, rotate it immediately, and load credentials from the environment or a secrets manager at runtime.

HIGHSensitive path / secret-location referenceST-SENS-PATH

The component references credential locations like ~/.ssh or .aws/credentials.

userCredsFilename    = "application_default_credentials.json"
curl 'http://169.254.169.254/metadata/identity/oauth2/token?resource=https://management.core.windows.net&api-version=2018-02-01' -H "Metadata: true"
imdsEndpoint             = "http://169.254.169.254/metadata/identity/oauth2/token"
imdsEndpoint                      = "http://169.254.169.254/metadata/instance/compute/location?format=text&api-version=" + defaultAPIVersion

Why it matters: Touching secret locations is a common first step before stealing them — confirm why it's needed.

Fix: Verify why the component references credential locations; reading these is a common precursor to secret exfiltration.

HIGHSensitive path / secret-location accessST-SENS-PATH-PY

A credential location (e.g. ~/.ssh, id_rsa) is passed to a file, process, or network call.

if config_file is not None or os.path.exists(os.path.expanduser("~/.kube/config")):

Why it matters: This reads or ships a secret location, not merely mentions it — a common precursor to theft.

Fix: Verify why the component accesses credential locations; reading these is a common precursor to secret exfiltration.

HIGHPython shell/command executionST-SHELL-PY

The component can run operating-system commands or spawn processes.

subprocess.check_call(
        [
            str(go_binary),
            "build",
            *build_tags,
            *coverage_flags,
            *race_detect_flags,
            *ld_flags,
            *output_flags,
            *vendor_fl …
subprocess.check_call(
            [
                objcopy,
                "--remove-section",
                ".gnu.version_r",
                "--remove-section",
                ".gnu.version",
                str(binary_path), …
subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=src_dir)
cargo_output = subprocess.check_output(cmd, cwd=arrow_rs_wrapper_dir, env=env)
subprocess.call(["docker", "run"] + args)
subprocess.call(["docker", "pull", image])
subprocess.call(["docker", "attach", existing.split("\n")[0]])
subprocess.call(command)
subprocess.call(["docker", "pull", "wandb/local"])
running = subprocess.check_output(
        ["docker", "ps", "--filter", "name=^wandb-local$", "--format", "{{.ID}}"]
    )
subprocess.call(["docker", "stop", "wandb-local"])
code = subprocess.call(command, stdout=DEVNULL)
subprocess.call(["docker", "stop", "wandb-local"])
subprocess.check_call(["git", "fetch", "--all"])
exit_code = subprocess.call(
                ["git", "apply", "--reject", patch_rel_path], cwd=root
            )
result = subprocess.run(args, env=env, close_fds=True)
subprocess.check_output(["docker"] + cmd, stderr=subprocess.STDOUT)
result = subprocess.run(
            ["docker", "--version"],
            capture_output=True,
        )
with subprocess.Popen(
        args,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        universal_newlines=True,
        bufsize=1,
    ) as process:
completed_process = subprocess.run(
        args, input=input, stdout=stdout_dest, stderr=stderr_dest, env=subprocess_env
    )
return int(subprocess.check_output(umask_cmd))
subprocess.check_output(["pip", "install", "uv"], stderr=subprocess.STDOUT)
subprocess.check_output(args, stderr=subprocess.STDOUT)
output = subprocess.check_output(
            ["gcloud", "config", "get-value", config_name], stderr=subprocess.STDOUT
        )
popen = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE)

Why it matters: Powerful and often legitimate — confirm the commands aren't built from untrusted input.

Fix: Confirm the command and its arguments are fully controlled and not derived from untrusted input; avoid shell=True.

MEDIUMPython filesystem readST-FS-PY-READ

The component reads files from disk.

with open(os.path.join(self._fpath, "wandb-job.json")) as f:
with open(os.path.join(self._fpath, "diff.patch")) as f:
with open(os.path.join(root, name), "rb") as f:
with open(path, "rb") as metadata_file:
with open(file, "rb") as f:
with open(self.save_path, "rb") as f:
with open(sm_files.SM_PARAM_CONFIG) as fid:
for line in open(sm_files.SM_SECRETS):
with open(relpath) as json_file:
with open(file_path) as f, open(checksum_path) as f_checksum:
with cache_open("wb") as f, open(entry.local_path, "rb") as src:
with open(data_or_path) as file:
with open(data_path, encoding="utf-8") as file:
with open(dir_or_file_path, "rb") as file:
with open(source_artifact.manifest.entries[entry_key].download()) as f:
self._fp = open(fname, open_flags)

Why it matters: Usually legitimate, but worth confirming it can't be steered into reading sensitive files.

Fix: Confirm which files are read and that paths cannot be influenced by untrusted input to reach sensitive locations.

MEDIUMPython filesystem write/deleteST-FS-PY-WRITE

The component writes or deletes files on disk.

shutil.copy(self._requirements_file, launch_project.project_dir)
shutil.copy(self._requirements_file, launch_project.project_dir)
with open(os.path.join(global_settings.wandb_dir, ".gitignore"), "w") as file:
shutil.rmtree(run.path)
shutil.rmtree(run.path)
DEVNULL = open(os.devnull, "wb")  # noqa: N806
with open(name, "wb") as f:
with open(patch_path, "w") as f:
with open(config_path, "w") as f:
shutil.copy2(req.path, path)
shutil.copy2(req.path, path)
with open(mlpipeline_ui_metadata_path, "w") as metadata_file:
with open(mlpipeline_ui_metadata_path, "w") as metadata_file:
with open(os.path.join(path, "secrets.env"), "w") as file:
shutil.copy(
                relpath,
                os.path.join(self.settings._tmp_code_dir, os.path.basename(relpath)),
            )
with open(
                os.path.join(
                    self.settings._tmp_code_dir,
                    nb_name,
                ),
                "w",
                encoding="utf-8",
            ) as f:
with open(
                os.path.join(
                    self.settings._tmp_code_dir, kaggle_ipynb["metadata"]["name"]
                ),
                "w",
                encoding="utf-8",
            ) as f:
with open(
                os.path.join(self.settings._tmp_code_dir, "_session_history.ipynb"),
                "w",
                encoding="utf-8",
            ) as f:
with open(
                os.path.join(self.settings.files_dir, state_path),
                "w",
                encoding="utf-8",
            ) as f:
with open(file_path, "w", encoding="utf-8") as tmp_f:
shutil.copyfile(path, staging_path)

Why it matters: Usually legitimate, but worth confirming the paths can't be controlled by untrusted input.

Fix: Confirm which files are written/deleted and that paths cannot be influenced by untrusted input.

MEDIUMPython network egressST-NET-PY

The component makes outbound network requests.

from urllib.parse import quote
entity, project = (quote(tags[k]) for k in ("entity", "project"))  # type: ignore[index]
from urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
"displayName": urllib.parse.unquote(name.replace("-", " ")),
name = urllib.parse.unquote(name)
urllib.parse.quote(
                            re.sub(
                                r"-+", "-", re.sub(r"\W", "-", self.display_name)
                            ).strip("-")
                        ),
urllib.parse.quote(str(self.entity), safe=""),
urllib.parse.quote(str(self.project), safe=""),
urllib.parse.quote(str(self.id), safe=""),
urllib.parse.quote_plus(self.entity),
urllib.parse.quote_plus(self.project),
urllib.parse.quote_plus(self.id),
from urllib.parse import urlparse
parsed_url = urlparse(url)
import urllib.parse
parsed_url = urllib.parse.urlparse(path)
isurl = urllib.parse.urlparse(document["image"]).scheme in (

Why it matters: Usually legitimate, but confirm the destinations are expected and no sensitive data leaves.

Fix: Confirm the destination hosts are expected and that no sensitive data is sent off-host.

Check your own component

Run the same evidence-backed scan on any MCP server, agent skill, or package.

Scan your own component

Or get notified if this component's risk changes:

How we determine this: deterministic static analysis (regex + AST), evidence-anchored, no code execution. Methodology →