How to Use IronZIP with Google Antigravity
A page that hands clients their case documents as a password-protected ZIP can compile cleanly and still send an archive with files missing, an error page for an ordinary case, or a document from a different case.
Google Antigravity's /browser command starts a Chrome browser subagent that can drive the running page, so one prompt can type a password, press Download for every test case and keep a screenshot of each result. IronZIP builds the archive on the server with AES-256 encryption and extracts it again, so the page can prove that every document came back byte for byte. This guide builds that download page in an ASP.NET Core MVC app, starting from IronZIP's password protection example, and checks it against five cases whose documents and SHA-256 hashes are known in advance.
Open the CaseDocs folder in Antigravity and describe the download page.
/plan CaseDocs is an ASP.NET Core MVC project that targets .NET 9.
Add the case document download that docs/case-download-rules.md describes.
The download works like this:
- A user opens /Cases/{id}, ticks documents and types a password.
- The user receives an AES-256 encrypted ZIP that the server builds with IronZIP.
- The NuGet package is IronZIP version 2026.10.2, and its namespace is IronZip.
Program.cs reads IRONZIP_LICENSE_KEY and assigns it once to IronZip.License.LicenseKey.
Confirm only that the variable exists and how many characters it holds.
Never print, log or write its value.
The Implementation Plan must state these answers:
- the entry name each document receives inside the archive
- the path the archive is saved to before the response, and when that file is deleted
- how the saved archive is compared with the ticked documents
Stop once dotnet build succeeds, and tell me the build result.
Read those three answers before the code changes. Each covers a way the archive can go wrong with no compiler error. In headless agy -p nobody approves the plan, so read the plan Artifact next to the code it describes.
Name the variable in the prompt and never paste the value. Anything typed into the conversation can reach a source file, a commit or a terminal transcript. IronZIP also accepts an IronZip.LicenseKey entry in appsettings.json, and a commit includes that file with everything else. Every supported location is in the license key guide.
Minimal Workflow (5 steps)
- Download the C# ZIP library from NuGet
- Run an ASP.NET Core MVC app on localhost before the first prompt
- Commit the download rules and a manifest with every document's SHA-256
- Have
/browserpress Download for each case and screenshot the result - Extract each archive with its password, hash every file, and confirm IronZIP members in the API reference
What should you review before accepting the changes?
Check where each entry name comes from, the folder each archive is saved to, the line that deletes it, and the code that reads the archive back. Then confirm each IronZIP member in the diff against the API reference.
Antigravity saves screenshots as image Artifacts that accept comments. When one shows an error page or the wrong document count, comment on that screenshot rather than writing a new prompt about it.
How do you test every download in a real browser?
Ask for a results table that only real downloads can fill.
/browser Start CaseDocs in the background with dotnet run --launch-profile http.
That profile listens on http://localhost:5065.
For each row of TestData/expected.csv, do this:
1. Open /Cases/<case> and leave every checkbox as the page sets it.
2. Type the row's password and press Download.
3. Capture a screenshot of what follows.
4. When the answer is a file, take its name and size from the download or the response headers.
In the Walkthrough and in your reply, give one line per row with these parts:
- the case
- the documents the page listed, against the documents column
- the expected outcome
- what actually happened, as the file name and size or the message shown
- a PASS or FAIL verdict
For any failing row, cite the server log entry or error page text responsible.
Do that before you edit any code.
After every fix, restart the app and go through all six rows again.
Stop the app when you are done.
A fix to entry names changes every archive, even for cases that already passed, which is why the prompt reruns all six rows.
What causes incomplete or failing downloads?
In our calibration, the code from the password protection example moved into a download action. It switched to AES-256 and counted the entries of the saved archive by opening it with the constructor. Headless Chrome then pressed Download for every case with IronZIP 2026.10.2. Every failure below compiled without a warning.
| What happened | Cause | What to ask for |
|---|---|---|
| The six-document case downloaded, but extracting it gave 3 files | IronZIP names each entry after the file alone, so two letter.pdf, two scan.jpg and two summary.pdf files shared names | Entry names built from the relative path, with copies saved under those names |
| The one-document case returned an error page | The constructor read-back threw "Invalid Password!" with the right password | A read-back that extracts the archive |
| The empty case returned an error page | Saving an archive with no entries threw "The existing archive is broken!" | A selection check before IronZIP runs |
| A checkbox value edited to ../C-1001/ledger.csv put another case's file in the download | The action trusted posted paths | Every posted path resolved and kept inside the case folder |
| Four ZIP files stayed in the temp folder | IronZIP saves only to a path, and nothing deleted the file | A folder per request, deleted when the request ends |
The entry count check passed for the six-document case, because the archive held six entries.
How do you set up a project Antigravity can work in?
The agent needs an app that already builds and answers on localhost. dotnet new mvc --framework net9.0 gives you one, and running it once shows the port in Properties/launchSettings.json. The port differs from project to project, and the /browser prompts must name it. The browser's allowlist holds only localhost at first, so keep the address in the http://localhost:<port> form.
Commit the case folders under CaseFiles, the rules in docs/case-download-rules.md, TestData/expected.csv with the outcome for each case and password, and TestData/manifest.csv with every document's entry name, size and SHA-256. The five test cases held five plain documents, six documents with three names repeated across subfolders, a single statement, three documents with accented and Japanese names, and no documents at all. Then have the agent audit the package and the key.
Which IronZIP version does CaseDocs.csproj reference? Quote its PackageReference line.
Then show the line in Program.cs that assigns the license key.
Add either line if it is missing.
IRONZIP_LICENSE_KEY is the only allowed source of that key.
Search these files for an IronZip.LicenseKey entry or a key value, and remove any you find:
- appsettings.json
- appsettings.Development.json
- Properties/launchSettings.json
When the variable is missing at startup, the app logs a single warning.
That warning mentions IRONZIP_LICENSE_KEY and contains no value.
Add it only if it is absent, and leave every other file alone.
Tell me what you found and what you changed.
Repeat that version number whenever a later prompt adds a project or a package, so every project builds against the same IronZIP release. An agent that writes a package name from memory can install a malicious package published under a hallucinated name, an attack called slopsquatting. The package is IronZIP and the namespace is IronZip.
With the variable set and License.LicenseKey never assigned, the calibration page failed at the save call for all four archive cases. Each threw a LicensingException reading "Production License Required". IronZIP did not read IRONZIP_LICENSE_KEY on its own, so the app has to assign IronZip.License.LicenseKey itself. For IronZIP's agent documentation files and a single-prompt acceptance test, see the AI coding assistants guide.
How should the service pass documents to IronZIP?
Add, AddArchiveEntry and FromFiles all dropped the folder in a probe, even from a relative path such as 2025/letter.pdf, and extraction kept the last of two same-named entries. Adding a folder path threw UnauthorizedAccessException. The service therefore copies each document into a temporary folder under the name the rules give it and adds the copies.
The constructor failure covers every way of opening a one-entry AES-256 archive, since the stream, byte array and FromArchive overloads threw the same System.Exception. With two or more entries, the constructor listed them even with a wrong password. Extraction returned the one-entry archive intact and threw ArgumentException with "Invalid password!!" for every wrong, empty or missing password.
The compression level made no difference in IronZIP 2026.10.2. Levels 0, 1, 6 and 9 produced the same 970,545-byte archive from 1,042,684 bytes of probe documents, and each build took 119 to 121 ms.
/plan Draft the Implementation Plan for this change, and wait for review before you edit anything.
Move every IronZIP call out of the controller into Services/CaseArchiveBuilder.cs.
Register it in DI as a singleton.
Given the case folder, the ticked relative paths and the password, it returns the ZIP bytes.
The service works in this order:
1. It copies each document into a new folder under the system temp path.
2. Each copy gets the entry name from the rules.
3. It adds those copies and saves the archive beside that folder.
4. It extracts that archive with the same password into a second folder.
5. It compares the SHA-256 of every extracted file with its source.
6. On any difference, it throws with the document's entry name.
Both folders are removed in a finally block.
The controller keeps the password, selection and path checks.
Afterward, repeat the /browser pass over TestData/expected.csv and report each row.
Compare the agent's service with this one. It builds against IronZIP 2026.10.2, and it returned all four archives intact and left no temporary folder behind.
using System.Security.Cryptography;
using IronZip;
using IronZip.Enum;
public sealed class CaseArchiveBuilder
{
public byte[] Build(string caseFolder, IReadOnlyList<string> relativePaths, string password)
{
var work = Path.Combine(Path.GetTempPath(), "case-" + Guid.NewGuid().ToString("N"));
var staged = Path.Combine(work, "staged");
var check = Path.Combine(work, "check");
Directory.CreateDirectory(staged);
try
{
var sources = new Dictionary<string, string>();
foreach (var relativePath in relativePaths)
{
var entryName = relativePath.Replace('/', '_');
var source = Path.Combine(caseFolder, relativePath);
File.Copy(source, Path.Combine(staged, entryName));
sources.Add(entryName, source);
}
var zipPath = Path.Combine(work, "case.zip");
using (var archive = new IronZipArchive())
{
archive.Encrypt(password, EncryptionMethods.AES256);
foreach (var entryName in sources.Keys)
archive.Add(Path.Combine(staged, entryName));
archive.SaveAs(zipPath);
}
IronZipArchive.ExtractArchiveToDirectory(zipPath, check, password);
if (Directory.GetFiles(check).Length != sources.Count)
throw new InvalidOperationException("The archive does not hold one entry per document.");
foreach (var (entryName, source) in sources)
{
var extracted = Path.Combine(check, entryName);
if (!File.Exists(extracted) ||
!SHA256.HashData(File.ReadAllBytes(extracted)).SequenceEqual(SHA256.HashData(File.ReadAllBytes(source))))
throw new InvalidOperationException($"{entryName} did not come back from the archive intact.");
}
return File.ReadAllBytes(zipPath);
}
finally
{
Directory.Delete(work, recursive: true);
}
}
}
How do you prove each archive holds the right files?
/goal runs until its goal is met and does not stop to ask for confirmation along the way.
/goal Every archive the download page produces matches TestData/manifest.csv.
Create a console project at tools/ArchiveAudit that meets these conditions:
- It references IronZIP 2026.10.2.
- The web project's compile items exclude it.
- Its startup copies IRONZIP_LICENSE_KEY into IronZip.License.LicenseKey.
Start CaseDocs with dotnet run --launch-profile http before the first post.
Stop it once the audit passes.
For each expected.csv row with the outcome zip, the audit does this:
1. Post that case page's form with every document ticked and the row's password.
2. Store the response as audit-output/<case>.zip.
3. Extract it with IronZipArchive.ExtractArchiveToDirectory and the password.
4. Confirm that the entry names and SHA-256 values equal that case's manifest rows.
Confirm too that extraction with a wrong password throws.
Keep fixing the app and rerunning the audit until all four archives pass.
Then put the results table in the Walkthrough and in your reply.
.NET's own reader cannot do this check. System.IO.Compression listed the entry names of the calibration AES-256 archives but threw InvalidDataException, "unsupported compression method", for the content of all 15 entries, so a test built on it can count entries without ever reading a byte.
How do you apply the download rules to later tasks?
According to Antigravity's rules documentation, rules come from AGENTS.md, GEMINI.md and the .agents/rules/*.md files in every folder from the edited file up to the workspace root. Files in .agents/rules must open with YAML frontmatter naming a valid trigger, and a missing or unrecognized trigger makes Antigravity drop the rule without a message. With model_decision, only the rule's path and description enter the prompt, and the agent loads the full text when its task fits the description.
/learn turns corrections from the session into a rule or a skill. The slash commands page says it can write AGENTS.md, GEMINI.md or a file in .agents/rules, so name the rule's path in the prompt.
/learn Turn what this session established about case downloads into a rule.
Write it at .agents/rules/case-archives.md.
Start the file with YAML frontmatter that sets trigger: model_decision.
Add a description saying the rule applies to work on case downloads or IronZIP archives.
The rule covers these points:
- how entry names are formed, and why
- how the saved archive is verified
- which temporary folders each request creates, and when they are deleted
- the messages for a short password and an empty selection
- that no change to the download is finished until two checks succeed
Those two checks are the expected.csv browser pass and the tools/ArchiveAudit run.
Record only what this session verified, and list anything unproven as open.
Afterward, read the first lines of the file and confirm that the frontmatter spells the trigger model_decision, in snake case.
When should you dispatch more than one agent?
Give the automated tests to one agent and the browser evidence to a second.
Split this work between Agent A and Agent B.
Agent A creates CaseDocs.Tests, an xUnit project that references IronZIP 2026.10.2.
Keep it outside the web project's build.
The tests work like this:
- They start the app with WebApplicationFactory<Program>.
- They post the download form for every expected.csv row and assert the outcome.
- For zip rows, they extract the response with IronZIP into a temporary folder.
- They compare the names and SHA-256 values with manifest.csv.
- One test shows that a path outside the case folder gets a 400 response.
- They assert on extracted files, never on archive bytes.
Agent B starts once Agent A's suite passes:
1. Start the app with dotnet run --launch-profile http (http://localhost:5065).
2. Use /browser to download C-1002 and C-1003 from their case pages, and record each session.
3. Read each file's name and size from what the browser receives or from the response headers.
4. Stop the app.
5. Add the two recordings and one screenshot per finished page to the Walkthrough.
If there is no Walkthrough, save them under audit-output/ instead.
List them with those names and sizes in your reply.
If Agent A's suite does not pass, report the failing tests instead.
Tests that post the form through WebApplicationFactory need no browser and no open port, so they catch a broken entry name in seconds.
What did the prompts produce in a real run?
All seven prompts ran in one Antigravity CLI conversation, in order, with nobody at the keyboard, on a new .NET 9 MVC app that held the five cases, the rules and both CSV files. The figures below come from our own Release builds, headless Chrome downloads and IronZIP extraction, not from the agent's replies.
- After prompt 01 and in the final app, the code matched all six expected.csv rows in the independent browser pass and answered the edited path with a 400.
- At both points, IronZIP extraction of each download returned the manifest's names and SHA-256 values for 5 of 5, 6 of 6, 1 of 1 and 3 of 3 documents.
- At both points, extraction with a wrong password threw "Invalid password!!".
- The final archives came to 979,949, 779,422, 3,637 and 154,332 bytes, the same sizes the service above produces.
- The final download pass left no CaseDocs file in the temp folder.
- Release builds of the app, the audit tool and the test project reported 0 warnings and 0 errors.
- The xUnit suite passed 7 of 7 in 2 seconds.
- Before it wrote the read-back, prompt 01 tested IronZIP in a scratch console project and hit the one-entry failure.
- Prompt 01's controller opened the archive with the constructor only when more than one document was ticked.
- For a single document, that controller listed the entry with System.IO.Compression.
- Prompt 04 replaced that check with extraction and a SHA-256 comparison.
- The /goal audit passed on its first run with nothing to fix.
- /learn wrote a 68-line rule with the model_decision trigger.
- In prompt 03 the agent tested the missing-key warning with a nested powershell -Command whose double quotes expanded IRONZIP_LICENSE_KEY.
- The resulting error message carried the key value to the model and into Antigravity's transcript files under .gemini/antigravity-cli, while no repository file held it.
- Some steps ran differently from the page. The /browser subagent could not connect to Chrome on the test machine ("Could not find DevToolsActivePort"), so the agent drove headless Chrome with its own Playwright script for every browser pass, the screenshots and both recordings. Its reply still credited the browser subagent. Prompts 01, 04 and 06 each said they were waiting for review and then edited in the same turn. The CLI kept one plan and one Walkthrough for the whole conversation, and later prompts overwrote both, so only the replies kept the earlier tables. The /learn rule stated a cause taken from decompiling IronZIP and named System.IO.Compression as a way to extract these archives. One agent did both halves of the two-agent prompt in a single turn of the same conversation. The audit tool targets .NET 10. Every permission prompt was skipped.
The ChatGPT Codex guide splits an archive intake job between explorer and worker agents, and its run spawned four agent threads. The Claude Code guide plans an in-place update of encrypted case archives in plan mode, as the /plan prompts do here. The hub's run record gives the Antigravity version, model, prompt durations and test machine.
Troubleshooting
- Password Protect ZIP for the example this page starts from
- Extract Protected ZIP for the extraction call every check relies on
- View Archive Entries when you need entry names and sizes without extracting
- IronZIP License Keys if every download ends in a LicensingException
- IronZIP File and Archive Size Limits before serving cases past 4 GB or 65,535 documents
Questions?
If you have any questions, reach out to support@ironsoftware.com
Curtis Chau holds a Bachelor’s degree in Computer Science (Carleton University) and specializes in front-end development with expertise in Node.js, TypeScript, JavaScript, and React. Passionate about crafting intuitive and aesthetically pleasing user interfaces, Curtis enjoys working with modern frameworks and creating well-structured, visually appealing manuals.