Skip to content
Malwagon
Sign up

Package analysis

What this module is

Malwagon Python package analysis takes one package requirement typed as text and installs it inside a disposable Linux virtual machine under a syscall tracer. It returns a report of what the install itself started, wrote, contacted and left on disk. The requirement string is the sample: no distribution file is uploaded, none is fetched onto the analysis host, and Python packaging is the whole scope of the module.

Where this capability came from

Package analysis reached Malwagon as a technology transfer from extuno.com. What crossed over was the method: treat installing as the event worth recording, and take a requirement string as sample enough to record it with. The module that runs that method here is this platform's own, from its specifier grammar and its disposable Linux guest to its tracing, its separation of install noise from the package's own code, its side-effect class names and the install object those names travel in, so a package report you read on this platform was produced by that build, on hardware this platform operates.

Installing a Python package runs code before you import a line of it

A source distribution carries a build script the installer has to execute to produce something installable, and a modern build backend is a program the installer imports and calls. Both run on the machine doing the install, with that machine's privileges, at the moment the requirement is resolved. No line of your application has to call the library for that code to have run; it ran because you asked for the package. That window is the whole subject of this module. The trace attaches to the install command as it launches and is released when that command exits, so the record Malwagon returns covers exactly the interval in which code nobody called gets to run.

The machine that normally executes it is rarely a workstation someone is watching. It is a build agent in the middle of a pipeline, or a developer's laptop a minute after a branch checkout, and what is within reach from there at that instant is the job's own credentials in the process environment, the token that can push to the source repository, the signing and publishing material for whatever that job releases, the tree that was just checked out and the internal network the runner sits on. The disposable guest stands in for that machine, and the run is read for the moves that would count on it: a process the build did not need, a write landing outside the environment the install was given, a connection opened by something that is not the installer, and reads of the shadow password file or of SSH private key material, which are recorded as credential access. An install-time script starts in a position most droppers never reach, and it gets there without a user opening anything.

The record a traced install leaves

The argument above is only worth as much as the evidence Malwagon can put behind it, and the evidence is the record a traced install leaves. A clean install has one shape. The installer resolves a name, fetches from the index, unpacks into the environment it was handed and exits; the process tree holds the installer and the build children it needed; the connections go to the index and to operating-system name resolution; the writes land under the install target. Every one of those things happens when you install an empty module, which is the point: on its own the shape says nothing about the package, so the module subtracts it.

An install carrying code of its own breaks that shape in ways the trace can name: a process appears that building a package did not call for, or something that is not the installer opens a connection, or a write lands outside the environment the install was given, or a boot or login surface is touched while the install is still running. The report states which of those happened and names the path, the command line or the endpoint behind each one, so a finding can be read back to the file it came from.

One field, two installsinstall summary
examplelib==1.4.0
  install_time_execution   false
  side_effects             none
  process tree             the installer and the build children it needed
  network                  the index, and name resolution for it
  files                    under the install target

examplelib-extras==0.9.2
  install_time_execution   true
  side_effects             suspicious_process_spawn, network_during_install,
                           persistence_during_install
  process tree             a shell started under the build step
  network                  a host that is not the index, opened by that shell
  files                    a write to a login startup file, path named
Illustrative shape, not a captured scan, and both distribution names are placeholders. The first of the two is what a benign install looks like once install noise is separated out, and it has to come back empty. That empty result is the harder half of the problem, because the noise is in every run and separating it is where the work goes.

Run it at these three moments

At adoption
Analyse the exact requirement string the build will resolve, before the first install lands on a build agent or a laptop.
On every version bump
Analyse it again. A reading six months old describes the release that was read, and the interval between a maintainer account changing hands and a new release appearing is short.
On every transitive change
Analyse it again when the resolved set moves under you. A dependency nobody named is installed by the same run, with the same privileges as the one that was named.

Gate the pipeline on the run's install-time execution flag, not on a person remembering to look.

