Skip to content
Appler-DPublic

About

Personal end-to-end encrypted phone-to-PC task channel. Source-available, noncommercial; not for after-hours work assignment.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

PhoneJob

手机 → 自己电脑的端到端加密任务通道。核心通道已实现并通过单元/浏览器测试;未经独立安全审计,不要直接部署到公网。

PhoneJob 让手机在公网把「任务 + 文件」加密发给自己的电脑;电脑端用动态密码子和屏幕挑战码人工确认后,任务才落入本地收件箱;DSH 插件读取执行,结果加密回传。它不是远程桌面,也不是给团队用的任务分发系统。

它只服务一个人、自己的设备。禁止用于任何形式的变相加班。

状态

  • 实现方案:docs/implementation-plan.md
  • 安全评审:docs/security-review.md(6 个 P0;密码子归属已定)
  • 部署检查表:docs/deployment.md
  • 实现:核心通道(电脑端 K_otp / DPAPI / TOTP / P-256 配对 / 配对服务 / HTTP 与本地确认页 / PC 配对首页与 QR / SQLite 设备与 job 状态 / PWA 配对、TOTP、发送任务与附件、挑战响应与结果展示、结果文件下载 / 服务端 job 校验、分片上传、pending 落盘、挑战码与电脑审批、结果加密回传、结果文件校验与加密下载、确认/上传/执行超时 / DSH 插件认领、会话唤醒、结果与文件回写,隔离 DSH 已验,桌面真实模型回复待验证);未经独立安全审计,不应直接部署到公网
  • 密码子归属:电脑首次安装时生成 K_otp,手机只加载 TOTP 公式并计算,不生成密码子

