Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Index macOS Client

Index is a macOS 13+ WKWebView client with a native, credential-free request bridge. It is an optional owner-control surface: standalone Hermes connection does not require the Index app to be installed or running.

Security model

The signed app stores one Better Auth session token — this device's own session — in the Keychain. Raw credentials and authorization headers never enter browser JavaScript, WebKit storage, Application Support records, logs, callback URLs, or generated HTML.

The native indexAPI bridge accepts only the exact bundled main document and document generation. JavaScript supplies structured, allowlisted requests; Swift constructs the fixed HTTP API URLs, reads the owner credential natively, validates body/schema/resource bounds, and returns sanitized data only. It permits bounded REST, upload, and SSE operations (32 pending requests, 1 MiB ordinary request/response, 8 MiB decoded images, 64 KiB events, 256 events; 30-second ordinary and five-minute stream deadlines). It never accepts a browser-supplied URL, header, credential, or transport override.

Owner sign-in

Login opens the web /cli-auth page (the same state-bound handshake the CLI uses) with a loopback callback on http://127.0.0.1:<port>/callback. The page runs the device authorization grant against the owner's browser session and returns only a short-lived device code, which the app exchanges at /api/auth/device/token for its own 30-day session; the app verifies the Keychain write/read-back before treating login as complete. There is no approval prompt: the page mints and approves the code itself, so no externally supplied code can enter the grant, and the loopback binding is what keeps that safe.

Logout quarantines bridge work, pauses/scrubs Hermes local activity, deletes the Keychain item, then revokes the session server-side with its own token. A failed Keychain deletion retains the credential and never claims logout completed. A failed revocation still signs the app out locally; the device can also be revoked from Index web settings.

Hermes runtime

The native app may show the same owner controls as the web, but it is not required for direct Hermes use. Hermes setup writes the session into ~/.hermes/.env and installs the plugin. The plugin authenticates with INDEX_SESSION_TOKEN. The local runtime uses generation-fenced fallback and cron ownership markers: it pauses only the exact owned schedule and preserves unrelated Hermes state.

Build and source checks

cd apps/mac
python3 assemble.py       # regenerates Resources/index.html
./scripts/build.sh                # macOS Swift build

Generated HTML must be regenerated through assemble.py, never hand-edited. The production source boundary disables Web Inspector; development inspection requires the explicit development build flag.

Direct Developer ID distribution

Production distribution is direct Developer ID distribution, not the Mac App Store. It requires macOS 13+, Universal 2 artifacts, Hardened Runtime, Developer ID signing, notarization, stapling, checksums, immutable production HTTPS endpoint inputs, and a clean-account acceptance run. App Sandbox is not a production requirement for this direct-distribution model; release validation rejects unexpected sandbox/debug entitlements rather than requiring them.

Pushes to dev and main that touch apps/mac run .github/workflows/mac-app-release.yml: Developer ID sign, notarize the app, package the branded DMG, notarize that, and attach Index.dmg plus Index.zip (the same stapled app) to a rolling release.

Signed installs update themselves: on launch and every 30 minutes the app compares its IndexBuildSHA with the commit in its channel's release notes, downloads Index.zip in the background, accepts it only if it satisfies the running app's designated requirement, prompts once with Restart Now / Later, and swaps the bundle when the app quits. Ad-hoc, translocated, or read-only installs fall back to the DMG from Index ▸ Check for Updates….

Branch Release Download
dev prerelease mac-dev https://github.com/indexnetwork/mac-client/releases/download/mac-dev/Index.dmg
main mac (latest) https://github.com/indexnetwork/mac-client/releases/download/mac/Index.dmg

Required Actions secrets (fail closed if any are missing): MAC_CODESIGN_P12, MAC_CODESIGN_P12_PASSWORD, MAC_CODESIGN_IDENTITY, MAC_APP_IDENTIFIER_PREFIX, MAC_PROVISIONING_PROFILE, MAC_NOTARY_KEY, MAC_NOTARY_KEY_ID, MAC_NOTARY_ISSUER. Pull requests do not produce a DMG; they stay on the ad-hoc compile in mac-app-build.yml.

./scripts/notarize.sh and ./scripts/dmg.sh accept either a local NOTARYTOOL_PROFILE or CI's NOTARYTOOL_KEY / NOTARYTOOL_KEY_ID / NOTARYTOOL_ISSUER.

Development: Hot-Reload Mode

For rapid iteration without rebuilding the Swift binary each time:

cd apps/mac
./dev.sh

This will:

  • Watch src/ for changes (JSX, HTML, CSS)
  • Re-run assemble.py on each change to update Resources/index.html
  • Automatically open the app (if not running) or trigger a reload

The app will hot-reload as you edit files, great for UI tweaking.

