Skip to content

Differentiate between ipa and other zip file types in ExtractInstallerMetadata#48802

Merged
jkatz01 merged 4 commits into
mainfrom
48102-error-ipa-when-msix
Jul 6, 2026
Merged

Differentiate between ipa and other zip file types in ExtractInstallerMetadata#48802
jkatz01 merged 4 commits into
mainfrom
48102-error-ipa-when-msix

Conversation

@jkatz01

@jkatz01 jkatz01 commented Jul 6, 2026

Copy link
Copy Markdown
Member

Related issue: Resolves #48102

Changes:

  • Renames ExtractIPAMetadata to ExtractZIPMetadata because the magic bytes for zip based installers (.ipa, .msix, .zip, etc) are the same so any zip file reaches it. If the zip does not contain an Info.plist file it will now fail with ErrInvalidType.
  • Did NOT make typeFromBytes return "zip" instead of "ipa" because meta.Extension is set from that which has downstream effects.
  • Added test files
    The actual error message is still just "invalid file type".

Checklist for submitter

If some of the following don't apply, delete the relevant line.

  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.
    See Changes files for more information.

  • Input data is properly validated, SELECT * is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters.

  • Timeouts are implemented and retries are limited to avoid infinite loops

  • If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes

Testing

  • Added/updated automated tests

  • Where appropriate, automated tests simulate multiple hosts and test for host isolation (updates to one hosts's records do not affect another)

  • QA'd all new/changed functionality manually

    • Tested adding a valid .ipa, a macos FMA that uses a .zip file (alt-tab/darwin), and a windows FMA that uses a .zip file (vnc-server/windows).
    • Tested an msix file (renamed or not) cannot be uploaded or edited for an existing msi installer
    • Also tested the same things via GitOps

Summary by CodeRabbit

  • Bug Fixes
    • Improved installer type detection so ZIP-based packages are less likely to be misidentified.
    • Fixed an error message that incorrectly referred to the wrong file type when detection fails.
    • MSIX packages are now reported more accurately when they don’t match IPA parsing rules.
  • Refactor
    • Cleaned up installer metadata handling for ZIP-based archives.

@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 68.04%. Comparing base (bd88e72) to head (66f66b6).
⚠️ Report is 18 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #48802      +/-   ##
==========================================
+ Coverage   68.02%   68.04%   +0.02%     
==========================================
  Files        3681     3684       +3     
  Lines      233961   234061     +100     
  Branches    12416    12416              
==========================================
+ Hits       159146   159263     +117     
+ Misses      60498    60482      -16     
+ Partials    14317    14316       -1     
Flag Coverage Δ
backend 69.70% <100.00%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jkatz01 jkatz01 marked this pull request as ready for review July 6, 2026 20:30
@jkatz01 jkatz01 requested a review from a team as a code owner July 6, 2026 20:30

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This change addresses misdetection of MSIX files as IPA files. ExtractIPAMetadata is renamed to ExtractZIPMetadata and now validates zip contents, requiring an Info.plist and the LSRequiresIPhoneOS key before classifying an archive as an IPA, returning ErrInvalidType otherwise. Callers in pkg/file/file.go are updated accordingly, magic-bytes detection comments are clarified, an outdated comment is removed from software_installers.go, a new test file validates the updated extraction logic, and a changelog entry corrects the related error message.

Changes

File(s) Change Summary
pkg/file/ipa.go Renamed ExtractIPAMetadata to ExtractZIPMetadata; added hasInfoPlist/isIPA tracking and validation returning ErrInvalidType when Info.plist is missing or LSRequiresIPhoneOS is absent
pkg/file/file.go Updated "ipa" case to call ExtractZIPMetadata; clarified zip magic-bytes detection comment
pkg/file/ipa_test.go New test file with helpers and TestExtractZIPMetadata covering valid/invalid IPA scenarios
ee/server/service/software_installers.go Removed outdated comment about IPA detection via shared magic bytes
changes/48102-fix-msix-detecting-as-ipa Corrected error message text

Sequence Diagram(s)

sequenceDiagram
  participant Caller as ExtractInstallerMetadata
  participant Extractor as ExtractZIPMetadata
  participant Zip as ZIP Entries

  Caller->>Extractor: ExtractZIPMetadata(tfr)
  Extractor->>Zip: scan entries
  Zip-->>Extractor: Info.plist found?
  Extractor->>Extractor: set hasInfoPlist
  Extractor->>Extractor: check LSRequiresIPhoneOS, set isIPA
  alt missing Info.plist or not IPA
    Extractor-->>Caller: ErrInvalidType
  else valid IPA
    Extractor-->>Caller: InstallerMetadata
  end
Loading

Related issues: #48102 (Error on PATCH /api/latest/fleet/software/titles/:id/package due to MSIX misdetected as IPA)

Suggested labels: bug, go, low-risk

Suggested reviewers: none identified from provided context

🐰 A zip disguised, an MSIX in vain,
Claimed IPA's crown, but the check said "explain!"
Now plists are checked, and flags must be true,
Before Fleet declares "an iOS app, that's you!"
Hop on, dear code, validated anew.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: distinguishing IPA handling from other zip-based installer types.
Description check ✅ Passed The PR description includes the related issue, change summary, checklist items, and testing notes, so it is mostly complete.
Linked Issues check ✅ Passed The code now rejects zip-based installers without an Info.plist with ErrInvalidType, addressing the misleading package-type failure.
Out of Scope Changes check ✅ Passed The changes stay focused on installer type detection, tests, and the required changes file; no unrelated scope is evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 48102-error-ipa-when-msix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
pkg/file/ipa_test.go (1)

91-103: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Latch test doesn't assert the returned bundle identifier.

This case only checks that detection isn't fooled by the framework's plist, but doesn't verify meta.BundleIdentifier. Given the app's Info.plist is processed before the framework's (which also sets a different CFBundleIdentifier), asserting the value here would catch the identity-field-clobbering issue flagged in pkg/file/ipa.go.

✅ Suggested strengthening
 	meta, err = file.ExtractZIPMetadata(latchTfr)
 	require.NoError(t, err)
 	require.NotNil(t, meta)
+	require.Equal(t, "com.example.ios", meta.BundleIdentifier)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/file/ipa_test.go` around lines 91 - 103, The latch test in
ExtractZIPMetadata should also verify the returned BundleIdentifier, not just
that ipa detection stays enabled. Update the test case in ipa_test.go that
builds the ZIP with the app Info.plist followed by the framework plist to assert
meta.BundleIdentifier still matches the app’s CFBundleIdentifier, so the
framework plist cannot clobber the identity fields handled by
file.ExtractZIPMetadata and the IPA parsing logic in pkg/file/ipa.go.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/file/ipa.go`:
- Around line 29-38: The IPA metadata selection in the plist parsing logic is
still using the last Info.plist seen, which can let nested framework or
extension plists overwrite the app’s real BundleID/Name/Version. Update the
parsing in the IPA handling code around the Info.plist scan so that when the
plist with LSRequiresIPhoneOS is found, the identity fields are latched from
that plist and preserved, or restrict the read to the root
Payload/*.app/Info.plist only. Use the existing plistData, hasInfoPlist, and
isIPA flow to ensure the app’s metadata cannot be replaced by later nested
plists.

---

Nitpick comments:
In `@pkg/file/ipa_test.go`:
- Around line 91-103: The latch test in ExtractZIPMetadata should also verify
the returned BundleIdentifier, not just that ipa detection stays enabled. Update
the test case in ipa_test.go that builds the ZIP with the app Info.plist
followed by the framework plist to assert meta.BundleIdentifier still matches
the app’s CFBundleIdentifier, so the framework plist cannot clobber the identity
fields handled by file.ExtractZIPMetadata and the IPA parsing logic in
pkg/file/ipa.go.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 997e6006-dc9b-4766-b5ad-8e77acbe6e75

📥 Commits

Reviewing files that changed from the base of the PR and between 3e4cf63 and 66f66b6.

⛔ Files ignored due to path filters (1)
  • pkg/file/testdata/software-installers/README.md is excluded by !**/*.md
📒 Files selected for processing (7)
  • changes/48102-fix-msix-detecting-as-ipa
  • ee/server/service/software_installers.go
  • pkg/file/file.go
  • pkg/file/ipa.go
  • pkg/file/ipa_test.go
  • pkg/file/testdata/software-installers/ipa_test.ipa
  • pkg/file/testdata/software-installers/msix_test.msix
💤 Files with no reviewable changes (1)
  • ee/server/service/software_installers.go

Comment thread pkg/file/ipa.go
Comment on lines +29 to +38
BundleID string `plist:"CFBundleIdentifier"`
Name string `plist:"CFBundleName"`
Version string `plist:"CFBundleShortVersionString"`
RequiresIPhoneOS bool `plist:"LSRequiresIPhoneOS"`
}
var hasInfoPlist, isIPA bool

