Hardening run · sample audit
What the agent did to this build.
You hand Fortifie a build. It reads what the build gives away, plans a protection path from what it found, applies it, and reports what held. This is that run, laid out step by step on fixtures we own.
Scanned build
VulnDemoowned fixture · 1.2.3
Profile
Standardheavy layers opt-in
Findings
172 critical · 10 high · 3 med · 2 low
Protections applied
8+ 5 recommended to you
Verify
10 / 10checks held
Secrets recoverable
1 → 0static lift, before → after
The run, end to end
5 stagesEvery stage is automated and gated. The agent only touches builds you own or are authorized to modify, and never hands back the internals of a protection.
- 1Authorize
The run refuses to start without an authorization record for the build. Owned sources carry an OWNED marker; third-party code is never rewritten.
- 2Scan
Reads the build with the same public tools an attacker uses. The app is never launched. On the website and the hosted MCP, the build is uploaded to Fortifie and deleted after the run. Output: the findings below.
- 3Plan
Maps each finding to the protections that actually address it, and decides what to leave opt-in given the profile and launch-cost budget.
- 4Protect
Applies the curated path: symbol strip, string and secret encryption, control-flow flattening, integrity sealing, runtime anti-debug, packing.
- 5Verify
Re-reads the protected build and reports which protections held, with measured evidence. Checks that fail are surfaced, not hidden.
Stage 2 · what the scan found
Newest severity first. Open a finding to read what it means and what the agent recommends. Every row is a real finding from the VulnDemo fixture scan.
| Severity | Class | Finding |
|---|---|---|
| Critical | entitlement_weakness | Shipped with get-task-allow / library-validation disabledDebug entitlements left on in a production build widen the attack surface for attaching and injecting. Recommend Ship with Hardened Runtime and without |
| Critical | leaked_secrets | Secrets / keys embedded in the shipped bundleStatic scan recovered credential-shaped strings from the bundle. Embedded secrets turn a licence bypass into account or supply-chain compromise. Recommend Remove secrets from the client and rotate anything exposed; move privileged calls server-side. The protect engine binds any residual to the runtime environment so a static lift recovers nothing usable. |
| High | ats_exceptions | App Transport Security disabledBlanket ATS exceptions make a local HTTPS proxy MITM trivial against the licence traffic. Recommend Remove blanket ATS exceptions; pin licence endpoints; pair with a signed licence payload so a proxy that sees traffic still cannot forge a grant. |
| High | electron_asar_license | Electron .asar contains Pro-gate logic in plain JSThe unlock decision lives in renderer/main JavaScript inside the asar, readable without a patch. Recommend Move the licence decision server-side; do not keep unlock booleans in client JS. Protect any residual check with JS hardening and remove secrets from the asar before ship. |
| High | local_state_license | Unlock state is a local key, editable without a patchUserDefaults / flat-file keys name licence or trial state and can be flipped on disk. Recommend Never let on-disk state alone grant Pro. Move the decision behind a signed server lease; if you cache, HMAC/sign it and bind it to device + grant. The signed-grant cache rejects unsigned or edited state. |
| High | local_store_sqlite_msgpack | Licence state in an unprotected SQLite / msgpack storeAuthoritative licence state kept in an editable local store. Recommend Do not keep authoritative state in editable SQLite / MessagePack. If a local cache is required, sign it and bind it to a server grant; prefer the Keychain or the signed activation cache. |
| High | sparkle_update_feed | Sparkle update feed without EdDSA signaturesAn unsigned update channel is an update-channel hijack risk. Recommend Require Sparkle EdDSA ( |
| High | supabase_rls | Supabase backend referenced, row-level-security exposure riskRow-level security that is too loose lets an authenticated user write licence or release rows. Recommend Audit RLS so no authenticated user can write entitlement or release rows; move the licence decision to a server function; treat the anon key as public and never authoritative. |
| High | trusted_license_json | Success-flag licence check next to a validate URLThe classic MITM-unlock pattern: trust a boolean instead of a server signature. Recommend Verify a server signature over the payload, not just a flag. The signed one-shot lease binds the unlock to a server-held secret a proxy cannot forge, and the useful feature becomes a keyed computation. |
| High | unsigned_license_cache | Unsigned licence cache already grants Pro (app.asar!/license.json)A dropped-in cache with an unsigned Recommend Sign cached state (Ed25519 or HMAC over canonical fields) and refuse to load an unsigned or edited cache. The activation cache already does this. |
| High | unsigned_license_cache | Unsigned licence cache already grants Pro (config.json)A second unsigned cache path carries the same unlock state. Recommend Sign cached state and refuse to load an unsigned or edited cache. The activation cache already does this. |
| High | unsigned_license_cache | Unsigned licence cache already grants Pro (license.json)A third unsigned cache path carries the same unlock state. Recommend Sign cached state and refuse to load an unsigned or edited cache. The activation cache already does this. |
| Medium | keychain_as_switch | Keychain item existence used as a Pro switchA Keychain API sits next to Pro flags; the presence of an item is treated as proof. Recommend Store a signed lease, not a bare flag, and verify its signature plus device/server binding on every launch, so a forged item fails closed. |
| Medium | leaked_prompt | System prompt recoverable from the bundlePrompt / instruction text is lifted straight out of the shipped bundle. Recommend Treat prompts as IP: fetch them server-side behind the licence, or bind them to the runtime environment with the protect engine. |
| Medium | raw_license_symbols | Licence-check symbols sit in plaintextNamed licence-check symbols give an attacker an obvious target. Recommend Spread and indirect the decision, strip and encrypt these symbols, and bind the real unlock to a keyed server computation. String protection removes the obvious target. |
| Low | provenance_build_path | Build-machine paths leak in the binaryA developer username in a debug path is a provenance tell. Recommend Strip debug paths ( |
| Low | provenance_rebadge | Upstream vendor strings presentLeftover third-party identifiers are a rebadge / provenance tell. Recommend Audit leftover strings and remove upstream identifiers you did not intend to ship. |
17 findings · real output of the VulnDemo fixture scan
Stage 4 · what it applied
8 protectionsThe curated path for this build. Each protection is tagged by the plan it ships in; heavy layers stay opt-in because of their launch cost.
- ChaCha20 packer + loader stubDefault
Payload encrypted at rest; a static read sees a high-entropy blob. Addresses
leaked_secrets,raw_license_symbols,leaked_prompt. - Symbol strip & renameDefault
Removes named licence symbols and build-path tells. Addresses
raw_license_symbols,provenance_build_path. - String & secret encryptionDefault
No plaintext secrets or loader strings in the shipped file. Addresses
leaked_secrets,leaked_prompt. - Control-flow flatteningDefault
Spreads and indirects the licence decision so there is no single branch to patch. Addresses
raw_license_symbols. - Integrity self-check (SHA-256 tag)Default
Detects patching of the encrypted payload. A modified copy fails closed.
- Runtime anti-debugDefault
PT_DENY_ATTACH plus a sysctl P_TRACED check in the stub. Raises the cost of attaching a debugger.
- Environment binding (env-fold)Default
Binds residual secrets to the runtime environment so a static lift recovers nothing usable. Addresses
leaked_secrets. - Signed activation grant + software attestationStudio+
A signed server lease and a code-signature / CDHash seal. Rejects unsigned caches and catches re-signed clones. Addresses
unsigned_license_cache,local_state_license,trusted_license_json,keychain_as_switch,provenance_rebadge.
Stage 5 · what held
measuredVerify re-reads the protected build. Measured on an owned native fixture (the licensed_tool build): stock verify failed because a lab secret was in plaintext; the packed build passed every check.
| Check | Result |
|---|---|
| Lab secret absent | present → none |
| ChaCha20 packer applied | loader + unpacker stub |
| Payload entropy | 7.9965 / 8.0 bits |
| Original symbols absent | stub symbols only |
| Loader strings encrypted | no plaintext |
| Runtime anti-debug present | PT_DENY_ATTACH + P_TRACED |
| Integrity surface present | SHA-256 tag + self-check |
| Attestation seal | MATCH |
| Re-signed clone | MISMATCH (cdhash, bundle id) |
Launch cost is honest: the packed fixture takes about 95 ms to launch versus about 5 ms unprotected, a one-time startup cost, not a per-call tax. That is why heavy layers are opt-in.
What it left to you, and what it did not claim
Judgment, stated plainly. Some protections are deliberately off, and some findings can only be fixed in your code or on your server.
Left opt-in or out, on purpose
- Heavy and dual-use layers stay off by default because of their launch cost; you turn them on per build
- The SoftPOS profile forces those layers off entirely
- The sharpest custom techniques are Business-only and are never detailed in a scan response or a run audit
Recommended to you (not a packer fix)
- Move the licence decision server-side and rotate any leaked secret
- Sign the Sparkle feed (EdDSA) and audit Supabase row-level security
- Remove blanket ATS exceptions; ship Hardened Runtime without debug entitlements
This raises attacker cost and time. It does not make reverse engineering impossible: a valid credential still runs the real path, and a determined attacker with enough budget can still recover these layers.
How this looks on a real build
On a live engagement the agent runs this against your own build, and the findings, the applied path and the verify results are yours, not a fixture like this one. After you ship, the same account surfaces crack canaries and threat events on your private dashboard, and the run data recommends stronger protect settings on your next signed release.