From da2c94e248f62147300b112d7ee5ccbb367cf021 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 27 Dec 2025 13:49:58 +0000 Subject: [PATCH 1/3] Add design docs for workflow test mode and .localmostrc - Add docs/roadmap/workflow-test-mode.md: Design for local workflow testing CLI - Add docs/roadmap/localmostrc.md: Design for declarative sandbox policy files - Update README roadmap to link to design docs - Add CLAUDE.md guidance for roadmap item formatting --- CLAUDE.md | 6 + README.md | 7 +- docs/roadmap/localmostrc.md | 235 +++++++++++++++++++++++++++++ docs/roadmap/workflow-test-mode.md | 233 ++++++++++++++++++++++++++++ 4 files changed, 478 insertions(+), 3 deletions(-) create mode 100644 docs/roadmap/localmostrc.md create mode 100644 docs/roadmap/workflow-test-mode.md diff --git a/CLAUDE.md b/CLAUDE.md index a99bccd..5bd8510 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,5 +1,11 @@ # Claude Code Guidelines +## Roadmap + +- Roadmap items in README.md should have a short name and one-sentence description +- Larger features may link to a design doc in `docs/roadmap/` +- Design docs should explain the problem, solution, key design decisions, and edge cases + ## Decision Making - Don't disable warnings, linters, or other developer guardrails without discussing first diff --git a/README.md b/README.md index 9f9362b..7de181c 100644 --- a/README.md +++ b/README.md @@ -217,14 +217,15 @@ npm run make ## Roadmap +Next release: [0.3.0 — Test Locally, Secure by Default](docs/roadmap/release-0.3.0.md) + Future feature ideas: +- **Workflow testing mode** - Run and validate workflows locally before pushing. ([design](docs/roadmap/workflow-test-mode.md)) +- **Declarative sandbox policy** - Per-repo `.localmostrc` files that specify allowed network and filesystem access, with audit logging. ([design](docs/roadmap/localmostrc.md)) - **Trusted contributors for public repos** - Control which repos can run on your machine based on their contributor list. Options: never build public repos, only build repos where all contributors are trusted (default: you + known bots, customizable), or always build (with high-friction confirmation). Repos with untrusted contributors fail with a clear error. - **Graceful heartbeat shutdown** - On clean exit, immediately mark heartbeat stale so workflows fall back to cloud without waiting for the 90s timeout. - **Quick actions** - Re-run failed job, cancel all jobs. -- **Audit logging** - Detailed logs of what each job accessed. -- **Network policy customization** - User-defined network allowlists per repo. -- **Workflow testing mode** - Run and validate workflows locally before pushing. - **Spotlight integration** - Check status or pause builds from Spotlight. - **Artifact inspector** - Browse uploaded artifacts without leaving the app. - **Disk space monitoring** - Warn or pause when disk is low, auto-clean old work dirs. diff --git a/docs/roadmap/localmostrc.md b/docs/roadmap/localmostrc.md new file mode 100644 index 0000000..37b35f5 --- /dev/null +++ b/docs/roadmap/localmostrc.md @@ -0,0 +1,235 @@ +# .localmostrc — Declarative Sandbox Policy + +A checked-in file that explicitly declares what network and filesystem access a workflow needs. + +## Problem + +The current sandbox uses a global allowlist (GitHub, npm, PyPI, etc.). This is: + +1. **Too permissive** — Every repo gets access to everything on the allowlist +2. **Not auditable** — No visibility into what a specific project actually needs +3. **Reactive** — You find out about new access requirements when things fail + +## Solution + +Each repo declares its sandbox policy in `.localmostrc`: + +```yaml +# .localmostrc +version: 1 + +network: + allow: + - registry.npmjs.org + - github.com + - api.fastlane.tools + +filesystem: + read: + - ~/.gitconfig + - ~/.ssh/known_hosts + write: + - ./build/ + - ./DerivedData/ + +env: + - DEVELOPER_DIR + - HOME +``` + +**Default sandbox: deny everything.** Only punch holes for what's declared. + +## The Workflow + +### 1. First run: Discovery mode + +```bash +localmost test --updaterc +``` + +Runs the workflow permissively but logs all access: + +``` +Discovered access: + network: + + registry.npmjs.org (npm install) + + github.com (actions/checkout) + + api.cocoapods.org (pod install) ← new + + filesystem: + + read: ~/.netrc (git credential) + + write: ./Pods/ ← new + +Write to .localmostrc? [y/n] +``` + +### 2. Subsequent runs: Enforced mode + +```bash +localmost test +``` + +Strictly enforces `.localmostrc`. New access = hard failure: + +``` +✗ Network access denied: api.sketchy-cdn.com + Not in .localmostrc allowlist + + To allow, run: localmost test --updaterc +``` + +### 3. Background runner: Cached policy + diff review + +The app caches `.localmostrc` per repo. When it changes: + +``` +┌─────────────────────────────────────────────────┐ +│ Policy change detected: myorg/mygame │ +│ │ +│ + network: api.newservice.com │ +│ - network: api.oldservice.com (removed) │ +│ │ +│ [Allow] [Deny] [View Diff] │ +└─────────────────────────────────────────────────┘ +``` + +A compromised dependency that tries to exfiltrate data would: +1. Fail immediately (not in allowlist) +2. Require explicit human approval to add + +## File Format + +### Full schema + +```yaml +# .localmostrc +version: 1 + +network: + allow: + - "*.github.com" # Wildcard subdomain + - "registry.npmjs.org" # Exact match + - "cdn.cocoapods.org" + deny: # Explicit denials (optional, for clarity) + - "*.analytics.com" + +filesystem: + read: + - "~/.gitconfig" + - "~/.ssh/known_hosts" + write: + - "./build/**" + - "./DerivedData/**" + deny: + - "~/.aws/*" # Explicit paranoia + - "~/.ssh/id_*" + +env: + allow: + - DEVELOPER_DIR + - HOME + - PATH + deny: + - AWS_* + - GITHUB_TOKEN # Don't leak to subprocesses +``` + +### Wildcards + +| Pattern | Matches | +|---------|---------| +| `*.github.com` | `api.github.com`, `raw.githubusercontent.com` | +| `registry.npmjs.org` | Exact match only | +| `./build/**` | All files under `build/` recursively | +| `~/.ssh/id_*` | `~/.ssh/id_rsa`, `~/.ssh/id_ed25519`, etc. | + +### Per-job overrides (future) + +```yaml +jobs: + build: + network: + allow: [npm, github] + deploy: + network: + allow: [npm, github, fastlane] +``` + +## Why Checked Into Git + +**Version controlled:** +- Team members share the same policy +- PR review catches suspicious additions +- History shows when/why access was added + +**Auditable:** +```bash +# Find all repos in your org that access unusual domains +grep -r "analytics" *//.localmostrc +``` + +**Diff-friendly:** +```diff +network: + allow: + - registry.npmjs.org + - github.com ++ - api.newservice.com # Added for feature X +``` + +## CLI Commands + +| Command | Behavior | +|---------|----------| +| `localmost test` | Enforce `.localmostrc`, fail on violations | +| `localmost test --updaterc` | Permissive, record access, prompt to update | +| `localmost test --dry-run` | Show what *would* be accessed without running | +| `localmost policy show` | Display current policy for this repo | +| `localmost policy diff` | Compare local vs cached policy | + +## Edge Cases + +### No `.localmostrc` exists + +``` +No .localmostrc found. Run with --updaterc to generate. +Running in permissive mode (not recommended for untrusted code). +``` + +### Conflict with global allowlist + +When a `.localmostrc` exists, it **replaces** the global allowlist entirely for that repo. This is intentional — the repo author knows what they need. + +### CI vs local differences + +Some access may only be needed in CI (deployment credentials) or only locally (debug tools). Use comments to document: + +```yaml +network: + allow: + - registry.npmjs.org + - fastlane.tools # CI only: app store deployment +``` + +## Security Benefits + +1. **Least privilege by default** — No more global allowlist. Each repo declares exactly what it needs. + +2. **Auditable** — The file is in git. You can grep your org for repos that access unusual domains. + +3. **Low friction** — `--updaterc` generates the policy for you. No manual authoring. + +4. **Supply chain defense** — A malicious package update that phones home gets blocked unless someone explicitly approves the new domain in a PR. + +5. **Defense in depth** — Even if code escapes the sandbox, it can only access declared resources. + +## Integration with Workflow Test Mode + +See [workflow-test-mode.md](./workflow-test-mode.md) for the local testing CLI design. + +The test CLI becomes the policy authoring tool: +- Run your workflow locally +- Let it discover what access it needs +- Review and commit the generated `.localmostrc` + +This creates a natural workflow where security policy is generated from actual behavior, not guessed upfront. diff --git a/docs/roadmap/workflow-test-mode.md b/docs/roadmap/workflow-test-mode.md new file mode 100644 index 0000000..b709ba1 --- /dev/null +++ b/docs/roadmap/workflow-test-mode.md @@ -0,0 +1,233 @@ +# Workflow Test Mode + +Run and validate GitHub Actions workflows locally before pushing. + +## Problem + +The CI feedback loop is painfully slow: + +``` +push → wait 20 min → CI fails (YAML typo) → fix → push → wait 20 min → repeat +``` + +This kills flow state. For developers iterating fast, it's death by a thousand cuts. + +## Solution + +A standalone CLI command that executes workflows locally in seconds: + +```bash +localmost test # Run default workflow +localmost test .github/workflows/build.yml # Run specific workflow +localmost test build.yml --job build-ios # Run specific job +``` + +### Core principle: Parity over perfection + +The goal isn't to perfectly simulate GitHub's environment — it's to catch 90% of failures in 10% of the time. Optimize for fast feedback, not identical reproduction. + +## User Experience + +Output shows real-time streaming, just like watching CI: + +``` +▶ build-ios + ✓ actions/checkout@v4 (0.3s) + ✓ Setup Xcode (1.2s) + ⠋ Run xcodebuild... (23s) +``` + +On failure, show exactly what diverged and suggest fixes. + +## Architecture + +``` +┌─────────────────────────────────────────────────┐ +│ localmost test │ +├─────────────────────────────────────────────────┤ +│ 1. Parse workflow YAML │ +│ 2. Resolve matrix/conditionals │ +│ 3. For each step: │ +│ - If action → fetch + run in sandbox │ +│ - If run → execute in sandbox │ +│ 4. Stream output, capture exit codes │ +│ 5. Report pass/fail + diff from expected │ +└─────────────────────────────────────────────────┤ + │ + ▼ + ┌──────────────────────────────┐ + │ Existing runner sandbox │ + │ (network allowlist, fs box) │ + └──────────────────────────────┘ +``` + +### Standalone CLI — No App Required + +``` +┌─────────────────────────────────────────────────────────┐ +│ npm install -g localmost │ +│ cd my-project │ +│ localmost test │ +│ │ +│ No app. No tray. No auth. No GitHub API calls. │ +└─────────────────────────────────────────────────────────┘ +``` + +This is critical for adoption. The test command should work without installing the full Electron app. + +The upgrade path: + +``` +Day 1: npx localmost test (zero install) + ↓ + npm install -g localmost (faster repeat runs) + ↓ +Day 7: localmost start (install app, get background CI) +``` + +## Key Design Decisions + +### 1. Use the working tree, not a fresh clone + +GitHub runners do a fresh checkout. But for local testing, use the current working directory — including uncommitted changes. That's the whole point: test *before* you commit. + +```bash +localmost test # Uses current directory with uncommitted changes +localmost test --staged # Uses staged changes only +``` + +**Implementation:** + +```bash +# Fast copy using rsync, respecting .gitignore +rsync -a --exclude-from=.gitignore ./ /tmp/localmost-run-xyz/ + +# Or for speed, use hard-link copy (instant, copy-on-write) +cp -al ./ /tmp/localmost-run-xyz/ +``` + +The temp directory becomes the runner's `$GITHUB_WORKSPACE`. + +### 2. Intercept `actions/checkout` + +When parsing the workflow, detect checkout steps and replace with a synthetic step: + +```typescript +if (step.uses?.startsWith('actions/checkout')) { + return { + type: 'synthetic', + run: async (ctx) => { + ctx.env.GITHUB_SHA = await exec('git rev-parse HEAD'); + ctx.env.GITHUB_REF = await exec('git symbolic-ref HEAD'); + ctx.log('Using local working tree'); + } + }; +} +``` + +**Edge cases:** + +| Case | Behavior | +|------|----------| +| `actions/checkout` with `ref:` param | Warn: "Ignoring ref: using local HEAD" | +| `actions/checkout` with `repository:` param | Clone that repo (it's a dependency) | +| Multiple checkouts (monorepo patterns) | First is stubbed, others clone normally | +| `actions/checkout` with `submodules: true` | Run `git submodule update` in the copy | + +### 3. Stub expensive/external steps by default + +Some steps don't make sense locally: + +- `actions/upload-artifact` — no point uploading to GitHub +- `actions/cache` — redirect to local cache transparently +- Deployment steps — dangerous to run locally + +Default behavior: stub with a warning. Opt-in to actually run them. + +``` +⚠ actions/upload-artifact@v4 — stubbed (use --live-artifacts to upload) +``` + +### 4. Secrets: prompt or stub, never guess + +``` +⚠ Secret APPSTORE_CONNECT_KEY not found + [s] Stub with empty string + [p] Prompt for value (stored in keychain) + [a] Abort +``` + +Store prompted secrets locally (encrypted) so you don't re-enter them every run. + +### 5. Matrix builds: run one by default + +```bash +localmost test # Run first matrix combo only (fast) +localmost test --full-matrix # Run all matrix combinations +``` + +Solo devs usually care about one platform. Don't waste time on the full matrix unless asked. + +### 6. Show environment diff + +After the run, surface environment differences: + +``` +Environment diff: + RUNNER_OS: macOS (local) vs macos-latest (GitHub: macOS 14.5) + Xcode: 16.0 (local) vs 15.4 (GitHub default) + + Suggestion: Add `xcode-select` step to pin Xcode version +``` + +This catches most "works locally, fails in CI" bugs. + +### 7. Redirect `actions/cache` to local storage + +Intercept cache actions and point them at a local directory: + +```typescript +if (step.uses?.startsWith('actions/cache')) { + // Redirect to ~/.localmost/cache/ + // Same key-based lookup, just local storage +} +``` + +This makes repeated test runs much faster than GitHub — no network upload/download. + +## What NOT to Build + +- **Perfect GitHub environment emulation** — Docker-based GitHub runner images exist. They're slow and heavy. Don't compete there. +- **Visual workflow editor** — Out of scope. This is a CLI-first power tool. +- **Hosted "preview" runs** — Keep it fully local. No new infrastructure. + +## Standalone CLI Components + +| Component | Source | +|-----------|--------| +| Workflow YAML parser | Shared library (already exists) | +| Step executor | Extracted from runner logic | +| Sandbox (fs/network) | Simplified version — no proxy server, just `sandbox-exec` | +| Action fetcher | Download from GitHub, cache in `~/.localmost/actions/` | + +The heavy parts of the current app — GitHub auth, runner registration, heartbeat, tray UI — aren't needed for local testing. + +## Integration with .localmostrc + +See [localmostrc.md](./localmostrc.md) for the sandbox policy file design. + +| Command | Behavior | +|---------|----------| +| `localmost test` | Enforce `.localmostrc`, fail on violations | +| `localmost test --updaterc` | Permissive, record access, prompt to update file | +| `localmost test --dry-run` | Show what *would* be accessed without running | + +## Why This Wins + +| Alternative | Problem | +|-------------|---------| +| `act` (existing tool) | Docker-based, slow, Linux-only containers | +| Manual script | Doesn't match workflow semantics | +| Just push and see | 20-minute feedback loop | + +localmost already has the sandbox, the runner infrastructure, and the macOS focus. This feature is a natural extension that reuses existing primitives while solving the tightest pain point in the CI loop. From 46dfb034ce9bdc0ea9644fa1fbd88d014985e9ad Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 27 Dec 2025 14:01:35 +0000 Subject: [PATCH 2/3] Add 0.3.0 release plan with implementation tasks Theme: Test locally, secure by default - Workflow test mode: local CLI for running workflows before push - Declarative sandbox policy: .localmostrc for per-repo access control - CLI polish: improved install and command experience Breaks down into 10 implementation phases with specific tasks. --- docs/roadmap/release-0.3.0.md | 191 ++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 docs/roadmap/release-0.3.0.md diff --git a/docs/roadmap/release-0.3.0.md b/docs/roadmap/release-0.3.0.md new file mode 100644 index 0000000..a30c18c --- /dev/null +++ b/docs/roadmap/release-0.3.0.md @@ -0,0 +1,191 @@ +# Release 0.3.0: Test Locally, Secure by Default + +Theme: Shift left on both feedback and security. Catch workflow problems before pushing, and enforce least-privilege sandboxing by default. + +## Features + +### 1. Workflow Test Mode ([design](./workflow-test-mode.md)) + +Run workflows locally before pushing. + +```bash +localmost test # Run default workflow +localmost test --updaterc # Discover and record access policy +``` + +### 2. Declarative Sandbox Policy ([design](./localmostrc.md)) + +Per-repo `.localmostrc` files that declare allowed access. Default-deny sandbox. + +```yaml +network: + allow: + - registry.npmjs.org +filesystem: + write: + - ./build/ +``` + +### 3. CLI Polish + +The CLI is now the primary entry point. It needs to be great. + +--- + +## Implementation Tasks + +### Phase 1: Standalone CLI Foundation + +The test command must work without the Electron app. + +- [ ] Extract shared code into `src/shared/` that works in both CLI and app contexts + - [ ] Workflow YAML parser + - [ ] Sandbox profile generator + - [ ] Action fetcher and cache +- [ ] Create `src/cli/test.ts` command structure +- [ ] Implement working tree snapshot + - [ ] Fast copy via hard links (`cp -al`) with fallback to rsync + - [ ] Respect `.gitignore` by default, `--no-ignore` flag to include all + - [ ] Create temp workspace in `~/.localmost/workspaces/` +- [ ] Add cleanup of old workspaces (keep last N, or age-based) + +### Phase 2: Action Interception + +Synthetic replacements for common actions. + +- [ ] `actions/checkout` interception + - [ ] Stub when checking out current repo (use local working tree) + - [ ] Clone normally when `repository:` points elsewhere + - [ ] Handle `submodules: true` via `git submodule update` + - [ ] Set `GITHUB_SHA`, `GITHUB_REF` from local git state +- [ ] `actions/cache` redirection + - [ ] Local cache directory at `~/.localmost/cache/` + - [ ] Same key-based lookup semantics + - [ ] Cache hit/miss reporting +- [ ] `actions/upload-artifact` stubbing + - [ ] Save to `~/.localmost/artifacts/` instead of uploading + - [ ] Report what would have been uploaded +- [ ] `actions/download-artifact` stubbing + - [ ] Look for artifacts from previous local runs + - [ ] Warn if artifact not found locally + +### Phase 3: Step Execution + +Run workflow steps in the sandbox. + +- [ ] Step executor that handles both `run:` and `uses:` steps +- [ ] Action fetcher + - [ ] Download actions from GitHub on first use + - [ ] Cache in `~/.localmost/actions/` + - [ ] Handle action versions (`@v4`, `@main`, `@sha`) +- [ ] Environment setup + - [ ] Set standard GitHub env vars (`GITHUB_WORKSPACE`, `RUNNER_OS`, etc.) + - [ ] Warn on vars that differ from GitHub (Xcode version, etc.) +- [ ] Output streaming with real-time display +- [ ] Exit code capture and reporting +- [ ] Matrix handling + - [ ] Run first combination by default + - [ ] `--full-matrix` to run all + - [ ] `--matrix "os=macos-latest,node=18"` to run specific combo + +### Phase 4: Secrets Handling + +- [ ] Detect secrets referenced in workflow (`${{ secrets.FOO }}`) +- [ ] Prompt modes: stub, prompt for value, abort +- [ ] Store prompted values in macOS Keychain (encrypted) +- [ ] `localmost secrets list` — show stored secrets for a repo +- [ ] `localmost secrets clear` — remove stored secrets + +### Phase 5: .localmostrc Parser and Validator + +- [ ] Define YAML schema for `.localmostrc` v1 +- [ ] Parser with helpful error messages for invalid files +- [ ] Wildcard expansion (`*.github.com`, `./build/**`) +- [ ] Schema validation on load + +### Phase 6: Discovery Mode (`--updaterc`) + +- [ ] Hook sandbox to log all access attempts (network, filesystem) +- [ ] Run workflow in permissive mode while recording +- [ ] Deduplicate and categorize access (by step, by type) +- [ ] Interactive prompt to write/update `.localmostrc` +- [ ] Diff display when updating existing file +- [ ] `--dry-run` to show what would be recorded without writing + +### Phase 7: Enforcement Mode + +- [ ] Generate `sandbox-exec` profile from `.localmostrc` +- [ ] Clear error messages when access is denied + - [ ] Show which policy would allow it + - [ ] Suggest `localmost test --updaterc` to add +- [ ] Fallback behavior when no `.localmostrc` exists + - [ ] Warn and run in permissive mode + - [ ] Or `--strict` flag to fail without policy file + +### Phase 8: Background Runner Integration + +- [ ] Cache `.localmostrc` per repo in app data +- [ ] On job pickup, compare repo's `.localmostrc` to cached version +- [ ] If changed: show diff in notification, require approval +- [ ] If new repo: show policy summary, require initial approval +- [ ] Policy approval UI in app + - [ ] Side-by-side diff view + - [ ] Per-line approve/reject (future) +- [ ] Audit log of policy changes and approvals + +### Phase 9: CLI Polish + +- [ ] Improve install experience + - [ ] `brew install localmost` (Homebrew formula) + - [ ] `npx localmost` works without global install + - [ ] Post-install message with next steps +- [ ] Consistent command structure + - [ ] `localmost test` — run workflow locally + - [ ] `localmost start` — launch background app + - [ ] `localmost status` — show runner state + - [ ] `localmost policy show` — display current repo's policy + - [ ] `localmost policy diff` — compare local vs cached +- [ ] Helpful error messages + - [ ] Suggest fixes for common problems + - [ ] Link to docs for complex issues +- [ ] `--help` for all commands with examples +- [ ] `--version` shows version, build info, and update availability + +### Phase 10: Environment Diff Reporting + +- [ ] Detect local environment (Xcode version, macOS version, installed tools) +- [ ] Compare to GitHub runner environment (fetch from known list) +- [ ] Report differences after test run +- [ ] Suggest workflow changes to pin versions +- [ ] `localmost env` — show local environment details + +--- + +## Out of Scope for 0.3.0 + +- Visual workflow editor +- Per-job policy overrides in `.localmostrc` +- Remote policy management (org-wide policies) +- Windows/Linux support + +--- + +## Success Criteria + +1. A developer can run `npx localmost test` in a repo with GitHub Actions and see their workflow execute locally in under a minute. + +2. When a workflow accesses something not in `.localmostrc`, it fails with a clear message explaining what was blocked and how to allow it. + +3. A developer can generate a complete `.localmostrc` for an existing project by running `localmost test --updaterc` once. + +4. The background runner refuses to execute jobs when the repo's `.localmostrc` has changed, until the user reviews and approves the diff. + +--- + +## Open Questions + +1. **Policy inheritance**: Should orgs be able to define base policies that repos inherit from? (Probably 0.4.0) + +2. **Transitive dependencies**: When `npm install` pulls a new package that phones home, how do we surface that it was the package, not the workflow directly? (Nice to have for 0.3.0) + +3. **CI-only steps**: Some steps only make sense in CI (deploy, release). Should `.localmostrc` have a way to mark steps as CI-only so they're skipped locally? Or is commenting them out sufficient? From ae0136dbe259ad2d5da7b6c47089e60d244beb79 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 27 Dec 2025 14:07:23 +0000 Subject: [PATCH 3/3] Add per-workflow policy sections to .localmostrc design - Policies now have shared (baseline) and workflows (per-workflow) sections - Each workflow inherits from shared, can add or deny access - Deploy workflow can access fastlane without exposing it to build/test - Updated release plan to include policy merge logic tasks --- docs/roadmap/localmostrc.md | 185 +++++++++++++++++++++++++--------- docs/roadmap/release-0.3.0.md | 21 ++-- 2 files changed, 151 insertions(+), 55 deletions(-) diff --git a/docs/roadmap/localmostrc.md b/docs/roadmap/localmostrc.md index 37b35f5..4a5df83 100644 --- a/docs/roadmap/localmostrc.md +++ b/docs/roadmap/localmostrc.md @@ -18,26 +18,23 @@ Each repo declares its sandbox policy in `.localmostrc`: # .localmostrc version: 1 -network: - allow: - - registry.npmjs.org - - github.com - - api.fastlane.tools - -filesystem: - read: - - ~/.gitconfig - - ~/.ssh/known_hosts - write: - - ./build/ - - ./DerivedData/ +shared: # Applies to all workflows + network: + allow: + - registry.npmjs.org + - github.com + filesystem: + write: + - ./build/ -env: - - DEVELOPER_DIR - - HOME +workflows: # Per-workflow additions + deploy: + network: + allow: + - api.fastlane.tools # Only deploy needs this ``` -**Default sandbox: deny everything.** Only punch holes for what's declared. +**Default sandbox: deny everything.** Only punch holes for what's declared. Each workflow gets shared policy plus its own additions. ## The Workflow @@ -105,33 +102,48 @@ A compromised dependency that tries to exfiltrate data would: # .localmostrc version: 1 -network: - allow: - - "*.github.com" # Wildcard subdomain - - "registry.npmjs.org" # Exact match - - "cdn.cocoapods.org" - deny: # Explicit denials (optional, for clarity) - - "*.analytics.com" - -filesystem: - read: - - "~/.gitconfig" - - "~/.ssh/known_hosts" - write: - - "./build/**" - - "./DerivedData/**" - deny: - - "~/.aws/*" # Explicit paranoia - - "~/.ssh/id_*" - -env: - allow: - - DEVELOPER_DIR - - HOME - - PATH - deny: - - AWS_* - - GITHUB_TOKEN # Don't leak to subprocesses +# Shared policy — baseline for all workflows +shared: + network: + allow: + - "*.github.com" # Wildcard subdomain + - "registry.npmjs.org" # Exact match + deny: # Explicit denials (optional, for clarity) + - "*.analytics.com" + + filesystem: + read: + - "~/.gitconfig" + - "~/.ssh/known_hosts" + write: + - "./build/**" + deny: + - "~/.aws/*" # Explicit paranoia + - "~/.ssh/id_*" + + env: + allow: + - DEVELOPER_DIR + - HOME + - PATH + deny: + - AWS_* + - GITHUB_TOKEN # Don't leak to subprocesses + +# Per-workflow policies — merged with shared +workflows: + build: + filesystem: + write: + - "./DerivedData/**" + + deploy: + network: + allow: + - "api.fastlane.tools" + secrets: + require: + - APPSTORE_CONNECT_KEY ``` ### Wildcards @@ -143,16 +155,93 @@ env: | `./build/**` | All files under `build/` recursively | | `~/.ssh/id_*` | `~/.ssh/id_rsa`, `~/.ssh/id_ed25519`, etc. | -### Per-job overrides (future) +### Per-workflow policies + +Policies have two levels: **shared** (applies to all workflows) and **per-workflow** (scoped to a specific workflow file). ```yaml -jobs: +# .localmostrc +version: 1 + +# Shared policy — applies to ALL workflows +shared: + network: + allow: + - "*.github.com" + - "registry.npmjs.org" + filesystem: + read: + - "~/.gitconfig" + write: + - "./build/**" + +# Per-workflow policies — only apply to specific workflows +workflows: + # Matches .github/workflows/build.yml build: network: - allow: [npm, github] + allow: + - "cdn.cocoapods.org" # CocoaPods for iOS builds + filesystem: + write: + - "./Pods/**" + - "./DerivedData/**" + + # Matches .github/workflows/deploy.yml deploy: network: - allow: [npm, github, fastlane] + allow: + - "api.fastlane.tools" # App Store deployment + - "itunesconnect.apple.com" + filesystem: + read: + - "~/.fastlane/**" # Fastlane credentials + env: + allow: + - FASTLANE_* + - MATCH_* + secrets: + require: # These secrets MUST be provided + - APPSTORE_CONNECT_KEY + - MATCH_PASSWORD + + # Matches .github/workflows/test.yml + test: + # No additional permissions — inherits only shared policy +``` + +**Resolution order:** +1. Start with `shared` policy +2. Merge workflow-specific policy (additive) +3. Explicit `deny` in workflow policy can revoke shared access + +**Why this matters:** +- A compromised test dependency can't access deploy credentials +- Build workflow can't phone home to analytics even if deploy can +- Each workflow gets exactly what it needs, nothing more + +**Workflow matching:** +- Keys under `workflows:` match the workflow filename (without `.yml`/`.yaml`) +- `build` matches `.github/workflows/build.yml` +- For matrix workflows, all jobs in the workflow share the workflow's policy + +**Discovery mode with per-workflow policies:** + +```bash +localmost test --updaterc build.yml +``` + +``` +Discovered access for build.yml: + shared (already allowed): + ✓ registry.npmjs.org + ✓ github.com + + workflow-specific (new): + + network: cdn.cocoapods.org + + filesystem write: ./Pods/** + +Add to .localmostrc under workflows.build? [y/n] ``` ## Why Checked Into Git diff --git a/docs/roadmap/release-0.3.0.md b/docs/roadmap/release-0.3.0.md index a30c18c..77ffad2 100644 --- a/docs/roadmap/release-0.3.0.md +++ b/docs/roadmap/release-0.3.0.md @@ -18,12 +18,15 @@ localmost test --updaterc # Discover and record access policy Per-repo `.localmostrc` files that declare allowed access. Default-deny sandbox. ```yaml -network: - allow: - - registry.npmjs.org -filesystem: - write: - - ./build/ +shared: + network: + allow: + - registry.npmjs.org +workflows: + deploy: + network: + allow: + - api.fastlane.tools # Only deploy needs this ``` ### 3. CLI Polish @@ -99,8 +102,12 @@ Run workflow steps in the sandbox. ### Phase 5: .localmostrc Parser and Validator - [ ] Define YAML schema for `.localmostrc` v1 + - [ ] `shared:` section for baseline policy + - [ ] `workflows:` section with per-workflow overrides + - [ ] Policy merge logic (workflow inherits from shared, can add or deny) - [ ] Parser with helpful error messages for invalid files - [ ] Wildcard expansion (`*.github.com`, `./build/**`) +- [ ] Workflow name matching (e.g., `build` matches `build.yml`) - [ ] Schema validation on load ### Phase 6: Discovery Mode (`--updaterc`) @@ -164,7 +171,7 @@ Run workflow steps in the sandbox. ## Out of Scope for 0.3.0 - Visual workflow editor -- Per-job policy overrides in `.localmostrc` +- Per-job policy overrides within a workflow (per-workflow is sufficient) - Remote policy management (org-wide policies) - Windows/Linux support