diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..c7a773d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,109 @@ +name: Bug report +description: Something in IPATool GUI does not work as expected +title: "[Bug] " +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to report this. A screenshot helps, but a screenshot + alone usually cannot be acted on: the same dialog can come from the app, from + macOS, or from the `ipatool` CLI running underneath. The fields below are what + makes a report actionable. + + - type: checkboxes + id: preflight + attributes: + label: Before you file + options: + - label: I searched existing issues and this is not a duplicate. + required: true + - label: This is not a security vulnerability (those go through the Security tab, see below). + required: true + + - type: input + id: app-version + attributes: + label: App version + description: Settings → About in the app, or the installer filename. + placeholder: "0.1.1" + validations: + required: true + + - type: dropdown + id: os + attributes: + label: Operating system + options: + - Windows + - macOS (Apple Silicon) + - macOS (Intel) + - Linux (AppImage) + - Linux (deb) + - Other / building from source + validations: + required: true + + - type: input + id: os-version + attributes: + label: OS version + description: e.g. "Windows 11 23H2", "macOS 14.5", "Ubuntu 22.04". + placeholder: "macOS 14.5" + validations: + required: true + + - type: input + id: ipatool-version + attributes: + label: ipatool engine version + description: | + Shown in Settings → Engine, or from `ipatool --version`. The engine is downloaded + separately from the app, so this is often where a problem lives. + placeholder: "2.6.0" + validations: + required: false + + - type: textarea + id: what-happened + attributes: + label: What happened + description: What you did, what you expected, what you got instead. Exact error text is more useful than a description of it. + placeholder: | + 1. Opened the app, clicked "Sign in" + 2. Typed my Apple ID and password, clicked "Sign in" + 3. A macOS "Keychain Not Found" alert appeared: A keychain cannot be found to store "account". + 4. Expected: sign-in completes and the app shows my account. + validations: + required: true + + - type: dropdown + id: reproducibility + attributes: + label: Does it happen every time? + options: + - Every time + - Only the first time after launching + - Intermittently + - Only happened once + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Logs + description: | + Activity → **Export logs** in the app, then attach the file (drag it into this box). + Credentials and tokens are redacted automatically, but give it a quick skim before posting. + render: text + validations: + required: false + + - type: textarea + id: extra + attributes: + label: Anything else + description: Proxy/VPN, a recently migrated or reset keychain, antivirus, unusual locale — anything that might be relevant. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..3a4d841 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Security vulnerability + url: https://github.com/a23bc/ipatool-gui/security + about: Please report vulnerabilities privately through the Security tab, not as a public issue. + - name: Build, test and release details + url: https://github.com/a23bc/ipatool-gui/blob/main/docs/architecture.md + about: Architecture, the security model, packaging and CI are documented here. + - name: Install / Gatekeeper / SmartScreen problems + url: https://github.com/a23bc/ipatool-gui#install + about: The README covers the "damaged" Gatekeeper warning and the SmartScreen prompt. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..3eed6e5 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,27 @@ +## What changed + + + +## Why + + + +## How it was verified + + + +## Not verified + + + +## Checklist + +- [ ] One concern only — unrelated changes are in another PR +- [ ] `npm run typecheck` passes +- [ ] `npm test` passes (coverage gate included) +- [ ] `npm run lint` passes +- [ ] New logic in `src/shared/` has tests +- [ ] No credential, token or local-only file is included +- [ ] User-visible behaviour changes are described above diff --git a/.gitignore b/.gitignore index 28600ab..fb1e133 100644 --- a/.gitignore +++ b/.gitignore @@ -25,9 +25,6 @@ coverage/ *.tsbuildinfo .vitest-cache/ -# Local assistant workspace (never part of a build) -.workbuddy/ - # Local environment .env .env.* diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..7a9b312 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,92 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and maintainers pledge to make participation in this +project a harassment-free experience for everyone, regardless of age, body size, +visible or invisible disability, ethnicity, sex characteristics, gender identity and +expression, level of experience, education, socio-economic status, nationality, +personal appearance, race, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, +inclusive, and healthy community. + +## Our Standards + +Examples of behaviour that contributes to a positive environment: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologising to those affected by our mistakes, and + learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behaviour: + +- The use of sexualised language or imagery, and sexual attention or advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, without + their explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional + setting + +## Enforcement Responsibilities + +Maintainers are responsible for clarifying and enforcing these standards and will take +appropriate and fair corrective action in response to any behaviour they deem +inappropriate, threatening, offensive, or harmful. + +Maintainers may remove, edit, or reject comments, commits, code, issues and other +contributions that are not aligned with this Code of Conduct, and will communicate the +reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all project spaces — issues, pull requests, +discussions, commit messages — and also when an individual is officially representing +the project in public spaces. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behaviour may be reported to +the maintainer privately, by contacting [@a23bc](https://github.com/a23bc) on GitHub. +Reports are reviewed promptly and handled confidentially; the reporter's identity is +kept private. Please do not use the Security tab for this — it is for vulnerabilities. + +All maintainers are obligated to respect the privacy and security of the reporter of any +incident. + +## Enforcement Guidelines + +Maintainers will follow these Community Impact Guidelines in determining the +consequences for any action they deem in violation of this Code of Conduct. + +**1. Correction.** *Community impact:* use of inappropriate language or other behaviour +deemed unprofessional or unwelcome. *Consequence:* a private, written warning, with +clarity around the nature of the violation and an explanation of why the behaviour was +inappropriate. + +**2. Warning.** *Community impact:* a violation through a single incident or series of +actions. *Consequence:* a warning with consequences for continued behaviour. No +interaction with the people involved for a specified period. Violating these terms may +lead to a temporary or permanent ban. + +**3. Temporary ban.** *Community impact:* a serious violation of community standards, +including sustained inappropriate behaviour. *Consequence:* a temporary ban from any +sort of interaction or public communication with the project for a specified period. + +**4. Permanent ban.** *Community impact:* demonstrating a pattern of violation of +community standards, including sustained inappropriate behaviour, harassment of an +individual, or aggression toward or disparagement of classes of individuals. +*Consequence:* a permanent ban from any sort of public interaction within the project. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, +available at +. + +[homepage]: https://www.contributor-covenant.org diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..67929b2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,96 @@ +# Contributing + +Thanks for helping. This is a small project, so the fastest way to get a change +merged is to keep it small and to show how you verified it. + +## Before you start + +- **Bugs vs. security.** Bugs go in the issue tracker (use the bug report form — it + asks for the app version, OS, engine version and logs). Anything that looks like a + vulnerability goes through the **Security** tab, not a public issue. +- **Open an issue first for anything larger than a typo.** A new feature, a new + dependency, or a change to the IPC surface is worth a sentence of agreement before + you write the code. +- **This repository ships no `ipatool` binary.** The app downloads it at runtime and + verifies the published SHA-256. Please do not add a bundled copy. + +## Development + +Requirements: Node 20.19+ and npm. + +```bash +npm install +npm run dev # Electron + Vite with HMR +``` + +Before opening a pull request, run the same checks CI runs: + +```bash +npm run typecheck +npm test # vitest, coverage gate enabled +npm run lint +npm run build +``` + +Packaging (rarely needed for a normal change): + +```bash +npm run dist:win # NSIS installer + portable exe +npm run dist:mac # .app (ad-hoc signed) +npm run dist:linux # AppImage + deb +``` + +## Where code goes + +The layering matters more than the file count — put logic in the lowest layer that +can hold it: + +| Path | What belongs there | +| --- | --- | +| `src/shared/` | Pure logic with no Electron or Node I/O: parsing, formatting, error classification, validation. **Fully unit-testable, and expected to be tested.** | +| `src/main/` | Electron main process: IPC handlers, queue, engine, settings, subprocess runner. | +| `src/preload/` | The frozen `window.api` bridge. Keep it a thin, explicit allow-list. | +| `src/renderer/` | React UI and zustand stores. Renderer state lives in stores, not components. | + +Two rules that come up in review: + +- **Everything crossing the IPC boundary is untrusted.** A renderer-supplied string + that becomes a path component or a URL segment must be validated in `src/main` + (see `src/shared/validate.ts`). Types do not survive the boundary. +- **User-visible behaviour changes need a sentence in the PR.** A different default, + a different error message, a new prompt — say so explicitly. + +## Tests + +- New logic in `src/shared/` needs tests. There is a per-area coverage gate, so an + untested module in the security-relevant paths will fail CI. +- `tests/` runs in the Node environment; `tests/dom/` covers renderer stores with a + stubbed `window.api`. +- A test that only proves "the code runs" is not worth much. Prefer one that fails + against the old behaviour — say in the PR what it catches. +- Signal/timing tests that cannot work on Windows should be scoped with + `describe.skipIf(process.platform === 'win32')` rather than deleted. + +## Style + +- Match the surrounding code. No reformatting of untouched lines, no "while I'm + here" refactors — they make a review impossible to follow. +- Comments explain **why**, not what. If a line looks odd, the reason it must be that + way belongs next to it. +- Commits: English, imperative mood, one logical change each (`fix(queue): ...`). +- No new runtime dependencies without discussing it first. +- Never commit credentials, tokens, or local-only files (editor config, scratch + scripts, exported logs). Keep those in `.git/info/exclude`, which is per-clone and + never pushed. + +## Pull requests + +- One concern per PR. If a change needs two independent explanations, it is two PRs. +- Describe: what was wrong, what you changed, how you verified it, and what you did + **not** verify. An honest "untested on macOS" is fine; a silent gap is not. +- CI must be green. `typecheck`, `test` and `build` all run on every PR. + +## Licence + +By contributing you agree that your work is licensed under the MIT licence, the same +as this project. diff --git a/README.md b/README.md index ebdfdf6..c02bca6 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,9 @@ More build, test, and release detail: [docs/architecture.md](docs/architecture.m | Doc | Contents | | --- | --- | | [docs/architecture.md](docs/architecture.md) | Multi-account model, pause/resume, progress parsing, security model, packaging & CI, code map | -| [Security](docs/architecture.md#安全披露) | Report vulnerabilities via Private vulnerability reporting — do not open a public issue | +| [docs/troubleshooting.md](docs/troubleshooting.md) | Engine not found, macOS keychain and Gatekeeper problems, proxy/network errors | +| [SECURITY.md](SECURITY.md) | What is in scope, and how to report a vulnerability privately — do not open a public issue | +| [CONTRIBUTING.md](CONTRIBUTING.md) | Development setup, the checks CI runs, where code belongs, what a useful test looks like | Worth knowing (details in the docs): diff --git a/README_CN.md b/README_CN.md index 763fc8a..563699c 100644 --- a/README_CN.md +++ b/README_CN.md @@ -113,7 +113,9 @@ npm run dist:linux # AppImage + deb | 文档 | 内容 | | --- | --- | | [docs/architecture.md](docs/architecture.md) | 多账户模型、暂停续传原理、进度解析、安全模型、打包与 CI、代码地图 | -| [安全披露](docs/architecture.md#安全披露) | 漏洞请走 Private vulnerability reporting,勿开公开 issue | +| [docs/troubleshooting.md](docs/troubleshooting.md) | 引擎装不上、macOS 钥匙串与 Gatekeeper 问题、代理/网络报错对照表 | +| [SECURITY.md](SECURITY.md) | 安全边界与私密报告渠道,勿开公开 issue | +| [CONTRIBUTING.md](CONTRIBUTING.md) | 开发环境、CI 检查项、代码该放哪一层、什么算有用的测试 | 几个值得知道的设计点(细节在文档里): diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..f77a990 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,70 @@ +# Security Policy + +## Supported versions + +Only the **latest release** receives security fixes. There are no long-lived +maintenance branches for older versions; if you are on an older build, upgrade +before reporting. + +## Reporting a vulnerability + +**Please do not open a public issue.** + +Use GitHub's private vulnerability reporting: + + +That channel is private between you and the maintainer and lets a fix be prepared +before the details are public. + +A useful report contains: + +- the app version (Settings → About) and the OS + version; +- what an attacker gains, and what they need to start with; +- reproduction steps, ideally a minimal one; +- any relevant lines from Activity → Export logs (credentials and tokens are + redacted, but skim it first). + +If you are unsure whether something counts as a vulnerability, report it privately +anyway — a false positive costs far less than a public disclosure of a real one. + +## What this project assumes + +Worth knowing before you spend time on a report, because these are deliberate +design decisions rather than bugs: + +- **The app ships no `ipatool` binary.** It is downloaded on first launch and + verified against the SHA-256 published by the upstream release. The checksum + sidecar is always fetched from `github.com`, even when a download mirror is + configured — a mirror that supplied both the binary and its checksum could vouch + for a tampered artifact. A missing or unparseable checksum **fails closed**: the + binary is not installed. +- **Builds are not code signed** (Windows: unsigned; macOS: ad-hoc, not notarized). + Each release publishes `SHA256SUMS.txt`; that manifest is the provenance anchor. + "The binary is unsigned" is therefore a known limitation, not a vulnerability. + A way to make a *verified* download install something else **is** in scope. +- **Credentials are never written in plaintext.** They go to the OS keychain, or + through Electron's `safeStorage`; when neither is available the app refuses + rather than falling back to a plaintext file. +- **The renderer is treated as untrusted.** `sandbox` and `contextIsolation` are on, + the preload exposes a fixed API, every IPC input that becomes a path or a URL is + validated in the main process, and `fs:open-path` is restricted by an allow-list. + +## Out of scope + +- **Bugs in `ipatool` itself.** That is a separate project — report them at + . If this app passes a value to it in a way that + makes a bug exploitable, that part *is* in scope here. +- **Apple ID consequences.** Account flags, rate limits or terms-of-service actions + resulting from automated App Store access are not something a code change can fix. +- **Anything that requires existing code execution as your user**, administrator + rights, or physical access — including patching the app's own files. At that point + the attacker already has what they wanted. +- **Gatekeeper / SmartScreen warnings** about the unsigned build. These are expected + and documented in the README; see [docs/troubleshooting.md](docs/troubleshooting.md). + +## What to expect + +This is a small project maintained in spare time. There is **no bug bounty**. Reports +are acknowledged as soon as possible, and you will be told whether the issue is +accepted, needs more information, or is out of scope. Accepted issues are fixed in a +normal release; credit is given in the release notes unless you ask otherwise. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..ad38881 --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,123 @@ +# Troubleshooting + +Start here for the four problems that come up most. If none of these match, open an +issue with the bug report form — it asks for the details this page keeps asking for. + +**Where the logs are:** Activity → **Export logs**. Credentials and tokens are +redacted automatically, but skim the file before sharing it. + +--- + +## The app cannot find or install the `ipatool` engine + +Settings → Engine shows what was detected and why it was rejected. + +| What you see | What it means | +| --- | --- | +| `ipatool was not found` | Not on `PATH`, not in the managed folder, and no path configured. Use **Install** in Settings → Engine, or point *ipatool path* at a real `ipatool` binary. | +| `not-executable` | A path is configured but it is not an `ipatool` binary. The basename must be exactly `ipatool` (`ipatool.exe` on Windows). A directory is also accepted — the app looks for `/ipatool` inside it. | +| `checksum-mismatch` | **This is the app protecting you.** The downloaded archive did not match the SHA-256 published by the upstream release, or the checksum sidecar was missing, unparseable, or described a different file. Nothing was installed. Retry; if it persists, download the release manually from and point *ipatool path* at it. | +| Download stalls behind a mirror | A GitHub mirror only accelerates the **asset** download. The checksum is still fetched from `github.com`, so a network that blocks `github.com` entirely will fail verification even with a mirror configured. | + +--- + +## macOS: "Keychain Not Found" while signing in + +You see a system alert titled **Keychain Not Found**: + +> A keychain cannot be found to store "account". + +The sign-in sheet behind it is the app's, but **the alert is macOS's own**, raised on +behalf of the `ipatool` command-line tool (the keychain icon in the alert carries a +small `exec` badge). `ipatool` stores the Apple ID credential in your login keychain; +if macOS cannot find a default keychain, the credential cannot be saved and sign-in +never completes. + +Check what the system thinks it has: + +```bash +security list-keychains +security default-keychain +``` + +- **If no default is set**, point it back at the login keychain: + + ```bash + security default-keychain -s ~/Library/Keychains/login.keychain-db + ``` + +- **If the "login" keychain is missing or damaged**, create one in Keychain Access + (File → New Keychain, name it `login`), unlock it, and set it as default with the + command above. + +- **Do not press "Reset To Defaults"** in that alert unless you mean it: on some macOS + versions it creates a new, empty keychain, which can make existing saved passwords + harder to reach. "Cancel" changes nothing. + +Migrating from a backup, or resetting the keychain recently, is a common cause. + +--- + +## macOS: "IPATool GUI is damaged and can't be opened" + +The download is fine — it is not notarized, and Gatekeeper reports the quarantine flag +as damage. After moving the app to `/Applications`, run once: + +```bash +xattr -dr com.apple.quarantine "/Applications/IPATool GUI.app" +``` + +Notes that catch people out: + +- `-r` is required: files inside the bundle are quarantined too. +- On macOS 15+, right-click → Open no longer bypasses Gatekeeper. The command above is + the simplest path; otherwise use System Settings → Privacy & Security → "Open Anyway". +- Extract with **Finder** or `tar -xzf`. Some third-party extractors drop symlinks or + the executable bit, which breaks the bundle for real. + +--- + +## Windows: SmartScreen blocks the installer + +The build is unsigned, so SmartScreen warns about an unknown publisher. Choose +**More info → Run anyway**. If your organisation blocks unsigned binaries by policy, +there is no per-user workaround — use the portable build or build from source. + +--- + +## Linux: AppImage will not start + +```bash +chmod +x IPATool-GUI-*.AppImage +./IPATool-GUI-*.AppImage +``` + +If it exits with a FUSE error, install `libfuse2` (Ubuntu 22.04+: +`sudo apt install libfuse2`). Alternatively use the `.deb`, which needs no FUSE. + +--- + +## Sign-in or download fails against Apple's endpoints + +Some networks cannot reach Apple's purchase/download hosts +(e.g. `p47-buy.itunes.apple.com`). The app classifies the failure for you: + +| Error | Meaning | What to do | +| --- | --- | --- | +| `dns` / `tls` / `network` | Could not reach Apple | Configure a system proxy or VPN, then retry. | +| `timeout` | Apple did not answer in time | Retry; first sign-in is often slow. | +| `rate-limited` | Too many requests | Wait; lowering Settings → Downloads → concurrency helps. | +| `app-not-found` | The App Store has no such app **for your storefront** | Check the country in Settings, and that the app exists in that storefront. | +| `version-not-found` | That version is not available for your account | Pick another version, or download the current one. | +| `resume-failed` | The partial file no longer matches the remote file | Retry — the app restarts the download from scratch. | +| `platform-mismatch` | The app does not support the selected platform | Switch the platform filter (iPhone / iPad / Mac). | +| `proxy` | The proxy refused the request or could not open a CONNECT tunnel | Check the proxy rules and credentials, or try without the proxy. | +| `disk` | No space left on the destination volume | Free space, or change the download directory in Settings. | + +--- + +## Reporting something not covered here + +Open an issue with the **bug report** form. It asks for the app version, OS, engine +version, reproducibility and logs — those five fields are what makes it possible to +act on a report instead of guessing.