Project 6: Secure Pocket SSH Relay
Turn the phone into a deliberately scoped SSH endpoint whose keys authorize named operations rather than an unrestricted Termux shell.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Level 2 - Intermediate |
| Time Estimate | 10-18 hours |
| Language | Shell configuration plus a Python policy wrapper (alternatives: Go, Rust) |
| Prerequisites | Projects 1, 2, and 5; SSH client basics; network bind addresses; private storage |
| Key Topics | Host identity, client keys, forced commands, SFTP limits, port forwarding, bind scope, revocation, audit |
1. Learning Objectives
By completing this project, you will:
- Explain the different jobs of SSH host keys, client/user keys, encryption, and authorization.
- Configure key-only access and verify the phone’s host identity out of band.
- Map separate keys to narrowly defined capabilities through a fixed policy gate.
- Scope listening and forwarded ports to the smallest necessary network boundary.
- Transfer a bounded import through an authorized interface without exposing a general shell.
- Test denial paths, revocation, host-key backup, interface change, and ambiguous mobile reachability.
- State honestly where application-layer SSH restrictions do not create a new Android UID or OS sandbox.
2. Theoretical Foundation
2.1 Core Concepts
SSH establishes several independent properties. During the handshake, the server presents a host key. The client compares its fingerprint with a trusted value in known_hosts; this authenticates the phone and detects unexpected identity changes. The client then proves possession of a private user key whose public half the phone has authorized. Encryption protects the transport. None of those steps answers which command that key may run. Authorization begins after authentication.
One key should represent one role. A health-monitoring key needs only a stable health operation. An import key needs one bounded ingestion path. A temporary emergency key may allow an interactive shell, but it should be exceptional, time-bounded, separately logged, and easy to revoke. Per-key options and a forced policy gate can ignore arbitrary requested commands and map an exact token to an exact operation. The wrapper treats SSH_ORIGINAL_COMMAND as hostile data; it never evaluates it as shell syntax.
Exposure is separate from authentication. Loopback accepts connections only from the phone. A specific LAN bind exposes the daemon on that interface while the address exists. An all-interface bind follows current and future interfaces and can expose the service unexpectedly when the phone changes networks. A mesh VPN can provide a stable authenticated overlay, while a reverse tunnel makes an outbound connection to create inbound reachability through a server. Each option adds a trust and recovery plane. Key authentication does not make accidental public exposure desirable.
Port forwarding is delegated reachability. A local forward lets the client reach a service from its side of the SSH connection. A remote/reverse forward asks the server side to listen and send traffic back. Agent forwarding delegates signing ability and is normally unnecessary here. Forwarding options must be granted per role because a tunnel can bypass the bind scope of the underlying loopback service.
Unrooted Termux has one app UID. OpenSSH, the policy wrapper, and PocketOps state all run as the same Android application UID. A forced command dramatically narrows the supported interface, but it does not create a kernel-enforced user boundary. Generic SFTP is particularly subtle: changing its starting directory is not the same as a chroot, and a robust chroot normally requires root-owned filesystem conditions unavailable to an unrooted Termux app. The hardened design therefore denies a general SFTP subsystem for restricted keys and uses a fixed, bounded upload operation over SSH transport. If the learner experiments with SFTP, it is an interface-level restriction, not proof that secrets are unreachable to compromised same-UID code.
Mobile reachability is conditional. Carrier NAT, changing addresses, captive networks, Wi-Fi isolation, Doze, and Android process death can make inbound SSH unavailable. Project 6 provides explicit manual start and health; Project 8 adds supervised recovery. The design must not promise that sshd is immortal or globally reachable.
Key lifecycle is operational work. Generate keys on their owning device, protect private halves, record fingerprints, label each authorization, and maintain a revocation path. Back up the phone’s host key securely if continuity matters; a regenerated host key should cause clients to stop and verify rather than delete warnings reflexively. Logs record key fingerprint/role, source, operation, outcome, and correlation ID, not command payloads containing secrets.
2.2 Why This Matters
A phone is an attractive maintenance and field-computing endpoint, but an unrestricted Termux shell carries every authority the Termux app UID has: private files, stored credentials, API bridges, network clients, and project state. This project teaches the difference between “encrypted access” and “safe remote operation.” The same design applies to deployment keys, CI agents, bastion hosts, device fleets, and support appliances.
2.3 Historical Context / Background
SSH replaced plaintext remote-login protocols by authenticating endpoints and encrypting the channel. OpenSSH later accumulated per-key restrictions, subsystems, forwarding controls, certificates, and modern key types. Those tools are powerful because they compose, but their defaults often assume a conventional server administered by an operator. Termux changes the filesystem, privilege, lifecycle, port, and reachability assumptions. The protocol remains SSH; the operational model is mobile and app-scoped.
2.4 Common Misconceptions
- “Encryption means authorization is safe.” Encryption protects the channel, not the command policy.
- “A high port is private.” It avoids privileged-port requirements; it does not reduce who can reach the listener.
- “
known_hostserrors are annoying cache problems.” They may indicate a changed or impersonated server identity. - “A forced command creates a new Unix user.” It is an application policy inside the same Termux UID.
- “Starting SFTP in an inbox confines it there.” Initial directory is not equivalent to a root-enforced chroot.
- “A reverse tunnel solves mobile reliability.” It also creates a persistent remote entry path that needs strict authorization and a kill switch.
3. Project Specification
3.1 What You Will Build
Build an SSH relay with three role profiles:
- health-only: returns one bounded PocketOps health report and cannot forward or request a shell.
- import-only: invokes a fixed upload gate that accepts one bounded artifact into the Project 2 quarantine/inbox flow; arbitrary SFTP and file reads are denied in the hardened profile.
- gui-forward: permits one explicitly named loopback destination and no arbitrary command.
An optional emergency-shell role is documented but disabled by default. A policy manifest maps public-key fingerprints to role, expiry, allowed operation, source constraints where practical, and audit label. The daemon starts manually in this project and reports current bind/listener state; persistence belongs to Project 8.
3.2 Functional Requirements
- Host verification: Clients pin a fingerprint obtained through an independent channel.
- Key-only login: Password and empty-password paths are rejected.
- Role separation: At least two keys prove distinct, non-overlapping capabilities.
- Forced policy gate: Unknown, malformed, compound, or argument-expanded commands fail closed.
- Controlled import: Accept size, name, hash, and content stream as data; write to a temporary private file; verify; then hand off to Project 2.
- Forwarding scope: Allow only the intended loopback host/port for the forwarding role.
- Listener scope: Bind only to the documented interface and unprivileged port.
- Revocation: Removing or disabling one role key does not affect others.
- Safe audit: Record fingerprint/role, source, operation, result, duration, and correlation ID without secrets.
- Mobile status: Distinguish daemon down, listener absent, interface changed, route absent, and authentication denied.
3.3 Non-Functional Requirements
- Security: No permissive fallback, password login, unverified host change, shell evaluation, or broad forwarding.
- Reliability: Manual recovery is documented; no claim is made that Android will keep the daemon alive.
- Performance: Authentication, commands, uploads, and idle sessions have explicit limits.
- Usability: Each denial explains the role and safe recovery without exposing policy internals.
- Auditability: Allowed and denied acceptance tests produce bounded correlated evidence.
3.4 Example Usage / Output
$ ssh -p 8022 -i ~/.ssh/pocketops-health phone
PocketOps Health node=field-phone
Runtime READY
Vault READY
Telemetry DEGRADED location_permission
Queue READY depth=0
Services READY worker=manual
Authorization: forced-command health-only
An attempted arbitrary command with the same key returns a stable authorization denial and nonzero status; it never launches a shell.
3.5 Real World Outcome
From a second device on the intended network, verify the phone’s host fingerprint, run the health command with the health key, upload one test artifact with the import key, and open one GUI loopback forward with the forwarding key. Then attempt an interactive shell, an unknown command, arbitrary forwarding, general SFTP browsing, a revoked key, and access from an unintended interface. The phone’s audit view shows the three permitted operations and every rejected attempt by role and correlation ID. No restricted key can request a general shell or general filesystem interface. This one allowed-and-denied matrix is the project’s observable outcome.
4. Solution Architecture
4.1 High-Level Design
remote client
key + verified known_hosts
|
v
intended interface : unprivileged SSH port
|
OpenSSH authentication
|
per-key forced policy gate <---- role manifest / revocation state
|
+----+-------------------+--------------------+
| | |
v v v
health operation bounded upload one permitted
(read-only result) -> vault quarantine loopback forward
| | |
+------------------------+--------------------+
|
safe audit log
No role boundary above creates a second Android UID.
Android may stop the entire process tree; Project 8 handles recovery.
4.2 Key Components
| Component | Responsibility | Key Decisions |
|---|---|---|
| OpenSSH server | Authenticate clients and establish channel | Key-only, narrow bind, no broad defaults |
| Host-key trust procedure | Let clients verify phone identity | Out-of-band fingerprint and protected backup |
| Per-key declarations | Attach role restrictions | One role/key; default deny |
| Policy gate | Map exact request to fixed operation | Parse tokens as data; never evaluate shell text |
| Health adapter | Return bounded status | Calls stable Project 1/PocketOps contract |
| Upload gate | Accept one artifact into quarantine | Size/hash/name limits and atomic handoff |
| Forward policy | Permit one loopback destination | No arbitrary, agent, or remote forwarding |
| Audit writer | Record safe identity and result facts | Fingerprint digest/label, not private material |
| Mobile status probe | Explain listener/reachability state | Separate process, bind, interface, and route evidence |
4.3 Data Structures
KeyRole
key_id, public_fingerprint, role, enabled, expires_at?
allowed_operation, source_policy?, forwarding_policy
RemoteRequest
correlation_id, key_id, requested_token, bounded_arguments[]
source_hint, received_at
AuthorizationDecision
allow, role, operation?, stable_reason_code
ImportDescriptor
filename_token, declared_size, sha256, media_type?
AuditRecord
time, correlation_id, key_label, role, source_hint
decision, operation, outcome, duration_ms
4.4 Algorithm Overview
Key Algorithm: Fail-Closed Remote Dispatch
- OpenSSH authenticates one enabled public key.
- The forced gate obtains the server-selected key role and requested command text.
- Reject missing, expired, oversized, compound, or unrecognized requests.
- Tokenize according to one documented grammar; never invoke a shell parser.
- Match an exact operation permitted by the role.
- Validate bounded typed arguments.
- Execute the fixed internal operation with deadline and output limit.
- Write safe audit evidence and return the operation’s stable status.
Complexity Analysis
- Time:
O(n)in bounded request length plus the explicitly bounded operation. - Space:
O(1)relative to system state for dispatch; uploads consume at most the configured artifact limit and stream to temporary storage.
5. Implementation Guide
5.1 Development Environment Setup
Install OpenSSH in Termux and use a second device with a current SSH client. Record the phone’s current interface addresses and Termux username through Project 1. Keep host keys, authorized-key policy, audit data, and temporary imports in private storage. Select a high unprivileged port such as the conventional Termux SSH port, but verify the actual configuration rather than relying on convention.
Begin with loopback or one trusted LAN. Do not configure router port forwarding, public binds, or reverse tunnels during foundation testing. Keep the existing terminal session open until a second administrative/recovery path is proven.
5.2 Project Structure
secure-pocket-ssh-relay/
|-- policy/
| |-- roles.example # conceptual role schema, no public keys
| `-- forwarding.md
|-- src/
| |-- gate/
| |-- operations/
| |-- import/
| `-- audit/
|-- ssh/
| |-- server-policy.md # required directives and rationale
| `-- key-lifecycle.md
|-- tests/
| |-- command-hostility/
| |-- network-exposure/
| `-- android-acceptance/
`-- README.md
5.3 The Core Question You Are Answering
“How can a remote operator prove the phone’s identity and perform one authorized action without receiving every capability the Termux account has?”
If your design answer is “because the connection uses SSH,” stop. SSH supplies mechanisms; your key roles, bind scope, policy gate, and tests create the authorization story.
5.4 Concepts You Must Understand First
- Server and client identity
- Why does the client trust a host fingerprint?
- Why must a client private key never be copied to the phone?
- Reference: OpenSSH
ssh,sshd, andssh-keygenmanuals.
- Per-key authorization
- Which restrictions apply before the wrapper?
- Why must the wrapper still validate every request?
- Book reference: Michael W. Lucas, “SSH Mastery,” Chapters 2-7.
- Bind and forwarding scope
- Who can reach loopback, LAN, overlay, and all-interface binds?
- How can forwarding expose a loopback service indirectly?
- Book reference: Kerrisk, Chapters 59-61.
- Same-UID limitation
- Why is a forced command not a new Android sandbox?
- Why is
-d inboxnot a filesystem jail?
5.5 Questions to Guide Your Design
- Which exact role needs each key, and what is explicitly denied?
- How is a lost laptop key revoked without replacing every key?
- How is the phone’s host key backed up and its replacement communicated?
- What listener is visible on Wi-Fi, cellular, overlay, and loopback?
- Does any key permit agent, local, remote, or dynamic forwarding accidentally?
- How are filename, size, digest, and content treated as data during import?
- What remains insecure because all Termux processes share one app UID?
5.6 Thinking Exercise
Draw Three Authority Envelopes
For health, import, and GUI-forward keys, draw the trust path from private key to public authorization, forced gate, operation, files, network destinations, logs, expiry, and revocation. Add an attacker who controls each key. Mark every resource that attacker can reach by the supported interface. Then add a compromised policy wrapper and mark the larger same-UID blast radius. This distinction prevents overclaiming isolation.
5.7 The Interview Questions They Will Ask
- “What does an SSH host key protect?”
- “How is authentication different from authorization?”
- “How do local and reverse port forwarding differ?”
- “How can
authorized_keysrestrict one key?” - “Why is binding to all interfaces risky on a phone?”
- “Why is a forced SFTP start directory not equivalent to a chroot?”
5.8 Hints in Layers
Hint 1: Prove identity before convenience
Record and verify the host fingerprint before adding aliases or automation.
Hint 2: One key, one forced role
Get health-only working with forwarding disabled before designing import.
Hint 3: Use an exact grammar
Accept a small token set such as health or import <descriptor-id>. Reject separators, substitutions, traversal, and unbounded arguments before dispatch.
Hint 4: Verify from the outside
Inspect listeners locally, then test allowed and denied access from a second device on each intended network.
5.9 Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Keys and server configuration | “SSH Mastery” | Chapters 2-7 |
| Port forwarding | “SSH Mastery” | Chapter 11 |
| Internet sockets | “The Linux Programming Interface” | Chapters 59-61 |
| Threat boundaries | “Security Engineering” | Access-control and authentication chapters |
5.10 Implementation Phases
Phase 1: Identity and Narrow Listener (3-5 hours)
Goals: Establish verified key-only SSH on one intended network.
Tasks:
- Generate/record server and role-specific client identities.
- Define required server directives and explicit denials.
- Verify listener scope and host fingerprint from a second device.
Checkpoint: The intended key authenticates; password, unknown key, wrong fingerprint, and unintended interface tests fail.
Phase 2: Role Gate and Operations (4-7 hours)
Goals: Replace shell access with exact health/import/forward capabilities.
Tasks:
- Implement conceptual role lookup and fail-closed grammar.
- Connect health and bounded import to stable PocketOps contracts.
- Constrain forwarding to one destination and add safe audit events.
Checkpoint: Hostile command corpus and cross-role attempts cannot invoke a broader operation.
Phase 3: Revocation and Mobile Acceptance (3-6 hours)
Goals: Demonstrate operational recovery and network truth.
Tasks:
- Revoke/re-enable one key and restore a protected host-key backup in a lab fixture.
- Test interface/address changes and daemon/process loss.
- Complete the allowed-and-denied outcome matrix.
Checkpoint: Every result is explainable by listener, route, identity, or policy evidence.
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale |
|---|---|---|---|
| Authentication | Password; shared key; role keys | Separate role keys | Revocable, attributable, least authority |
| Remote command | Shell evaluation; exact dispatch | Exact dispatch | Prevents metacharacters becoming syntax |
| Transfer | General SFTP; bounded forced import | Bounded forced import | Generic same-UID SFTP cannot prove path isolation |
| Bind | All interfaces; specific interface/overlay | Narrowest required | Avoids accidental exposure after network change |
| GUI reachability | Public listener; SSH local forward | Loopback plus permitted local forward | Keeps GUI off the LAN by default |
| Persistence | Shell loop; manual now, runit later | Manual now, Project 8 supervision later | Avoids false reliability claims |
| Emergency shell | Always enabled; break-glass disabled by default | Break-glass | Makes broad authority exceptional and auditable |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples |
|---|---|---|
| Identity | Verify both directions | Pinned host, wrong host, valid key, unknown/revoked key |
| Authorization | Prove role boundaries | Health key import attempt, import key shell attempt |
| Hostile input | Prevent code interpretation | Separators, substitutions, newlines, traversal, oversized arguments |
| Exposure | Verify network scope | Loopback, intended LAN/overlay, cellular, unintended Wi-Fi |
| Transfer | Prove bounded quarantine | Valid upload, size mismatch, hash mismatch, overwrite, unsafe name |
| Lifecycle | Document mobile limitations | Daemon kill, Termux process kill, interface address change |
6.2 Critical Test Cases
- Changed host key: Client stops; operator verifies rather than deleting trust blindly.
- Role crossover: Every key is denied every other role.
- Compound request: Metacharacters and extra tokens cannot invoke a shell.
- Forwarding escape: Unlisted host/port and remote/dynamic/agent forwarding fail.
- Import attack: Traversal, symlink target, oversize, digest mismatch, and duplicate name fail safely.
- Process loss: Existing session ends and status reports manual recovery, not “service healthy.”
6.3 Test Data
Keys: health-valid, import-valid, forward-valid, revoked, unknown
Commands: exact health, unknown token, extra argument, separator, newline, substitution
Imports: safe small file, ../name, symlink target, too large, wrong digest, duplicate identity
Networks: loopback, trusted LAN, untrusted LAN fixture, overlay if selected, cellular hint
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution |
|---|---|---|
Wrong known_hosts handling |
Identity warning is routinely deleted | Verify fingerprint through independent channel |
| Broad forced wrapper | Key can append arbitrary arguments | Exact grammar and default deny |
| Shell evaluation | Separators execute extra commands | Tokenize as data; call fixed operation directly |
| Accidental all-interface bind | Service appears on unexpected network | Inspect actual listener after every interface change |
| Generic SFTP claim | “Inbox-only” key can navigate elsewhere | Deny general SFTP; use bounded import or document lack of isolation |
| Broad forwarding | Key reaches arbitrary local services | Permit one destination and deny other forwarding modes |
| Persistence assumed | SSH disappears after process kill | Keep manual status now; add runit/Boot in Project 8 |
7.2 Debugging Strategies
- Diagnose in order: daemon process, listening socket, bind address, current interface, route, SSH handshake, client key, role decision, operation.
- Run the policy gate directly with hostile fixture strings before involving SSH.
- Use verbose client/server diagnostics only in a protected test mode; redact keys and environment.
- Test from a second device. A successful phone-local connection proves only loopback behavior.
7.3 Performance Traps
Long idle sessions, unbounded uploads, slow clients, reverse-tunnel reconnect loops, and verbose logs consume battery/storage. Bound authentication attempts, sessions, upload bytes, operation output, and reconnect frequency. Do not optimize by weakening cryptographic verification.
8. Extensions & Challenges
8.1 Beginner Extensions
- Add a human-readable key inventory with role, age, expiry, and last use.
- Add a one-tap widget that displays listener scope without starting broad access.
8.2 Intermediate Extensions
- Add short-lived OpenSSH user certificates from a small lab CA.
- Add time-bounded break-glass authorization with a visible Android notification.
- Use a mesh VPN and compare its identity/bind plane with LAN access.
8.3 Advanced Extensions
- Design an outbound reverse tunnel with server-side forced account, host-key pinning, narrow remote bind, backoff, and kill switch.
- Build a separately sandboxed companion service and compare its authority with same-UID Termux policy.
- Integrate Project 11 to audit listeners, key roles, forwarding, and stale trust.
9. Real-World Connections
9.1 Industry Applications
- Bastion and support access: Keys grant named operations rather than generic shells.
- Deployment keys: CI identities use forced commands and narrow repositories/actions.
- IoT field service: Mobile/edge devices expose audited maintenance under unreliable reachability.
- Secure import channels: Remote uploads enter quarantine before becoming trusted state.
9.2 Related Open Source Projects
- OpenSSH: openssh.com - Protocol implementation and authoritative manuals.
- Termux packages: GitHub - Android-compatible OpenSSH packaging.
- Tailscale: tailscale.com - Optional mesh reachability; still requires SSH authorization design.
- rsync restricted wrapper (
rrsync): OpenSSH distribution example of a forced transfer interface; study its limits rather than copying blindly.
9.3 Interview Relevance
You will be ready to discuss mutual identity, least-privilege keys, forced commands, port forwarding, server exposure, chroot prerequisites, revocation, audit, and the difference between application policy and OS isolation.
10. Resources
10.1 Essential Reading
- OpenSSH manual pages -
sshd,sshd_config,ssh_config,authorized_keys,ssh-keygen, and forwarding behavior. - “SSH Mastery” by Michael W. Lucas, Chapters 2-7 and 11 - Operational SSH design.
- “The Linux Programming Interface” by Michael Kerrisk, Chapters 59-61 - Internet socket behavior.
- RFC 4251, SSH Protocol Architecture - Security properties and protocol components.
10.2 Video Resources
- OpenSSH project or BSD conference talks on modern key management and restrictive server design.
- Android/Termux demonstrations are useful only when paired with an explicit threat model and current official docs.
10.3 Tools & Documentation
- OpenSSH manuals: openssh.com/manual.html
- Termux execution environment: GitHub wiki
- Termux app lifecycle notice: Termux app repository
- Android local network permission: developer.android.com/privacy-and-security/local-network-permission
10.4 Related Projects in This Series
- Previous - Project 5: Supplies precise network and interface diagnosis.
- Project 2: Supplies the quarantine and atomic import boundary.
- Next - Project 7: Applies the same least-authority rule to an Android automation app.
- Project 8: Adds supervised start/recovery without making SSH immortal.
11. Self-Assessment Checklist
11.1 Understanding
- I can explain host-key versus client-key authentication.
- I can separate encryption, authentication, authorization, and audit.
- I can explain every forwarding direction and its exposure.
- I can state why forced commands do not create another Android UID.
- I can explain why generic SFTP is not an inbox-only sandbox in this model.
11.2 Implementation
- Password and unknown-key access are denied.
- At least two keys have distinct, tested roles.
- The policy gate passes hostile-input tests.
- Listener and forwarding scope are proven from a second device.
- Imports enforce name, size, digest, atomicity, and quarantine.
- Revocation and host-key continuity procedures are tested.
11.3 Growth
- I documented the same-UID limitation without overstating protection.
- I can explain the design and denial matrix in an interview.
- I recorded one mobile reachability failure and the evidence that localized it.
12. Submission / Completion Criteria
Minimum Viable Completion
- Verified host identity, key-only login, one forced health role, and narrow listener scope.
- Unknown commands, passwords, and unrestricted shells fail.
- Audit evidence identifies the role and decision safely.
Full Completion
- All minimum criteria plus distinct health/import/forward roles, bounded quarantine import, exact forwarding policy, revocation/host-key recovery, external exposure tests, and the complete allowed-and-denied matrix.
Excellence (Going Above & Beyond)
- Add short-lived SSH certificates or a carefully constrained mesh/reverse reachability path.
- Produce a formal authority table showing supported-interface restrictions versus same-UID compromise limits.
- Demonstrate recovery after interface change and Termux process death without claiming Android guarantees uptime.
This guide was generated from TERMUX_ANDROID_APPS_TOOLS_MASTERY.md. For the complete learning path, see the expanded project index.