开发(P0 起步)

  • bridge/key-store.js:电脑生成 32B K_otp,DPAPI(用户级)保护后写入 keys.bin
  • bridge/totp.js:RFC 4226 / RFC 6238 HOTP/TOTP,支持 ±1 步验证
  • bridge/crypto.js:P-256 ECDH、HKDF、SAS、AES-GCM 信封、K_otp 加密下发
  • bridge/pairing.js:start → claim(SAS 证明)→ 本地确认 SAS → K_otp 下发 → 设备登记/吊销;吊销会轮换 K_otp,剩余设备通过签名刷新接口取新码;配对/刷新/轮换写审计
  • bridge/device-store.js:SQLite 设备元数据 + 每设备 kEnc/kConfirm/kAuth 的 DPAPI blob + Web Push 订阅;内存 store 供测试
  • bridge/vapid-store.js:DPAPI 保护 VAPID P-256 公私钥(vapid.bin)
  • bridge/push.js / bridge/push-service.js:RFC 8291 aes128gcm 负载加密、VAPID JWT 签名、推送投递与 404/410 订阅清理
  • bridge/job-service.js:job 信封校验(设备/时间窗/重放/TOTP/AAD/工作区)、风险分级、每设备每小时任务上限、活跃任务/收件箱字节上限、OTP/挑战码失败锁定、pending/<job_id>/job.json 原子落盘、分片上传与文件 SHA-256/大小复核、zip 内部条目扫描、挑战码/job_hash、手机签名状态查询(状态响应用 K_enc 加密并绑定请求 ts)、审批/拒绝并移动目录、观察 working/ 标记 RUNNING、扫描 done/ 结果与结果文件并按设备加密、确认/上传/执行超时、结果与收件箱保留清理
  • bridge/zip-scan.js:zip 内部路径、类型、加密、嵌套深度、条目数与解压大小检查;.docx/.xlsx/.pptx 容器额外拒绝宏工程、嵌入对象、ActiveX、externalLinks 目录与非超链接外部关系(.rels);结果文件同样扫描
  • bridge/confirm.js:HMAC-SHA256 + Base32 的 8 位确认码(绑定 job_hash 与挑战码)
  • bridge/result.js:结果与结果文件元数据校验、大小/类型限制、密钥/私钥模式扫描;K_otp 泄露阻断
  • bridge/crypto.js:新增文件分片 AES-GCM 的 sealFileChunk / openFileChunk
  • bridge/job-store.js:SQLite jobs + seen_messages(seq/nonce 防重放,重启保留;challenge/job_hash/decided_at)
  • bridge/audit.js:配对/任务事件写 audit.jsonl;字段白名单(job/pairing/device/state/risk/intent/文件数/字节/错误),不记录正文、路径、挑战码、job_hash、SAS、公钥或密钥;按 5MB 轮转、保留 4 份备份、启动时清理 90 天前条目
  • bridge/backup.js / tools/backup.mjs / tools/restore.mjs:SQLite VACUUM INTO 快照 + 密钥/审计/可选收件箱;manifest SHA-256 校验;恢复前需停 bridge
  • tools/install-autostart.ps1 / tools/remove-autostart.ps1:为当前 Windows 用户注册/移除登录时启动 bridge 的计划任务
  • bridge/servers.js:API 端口放 health/claim/provision/device/otp/device/push/job、带设备签名的 GET /api/job/:id|result|result/file/:idx 与 PUT /api/job/:id/file/:idx;health 返回 VAPID 公钥;本地端口放 start/status/confirm/cancel/devices/jobs 与 approve/reject,带 Host/Origin/JSON/admin/pair token 校验;生成 PWA 配对二维码
  • bridge/local/:PC 配对首页(/)、待确认任务页(/jobs)与配对确认页(CSP、X-Frame-Options: DENY、一次性 pair token);任务页显示被锁定设备的可疑活动提示
  • bridge/index.js:启动入口;首次启动生成/加载 K_otp,写 admin.token,起 API 7788 + 本地 7789,打开 SQLite,每秒扫描 done/ 结果、每 30 秒处理超时、每 30 分钟清理过期结果,任务状态写入 audit.jsonl
  • bridge/key-init.js:初始化与轮换 CLI,不打印密码子
  • pwa/crypto.js:浏览器 P-256/HKDF/SAS/AES-GCM;把电脑下发的 K_otp 导入为不可导出 HMAC 密钥,只计算 TOTP;sealEnvelope 用于提交 job
  • pwa/store.js:IndexedDB 保存设备元数据、TOTP 密钥、不可导出的 kEnc/kConfirm/kAuth,以及加密的待发送队列
  • pwa/app.js / pwa/index.html:配对 → 显示 SAS → TOTP;启动/发送前用签名刷新接口获取轮换后的 K_otp;发送任务表单(工作区从二维码预填、seq 持久化、可选附件分片上传);网络失败时文本任务加密进入 IndexedDB 队列并按指数退避重试(最长 10 分钟,附件任务暂不排队);可启用 Web Push 完成通知;轮询并解密状态、显示挑战码/响应码;结果返回后解密显示并下载结果文件;所有请求带超时,避免挂起阻塞队列
  • pwa/sw.js:缓存应用壳(网络优先、失败回退缓存),断网仍可打开 PWA;接收推送并显示通知,点击通知回到 PWA
  • pwa/outbox.js:离线队列退避与过期判断
  • plugin/index.js:DSH Cordis 插件;轮询 approved/、原子认领到 working/、把附件复制到 <workspace>/.phonejob/<id>/、唤醒按 workspace 复用的专用会话;agent 的最终回复自动写成 done/<id>/result.json,输出目录 out/ 中的文件复制到 done/<id>/files/;超过 30 分钟取消当前轮并回写超时失败;隔离 DSH 已实机验证认领/会话/失败结果回写
  • plugin/cordis.patch.yml:bundle patch,安装时自动插入 phone-job 行(pollMs / jobTimeoutMs)
  • tools/serve-pwa.js:本地 PWA 开发服务器(PWA 生产托管须独立稳定 origin,并在 bridge 配 PHONEJOB_PWA_ORIGIN)
  • 测试:npm test(Node 24 test runner;qrcode-generator 仅用于本地 QR;98 项通过)
  • 已验证:Chrome 端到端配对 + TOTP + 附件分片上传 + 发送任务 + 挑战码/响应 + 电脑审批 + 结果回传;浏览器实测配对/TOTP、加密状态、结果与文件下载、bridge 重启后的离线队列补投、Service Worker 注册与断网离线壳加载;600KB 文件按 2 片上传并审批进 approved/;隔离 DSH(临时 DSH_HOME + 空凭据)里插件真实加载、认领 job_dsh_test、创建会话、等待 agent,并在无 API key 时写出 failed 结果;插件纯函数(认领、附件复制、提示词、结果解析/回写/输出文件收集)已测;确认/上传/执行超时、RUNNING 状态检测、结果文件哈希/密钥扫描与加密下载、zip/Office 容器扫描、任务与配对审计字段白名单、轮转/90 天清理、任务限流、活跃任务/收件箱字节上限、OTP/挑战码锁定及其 SQLite 持久化、结果保留清理、吊销轮换与设备 OTP 刷新、状态响应 E2E、离线队列退避/过期、VAPID JWT 与推送负载加密/订阅注册、SQLite/密钥/审计/收件箱备份恢复与校验和、PWA 子路径二维码已用单元/HTTP 测试覆盖;真实推送服务、iOS 推送与桌面 DSH 真实模型尚未实测
  • 桌面版真实模型回复仍待验证:npx CLI 0.1.0-rc.6 读取桌面版 0.2.0-rc.2 的 .credentials.yaml 时格式不兼容(在插件之前中止),所以没有用 npx CLI 启动同一个 DSH_HOME;没有修改你的凭据文件
  • 下一步:在桌面版安装插件并跑一条真实模型任务;配置稳定 HTTPS PWA origin 与真实推送服务实测 Web Push;iOS Safari 实测