Pinning is worth doing and it does not answer this question. A pin fixes which release of the package you named; it says nothing about what that release does while it installs, and it does not reach the dependencies resolved alongside it in the same run. Re-analysing a pinned string is where that shows, because the target line comes back identical and the install object under it does not have to: a run whose install-time execution flag was false can come back true with side-effect classes named beside it, or come back true with the same classes over a command line the earlier run's spawned processes did not hold, a remote endpoint the earlier connections never reached, a SHA-256 on a dropped file that was not there before. Pinning and analysis fit together in one direction only: the pin is what makes an analysed version the version that actually lands, so the pin is the mechanism and the analysis is the answer. A lockfile full of exact versions nobody has ever detonated is a repeatable install of unknown code.

Why does the same requirement install different code next month?

Because a requirement is usually a range, and a range is a question the resolver answers on the day the build runs. >=1.4 asks for whatever is newest and compatible at that moment, and a ~=2.0 constraint accepts any compatible release in a series that is still being published into. Even an exact pin on the package you named leaves its dependencies free unless every one of them is pinned too, and those are resolved in the same run, from the same index, by the same process, with the same privileges. In the install record a resolver-driven change is not a subtle thing to spot. It arrives as a process in the traced tree the previous run's tree did not hold, or a name in the DNS section nothing resolved last time, or a write landing outside the install target where the earlier run wrote only under it; and because a dependency's install behaviour is captured as part of the same run rather than split out from the package you named, a change well down the resolved set still surfaces as a change in the record filed under the name you submitted. The requirement text is stable. What it resolves to is not.

So the code you analysed last month and the code your build installs this morning can differ under one name, with nothing in your repository changed to record it. The report is where it does get recorded: it names the target exactly as it was submitted, the installer's exit status, whether the install ran code of its own and the side-effect classes when it did, and it keeps the conditions the run happened under beside them: the guest image, whether the machine came from a pre-warmed snapshot or a cold boot, and how long the install ran. Two readings of one string can then be set against each other instead of both being separately true. Reproducibility is a property of the resolved set, not of the requirement file, which is why the useful unit of analysis is the string as it resolves today. It is also why the recommendation above is keyed to the resolved set moving, not to a calendar.

A name is what an analyst has in hand

An advisory names a release. It gives you a distribution name and a version, and the move it invites is the one worth not making: fetch the artifact onto a machine you care about, unpack it and look. A name is what an analyst has, so a name is what this module takes as the sample.

The sample is a name, and the grammar is the first gate

target
One package requirement typed as text, up to 200 characters. There is no file field on this form at all. The string itself is stored as the sample and is what the scan is filed under.
guest
A disposable Linux image, fixed server-side. No operating-system selector, no work path, no manual launch mode, no memory-dump options. The form renders none of them.
egress
Required, and structural. The guest has to reach the index for there to be anything to observe, so a plan without egress is refused at validation, before a credit moves.
run
A fixed install window, with no duration control on the form, and one credit per scan. Dependencies are resolved and installed inside it, not stubbed.
layers
Static and dynamic start selected, AI starts off, at least one is required. Over REST and MCP the dynamic layer is forced on and the AI layer forced off.
output
The run is recorded with detonation_mode set to pip_install, and carries one install-time execution flag with its side effects named.

The field is validated against one grammar before anything is stored, and that grammar is an allowlist: a name, optional extras, at most one version specifier, anchored at both ends. Everything else is refused with a message that names the shape the field wants, so the reply tells a submitter what to type next. The point of writing it as an allowlist is that a requirement string is the one piece of caller text on this module that ends up as an argument, and an allowlist admits only the shapes named in it, so a syntax nobody has considered has to be added on purpose before it is accepted.

The accepted specifier, part by part.
Part What is accepted Bound Accepted
Distribution name A letter or a digit first, then letters, digits, dot, underscore and hyphen. A leading dot or hyphen is not a name. 1-99 chars examplelib
Extras One pair of square brackets holding letters, digits, comma, dot, underscore and hyphen. No spaces inside them. 1-60 chars examplelib[extra]
Version specifier One of ==, >=, <=, ~=, !=, >, <, followed by letters, digits, dot, underscore, asterisk, plus, exclamation and hyphen. One specifier, not two. 1-40 chars ==1.4.0, >=1.2, ~=2.0, ==1.4.*
The whole field Name, then optional extras, then the optional specifier, and nothing else on either side of it. 200 chars examplelib[extra]==1.4.0
Distribution names in the Accepted column are placeholders, written so no reader takes a real project as a worked example. One grammar object backs them, not three: the console form owns it, the REST endpoint and the MCP tool import that same object, so a string accepted on one surface is accepted on all three and a refusal is a refusal everywhere. Three copies of a pattern is three chances for one of them to be looser.
Accepted, and refusedfield validation
examplelib                     accepted
examplelib[extra]==1.4.0       accepted
otherlib>=1.2                  accepted
pkg~=2.0                       accepted
pkg==1.4.*                     accepted

