📅 Day 161 — Hardening the iOS Companion: Untrusted Links, Redacted Errors, and the 'Full Power' Question
🔄 Topic
A full-app security pass on the iOS companion: how it opens links, downloads images, reports errors, and manages connection state — plus an honest argument with myself about how much power the phone should have.
🎯 Goal
Make the app safe to use with content it did not author, and settle the tension between “give the app full power like my laptop” and containment.
🛠 What I Did
I audited and hardened the whole app instead of adding features.
Main areas covered:
- hardened untrusted link opening: URLs arriving in agent output are not blindly trusted anymore
- added validation for image downloads instead of accepting whatever bytes arrive
- added HTTP error redaction contracts, with tests, so error surfaces cannot leak sensitive details
- hardened security and connection state handling, and fixed session churn found in the full-app audit
- added a unit-test target and fixed a bug where opt-out phrases skewed the planner
- fixed cold-start notification routing and foreground resync so the app’s picture of the backend is current
- ran an autonomous audit loop against the app and documented what it caught
- revisited my own earlier request that the app get “full power just as if I typed on the laptop” — and kept the guardrails instead
🔗 Key Cybersecurity Connections
The companion displays content produced by agents that read the public internet. That makes agent output untrusted input: a link in a summary is exactly as dangerous as a link in an email. Link handling, download validation, and error redaction are the mobile equivalents of the input-handling rules every web developer learns.
The “full power” question is the deeper one. Convenience argued for parity with the laptop; the phone is a lost-device scenario waiting to happen. The guardrails stayed.
🔍 Investigation Questions
- What happens when the app is handed a hostile URL inside agent output?
- Are downloaded images validated before display?
- What exactly appears in an error message a shoulder-surfer could read?
- What could a stolen, unlocked phone make the workstation do?
- Which capabilities does convenience want that containment forbids?
🚨 Detection Opportunities
Checks for a companion app to an agent backend:
- app opening a URL that was never shown to the user
- image fetch accepting a non-image or oversized payload
- error message containing tokens, paths, or internal hostnames
- session state churning without user action
- commands issued from the phone outside normal patterns
Example:
project=ios-companion
signal=error_surface_contains_internal_detail
risk_area=information_disclosure
triage=check_redaction_contract_tests_and_offending_code_path
🧭 MITRE ATT&CK Techniques
Possible mappings for the threats being defended against:
- T1204 — User Execution (malicious link)
- T1552 — Unsecured Credentials (leaky error surfaces)
🗺 Visual Investigation Diagram
Agent output arrives
↓
Treat as untrusted input
↓
Links → validated before opening
Images → validated before display
Errors → redacted by contract
↓
Tests enforce all three
↓
Phone stays a limited remote, not a master key
⚠ Challenges
The hardest bug was conceptual: I had asked for the app to have full power, and the honest answer was no. Arguing against my own past request — and winning — took longer than any code change.
📚 What I Learned
I learned that error messages are an attack surface. The redaction contracts with tests turned “try not to leak things” into a property the build enforces.
➡ Next Steps
- Keep the autonomous audit loop running per release
- Extend redaction contracts to new surfaces as they appear
- Threat-model the lost-phone scenario explicitly
- Document why full power was refused, so future me does not re-litigate it
🧠 Reflection
Auditing an app I built for an agent I built, and finding real issues in both, was the clearest sign yet that the attacker mindset and builder mindset belong in the same head.
🧩 Lessons Learned
What worked
Treating agent output as untrusted input everywhere the app consumes it.
What broke
Link opening, image handling, and error surfaces were all too trusting.
Why it broke
I had implicitly trusted my own agents’ output as if I had written it.
Fix / takeaway
Anything that read the internet is the internet. Validate, redact, test.
📈 Skill Progression Context
This supports my cybersecurity progression because mobile input handling, information disclosure, and privilege boundaries for remote-control apps are textbook application security — practiced on my own production code.
😄 TL;DR
Hardened the app against its own agents’ output, and told past me no about full power.