for _, f := range r.File {
// Matches any Info.plist and the last wins, so a nested framework or
// extension plist can override the app's own plist.

@coderabbitai coderabbitai Bot Jul 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant file and test names, then read only the needed slices.
git ls-files | rg '(^|/)pkg/file/ipa\.go$|(^|/)pkg/file/ipa_.*\.go$|(^|/)pkg/file/.*_test\.go$' || true

echo '--- pkg/file/ipa.go (lines around the loop and return) ---'
nl -ba pkg/file/ipa.go | sed -n '1,220p'

echo '--- tests mentioning IPA / Info.plist / RequiresIPhoneOS ---'
rg -n 'RequiresIPhoneOS|Info\.plist|BundleIdentifier|ExtractZIPMetadata|latch|framework' pkg/file -g '*_test.go'

Repository: fleetdm/fleet

Length of output: 476


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- pkg/file/ipa.go ---'
sed -n '1,220p' pkg/file/ipa.go | cat -n

echo '--- pkg/file/ipa_test.go matches ---'
rg -n 'RequiresIPhoneOS|Info\.plist|BundleIdentifier|ExtractZIPMetadata|latch|framework' pkg/file/ipa_test.go

echo '--- pkg/file/ipa_test.go relevant ranges ---'
sed -n '1,260p' pkg/file/ipa_test.go | cat -n

Repository: fleetdm/fleet

Length of output: 8107


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the implementation around the plist parsing loop and return path.
sed -n '1,220p' pkg/file/ipa.go | cat -n

# Read the focused test cases.
sed -n '1,260p' pkg/file/ipa_test.go | cat -n

Repository: fleetdm/fleet

Length of output: 6887


Latch the bundle metadata when the IPA plist is found.

isIPA is latched, but plistData.BundleID/Name/Version still come from the last Info.plist seen, so a nested framework or extension plist can overwrite the app’s real metadata. Snapshot the identity fields from the plist that sets LSRequiresIPhoneOS, or only read the root Payload/*.app/Info.plist.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/file/ipa.go` around lines 29 - 38, The IPA metadata selection in the
plist parsing logic is still using the last Info.plist seen, which can let
nested framework or extension plists overwrite the app’s real
BundleID/Name/Version. Update the parsing in the IPA handling code around the
Info.plist scan so that when the plist with LSRequiresIPhoneOS is found, the
identity fields are latched from that plist and preserved, or restrict the read
to the root Payload/*.app/Info.plist only. Use the existing plistData,
hasInfoPlist, and isIPA flow to ensure the app’s metadata cannot be replaced by
later nested plists.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to leave this out of the scope of this PR, since it was already getting kind of big for what the fix is.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jkatz01, that makes sense — keeping the fix focused is a good call. Would you like me to open a follow-up issue to track the nested Info.plist metadata latching concern so it doesn't get lost? Just let me know and I'll create it, referencing this PR and comment.


🧠 Learnings used
Learnt from: iansltx
Repo: fleetdm/fleet PR: 42393
File: server/service/integration_enterprise_test.go:23209-23209
Timestamp: 2026-04-02T21:46:15.560Z
Learning: This codebase targets Go 1.26. In Go 1.26, the built-in `new` accepts an expression operand (e.g., `new(true)` is valid and evaluates to a `*bool` initialized to `true`). Do not flag usages of `new(<value>)` as syntax errors in this repository; only treat `new` as invalid when it’s not conforming to the Go 1.26 expression form.

Comment thread pkg/file/ipa.go
Version string `plist:"CFBundleShortVersionString"`
RequiresIPhoneOS bool `plist:"LSRequiresIPhoneOS"`
}
var hasInfoPlist, isIPA bool

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isIPA isn't necessary for the fix but I thought it would be useful there in case we decide to support .zip for macOS apps in the future and will need to differentiate between those and .ipa.

@jkatz01 jkatz01 merged commit 2c383d7 into main Jul 6, 2026
63 of 65 checks passed
@jkatz01 jkatz01 deleted the 48102-error-ipa-when-msix branch July 6, 2026 22:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error on PATCH /api/latest/fleet/software/titles/:id/package

2 participants