-r requirements.txt            refused    a requirements file is not a requirement
https://host/pkg.whl           refused    URLs
git+https://host/repo.git      refused    version-control references
./local/pkg                    refused    paths, relative or absolute
pkg>=1.0,<2.0                  refused    more than one constraint on one name
pkg; python_version<'3.9'      refused    environment markers
.hidden                        refused    a leading dot or hyphen
examplelib\n                   refused    the pattern is anchored at end of string,
                                          not end of line
Placeholder names again, in the shapes the field actually takes. One requirement per scan, and the module has no reading of a requirements file, a repository reference or a local path. A lockfile is one submission per line, which is also the shape that gives you one report per name; a hundred packages do not collapse into a single verdict.

Egress here is a property of the plan, not a checkbox

This module is paid plans only, and the refusal happens during form validation, not at the end of a queued scan that could never have worked. There is no isolated version to fall back to: an install with no route to the index produces a timeout and nothing else, which is not a cheaper answer, it is no answer. The second gate is further down, at the point the machine is created and the network it attaches to is chosen, so a hand-built submission that talks its way past the first one still does not land on a routed network.

Where does the install actually happen?

Not on this host. Nothing about the named distribution is downloaded, unpacked or installed here: the host validates a string, creates a scan and starts a machine. Inside that machine the installer resolves the requirement, fetches it and installs it. That is how the install becomes observable: the event is watched while it happens, and nobody is left holding an artifact and a guess. Installing is the event under analysis, so the install has to happen somewhere, and the somewhere is a machine that exists for this one run and is discarded afterwards.

That machine is a full virtual machine under a hypervisor, on hardware Malwagon operates. It is restored from a live memory snapshot of a booted Linux system, so the install begins against a system that is already up and has never held anyone else's run, and it is given a disposable disk of its own for the duration. The recording is taken from outside it, which is the subject of the diagram below and of the paragraph after it.

ANALYSIS HOST Requirement string One grammar Nothing fetched here the sample is this text, no file is uploaded console, REST and MCP share the same check no download, no unpack, no install on this host DISPOSABLE LINUX GUEST restored from a live memory snapshot, discarded when the run ends a second source, never the only one Installer resolves and installs Install-time code executes the requirement and every dependency it pulls in with it as its own process, in the one place it can be watched Syscall trace over the whole install tree RECORDED FROM OUTSIDE THE GUEST Packet capture Screen recording Trace normalised at the hypervisor link framebuffer, plus live view into typed events, host-side Install block: mode, exit status, one install-time execution flag, named side effects the same object in the report, in the API response and over MCP
The path a named requirement takes. The blue boxes are the load-bearing ones: the host holds no distribution, and the capture and the recording are taken from outside the guest, so an install script that kills the tracer next to it removes one source and leaves the others running.

The install goes into an environment built for that run, so the package's own installed files stay separable from anything it writes elsewhere on the machine. Dependencies are resolved and installed for real, and that is deliberate: a clean package with a poisoned dependency is a genuine supply-chain shape, and a stubbed tree would put it outside the observed run. The whole install process tree is traced at the syscall level for the length of the run, with kernel event tracing where the guest supports it and process tracing where it does not, and the raw trace is normalised host-side into typed process, file, network, DNS and signal records.

Attribution is a property of how the evidence was collected, fixed as each record is written and never reconstructed afterwards. Each recorded file operation, process and connection carries the process id that produced it and is scoped to the traced install tree, so "what the install did" is not inferred by subtracting machine-wide activity from a baseline. Read the process graph and you are reading parents and children, not a busy Linux box.