dev.sh defaults INDEX_DEVELOPMENT_BUILD=1: the build enables the web inspector and, because an ad-hoc build carries no provisioning-profile-authorized Keychain access group, stores the owner credential in the login keychain so sign-in works locally. Production (signed) builds are unaffected and still fail closed without the authorized owner group. Override with INDEX_DEVELOPMENT_BUILD=0 ./dev.sh.

To manually reload during development, press Cmd+R (standard browser reload) in the app, or close and relaunch.

Troubleshooting Build

"AssertionError: no @font-face url() references found"

  • The CSS must reference fonts at fonts/jetbrains-mono-latin-var.woff2 etc.
  • Check src/index.html for correct paths.

Swift compilation fails

  • Ensure you have Xcode Command Line Tools: xcode-select --install
  • Try swiftc -version to verify.

"codesign failed"

  • Ad-hoc signing is skipped for local builds. The app will still run locally.

Deep links

The app opens two URL families, and all routing lives in one pure function, parseDeepLink in api/deeplink.mjs, inlined into the bundle as window.IndexApi.parseDeepLink. The Swift shell only delivers URLs — it raises the window and forwards the raw string to the page as an index-deeplink CustomEvent, queuing anything that arrives before the web view has finished loading (cold launch).

URL Opens
https://index.network/o/<id> · index://o/<id> that opportunity's card
https://index.network/u/<id> · index://u/<id> that person's profile
https://index.network/i/<id> · index://i/<id> that signal
index://q/<question-id> the signal that owns that pending question
index://chat/<conversation-id> that conversation's chat inside its signal

The q/chat routes are minted only by the app's own desktop notifications (no web page serves them), so they matter mostly as the toast tap target.

Query strings, fragments and trailing slashes are ignored; foreign hosts, unknown paths and malformed URLs are ignored silently. Extra hosts (staging) are a hosts argument, not a code change.

Known limitation: universal links need a real signature

The https:// half only works in a Developer ID-signed, notarized build. build.sh generates the com.apple.developer.associated-domains entitlement for the selected INDEX_LINK_HOST and passes that generated plist to codesign; Index.entitlements is not the signed build input. macOS verifies the resulting signed-app entitlement (for the default profile, applinks:index.network) against the host's apple-app-site-association, which lists <APPLE_TEAM_ID>.network.index.system6 — so the web host also needs APPLE_TEAM_ID set. Inspect the built artifact with codesign -d --entitlements :- dist/Index.app. An ad-hoc dev build has no team, so macOS never hands it a universal link and build.sh says so.

The index:// scheme (registered via CFBundleURLTypes in Info.plist) has no such requirement and is the way to exercise deep links locally:

open "index://o/<opportunity-id>"
open "index://u/<user-id>"
# only on a signed, notarized build:
open "https://index.network/o/<opportunity-id>"

Developer ID dev handoff

This operator-only handoff runs on macOS with a Developer ID Application identity and local notarytool profile. It never places endpoint credentials in the bundle.

  1. Register the explicit App ID network.index.system6, enable Associated Domains enabled, and create a Developer ID provisioning profile that authorizes exactly the owner Keychain group.
  2. Build with all four required inputs. The prefix has a trailing period and must match the profile/Team application-identifier prefix:
cd apps/mac
INDEX_LINK_HOST=dev.index.network \
INDEX_APP_IDENTIFIER_PREFIX='TEAM123ABC.' \
CODESIGN_IDENTITY='Developer ID Application: <name> (<team-id>)' \
PROVISIONING_PROFILE='<path-to-downloaded-profile>' \
./scripts/build.sh
  1. Verify embedded.provisionprofile, signature/entitlements, then notarize using a local keychain profile:
NOTARYTOOL_PROFILE='<local-keychain-profile>' ./scripts/notarize.sh

A runtime error such as No matching profile found means the profile does not authorize the launched app; do not treat prior signing/notarization checks as success. Verify that https://dev.index.network/u/<id>/chat stays in the browser while an allowed opportunity/profile link opens the signed app.

  1. Package the distributable disk image from the same verified app:
NOTARYTOOL_PROFILE='<local-keychain-profile>' ./scripts/dmg.sh
xcrun stapler validate dist/Index.dmg

./scripts/dmg.sh revalidates the signed, stapled bundle, lays out the branded disk image, notarizes it, and staples dist/Index.dmg. The local DMG is for debugging; GitHub Releases are the distribution path. Replace scripts/dmg-background.png (660×420) and scripts/dmg-background@2x.png (1320×840) to change the Finder window art; keep those sizes so they match the icon layout. The art carries only the card and arrow: Finder draws the real icons and names at the positions set in dmg.sh, so do not bake them into the image. The mounted-disk glyph (title bar / desktop) is .VolumeIcon.icns, copied from the app's AppIcon.icns.

Record only redacted commands and pass/fail status in PR evidence; never IDs, credentials, certificate subjects, or profile names.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages