Murasaki
ビルド & 配布

セキュリティ

Murasaki の信頼境界、組み込み runtime protection、アプリ側の責務。

デスクトップセキュリティはプロセス境界から始まります。Murasaki は Node を renderer から分離し、アプリ WebView の遷移先を自身の origin に制限し、特権 loopback endpoint を app-session token で保護します。通常の renderer bug の影響を減らしますが、完全な 権限システムではありません。

renderer native command は deny-by-default で、型付き capabilities list から許可 します。宣言windowごとに独立したallowlistがありますが、argument / path / URL scope はありません。renderer-to-Node関数もprivileged application codeとして扱い、surfaceを 小さく保ってください。

信頼境界

境界組み込み保護アプリ側の責務
Remote URL → app WebViewapp origin と完全一致する遷移だけを window 内で許可。off-origin HTTP(S) は system browser で開き、file: / data: / javascript: は拒否navigation behavior を弱めず、remote script を renderer に注入しない
Renderer → Rust native commandexact-origin IPC checkと、call元windowごとに評価するtyped / deny-by-default allowlistそのrendererが使うcommandだけをgrant。window:manageなど広い操作のapp-level intentを検証
Renderer → local Node/api/*/__murasaki/* は random 256-bit HttpOnly / SameSite=Strict cookie を要求し Host / Origin を検証。wire payload に上限各 action / route / Main function 内で引数を検証し操作を認可
OS URL / file activation → Node Main設定済み scheme / extension だけを normalize して openRequested() target にし、認証付き app-local channel で配送URL / path をすべて attacker-controlled として扱い、操作前に intent、identifier、host、file content を検証
App → update hostmanifest raw bytes の Ed25519 signature、その後 payload の SHA-256 を検証private update key を保護し、hash/signature を同時に公開
OS → installed appmacOS Developer ID / notarization と Windows Authenticode orchestration が最終 app-owned artifact を署名・検証platform certificate / provider を用意して保護し、clean machine で trust policy と installed artifact をテスト

本番は全 interface ではなく 127.0.0.1 だけで listen します。開発時は loopback の localhost / 127.0.0.1 / [::1] を許可し、cross-site Fetch Metadata を拒否します。 session cookie は最初の document response が設定し、JavaScript からは読めません。

Native command を明示的に許可する

murasaki.config.ts
export default defineConfig({
  appId: 'com.example.notes',
  productName: 'Notes',
  window: {
    route: '/',
    capabilities: ['app:quit', 'dialog:openFile', 'shell:openExternal', 'window:open'],
  },
  windows: {
    preview: { route: '/preview', capabilities: ['clipboard:writeText'] },
    settings: { route: '/settings', capabilities: [] },
  },
})

capabilityを省略すると何も許可されません。ただしprimaryはwindow.capabilitiesがない 場合だけlegacy top-level listへfallbackし、secondaryは継承しません。未知の文字列も何も 許可せず、bridgeはtrusted app originと完全一致するcallだけを受け付けます。allowlistは command単位で、dialogを1 directory、external URLを1 hostに限定できません。 window:manageは全宣言windowをtargetにできるため、信頼するrendererだけへ付与し、より 狭いintentはapp codeでも強制します。application全体のprogrammatic shutdownは app:quit で別に制御されます。

シークレットは Node に置く

renderer code から import したものは public client JavaScript になり得ます。API token、 private key、DB credential、license validation は src/main.ts'use main''use server'、server-only API route module に置きます。

src/backend/account.ts
'use main'

export async function loadAccount(accountId: string) {
  if (!/^[a-z0-9_-]{1,64}$/i.test(accountId)) {
    throw new TypeError('invalid account id')
  }

  const token = process.env.ACCOUNT_API_TOKEN
  if (!token) throw new Error('account service is not configured')

  const response = await fetch(`https://api.example.com/accounts/${accountId}`, {
    headers: { authorization: `Bearer ${token}` },
    signal: AbortSignal.timeout(10_000),
  })
  if (!response.ok) throw new Error(`account service returned ${response.status}`)
  return response.json()
}

runtime session は request がアプリ renderer origin を経由したことを示しますが、 renderer が侵害されていないことは保証しません。XSS は client bundle から呼べる 関数を実行できます。

Path と URL を検証する

renderer input を無制限の filesystem path に連結しないでください。app-owned directory 配下に resolve し、結果がその内側に留まることを確認します。raw path より opaque ID を優先してください。

Node 関数が fetch() / URL open を行う前には protocol と host を allowlist します。 これは意図しない local-network access と SSRF を防ぎます。renderer の request header をそのまま remote service に転送しないでください。

同じ rule は openRequested() にも適用されます。登録済み scheme は dispatch の仕組みであり、 認証の仕組みではありません。local process、browser、document のいずれからも open を 試行できます。file association も path を示すだけで、trusted file を示すものではありません。

src/main.ts
import { defineMain } from 'murasaki/main'

export default defineMain({
  async openRequested(_context, event) {
    for (const target of event.targets) {
      if (target.kind === 'url') {
        const url = new URL(target.url)
        if (url.protocol !== 'example-notes:' || url.hostname !== 'open') continue
        const id = url.pathname.slice(1)
        if (!/^[a-z0-9_-]{1,64}$/i.test(id)) continue
        // 検証済み identifier を application code で解決する。
      } else {
        // target.path を parse する前に extension、access policy、size、
        // file format を確認し、名前だけを根拠に content を実行しない。
      }
    }
  },
})

deep-link URL へ renderer を自動 navigation させたり、query 値を HTML に埋め込んだり、 登録済み extension があるという理由で file を実行したりしないでください。renderer に 結果が必要なら、Main event API から狭く検証済みの値だけを渡します。

Content Security Policy

Murasaki は document に X-Content-Type-Options: nosniffReferrer-Policy: no-referrerを設定し、framework-owned / user-owned HTMLへCSP meta tagを1つ注入します。 production既定値はscript-src 'self'を使い、object、frame、base URL変更、inline script attribute、他originへのform送信を遮断します。一方remote-backed appのHTTPS / WSS接続は 許可し、image / font / mediaには一般的なHTTPS / data / blob sourceを許可します。 Reactのstyle attributeとruntime CSS互換性のため、style-src 'self' 'unsafe-inline'は 残しています。

developmentでは別policyを使い、Vite / React Refreshが注入するinline scriptとHMR用の ws:を追加で許可します。'unsafe-eval'は追加せず、この緩和はproductionには入りません。

policy全体のoverride、または別layerが管理する場合のopt-outを設定できます。

export default defineConfig({
  appId: 'com.example.notes',
  productName: 'Notes',
  security: {
    csp: "default-src 'self'; script-src 'self'; style-src 'self'; connect-src 'self' https://api.example.com; object-src 'none'; base-uri 'none'; frame-src 'none'",
  },
})

// Murasakiの注入だけを無効化:
// security: { csp: false }

stringはdirective mergeではなく完全なoverrideです。user-owned index.htmlにCSP metaが あればそれを維持して<head>先頭へ移動します。既存tagとsecurity.csp stringの両方を 設定すると、片方を暗黙に選ばずbuild errorにします。blockされたsourceはbrowser consoleの CSP violationで確認し、そのsourceだけを追加するかapp originへ移してください。

既存appを移行するときはproductionのinline scriptをexternal moduleへ移します。remote script / frameは既定でblockされるため、必要なら明示的にoverrideしてください。HTTPS / WSS backendは既定で利用できます。remoteのplain HTTP backendにはoverrideが必要です (same-origin loopback callは許可されます)。

meta配信CSPにはbrowser定義の制約があります。tagをparseした位置から適用されるため Murasakiは<head>先頭へ注入または移動します。frame-ancestorssandbox、reportingなどの directiveはmeta tagから完全には利用できないため、既定policyにも含めていません。これらが 必要な実network serviceではresponse headerを使ってください。CSPはHTMLをsanitizeせず、 Node関数をauthorizeしません。 既定のinline style許可も互換性上のtradeoffであり、XSS安全性の保証ではありません。 引き続きdangerouslySetInnerHTMLを避け、user-authored HTMLをsanitizeし、third-party codeを 固定してください。

実ネットワークサービスを公開する場合

Murasaki API route は app-local で、public service としては到達できません。Main から Node の TCP / WebSocket / HTTP listener を作れますが、Murasaki runtime-session 保護の 外側に新しい security boundary ができます。

  • remote client が明示的な製品機能でなければ 127.0.0.1 に bind
  • message 処理前に認証
  • message / payload 上限を定義
  • 高コスト処理を rate limit
  • shutdown() で listener を閉じる
  • Murasaki runtime token を独自 protocol の credential に再利用しない

Code signing と update key は別物

Code signing は OS に「誰がこの executable を作ったか」を示します。Ed25519 update signature は Murasaki に「この manifest を app の update key 保有者が承認したか」を 示します。本番 updater は両方を使ってください。OS artifact は Developer ID + notarization または Murasaki の Windows Authenticode --sign flow、更新は manifest signature と payload hash で保護します。

既知の security gap

  • Native capabilityはcommand単位・window別だが、path / URL / argument scopeや独立policy fileがない
  • 宣言windowは1つのapplication originと認証済みlocal Node sessionを共有する。native allowlistはServer Actions、'use main'、API route、updater endpointを隔離しないため、 application code側で操作を認証・認可する必要がある
  • Linux package signing は未実装。Windows Authenticode には developer が用意した certificate / Artifact Signing provider と Windows SDK SignTool が必要
  • Linux の protocol / file-association 登録は未実装。Windows portable archive は意図的に self-register しない
  • renderer native APIは src/main.ts から直接呼べない

正確な状況はプラットフォームと機能の状況 で確認できます。

脆弱性を報告する

public issue を作らないでください。private GitHub Security Advisory または [email protected] を利用してください。1.0 未満では最新 minor release が サポート対象です。

次へ

On this page