Two of the recording layers are agentless: they do not run inside the guest at all. The packet capture is taken at the hypervisor link and the screen is captured from the framebuffer, both outside the guest's own operating system, so an install script that kills, unhooks or blinds the in-guest tracer sitting next to it removes one source and leaves those two recording. The in-guest trace is what supplies the call-level detail those two do not carry, and it is a second source, never the only one. Beside it sits a small but decisive record: the guest declares that it launched, with the pid and the full install command line. That is what separates a report with nothing in it from a run that did not reach the install, which is the failure mode that quietly turns "we never ran it" into "it did nothing".

Which layers run for a package scan, and which do not.
Layer On this module Why
Dynamic Runs The traced install is the method. Turn it off and the module has almost nothing left to say.
Packet capture Outside Full capture at the hypervisor link, downloadable, and analysed for signature alerts, TLS client fingerprints, SNI hosts, HTTP metadata, cleartext protocols, DNS, beaconing, domain-generation likeness and fast flux.
Screen recording Outside Framebuffer frames for the run, plus a live view of the guest while the install is happening.
Threat intelligence Runs Over the indicators the run derived: URL, address and domain feed lookups plus an offline known-bad hash check, with this platform's own baseline destinations excluded so a stale listing on ordinary infrastructure does not manufacture a finding.
Static Partial On a name submission there is no distribution file on the platform, so the static layer sees the reference text only. The full archive reading belongs to an uploaded sdist or wheel, which is the section below.
AI narrative Optional Off by default, and built from derived data only: the install summary, the side-effect flags, the process, file and network summaries and the metadata. The package itself is not sent to it. Forced off on the REST and MCP paths.
Memory forensics Not run There is no RAM capture or introspection pass on this path. An install-time stage that stays resident leaves its syscalls behind, not a memory image.
Document handling Not run Nothing in a package run is a document, and the module does not pretend otherwise.

Install noise is subtracted, not reported at you

Tracing an install captures everything, and everything includes the network connections, the file writes and the spawned processes that come with installing any package at all, up to and including one that contains no code. A signal that fires on every input is not a signal. The work is separating the activity that installing anything produces from the activity attributable to this package's own install-time code.

WHAT INSTALLING ANYTHING PRODUCES WHAT THE PACKAGE'S OWN CODE DID The environment built to install into The installer's own scratch files Traffic to the index and OS services Process spawn during the install File write outside the install target Network contact away from the index Persistence write during the install Subtracted, not reported install_time_execution: true this is what every install produces with the classes above named beside it One boolean and a named list, in the report and in the API response, so a build can act on it.
The separation that decides whether this module is usable. Everything on the left happens on every install and is taken out; everything on the right is what the package's own install-time code did, and it is what raises the install-time execution flag.

What survives the subtraction is classified into four named classes, and those names travel intact into the report, the API response and the MCP result. The network class shows how the separation is done: the question is asked of the process that opened the socket, not of the address it reached, because the installer talking to the index is the operation itself, and flagging that fires on every package including the ones that do nothing.

The four install side-effect classes, as they are named in the data.
Class What it means
suspicious_process_spawn Something in the traced install tree started a process that building a package does not need.
file_write_outside_install A file was created or written somewhere that is not the environment the install was given.
network_during_install A process in the tree that is not the installer opened a connection, or a name was resolved that is not the index.
persistence_during_install A create, write, modify or rename touched one of the persistence surfaces below.
Any one of the four sets the install-time execution flag true and maps the run to Supply Chain Compromise: Compromise Software Dependencies and Development Tools, T1195.001, which is the technique the behaviour belongs to. None of them raised means the flag is false, which is a statement about the run and not an absence of output.

A written file is called a payload only when the run earned that word. A payload-shaped name is not enough on its own, because benign installs write scripts and shared objects constantly, so the strong claim needs corroboration from the run itself. Every written file is recorded either way, with path, size where known and a SHA-256; what changes is whether the report calls one a payload, and a file recorded without that label is still there to be looked up.

