Principles
每個套件都是第三方套件
Shinsou X 不內建、不審核、也不替任何套件背書,包含專案作者自己維護的套件。App 內沒有任何依套件名稱、網址或雜湊值例外放行的清單;安全性來自沙箱隔離、最小權限與使用者逐項授權,而不是來自誰發佈。
發佈者簽章
作者以自己的 Ed25519 金鑰簽署整個儲存庫。你信任的是發佈者金鑰,不是網址。
一律在沙箱執行
腳本在隔離的 QuickJS 沙箱中執行,沒有網路、檔案或子程序權限,只能透過受控的 bridge 向 App 請求。
逐項授權
執行與連線到宣告的網站隨安裝授予;Cookie、帳密、登入、收藏修改等敏感權限預設關閉,由你逐項開啟,也可隨時撤銷。
沒有中央封鎖清單
Shinsou X 不維護任何遠端封鎖清單。唯一能遠端停用套件的,是發佈者自己簽署的撤回清單與金鑰撤銷。
簽章只證明內容出自持有該金鑰的作者;SHA-256 與 sidecar 核對只證明下載的檔案與作者宣告一致。兩者都不代表程式行為安全,也不代表任何人審核過它。只安裝你信任的發佈者。
名稱含「官方」、「公式」或 official 的套件、來源與發佈者,會在安裝與信任時被拒絕;「非官方」、Unofficial 這類明確的免責聲明則可以使用。
How it works
從儲存庫到閱讀器
儲存庫只是一組靜態檔案,不是內容託管服務。App 負責驗證、授權、網路、敏感資料與閱讀;套件只負責和來源網站溝通,回傳作品、章節與內容。
驗證發佈者
檢查儲存庫簽章、發佈者金鑰與撤回清單是否有效且未過期。
核對宣告
索引、sidecar 與腳本三處的版本、來源、內容種類、網站與權限必須一致;超出沙箱上限的宣告直接拒絕。
驗證腳本
下載後比對 byte size 與 SHA-256,不符就不安裝。
你決定授權
在權限頁看到它會連到哪些網站、要求哪些敏感權限,逐項決定是否開啟。
沙箱執行
來源出現在「瀏覽 → 來源」,書庫、閱讀器與下載都透過 App 受控的介面取得內容。

