diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..54dc308 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +# Dependabot: monthly version updates for the pinned GitHub Actions. +# Pull requests only; nothing merges itself. Security advisories are handled by GitHub +# Dependabot security updates on the repository settings. +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly diff --git a/.github/workflows/aien-verification.yml b/.github/workflows/aien-verification.yml index bf1fb14..adcdb1b 100644 --- a/.github/workflows/aien-verification.yml +++ b/.github/workflows/aien-verification.yml @@ -30,7 +30,7 @@ jobs: RECEIPTS_RAW: https://raw.githubusercontent.com/aien-dev/aien-receipts/main steps: - name: Checkout the head under test - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: fetch-depth: 0 @@ -52,7 +52,7 @@ jobs: fi - name: Checkout the pinned crumb tool source (aien-architecture @ 12ad0141b41ec67ef5aa51cb4e1815894c1083bb) - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: repository: aien-dev/aien-architecture ref: 12ad0141b41ec67ef5aa51cb4e1815894c1083bb @@ -69,13 +69,13 @@ jobs: run: crumb verify . - name: Install Stable Rust Toolchain (for the pinned aien-test build) - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable branch head 2026-10-26 if: steps.anchors.outputs.enrolled == 'true' with: components: rustfmt - name: Checkout the pinned aien-test source (aien-sovereign-core @ ${{ env.AIEN_TEST_PIN }}) - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 if: steps.anchors.outputs.enrolled == 'true' with: repository: aien-dev/aien-sovereign-core @@ -88,7 +88,7 @@ jobs: # Cache dependencies only, never verifier executables or candidate-controlled target output. - name: Cache the pinned aien-test dependencies - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 if: steps.anchors.outputs.enrolled == 'true' with: path: | @@ -139,7 +139,7 @@ jobs: RECEIPTS_RAW: https://raw.githubusercontent.com/aien-dev/aien-receipts/main steps: - name: Checkout the tagged commit - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: fetch-depth: 0 @@ -166,12 +166,12 @@ jobs: echo "verify.pub (origin/main): $(cat "$RUNNER_TEMP/anchors/verify.pub")" - name: Install Stable Rust Toolchain (for the pinned aien-test build) - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable branch head 2026-10-26 with: components: rustfmt - name: Checkout the pinned aien-test source (aien-sovereign-core @ ${{ env.AIEN_TEST_PIN }}) - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: repository: aien-dev/aien-sovereign-core ref: ${{ env.AIEN_TEST_PIN }} @@ -182,7 +182,7 @@ jobs: # Cache dependencies only, never verifier executables or candidate-controlled target output. - name: Cache the pinned aien-test dependencies - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ~/.cargo/registry diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml new file mode 100644 index 0000000..8b8c9df --- /dev/null +++ b/.github/workflows/sbom.yml @@ -0,0 +1,23 @@ +# SBOM: writes an SPDX JSON inventory of the tree at every push to main and on pull requests, as +# a workflow artifact, so outside auditors can diff what a commit contains. Read-only token; +# every action pinned by full commit sha. It judges nothing and gates nothing. +name: SBOM +on: + pull_request: + push: + branches: [main] +permissions: + contents: read +jobs: + sbom: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false + - uses: anchore/sbom-action@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c # v0.24.3 + with: + path: . + format: spdx-json + artifact-name: sbom-${{ github.sha }}.spdx.json diff --git a/AUDIT_SCOPE.md b/AUDIT_SCOPE.md new file mode 100644 index 0000000..c9d8579 --- /dev/null +++ b/AUDIT_SCOPE.md @@ -0,0 +1,132 @@ +# Audit scope + +Written for an outside, independent security audit. Revision described: +`5f9a638d6be5c271748dc740f69159422788ca81` on `main`, 2026-10-30. Everything below is taken from +the repository's own files at that revision; items marked NOT_RUN were not executed while writing +this document. + +## What this repository is + +Synapse is source control for AI agents. Agents submit signed changesets, inspect virtual trees and +diffs without a working-tree checkout, attach evidence and queue integration. Accepted changesets +materialize deterministically into standard Git objects; GitHub pull requests are only a human +projection; canonical promotion needs a verified Radicle delegate quorum. One Rust Cargo workspace +under `crates/`: `synapse-core`, `-store`, `-auth`, `-api`, `-git`, `-radicle`, `-bench`, +`-crumb`, `-coord`, `-evidence`, `-wire`. The library crates read no configuration files. + +## What to review, in priority order + +1. `crates/synapse-auth`: `did:key` Ed25519 extraction and signature verification, capability + grants (`Read`, `Propose`, `Evaluate`, `Review`, `Publish`), expiry, replay nonces, quotas, + self-review refusal, `PolicyEnforcer::authorize` and `authorize_chain`, the audit log. +2. `crates/synapse-radicle`: delegate threshold verification over `refs/rad/id` and + `refs/namespaces//refs/rad/sigrefs`; split-brain refusal. +3. `crates/synapse-wire`: frame decoding (`docs/G1_WIRE_FRAMES.md`), the stream transport (any + ordered byte stream, including `UnixStream` and `TcpStream`), the shared-memory ring + (`docs/G3_SHM_RING.md`), connection closing after 64 refusals. +4. `crates/synapse-store`: SWAL v1 parsing and recovery, blob store, compaction and GC, torn write + handling; malformed corpus tests; the R1 raw block store repair after `CheckpointCorrupt` + (`src/blockdev/repair.rs`, `docs/R1_RAW_BLOCK_STORE.md`), which writes to the device; the G2 + content-defined chunking and WAL v2 path (`src/chunk.rs`, `src/wal2.rs`, `src/segment.rs`, + `docs/G2_CONTENT_DEFINED_CHUNKING.md`), which parses committed v2 checkpoints and logs. +5. `crates/synapse-api`: bounds, pagination, cross-repository lookups; the M1 admission secret + scanner (`src/secret_scan.rs`: hand-written single pass, refuse by default, no allowlist). + With `crates/synapse-core/src/merge.rs` and both `derivation.rs` files: merge tier T1, which + combines changes to different parts of one file (`docs/D1_MERGE_TIER_T1.md`). +6. `crates/synapse-git`: loose object writer and reader, `ChangesetId <-> GitOid` map, ref + namespaces, GitHub pull request projection. +7. `crates/synapse-coord` (Unix datagram socket notifications in `src/board/notify.rs`) and + `crates/synapse-evidence`. +8. `tests/gates/run.sh`, `scripts/ci/portable.sh`, `.aien/always`, `.aien/unmapped-ok`, + `.aien/verify.pub`. +9. `.github/workflows/aien-verification.yml`: pull request and push, `contents: read`, advisory; + nothing on GitHub builds this repository's code (AGENTS.md). + +AGENTS.md: a change to `.aien/`, `tests/gates/`, `.github/workflows/` or `.forgejo/` is +`BLOCKED_OPERATOR` and runs only when the operator starts it by hand. + +## Trust boundaries + +From `docs/THREAT_MODEL.md`: + +- Agent clients are untrusted until authenticated by an Ed25519 signature and authorized by an + unexpired, repository-scoped `AgentCapabilityGrant`. +- Disk files and incoming payloads are treated as possibly corrupted or adversarial; digests and + WAL chain links are recomputed on ingest and recovery. +- GitHub pull requests are human projections, never proof of canonical promotion. +- Only a `RadicleCanonicalHead` backed by a verified identity document and a threshold of delegate + signatures marks a lineage canonical. +- CI: the DGX Spark is the test judge, running `tests/gates/run.sh`; GitHub checks are advisory + (AGENTS.md, `docs/PIPELINE.md`). + +## Threat model summary + +- Assets: canonical branch authority, changeset and blob integrity, capability grants, the audit + log. +- Attackers considered (T1 to T6): forged agent identity; cross-repository token or object reuse; + expired or replayed mutations; memory or disk exhaustion; split-brain canonical spoofing; crash + or torn write corruption. +- Defended: each threat above with a deterministic `RefusalCode`; fail closed on unknown versions, + malformed input and conflicting `IdempotencyKey`. +- Not defended: the audit log is in memory, not independently signed or persisted; at 4,096 + entries authorization refuses until an operator export or restart (backpressure, not drain). +- Explicitly out of scope: the security of Git, Radicle and GitHub. + +## Build from source at a pin + +Rust toolchain (the gates record Rust 1.98.1; there is no `rust-toolchain.toml`), `git`, and `cc` +for the crumb qualification scripts. `Cargo.lock` is committed. + +```sh +git clone https://github.com/aien-dev/synapse.git +cd synapse +git checkout 5f9a638d6be5c271748dc740f69159422788ca81 +cargo build --locked --workspace +``` + +## Run every test + +```sh +bash scripts/ci/portable.sh workspace +cargo fmt --all --check +cargo clippy --locked --workspace --all-targets -- -D warnings +cargo test --locked --workspace +tests/gates/run.sh zero-disk-secrets +tests/gates/run.sh workspace +tests/gates/run.sh crumb-verify +tests/gates/run.sh conformance +``` + +- `crumb-verify` and `conformance` need a sibling `../aien-architecture`; `crumb-verify` builds + the crumb tool at `crumb.lock` `12ad0141b41ec67ef5aa51cb4e1815894c1083bb`, `conformance` checks out + `29780e56c9e6b9111d884e0d4b146737228c19f6`. +- `.aien/always`: `CRUMB-VERIFY`, `SYNAPSE-ZERO-DISK-SECRETS`. Expected line from the secrets + gate: `Zero plaintext .env files verified.` +- `scripts/run_gate4_qualification.sh` (comparative benchmark) needs a fixture repository and + writes `evidence/gate4-qualification/`; `SMOKE=1` runs a small check. +- All commands above: NOT_RUN while writing this document. + +## Known limitations and NOT_RUN + +- No toolchain pin; the recorded Rust version is 1.98.1. +- The audit log is in memory only (above). +- Published performance numbers come from runs on the DGX Spark, not from other machines. +- Fuzzing: `crates/synapse-wire/src/fuzz.rs` exists; whether a fuzzing campaign has been run is + unknown from the repository. + +## Dependencies and provenance + +- Crates.io dependencies locked in `Cargo.lock` (57 packages). +- Sibling aien-architecture at the two commits above for the crumb gates. +- Licence: Apache-2.0 (`LICENSE`). + +## Evidence + +- `evidence//`, 27 items, treated as immutable; a new run writes new evidence. 17 carry a + top-level `SHA256SUMS` (for example `gate0-baseline`, `gate4-qualification`, + `f1-capability-chains`, `j3-crypto-vectors`, `d1-merge-tier-t1`); 10 do not: + `REPAIR-RECOVERY-L662`, `a1-binary-codec`, `a2-segment-store`, `bench-pmu-diagnostics`, + `g1-wire-frames`, `g2-content-defined-chunking` (one only under `x6/`), `g3-shm-ring`, + `j1-codec-corpus`, `r1-raw-block-store`, `swarm-board`. +- Signed Spark bundles: `aien-dev/aien-receipts` at `synapse//bundle.json` (workflow path). + Receipts are append-only and never edited. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..917882b --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,52 @@ +# Security policy + +## Reporting a vulnerability + +Report privately. Do not open a public issue, pull request or discussion about it. + +1. GitHub: open this repository's Security tab and choose "Report a vulnerability". This + creates a private advisory that only the maintainers can see. If that option is not shown, + private vulnerability reporting has not been enabled for this repository yet; use email. +2. Email: drake@aienos.com with the subject `[synapse] security report`. + +Include the commit or release you tested, the component (path), steps to reproduce, and what an +attacker gains. If the report involves a secret, say where it was exposed and do not send the +secret itself. Encrypted mail is not required; do not post proof-of-concept payloads publicly. + +## Response targets + +This project has one maintainer; the targets below are best effort and stated honestly. + +- Acknowledgement: within 3 working days. +- Triage and severity: within 7 days of acknowledgement. +- Fix or mitigation for confirmed issues: aim for 30 days for high and critical, 90 days + otherwise, with a status update at least every 14 days until closed. +- Disclosure: coordinated. An advisory is published with credit (unless you ask otherwise) + once the fix is on the default branch. Default embargo 90 days, negotiable. + +## Scope + +In scope: + +- `synapse-auth`: Ed25519 `did:key` verification, capability grants, expiry, replay and quota + guards, the hash-chained audit log. +- `synapse-radicle`: the `refs/rad/id` and `sigrefs` delegate threshold verifier and split-brain + refusal. +- `synapse-store`: the append-only WAL (CRC32C and SHA-256 chain), blob store, recovery. +- `synapse-wire` frames, transport and shared-memory ring; `synapse-api` bounds and pagination. +- `synapse-git`: deterministic Git object writing and reading, ref namespace separation. +- `tests/gates/run.sh`, `.aien/`, `.github/workflows/aien-verification.yml`. + +Out of scope: Git, Radicle and GitHub themselves; the Spark test judge `aien-test` (report to +aien-sovereign-core-mv); denial of service through the documented audit log backpressure, which is +by design (`docs/THREAT_MODEL.md`). + +## Supported versions + +Only the default branch `main`; there is no release (workspace version `0.1.0`). Fixes land on +the default branch only. + +## Status + +Research code with a written threat model in `docs/THREAT_MODEL.md`. There is no certification +and no bug bounty. See AUDIT_SCOPE.md for what to review and how to build and test it.