Persistence surfaces watched while a package installs.
Surface What a write there buys the author Technique
Cron locationsA command that keeps running long after the build job has finished and been forgotten.T1053.003
Systemd unit directoriesA service or timer that starts on boot, under a name that reads like infrastructure.T1543.002
rc.localA command run late in boot on images that still honour the file.T1543.002
Shell startup filesCode that runs the next time anyone opens a shell as that account, including the next interactive session on a developer machine.T1546.004
Dynamic-linker preload fileA library loaded into every dynamically linked process that starts afterwards.T1574.006
Desktop autostart entriesA program started at session login on a workstation.T1547
SSH authorized keysInteractive access as that account, with no further code needed.T1098.004
Each surface is reported as a category plus the path that touched it, so a persistence finding always arrives with the file it rests on and stays checkable against the run it came from.

Alongside the install-specific classes, the run is read for the abuse and evasion primitives that mean the same thing wherever they appear: process tracing and injection through the debugging interface, anonymous in-memory file creation for fileless execution, kernel module loading, setuid and setgid transitions, deliberate process renaming, and eBPF program loading. Reads of the shadow password file and of SSH private key material are recorded as credential access. Sigma rules are matched over the derived process-creation, file and network events, and the techniques of matched rules are folded into the same technique list, so a rule hit and an observed primitive land in the same list, with no second panel quietly disagreeing with the first.

Runtime signals and where they land in ATT&CK.
Observed in the install tree Tactic Technique
A process is startedExecutionT1059
Native API use inside the traced treeExecutionT1106
Another process is traced or injected intoDefense EvasionT1055.008
An anonymous in-memory file is created and runDefense EvasionT1620
A file is deleted after being writtenDefense EvasionT1070.004
Permissions are changed on a fileDefense EvasionT1222.002
A process name is deliberately changedDefense EvasionT1036
A kernel module is loadedPersistenceT1547.006
A setuid or setgid transition happensPrivilege EscalationT1548.001
The shadow file or an SSH private key is readCredential AccessT1552.001
A remote endpoint is contactedCommand and ControlT1071
The install executed code of its ownInitial AccessT1195.001

The packet capture gets the same treatment every other module's capture gets, because the hypervisor does not care what the guest was doing when it produced the frames: signature alerting, TLS client fingerprinting with JA3 and JA4, SNI extraction, HTTP metadata, cleartext protocol detection, DNS, and the timing analyses for beaconing, domain-generation likeness and fast flux. Baseline destinations that every guest of this kind reaches are filtered out of the intelligence layer, and the count of filtered rows is recorded, so a quiet network section is distinguishable from a network section that was quietly emptied. Endpoints and process command lines are then read for configuration and endpoint extraction.

Indicators are derived and stored from the run itself: addresses, domains, URLs, dropped-file hashes and TLS fingerprints, with loopback, private and link-local ranges excluded and platform infrastructure scrubbed. That scrub exists for a specific reason worth being blunt about. A platform that feeds its own sandbox traffic into its own reputation data poisons itself, and then confidently reports its own infrastructure as an indicator of compromise. Indicators are defanged on display and exportable from the scan.

An answer a build can act on

The output of a package run is not meant to be read by a person every time. It is meant to be read once by a person and then continuously by a pipeline, which is why the install result is a small typed object: the target as submitted, whether it came from a name or a copied file, the installer's exit status and a success flag, one install-time execution boolean, and the side effects by name. That object is in the report, in the REST response and in the MCP result, through the same allowlist that decides what is allowed to leave in a machine-readable report at all.

The install objectpost /api/v1/scans
submit    {"module": "package", "target": "examplelib[extra]==1.4.0"}

report    "detonation_mode": "pip_install",
          "pip_install": {
            "target": "examplelib[extra]==1.4.0",
            "from_file": false,
            "success": true,
            "install_time_execution": false,
            "side_effects": []
          }

gate      install_time_execution == false  ->  the install ran no code of its own
          install_time_execution == true   ->  read side_effects, then the run
Illustrative shape, not a captured scan, and the distribution name is a placeholder. The behaviour summary on the report opens with the same statement in words and names the side effects when there were any. The REST surface is on paid plans; the MCP tool is on every paid plan, where an assistant holding a token can submit a name and read the result itself.