npm install
npm test
node bridge/key-init.js            # 首次初始化;默认 %DSH_HOME%\phonejob\keys.bin
node bridge/key-init.js --rotate   # 轮换;旧码失效,需重新下发设备
node bridge/index.js               # 启动 bridge;API 127.0.0.1:7788,本地 127.0.0.1:7789
npm run pwa:dev                    # PWA 开发服务器 http://127.0.0.1:8000

备份与恢复(恢复前先停 bridge):

node tools/backup.mjs --include-inbox --out D:/phonejob-backup
node tools/restore.mjs D:/phonejob-backup --force

keys.bin / vapid.bin 是 DPAPI 用户级密钥,备份只能在保存它们的同一 Windows 用户下恢复;备份文件含敏感数据,请自行加密保管。

Windows 登录自启动(创建当前用户的计划任务,日志在 %LOCALAPPDATA%\PhoneJob\bridge.log):

powershell -ExecutionPolicy Bypass -File tools/install-autostart.ps1 -Workspace "D:\your-workspace" -PwaOrigin "https://your-pwa-origin" -PwaUrl "https://your-pwa-origin/phonejob/"
powershell -ExecutionPolicy Bypass -File tools/remove-autostart.ps1

CI:.github/workflows/test.yml 在 Windows 与 Ubuntu 上执行 npm ci 和 npm test;Windows 覆盖 DPAPI 测试,Ubuntu 跳过依赖 DPAPI 的用例。

本地联调时让 bridge 允许 PWA origin,并配置允许的 job 工作区:

PHONEJOB_PWA_ORIGIN=http://127.0.0.1:8000 \
PHONEJOB_WORKSPACES="C:/Temp/phonejob-ws" \
node bridge/index.js

PHONEJOB_WORKSPACES 为空时所有 job 都会被拒绝。附件限制:最多 20 个、单文件 ≤50MB、任务总计 ≤200MB、512KB 分片;类型白名单 jpg/jpeg/png/pdf/docx/xlsx/pptx/txt/md/zip。zip 会检查内部路径、类型、加密、嵌套深度与声明解压大小;docx/xlsx/pptx 会拒绝宏工程、嵌入对象、ActiveX、externalLinks 目录与非超链接外部关系(超链接本身允许)。结果文件限制:最多 10 个、单文件 ≤10MB、总计 ≤20MB;类型白名单在附件基础上增加 csv/json,zip 与 Office 容器同样扫描。审批已实现(pending/ → approved/);DSH 执行已实现,未获批的任务不会进入 approved/。

超时默认值:上传 30 分钟、确认 10 分钟(超时移入 expired/ 并标记 EXPIRED)、执行 35 分钟(bridge 兜底标记 FAILED;插件自身 30 分钟取消当前轮)。可用 PHONEJOB_UPLOAD_TTL_MS / PHONEJOB_CONFIRM_TTL_MS / PHONEJOB_EXECUTION_TTL_MS 覆盖 bridge 默认值。

限流:每设备每小时最多 10 个任务(PHONEJOB_MAX_JOBS_PER_HOUR);OTP 或挑战码连续失败 3 次锁 5 分钟、10 次锁 1 小时。锁定状态存 SQLite,bridge 重启后仍保留;本地 /jobs 页会显示锁定设备。

队列与磁盘:同时活跃任务默认上限 20(PHONEJOB_MAX_ACTIVE_JOBS),收件箱总字节默认上限 2GB(PHONEJOB_MAX_INBOX_BYTES);超限拒绝新任务,已有任务仍可完成。

PWA 生产的静态托管需在响应头设置 CSP(含 frame-ancestors,meta 里无效);bridge 的 PHONEJOB_PWA_ORIGIN 必须与 PWA origin 完全一致。托管在子路径(如 GitHub Pages 的 /phonejob/)时,另设 PHONEJOB_PWA_URL=https://<user>.github.io/phonejob/,二维码会使用该完整地址。

Web Push 需要 HTTPS 的 PWA origin(或 localhost 调试)和可访问的推送服务;VAPID 私钥由 DPAPI 保存到 %DSH_HOME%\phonejob\vapid.bin,可用 PHONEJOB_VAPID_SUBJECT 设置 VAPID 联系人(默认 mailto:phonejob@localhost)。

配对流程:打开 http://127.0.0.1:7789/ → 确认 Bridge URL(本机 http://127.0.0.1:7788)→ 生成二维码 → 手机扫描打开 PWA → 在电脑上打开确认页核对 SAS。二维码需要配置 PHONEJOB_PWA_ORIGIN;未配置时仍可用 PWA 手动粘贴配对信息。二维码会带上 PHONEJOB_WORKSPACES,PWA 发送任务时自动预填工作区。