Repositories
加入儲存庫並確認發佈者
任何發佈者都可以提供自己的儲存庫。加入時 App 會顯示發佈者自稱的名稱(標示為未驗證)與金鑰指紋,請和發佈者在自己網站公布的完整指紋比對。
新增儲存庫
開啟「瀏覽 → 擴充套件 → 新增」,貼上發佈者提供的儲存庫基底網址;或點擊發佈者提供的 shinsou:// 加入連結。
確認發佈者
預設為首次使用即信任;若連結或你自己輸入了完整指紋,就一定要相符才能信任。
選擇套件並授權
重新整理後選擇套件,在權限頁決定要開啟哪些敏感權限。
shinsou://add-repository?url=https://you.github.io/plugins&fp=sha256:<full-fingerprint>
在「套件安全」開啟後,沒有完整指紋或指紋不符就不能信任發佈者。只用首次使用即信任的發佈者會顯示為待比對:已安裝的套件繼續執行,但比對完成前不安裝新套件、也不更新。
專案作者以一般發佈者身分維護 Shinsou Community Plugins,地位與其他第三方發佈者相同,同樣需要你確認發佈者與逐項授權。需要 App v1.0.1-beta.15 以上。
儲存庫網址:https://raw.githubusercontent.com/aluo96078/shinsou_plugin/refs/heads/master
完整指紋:sha256:o-kWmdLK5JsPBIkzNB0FNsomhKpPqWB5QunWv2O137o
| 索引格式 | App 行為 |
|---|---|
| 簽章的 Shinsou 儲存庫 | 驗證後可安裝,套件在沙箱中執行 |
| 未簽章的儲存庫 | 只在開發者模式、且位址為本機或你輸入的區網位址時載入 |
| Mihon/Tachiyomi 索引 | 只讀取 metadata,APK 不會在 Shinsou X 執行 |
| ShuYue 格式套件 | 不再執行,儲存庫中的這類項目會被略過 |
Permissions
宣告要求,不等於取得權限
有效權限 = 套件宣告(索引、sidecar、腳本三方一致)∩ App 支援 ∩ 沙箱上限 ∩ 你對這個確切版本的授權。沙箱上限是 App 常數,套件與使用者都無法提高。
EXECUTE_SCRIPT隨安裝授予:在沙箱中執行套件腳本。
NETWORK隨安裝授予:只能連到套件宣告的 HTTPS 網站。
COOKIE_STORAGE敏感:寫入由 App 代管的 Cookie。
COOKIE_READ敏感:讓腳本讀取 Cookie 的值。沒有此權限時,App 仍會在請求中帶上 Cookie,只是腳本看不到。
CREDENTIAL_ACCESS敏感:存取已保存的帳號;密碼只在 App 啟動的一次性登入沙箱中出現。
LOGIN_PROMPT敏感:要求 App 顯示登入畫面。
FAVORITE_MUTATION敏感:加入或移出網站帳號的收藏。
BROWSER_CHALLENGE敏感:對宣告的 browserSessionOrigins 使用 Web challenge 與瀏覽器傳輸。
BROWSER_IMAGES敏感:在 macOS 與 iOS 以一次性私密瀏覽器下載指定 CDN 的圖片,不帶 Cookie 與 Referer。
contentHostSuffixes敏感:每個 CDN 後綴家族各自一個開關,只放寬不帶憑證的內容下載。
REQUEST_* / REPORT_DIAGNOSTIC系統事件:要求 App 顯示登入、重新整理來源、登出或寫入診斷記錄,需另外授權。
REPORT_USER_MESSAGE永不授予:宣告此權限的套件會在安裝前被拒絕。
API 請求、登入、內容下載與瀏覽器傳輸各有自己的 origin 清單。App 會釘選 DNS 解析結果、逐跳檢查 redirect 並拒絕降級為 HTTP;圖片與正文由 App 代為下載,不帶任何來源憑證。
Updates & revocation
更新、撤回與金鑰事件
沒有新要求的更新會沿用授權
同一發佈者金鑰、沒有新權限、新 CDN 後綴、新來源或新網站時,更新會自動沿用你先前的決定並通知你;你拒絕過的仍然拒絕。
有新要求就先審核
只要多要求任何東西,或換了發佈者金鑰,你必須在安裝前審核;接受之前舊版本繼續執行。
降版一律拒絕
versionCode 或 digest 回退的版本不會被安裝。
| 機制 | App 行為 |
|---|---|
| 版本撤回 | 發佈者簽署的撤回清單列出的版本會立即停止執行,並提示更新 |
| 撤回清單過期 | 不再從該發佈者安裝或更新;已安裝的繼續執行並顯示警示 |
| 金鑰輪替 | 新舊金鑰雙簽,App 自動換成新金鑰,不需重新信任 |
| 金鑰自我撤銷 | 該發佈者的套件全部停止執行,需重新信任新金鑰 |
| 本機封鎖 | 你可以封鎖單一套件、單一版本或整個發佈者;只存在這台裝置 |
Sandbox
所有平台都在沙箱中執行
平台沒有可用的沙箱時一律拒絕執行,不會退回 App 內的引擎。每個套件有自己的記憶體、時間與 bridge 呼叫上限,超限以穩定錯誤碼失敗,不回傳部分結果。
| 平台 | 沙箱 | Web challenge |
|---|---|---|
| Android | 每個套件一個 isolated process | 支援時使用隔離的 App 內 WebView,否則開外部瀏覽器 |
| iOS/iPadOS | WebAssembly 沙箱 | App 內 non-persistent WKWebView |
| macOS | deny-all 的獨立 helper 程序 | 獨立的 non-persistent WKWebView 視窗 |
| Windows | 低權限 AppContainer helper 程序 | 只開外部瀏覽器,無法自動帶回 Cookie |
Content contract
漫畫、小說,也可以在同一個套件
| contentKinds | 內容 | 腳本實作 |
|---|---|---|
IMAGE_SEQUENCE | 漫畫、逐頁圖片 | getPageList(chapter) |
PLAIN_TEXT | 小說正文,單章最多 4 MiB | getChapterText(chapter) |
一個套件可以混合多個來源,每個來源各自宣告內容種類;App 依確切的 sourceId 選擇來源,不依陣列順序。sourceId 一經發佈就不能更改,書庫、閱讀進度與登入都以它綁定。
Data boundary
備份帶走儲存庫清單,不帶走套件與秘密
| 資料 | 備份與還原 |
|---|---|
| 儲存庫清單與書庫、閱讀進度 | 可攜,依備份選項納入可攜備份 |
| 已安裝的套件、信任與授權 | 只在這台裝置;新裝置需重新安裝並授權 |
| Cookie、密碼、token、API key | 不進任何備份 |
For developers
開發指引:從金鑰到使用者安裝
套件是一支 JavaScript 腳本加上一份宣告檔。儲存庫是靜態檔案,可以放在 GitHub Pages、raw GitHub 或任何靜態主機,不需要伺服器,也不需要任何人核准上架。
keygen → init → build → lint → test → serve (developer mode) → sign → publish
then: bump versionCode → build / lint / test → sign
refresh (scheduled) / revoke / rotate / revoke-key
1. 安裝 CLI
需要 Node.js 20 以上。CLI 尚未發佈到 npm,請從 GitHub 安裝:
npm install --global github:aluo96078/shinsou-plugin-cli shinsou-plugin help
2. 產生發佈者金鑰
shinsou-plugin keygen --name mykey --passphrase shinsou-plugin fingerprint --key mykey
加密並備份金鑰檔,不要提交到版本控制。遺失私鑰無法恢復,使用者只能改信任新金鑰並重新授權。請在你的網站與 README 公布完整指紋(sha256: 開頭),比對只認完整值。
3. 建立專案
shinsou-plugin init my-plugin --template manga --id zh.example --name "Example" --lang zh
| 範本 | 用途 |
|---|---|
manga | 解析 HTML 的漫畫來源 |
novel | 小說來源,實作 getChapterText |
mixed | 同一套件內的漫畫與小說兩個來源 |
async | JSON API、非同步 hook 與多檔模組 |
4. 撰寫宣告檔
shinsou.plugin.json 是唯一的事實來源,索引、sidecar 與腳本內的宣告都由它產生。只宣告真正用到的能力、權限與網站。
{
"manifestVersion": 1,
"packageId": "zh.example",
"name": "Example",
"version": "1.0.0",
"versionCode": 1,
"lang": "zh",
"contract": "shinsou",
"runtime": "legacy-shinsou-adapter-v2",
"contentType": "manga",
"contentKinds": ["IMAGE_SEQUENCE"],
"capabilities": ["CATALOGUE", "LATEST", "BROWSE", "METADATA", "UNITS", "CONTENT", "SEARCH"],
"runtimePermissions": ["EXECUTE_SCRIPT", "NETWORK"],
"requestedHostPermissions": [],
"systemEvents": { "required": [], "optional": [] },
"entry": "src/index.js",
"sources": [{
"sourceId": "1234567890123456789",
"name": "Example",
"lang": "zh",
"baseUrl": "https://example.com",
"requestOrigins": ["https://example.com"],
"credentialOrigins": [],
"contentOrigins": ["https://img.example.com"],
"browserSessionOrigins": [],
"contentHostSuffixes": []
}],
"repository": {
"name": "My Plugins",
"website": "https://github.com/you/plugins",
"mirrors": ["https://you.github.io/plugins"]
}
}
packageId
小寫英數與點、底線、連字號,不可含 ~,最長 111 字元。不同發佈者可以使用相同 packageId,App 會視為不同套件並存安裝。
versionCode
每次發佈都必須遞增,App 拒絕降版。
sourceId
64-bit 整數字串;或小寫點分隔字串搭配數字 legacyLongId。發佈後不可更改。
origin 欄位
全部是確切的 HTTPS origin,不含路徑;不接受 HTTP、IP、localhost 與私有網路主機。
Script API
腳本結構與可呼叫的 bridge
腳本是 classic script,進入點在頂層宣告 source 物件(多來源用 sources 陣列);build 會把 import 打包成單一檔案。Hook 可以回傳 Promise。
var source = {
baseUrl: "https://example.com",
supportsLatest: true,
getPopularManga: function (page) {
var doc = Jsoup.parse(bridge.httpGet(this.baseUrl + "/popular?page=" + (page + 1)), this.baseUrl);
var list = doc.select(".item").map(function (el) {
var m = SManga.create();
m.url = el.selectFirst("a").attr("href");
m.title = el.selectFirst(".title").text();
m.thumbnail_url = el.selectFirst("img").absUrl("src");
return m;
});
return new MangasPage(list, list.length > 0);
},
getChapterList: function (manga) { /* return SChapter[] */ },
getPageList: function (chapter) { /* return [new Page(0, "", imageUrl), ...] */ }
};
| Hook | 用途 |
|---|---|
getPopularManga / getLatestUpdates / getSearchManga | 回傳作品清單與是否有下一頁 |
getMangaDetails / getChapterList | 補完作品資料、取得章節 |
getPageList / getChapterText | 漫畫圖片頁/小說章節文字 |
getFavoriteManga / setFavorite | 選用:網站帳號的書架與收藏 |
login(username, password) | 選用:在一次性沙箱中執行的登入流程 |
| Bridge | 需要的權限 |
|---|---|
bridge.httpGet / httpPost / await bridge.net.fetch | NETWORK,只限 requestOrigins |
bridge.setCookie / deleteCookie | COOKIE_STORAGE |
bridge.getCookie / getCookies | COOKIE_STORAGE + COOKIE_READ |
bridge.getCredentialUsername / hasCredential | CREDENTIAL_ACCESS |
bridge.requestLogin | LOGIN_PROMPT |
bridge.browserSessionRequest | BROWSER_CHALLENGE |
bridge.getPreference / setPreference | 無 |
使用者可以不開啟敏感權限。缺少權限的呼叫會以 PERMISSION_DENIED 失敗,腳本應該在這種情況下仍能提供基本功能。
Build、lint 與 test
shinsou-plugin build # bundle, digest, index + sidecar; verifies all three agree shinsou-plugin lint # undeclared bridge calls, non-HTTPS origins, misleading names shinsou-plugin test # replay recorded HTTP fixtures in a QuickJS sandbox shinsou-plugin test --record # record missing fixtures from the live site
在 App 上試用:開發者模式
在 App 的「套件安全」開啟開發者模式,就能載入未簽章的開發儲存庫。手機測試時,在開發伺服器位址填入電腦的私有區網 IPv4 位址。
shinsou-plugin serve # http://127.0.0.1:8787 shinsou-plugin serve --host lan # private LAN IPv4, prints a shinsou:// link and QR code
不會取代正式安裝
開發中的套件以獨立身分安裝並標示「開發中」,只在開發者模式開啟時執行。
沒有額外放寬
開發儲存庫可以用 HTTP,但套件自己的請求仍只能是 HTTPS,敏感權限一樣要逐項開啟。
Sign & publish
簽章並發佈到靜態主機
shinsou-plugin sign --key mykey --mirror https://you.github.io/plugins
每個網址各簽一份
簽章綁定儲存庫網址,每個鏡像輸出到各自的目錄。上傳後的網址必須和簽章時完全一致。
提交 .shinsou/state.json
它記錄每份文件嚴格遞增的序號,不含秘密。遺失它會讓新簽的文件被 App 當成重播而拒絕。
公布加入方式
在你的網站提供儲存庫網址、完整指紋與 shinsou:// 加入連結,讓使用者能獨立核對。
repo.json index.json sidecars/<packageId>.json plugins/<packageId>.js publisher-revocations.json publisher-key-rotation.json (after rotate) publisher-key-revoked.json (after revoke-key)
Maintenance
維護你的發佈
撤回清單預設 30 天到期。過期後使用者無法再安裝或更新你的套件,即使你沒有任何撤回也一樣。init 已附上每週執行的 GitHub Actions workflow,請在 secrets 設定 SHINSOU_PLUGIN_KEY 與 SHINSOU_PLUGIN_PASSPHRASE。
shinsou-plugin refresh --key mykey # renew the revocation list shinsou-plugin revoke <sha256> --key mykey # pull a bad version shinsou-plugin rotate --key mykey --to newkey # rotate keys, users keep trust shinsou-plugin revoke-key --key mykey # leaked key: disable everything it signed
小修補不要加權限
沒有新要求的更新會自動沿用授權;新增權限或網站會讓使用者在更新前重新審核。
長期保留輪替檔
還沒更新的使用者靠 publisher-key-rotation.json 換成新金鑰。
在各平台實測
離線測試不會驗證第三方登入、Cloudflare、限流或網站 API 變更,請用開發者模式在真實 App 試過。
GitHub Actions
用 GitHub Actions 自動簽章
把金鑰放進儲存庫的 secrets 後,GitHub Actions 可以在你推送程式碼時自動檢查、簽章並發佈,也能定期續簽撤回清單,不必每次在自己電腦上手動執行。
| Workflow | 觸發時機 | 做什麼 |
|---|---|---|
shinsou-refresh.yml | init 內建;每週一次,或手動觸發 | 只續簽撤回清單,並提交簽章檔與序號 |
shinsou-sign.yml | 自行加入;推送原始碼時、每週一次,或手動觸發 | lint、test、簽章、到期前續簽撤回清單,再部署到 GitHub Pages |
1. 把 CLI 依賴固定到 GitHub 版本
init 產生的 package.json 依賴的 CLI 版本尚未發佈到 npm,Actions 執行 npm ci 時會失敗。請改成固定的 GitHub commit:
npm install --save-dev github:aluo96078/shinsou-plugin-cli#<commit>
2. 設定 secrets
在 GitHub 儲存庫的 Settings → Secrets and variables → Actions 新增兩個 secret,或使用 GitHub CLI:
gh secret set SHINSOU_PLUGIN_KEY < ~/.config/shinsou-plugin/keys/mykey.json gh secret set SHINSOU_PLUGIN_PASSPHRASE
SHINSOU_PLUGIN_KEY
金鑰檔的完整 JSON 內容。CLI 會依其中的名稱找到這把金鑰。
SHINSOU_PLUGIN_PASSPHRASE
金鑰的密語;只有金鑰有加密時才需要,但強烈建議加密。
3. 推送時自動簽章並部署
以下範例在原始碼改變時檢查並簽章、每週在撤回清單剩不到一半效期時續簽,最後把簽好的鏡像目錄部署到 GitHub Pages。請在 Settings → Pages 把來源設為 GitHub Actions,並把 SITE_DIR 換成 sign 輸出的鏡像目錄。
# .github/workflows/shinsou-sign.yml
name: Sign and publish
on:
push:
branches: [main]
paths: ["src/**", "test/**", "shinsou.plugin.json"]
schedule:
- cron: "17 3 * * 1" # every Monday 03:17 UTC
workflow_dispatch:
permissions:
contents: write
pages: write
id-token: write
concurrency:
group: shinsou-sign
cancel-in-progress: false
env:
SITE_DIR: public/you.github.io_plugins # the mirror directory written by sign
jobs:
sign:
runs-on: ubuntu-latest
environment: github-pages
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npx shinsou-plugin lint
- run: npx shinsou-plugin test
- name: Sign
if: github.event_name != 'schedule'
env:
SHINSOU_PLUGIN_KEY: ${{ secrets.SHINSOU_PLUGIN_KEY }}
SHINSOU_PLUGIN_PASSPHRASE: ${{ secrets.SHINSOU_PLUGIN_PASSPHRASE }}
run: npx shinsou-plugin sign --key "$(node -p 'JSON.parse(process.env.SHINSOU_PLUGIN_KEY).name')"
- name: Renew the revocation list when due
env:
SHINSOU_PLUGIN_KEY: ${{ secrets.SHINSOU_PLUGIN_KEY }}
SHINSOU_PLUGIN_PASSPHRASE: ${{ secrets.SHINSOU_PLUGIN_PASSPHRASE }}
run: npx shinsou-plugin refresh --if-due --key "$(node -p 'JSON.parse(process.env.SHINSOU_PLUGIN_KEY).name')"
- name: Commit signatures and sequence numbers
run: |
git config user.name "shinsou-sign"
git config user.email "shinsou-sign@users.noreply.github.com"
git add .shinsou/state.json public
git diff --cached --quiet || { git commit -m "chore: sign repository"; git push; }
- uses: actions/upload-pages-artifact@v3
with:
path: ${{ env.SITE_DIR }}
- uses: actions/deploy-pages@v4
sign 會自己 build
sign 會先重新打包,再依 shinsou.plugin.json 的 repository.mirrors 為每個鏡像各簽一份。
一定要提交序號檔
workflow 會把 .shinsou/state.json 推回儲存庫;少了它,下一次簽章的序號會倒退而被 App 拒絕。
只保留一個 workflow
使用這個範例後,可以刪除 init 附的 shinsou-refresh.yml。兩者共用同一個 concurrency 群組,不會同時寫入序號檔。
先手動觸發一次
在 Actions 頁選擇這個 workflow 按 Run workflow,確認出現簽章 commit、Pages 部署成功,再用 App 重新整理儲存庫。
只把金鑰 secrets 傳給需要簽章的步驟,不要設在整個 job,避免 npm 套件的安裝腳本讀到。不要讓 pull_request_target 或 fork 的程式碼觸發簽章;限制誰能推送到主分支。金鑰外洩時立刻執行 revoke-key 並更換 secrets。
AI Skill
讓 AI 開發助手協助撰寫套件
shinsou-extension-creator 是給 AI 開發助手使用的工作指南,協助建立、遷移或修復套件:保持穩定的來源身分、如實宣告內容、最小化權限,並讓腳本、sidecar 與索引一致。它不是 App 內的套件,也不會替你簽章或發佈。
發佈前請自己執行 build、lint 與 test,並在開發者模式中實測。不要把私鑰或密語貼給 AI 助手。
FAQ
套件常見問題
Shinsou X 有官方套件嗎?
沒有。每個套件都是第三方套件,包含專案作者自己維護的。App 不替任何發佈者背書,請依你對發佈者的信任決定是否安裝。
為什麼加入社群儲存庫後讀不到套件?
社群儲存庫使用字串 sourceId,需要 App v1.0.1-beta.15 以上。也請確認網址是儲存庫基底網址,而不是 GitHub 專案頁面。
為什麼不能安裝或更新某個發佈者的套件?
最常見的原因是該發佈者的撤回清單過期或缺少。已安裝的套件會繼續執行並顯示警示,需要等發佈者續簽。
Shinsou X 能安裝 Mihon APK 擴充嗎?
不能。Mihon 索引只作為 metadata 相容輸入,APK 不會在 Shinsou X 執行。
兩個發佈者都發佈了相同 packageId 怎麼辦?
App 把它們視為不同套件,可以並存安裝;信任、授權與書庫綁定分開計算。
為什麼另一台裝置有儲存庫,卻沒有來源或登入狀態?
儲存庫清單包含在可攜備份中,但已安裝的套件、授權、Cookie 與帳密都不在備份內,只存在原本的裝置。請在新裝置重新安裝、授權並登入。
網站可以開啟,來源卻載入失敗怎麼辦?
依序檢查登入、Cookie、User-Agent、Web challenge 與來源的 proxy 設定。Windows 的外部瀏覽器完成驗證後不會自動把 Cookie 帶回 App,可改用 Cookie 匯入。