Every run also records the conditions it happened under, and those belong in the same breath as the verdict: which guest image was used, how long the run was, whether the machine came from a pre-warmed snapshot or a cold boot, how long it took before the install actually started, the total elapsed time, and whether the guest reported finishing. Anything the guest noticed about its own degraded state is kept separate from what the sample did, so a slow restore is not read as package behaviour.

Each section of the report works to a budget, and the report says when a section reached one. A long trace is summarised, not reproduced, and a truncated section is marked as truncated where it is displayed, so a short table reads as a short table and not as a quiet run. The figures themselves stay unpublished: a budget a submitter can see is a budget a deliberately noisy submission can be written against.

What a package report keeps, and what each row carries.
SectionWhat a row holds
Syscall sequenceThe ordered calls made inside the traced install tree.
File operationsThe path, the pid behind it, a timestamp, and the destination when the operation was a rename.
Spawned processesThe process, its parent and its full command line.
Network connectionsEach connection, plus the unique remote endpoints behind them.
DNS queriesNames the guest saw resolved, merged with the ones the capture recorded.
IndicatorsAddresses, domains, URLs and dropped-file hashes, defanged on display and exportable.
Syscall count summaryCall names ordered by how often the run made them.
Dropped filesPath, size where known, and a SHA-256.
Persistence surfaces touchedThe surface category and the path that touched it.
Side effects in the install objectThe class names, carried into the API response and the MCP result.
Every one of these sections is bounded per scan, and the bound is reported when it is hit. The collector's own working limits are a separate set of figures and are not published either.

What if you already have the distribution file?

Sometimes the artifact is in your hand: a wheel pulled from an internal mirror, an sdist attached to a ticket, a build output you want read before it is published. That goes in as a file submission, on the file module's form, and the platform types it by content, not by extension. A gzip tar carrying setup.py, PKG-INFO, setup.cfg or pyproject.toml is recognised as a source distribution; a zip carrying a metadata entry or a dist-info directory is recognised as a wheel. Either way the distribution gets a static package reading, and choosing the Linux image additionally detonates it as a traced install of the file you uploaded.

Nothing in that static reading executes. The archive is walked with bounds on it, nothing is written to disk, and setup.py is parsed as an abstract syntax tree, so a file whose whole purpose is to be executed by an installer can be read without ever executing it. Every bundled module in the distribution is inventoried the same way, for dangerous call categories, imports, network indicators and locally decoded blobs, across ten categories: dynamic code execution, dynamic import, process execution, network, encoding and decoding, decompression, serialised-code execution, deserialisation, reflection and native memory access. The distribution name is compared offline against a local corpus of common names for typosquatting, with no lookup leaving the platform.

Install-time evidence from setup.py, without running itstatic reading
top-level call: urlopen (line 14)
top-level try block (line 22)
cmdclass override (custom install/build command)
process_execution: subprocess.Popen (line 31)
network: socket.create_connection (line 44)
dynamic_code_execution: exec (line 47)
Illustrative lines in the real shapes the card uses. A statement at module level in setup.py runs when the installer imports it, and a guarded top-level block is the classic dropper placement, so both are evidence and not style notes. The card reports install-time execution as yes, no or unknown, and unknown means the file could not be parsed, which is itself worth seeing.
A name, or a distribution file: what each one gets.
Analysis Name submission Uploaded sdist or wheel
Traced install in a disposable guest Yes the module is this run Yes on the Linux image, installing the uploaded file
Archive walk and file inventory No no distribution is held here Yes nothing executed, nothing written to disk
setup.py install-time evidence No Yes parsed as code, not run
Metadata, version and declared dependencies No Yes from the metadata entry or the project file
Typosquat comparison No Yes computed offline, with the near name and the edit distance
File reputation on the artifact No there is no artifact to look up Yes
Dropped-file hashing, indicators and intelligence from the run Yes Yes
An uploaded distribution is the case where both readings exist for the same artifact: what the code says it will do at install time, parsed without running it, sitting beside what it actually did when it was installed. Disagreement between the two is the interesting part.

What this module does not do