审批流程:PWA 发送任务后显示电脑挑战码与手机响应码;打开电脑 http://127.0.0.1:7789/jobs,选择任务,核对挑战码,输入手机响应码并批准。确认码绑定 job_hash,错误响应不会移动 pending/。

结果回传:DSH 插件把 done/<job_id>/result.json 写入收件箱,并把 <workspace>/.phonejob/<job_id>/out/ 下的产物复制到 done/<job_id>/files/;bridge 每秒扫描,校验文本、文件元数据、SHA-256 和密钥模式后加密;PWA 通过签名接口拉取文本,结果文件按索引单独加密下载并在手机上复核 SHA-256。结果文件不执行、不预览,下载后请在本地自行判断。

结果和 done/、working/ 目录默认保留 7 天(PHONEJOB_RESULT_TTL_MS);过期后任务转为 EXPIRED/RESULT_EXPIRED,手机显示“结果已过期”。rejected/、expired/ 目录按同一 TTL 清理。

DSH 插件

插件是自带 bundle patch 的本地包;用 DSH 自带的 CLI 安装,然后重启桌面应用:

dsh plugin --profile desktop add link:D:\phonejob\plugin

插件默认监听 %DSH_HOME%\phone-inbox,与 bridge 默认一致。审批后的任务会被认领,附件复制到 <workspace>\.phonejob\<job_id>\,插件按 workspace 复用专用会话;agent 的最终回复自动回写结果。桌面版首次运行会在 DSH 里出现对应会话,工具调用可能仍需你在 DSH 中确认。

已在隔离 DSH_HOME 里用空凭据验证插件加载与执行路径(模型请求按预期因无 key 失败并回写 failed)。本机 npx CLI 是 0.1.0-rc.6、桌面版是 0.2.0-rc.2,两者凭据文件格式不一致;桌面真实模型验证请用桌面版自带的 CLI/应用,不要用 npx CLI 启动同一个 DSH_HOME。

bridge/index.js 首次启动也会加载/生成 K_otp;本地管理 token 写到 %DSH_HOME%\phonejob\admin.token,不打印到日志。配对与任务审计写到 %DSH_HOME%\phonejob\audit.jsonl,不包含任务正文、工作区路径、挑战码、SAS 或密钥;按 5MB 轮转,保留 4 份备份,启动时清理 90 天前条目。

许可:源码可见,但禁止商业使用与劳动剥削

本仓库不是 OSI 开源项目。它采用 PhoneJob Noncommercial License 1.0:以 PolyForm Noncommercial 1.0.0 为基础,并附加以下条件:

  • 禁止任何商业用途;商业使用需另行取得书面许可。
  • 禁止任何组织(包括公司、团队、学校、政府、公益机构)使用本软件:
    • 在员工、外包或学生约定工作时间之外派发、安排、监控、记录、考核或催办任务;
    • 要求或暗示任何人在休息、休假、下班时间保持在线、响应或待命;
    • 掩盖工时、规避加班费/调休/休息休假,或其他规避劳动法的行为。
  • 违反上述条件即终止许可;对劳动用途不适用 32 天整改期。

一句话:这是给自己用的工具,不是给老板用来变相加班的工具。

基础许可文本:PolyForm Noncommercial 1.0.0

可接受使用摘要 · 完整许可

安全说明

  • 未经过独立安全审计;请自行评估风险,不要直接暴露到公网。
  • 隧道只应转发到本机回环地址;桥不信任隧道传来的任何身份声明。
  • 许可证只约束对本软件的使用,不能替代劳动法,也不能约束雇佣关系。

计划结构

bridge/   常驻桥:配对、解密、TOTP、挑战码、收件箱
pwa/      手机端 PWA
plugin/   DSH 插件
tests/    密码学、端到端与攻击用例
docs/     设计文档

贡献

提交贡献前请先开 issue 确认许可安排,详见 CONTRIBUTING.md。安全问题请走 SECURITY.md。

版权

Copyright (c) 2026 Appler-D.

Required Notice: Copyright 2026 Appler-D (https://github.com/Appler-D/phonejob)


English summary

PhoneJob is a personal, end-to-end encrypted phone-to-PC task channel. This repository is source-available, not OSI open source. The license prohibits commercial use and any organizational use to assign, monitor, evaluate, or enforce work outside agreed working hours, including by noncommercial organizations. See LICENSE. The design is not implemented or security-audited.

About

Personal end-to-end encrypted phone-to-PC task channel. Source-available, noncommercial; not for after-hours work assignment.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages