{"id":50969364,"url":"https://github.com/megabyte0x/safegit","last_synced_at":"2026-06-19T00:30:42.640Z","repository":{"id":358896361,"uuid":"1241752145","full_name":"megabyte0x/safegit","owner":"megabyte0x","description":"Safe-backed Git approval CLI, API, and GitHub required-check workflow","archived":false,"fork":false,"pushed_at":"2026-05-21T08:31:47.000Z","size":69,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-21T14:09:21.459Z","etag":null,"topics":["eip-712","erc-1271","git","github-actions","multisig","safe","viem"],"latest_commit_sha":null,"homepage":"https://agent-cortex.github.io/safegit-site/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/megabyte0x.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-17T19:16:39.000Z","updated_at":"2026-05-21T08:31:52.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/megabyte0x/safegit","commit_stats":null,"previous_names":["agent-cortex/safegit","megabyte0x/safegit"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/megabyte0x/safegit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/megabyte0x%2Fsafegit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/megabyte0x%2Fsafegit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/megabyte0x%2Fsafegit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/megabyte0x%2Fsafegit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/megabyte0x","download_url":"https://codeload.github.com/megabyte0x/safegit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/megabyte0x%2Fsafegit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34513020,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-18T02:00:06.871Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["eip-712","erc-1271","git","github-actions","multisig","safe","viem"],"created_at":"2026-06-19T00:30:41.761Z","updated_at":"2026-06-19T00:30:42.619Z","avatar_url":"https://github.com/megabyte0x.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SafeGit\n\nSafeGit makes protected Git changes require Safe multisig approval.\n\nIt creates an EIP-712 `GitCommitApproval` payload for a commit, collects Safe owner signatures, stores approval state in Postgres, and lets CI enforce a `Safe Verified` required check before merge.\n\n\u003e SafeGit does **not** try to fake GitHub's native `Verified` badge. GitHub's badge supports GPG, SSH, and S/MIME signatures; Safe smart accounts validate signatures through Safe owner signatures / ERC-1271 patterns. SafeGit uses a GitHub required-check flow instead.\n\n## What is included\n\n- Git wrapper CLI: `safegit add`, `commit`, `status`, `push`, and other Git commands pass through to `git`\n- SafeGit overlay commands: `safegit setup`, `sign`, `approval status`, `verify`, plus legacy `request` and `attest`\n- HTTP API for approval retrieval and signature submission\n- Built-in browser approval page at `/approve/:approvalId`\n- Postgres shared state for repos, approval requests, and signatures\n- EIP-712 typed-data payload generation\n- Signature recovery with `viem.recoverTypedDataAddress`\n- Optional live Safe `getOwners()` / `getThreshold()` validation via RPC\n- Docker Compose for local API + Postgres\n- GitHub Action template for a `Safe Verified` required check\n- pnpm-only package management\n\n## Architecture\n\n```text\nDeveloper runs safegit as a git wrapper\n  -\u003e safegit add / commit delegate to git\n  -\u003e safegit sign creates a commit approval request\n  -\u003e Postgres shared approval state\n  -\u003e SafeGit API + approval page\n  -\u003e Safe owners sign EIP-712 typed data\n  -\u003e safegit push verifies approval, then delegates to git push\n  -\u003e GitHub Action can also run safegit verify\n```\n\n## Requirements\n\n- Node.js 22+\n- pnpm 11+\n- Git\n- Postgres 17+ or Docker Compose\n- A Safe address and threshold\n- Optional but recommended: RPC URL for the Safe's chain\n\n## Install locally\n\nSafeGit is pnpm-only.\n\nRun these commands from the SafeGit CLI package directory, the directory that contains `package.json` and `bin/safegit.js`.\n\n```bash\npnpm install\n```\n\nCheck the local entrypoints before linking globally:\n\n```bash\npnpm safegit -- --help\npnpm run safegit:server -- --help\n```\n\nLink the CLI globally when you want to run `safegit init` from arbitrary target repos:\n\n```bash\npnpm link:global\nhash -r\n```\n\nCheck the global shims:\n\n```bash\nsafegit --help\nsafegit-server --help\n```\n\nIf `safegit migrate` or `safegit init` fails with `Cannot find module '.../bin/safegit.js'`, the global shim was linked from the wrong directory or an older checkout. Re-run `pnpm link:global` from the CLI package directory and clear the shell command cache with `hash -r`. On pnpm 11+, the underlying command is `pnpm link --global .`; the trailing `.` is required.\n\nWithout a global link, use the bin path directly from any target repo:\n\n```bash\nnode /absolute/path/to/safegit/bin/safegit.js init --safe 0xYourSafeAddress --chain-id 11155111 --threshold 2\n```\n\n## Run local API + Postgres\n\n```bash\ndocker compose up --build\n```\n\nThe API starts at:\n\n```text\nhttp://127.0.0.1:8787\n```\n\nPostgres is published on the host at `127.0.0.1:15432` by default so it does not collide with an existing developer-machine Postgres on `5432`. Override the host port with `SAFEGIT_POSTGRES_HOST_PORT` before running Docker Compose.\n\nThe API server runs migrations automatically on startup. Use `safegit migrate` directly for manual setup, hosted Postgres, CI/CD migrations, or future schema upgrades.\n\nFor CLI commands in another shell:\n\n```bash\nsafegit env\nsafegit doctor\n```\n\nThis creates `.env` in the current directory with the Docker Compose database URL, `postgres://safegit:safegit_dev_password@127.0.0.1:15432/safegit`, if `SAFEGIT_DATABASE_URL` is missing. `safegit doctor` validates the URL, DNS, and TCP connectivity. Use `safegit env --database-url 'postgres://USER:PASSWORD@HOST:5432/DB'` for a hosted or manually managed database. If an existing `.env` contains a stale or malformed value, use `safegit env --force`.\n\nOptional live Safe validation:\n\n```bash\nexport SAFEGIT_RPC_URL='https://ethereum-sepolia-rpc.publicnode.com'\n```\n\nWhen `SAFEGIT_RPC_URL` is set on the API server, submitted signatures are checked against the live Safe owner set and threshold.\n\n## Exact usage flow\n\n### 1. Prepare the database\n\nIf you are using Docker Compose, the API does this automatically. If you are using a manually managed database, run:\n\n```bash\nsafegit env --database-url 'postgres://USER:PASSWORD@HOST:5432/DB'\nsafegit doctor\nsafegit migrate\n```\n\n### 2. Configure SafeGit inside the target Git repo\n\n```bash\ncd /path/to/your/repo\nsafegit env\nsafegit setup \\\n  --safe 0xYourSafeAddress \\\n  --chain-id 11155111 \\\n  --threshold 2\n```\n\nThis writes `.safegit.yml` and stores the repo → Safe mapping in Postgres.\n\n`safegit init --safe ...` remains as a legacy alias for setup. Plain `safegit init` delegates to `git init`.\n\n### 3. Stage and commit exactly like Git\n\n```bash\nsafegit add path/to/file\nsafegit commit -m \"describe the protected change\"\n```\n\n`safegit add`, `safegit commit`, `safegit status`, and other Git-shaped commands pass their original arguments to `git`. SafeGit does not reimplement Git's index, pathspec, hooks, editor, or transport behavior.\n\n### 4. Create an approval request for the commit\n\n```bash\nsafegit sign\n```\n\nThis creates a `GitCommitApproval` EIP-712 payload for `HEAD` containing:\n\n- repo host, owner, and name\n- branch\n- commit SHA\n- tree SHA and parent SHAs\n- author and committer\n- Safe address\n- chain ID\n- approval ID\n- creation and expiry timestamps\n\nThe command prints an `approvalId` and `approvalUrl` like:\n\n```text\n{\n  \"approvalId\": \"appr_5f4074bfbf53\",\n  \"approvalUrl\": \"http://127.0.0.1:8787/approve/appr_5f4074bfbf53\"\n}\n```\n\nThe legacy `safegit request --ref HEAD` command is still available, but `safegit sign` is the normal wrapper flow.\n\n### 5. Open the approval page\n\n```text\nhttp://127.0.0.1:8787/approve/\u003capprovalId\u003e\n```\n\nThe page:\n\n1. loads the backend-provided EIP-712 payload\n2. connects an injected wallet\n3. calls `eth_signTypedData_v4`\n4. posts `{ signer, signature }` to the SafeGit API\n5. marks the approval `approved` after the configured threshold is reached\n\n### 6. Check approval status\n\n```bash\nsafegit approval status --ref HEAD\n```\n\n`safegit status` delegates to `git status`.\n\nBefore enough signatures:\n\n```json\n{\n  \"status\": \"pending\"\n}\n```\n\nAfter threshold:\n\n```json\n{\n  \"status\": \"approved\"\n}\n```\n\n### 7. Push through the SafeGit wrapper\n\n```bash\nsafegit push\n```\n\n`safegit push` first checks that `HEAD` has an approved SafeGit approval. If approval is missing or pending, it exits before running `git push`. Once approved, it delegates to `git push` with the original arguments.\n\n### 8. Enforce approval in CI\n\n```bash\nsafegit verify --ref HEAD\n```\n\n`verify` exits non-zero unless the commit is approved. Use that as a GitHub required check.\n\n## Manual signature submission\n\nIf you collect a signature elsewhere, submit it manually:\n\n```bash\nsafegit attest \\\n  --approval-id appr_xxxxxx \\\n  --signer 0xSignerAddress \\\n  --signature 0xSignatureBytes\n```\n\nOr through the API:\n\n```bash\ncurl -X POST http://127.0.0.1:8787/api/approvals/appr_xxxxxx/signatures \\\n  -H 'content-type: application/json' \\\n  -d '{\n    \"signer\": \"0xSignerAddress\",\n    \"signature\": \"0xSignatureBytes\"\n  }'\n```\n\n## API endpoints\n\n```text\nGET  /healthz\nGET  /approve/:approvalId\nGET  /api/approvals/:approvalId\nPOST /api/approvals/:approvalId/signatures\n```\n\n## Production hardening\n\nSafeGit's approval page is intentionally small, but a public deployment should still sit behind basic controls.\n\n### API bearer token\n\nSet `SAFEGIT_API_TOKEN` to require `Authorization: Bearer \u003ctoken\u003e` on all `/api/*` routes:\n\n```bash\nexport SAFEGIT_API_TOKEN='replace-with-a-long-random-token'\n```\n\nThe browser approval page still renders at `/approve/:approvalId`. If API auth is enabled, signers can either paste the token into the page once or open:\n\n```text\nhttps://safegit.example.com/approve/\u003capprovalId\u003e#token=\u003ctoken\u003e\n```\n\nDo not put this token in GitHub Actions logs, public issues, or screenshots.\n\n### Rate limits and CORS\n\nBy default, `/api/*` routes are limited to 60 requests per minute per IP. Tune with:\n\n```bash\nexport SAFEGIT_RATE_LIMIT_WINDOW_MS=60000\nexport SAFEGIT_RATE_LIMIT_MAX=60\n```\n\nLock CORS to your deployed origin instead of `*`:\n\n```bash\nexport SAFEGIT_CORS_ORIGIN='https://safegit.example.com'\n```\n\n### Reverse proxy\n\nFor production, terminate HTTPS at a reverse proxy such as Caddy, Nginx, Cloudflare Tunnel, Fly, Render, or a platform load balancer. Keep Postgres private, rotate `SAFEGIT_API_TOKEN`, and enable platform-level request logging/rate limits too. If the service is behind one trusted proxy, set:\n\n```bash\nexport SAFEGIT_TRUST_PROXY=true\n```\n\nThis makes per-IP rate limiting use the forwarded client IP instead of the proxy's IP.\n\n### Live Safe validation\n\nSet `SAFEGIT_RPC_URL` so submitted signatures are checked against live `getOwners()` / `getThreshold()` before they count:\n\n```bash\nexport SAFEGIT_RPC_URL='https://ethereum-sepolia-rpc.publicnode.com'\n```\n\nFor rigorous historical validation, use an archival RPC and verify owner/threshold state at the relevant chain block for the protected change. The MVP validates current Safe ownership; that is enough for demos and most active approvals, not for backdated forensic guarantees.\n\n### GitHub App roadmap\n\nThe MVP uses a GitHub Action required check because it is simple and works today. A future GitHub App should add webhook-triggered check-runs, centralized repo configuration, audit pages, and secretless installation UX.\n\n## GitHub Actions required check\n\nThe repo includes:\n\n```text\naction.yml\ntemplates/github-action.yml\n```\n\nExample workflow:\n\n```yaml\nname: Safe Verified\non: [pull_request]\n\njobs:\n  safe-verified:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: agent-cortex/safegit@main\n        with:\n          database-url: ${{ secrets.SAFEGIT_DATABASE_URL }}\n          ref: HEAD\n```\n\nThen require the `Safe Verified` check in GitHub branch protection.\n\n## What Postgres stores\n\n- `safegit_repos` — repo slug, host, owner, repo name, Safe address, chain ID, threshold\n- `safegit_approval_requests` — commit SHA, branch, EIP-712 payload, message hash, status, expiry\n- `safegit_signatures` — signer address, signature, timestamp\n\n## Testing\n\n```bash\npnpm install --frozen-lockfile\npnpm test\npnpm pack --dry-run\n```\n\n## GitHub App status\n\nA GitHub App is not required to test SafeGit. The current MVP uses a GitHub Action required check.\n\nA future GitHub App would improve production UX by creating check-runs directly from push/PR webhooks, centralizing repo configuration, and avoiding manual workflow setup.\n\n## Security notes\n\n- Do not commit private keys, RPC secrets, database passwords, or `.env` files.\n- Use a real shared Postgres database for team usage; local DBs are only for demos.\n- For production, run the API behind auth/rate limits.\n- Enable `SAFEGIT_RPC_URL` if you want live Safe owner/threshold validation.\n- For rigorous historical validation, verify the Safe owner set at the relevant block.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmegabyte0x%2Fsafegit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmegabyte0x%2Fsafegit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmegabyte0x%2Fsafegit/lists"}