Paid plans only, with no isolated fallback
Community and anonymous scanning cannot run this module at all. The refusal happens during validation, before a credit is spent, because the guest has to reach the index and an install with nowhere to fetch from is not a cheaper analysis.
Python packaging only
Other language ecosystems and operating-system packages are not accepted. The grammar, the install and the side-effect classes are shaped around one ecosystem, and a thin veneer over the rest would be worse than not offering them.
One requirement per scan
No requirements file, no version-control or URL or local-path installs, no environment markers, no two constraints on one name. A lockfile is one submission per line.
The install is a Linux install
There is no Windows package run on this module, so install-time code that only fires on Windows will not show itself here.
No memory forensics on this path
No RAM capture and no introspection pass, so an install-time stage that unpacks itself in memory and never touches disk is described by its syscalls and its network, not by a memory image.
Dropped files are identified, not retrieved
Path, size and SHA-256 are recorded. The bytes are not brought back from the guest, so there is no download of what the install wrote and no automatic second-stage analysis of it. A hash you can look up is what you get.
No decrypted HTTPS content here
Encrypted destinations appear as addresses, SNI hostnames and TLS client fingerprints, not as plaintext request bodies. What was carried inside the session is not part of this module's answer.
The install window is fixed
The form offers no duration control, so behaviour that is deliberately delayed well past the install falls outside the run. Time-bombed install code is a real technique and this is a real gap.
Nothing imports the package afterwards
This module answers one question: does installing this run code, and what does that code do. It is not a test of what the library does when your application later calls it, and a package whose payload is in a function nobody called during the install is not exercised.
A name submission has no artifact to read
Without a distribution file on the platform there is no archive walk, no setup.py evidence, no metadata, no dependency list and no typosquat check on a name-only scan, and no file reputation lookup either. That reading belongs to an uploaded sdist or wheel. The value on a name submission comes from the run.
Attribution needs the code to be its own process
Separating the package's own code from the installer doing its job rests on process identity: every recorded operation is tied to the process that produced it, and that is the line the report draws. Where process identity does not separate two actors, the report does not pretend that it did. A dependency is one such case: its install behaviour is captured as part of the same run, without being split out from the package you named.
The answer is about one release at one moment
What the index served when the run happened is what was analysed. A later release under the same name can behave differently, which is exactly why the recommendation above is to run this on every version bump; a yanked or deleted release cannot be analysed at all.
A failed install is a short report
Name does not exist, build error, resolver conflict: the report states the installer's exit status and there is little behaviour to describe. That is an account of the run, not a verdict on the package.
Rule coverage is process, file and network shaped
There is no registry-equivalent surface on this platform to match rules against, so detection content written around registry keys has nothing to fire on here.
Very noisy installs are summarised
A long trace is truncated and the document says so. Every stored table works to a budget of its own and reports when it reached it, so what you are reading is either the whole section or a section marked as cut.

Running one, and where to put it

Console, REST and MCP

Type the requirement into the console exactly as your build would resolve it, leave static and dynamic selected, and add the AI layer if you want the narrative. Through REST, set the module to package and send the same string as the target; through MCP, the submit tool takes it in the same shape and validates it against the same grammar. One credit, one disposable machine, one report.

Read the install-time execution flag before anything else. False means the install ran no code of its own. True means read the named side effects, then the run behind them. A pipeline that can call an API can gate on that one field, and a person stays out of the loop until there is something for a person to look at.

Where to read next depends on which half of this you want. The file module takes over the moment you are holding the sdist or the wheel rather than the name for it. The guest an install runs in, how it is restored and how it is discarded, is written once for every module on the platform page. That the requirement string is what gets stored as the sample, and that hashes and derived indicators are all that ever leave Malwagon, is the boundary set out in full on the security page. And the submit-then-poll shape behind the install object above, on both surfaces that return it, is on the API and MCP page.

Sign in

Sign in

The analyst console and your scan history. Private scans and the API come with a paid plan.

or
Continue with Google

New team? Create a free account

Sign up

Create your account

Free tier: 20 scans a month, three sandbox images, reports public. No card required.

12 characters minimum

or
Continue with Google

By creating an account you accept the terms and privacy policy.

Already provisioned? Sign in