Conversation
added 23 commits
September 27, 2026 04:37
govulncheck v1.8.0 (the version @latest resolves to) refuses to run on Go 1.24, and Go 1.24 is outside the upstream support window; the reachable stdlib fixes the security job needs ship in Go 1.26.6, the toolchain the rest of the GrayCode ecosystem already uses. - go.mod: go 1.26.6 (go mod tidy -diff is clean) - Makefile: add fmt-check, test-e2e, coverage, cross-check and vulncheck (govulncheck pinned to v1.8.0); make check now runs the gofmt gate; fuzz no longer swallows failures with || true - fmt-check escapes its command substitution: the unescaped $(gofmt -l .) was expanded by make to an empty string, so the gate could never fail - AGENTS.md, README.md, CONTRIBUTING.md: document the targets, the Go version, and the cgo requirement
- Pin every action to a full commit SHA with a version comment (same pins as rho/flux), check out without persisted credentials, and keep the workflow token read-only. - Use Go 1.26.6 with GOTOOLCHAIN=local so the job runs the toolchain it names instead of silently switching. - Split quality (tidy -diff, gofmt, build, vet, test on Linux and macOS), race, E2E, coverage and govulncheck (pinned) jobs; each runs the Makefile target a contributor runs locally. - Windows was added to the test matrix without qualification: the E2E and unit suites exec sh/python3, create symlinks and rely on cgo, and no test is gated by GOOS. Keep Windows as a compile-only check until it is qualified; README already lists it as untested. - Add .gitattributes (eol=lf) so a Windows checkout cannot turn every file into a gofmt failure.
- schema_migrations gains a checksum column; existing 0.0.1 ledgers are backfilled, and Migrate refuses unknown or future versions, gaps and edited migrations. Migrations run under BEGIN IMMEDIATE on one connection so concurrent first opens serialize. - migration 4 adds the indexes the list/search queries use. - migration 5 adds import provenance (content hash, size, parser version, redaction/import status), session lineage and checkpoint bundle columns, and the context_manifests, context_items, handoffs and evidence_bundles tables. - Open retries the connection pragmas on SQLITE_BUSY. Migrations 1-3 are byte-identical, so 0.0.1 databases upgrade in place.
EnsureHome/EnsureDirectory create directories one component at a time and refuse symbolic links outside a small set of system prefixes; ResolveDirectory returns a real, symlink-free directory for callers that write into it (hook directories, restore parents).
ResolveRevision runs `git rev-parse --verify --end-of-options
<rev>^{commit}` so checkpoints, workspaces and context manifests only
ever record a revision that exists in the repository.
…rades InstallHook validates the hook name and directory, refuses symlinked hooks and hook directories, preserves a foreign hook as <hook>.across-orig, identifies Across-owned hooks by a marker line, and replaces hooks through a staged temp file and rename with rollback so repeated upgrades keep the preserved original. UninstallHook restores the original.
The nine across-agent-* binaries advertised capture, hook, resume and token-usage support and answered every request with success without doing the work. They now share internal/adapter.Run: `capabilities` reports status protocol_shell with every capability false and qualification UNIMPLEMENTED, `ping` succeeds, and any other method returns a typed UNSUPPORTED_METHOD error.
Owner's phase-zero work on the command layer: - CLIError with stable codes and exit statuses (invalid_argument 2, not_found 3, conflict 4, operation_failed 5, internal 1) printed as `across: <code>: <message>`; flag errors, required flags, enums and empty positional arguments are rejected before the store is opened. - withTx runs multi-step mutations (imports, source delete, sessions, checkpoints and restore bookkeeping, memory lifecycle, handoffs, context manifests, bundles) in one BEGIN IMMEDIATE transaction, and repository/session/source/memory references are validated. - Transcript imports record content hash, size and parser version, are confined to the repository root, and tombstoned identities are refused; `source import --native-id` and `session fork` are added. - Versioned, hashed contracts: handoff envelope, `context pack/show` manifests and `checkpoint bundle`. - MCP advertises only the eight store-backed tools and refuses placeholder, unknown and mutation names; agent-help reports the live inventory and immutable_enforced:false. - `agent info` checks adapter identity and protocol; `verify run` records a failing command and exits 5; plugin install/run confine plugins to the Across plugin directory.
- create: SQLite snapshot via VACUUM INTO, archive written to a temp file and renamed with mode 0600; serve.token, tmp/, backups/, logs/ and live database files are excluded. - verify/restore: every member must be a unique regular file listed in the manifest with matching size, SHA-256 and mode; traversal, absolute, drive-letter, control-character, linked and special members and trailing data are rejected, with size and member-count limits. - restore stages beside the target, never writes through symlinked parents, requires the candidate snapshot to pass PRAGMA integrity_check with a schema ledger, and commits by rename with rollback.
- The web console (index.html, app.js, styles.css) is embedded in the binary and served from it, so the served UI is the checked-in UI; records render with textContent only. - `/?token=` sets an HttpOnly SameSite=Strict cookie and redirects so the token leaves the address bar; the UI fetches same-origin. - --addr must be loopback; Host/Origin checks reject lookalikes; the http.Server has read/write/idle timeouts; the CSP adds frame-ancestors 'none'.
…d hooks Adds end-to-end coverage for the typed exit codes, failing `verify run`, the adapter protocol shells, the MCP inventory and error paths, repository-root confinement of `agent import-session` (outside rejected, inside imported) and post-commit hook chaining.
…inks EnsureHome refused any symbolic link in the home path outside a few system prefixes, so `across --home link/home repo list` (or an ACROSS_HOME under a symlinked ~/.local/share or another volume) failed every command with operation_failed after upgrading from 0.0.1, which simply called MkdirAll. The path the user chooses is now resolved once with EvalSymlinks (ResolveUserPath: deepest existing ancestor resolved, missing components appended), and the rule is applied where it protects something: the directories Across creates and writes inside the home (repositories/, mirrors/, workspaces/, plugins/, backups/, tmp/) must still be real directories. Tests: a home beneath a symlinked ancestor and a symlinked home are accepted and created at the resolved location; a symlinked managed subdirectory is still rejected. Fixes F054.
`backup restore --target-home DIR` renamed any existing DIR aside, moved the restored home into place and then RemoveAll'd the old directory, so a mistyped target (a project or ~/Documents) was deleted with exit 0 and no copy left. Restore now classifies the target before staging and again at commit: - missing or empty: restored in place; - a non-empty directory without across.db: refused as a conflict (exit 4), even with --force; - an existing Across home: replaced only with the new --force flag, and the previous home is kept as DIR.across-old-<UTC timestamp> (never deleted) and reported. The target's parent is resolved with config.ResolveUserPath, matching how the home itself is resolved. manifest.json is removed from the stage before commit, so it no longer lingers in the restored home. Tests cover a foreign target (with and without --force), an Across home without and with --force (previous home kept, manifest absent), an empty target, a symlinked parent, and a failed snapshot validation that must not displace the target; the E2E round trip exercises the same rules through the binary. Fixes F046; fixes the restore half of F061.
The managed wrapper appended `exec <original> "$@"` after the Across hook body. The pre-receive body ends in `exit 0`, so the chain line was unreachable: installing a branch rule on a hosted repository silently disabled the operator's existing pre-receive policy (and the original would not have received the ref updates on stdin even if reached). A chained hook is now generated as a wrapper that runs both parts: - hooks that read stdin (pre-receive, post-receive, pre-push, post-rewrite, reference-transaction, proc-receive) capture stdin to a temp file, run the original first with that input and stop if it rejects, then run the Across body in a subshell with the same input; - other hooks run the Across body in a subshell, then the original, and fail if either fails. Tests: unit tests execute a chained pre-receive (original receives the refs; Across still rejects main; an original rejection propagates) and a chained post-commit (both run, in order); an E2E test pushes to a hosted repo with a pre-existing pre-receive through `branch-rule add`. Both unit tests fail against the previous wrapper. Fixes F053.
`agent import-session` parses a private staged copy under os.MkdirTemp and passed that path as `file`; the confinement check already used the caller's resolved path, but sources.origin and the tombstone lookup still used the staged copy, so provenance pointed at a deleted temp file (/var/folders/.../across-import-XXXX/transcript.jsonl). The import now uses one `origin` (the caller's resolved path, or the file itself for `source import`) for confinement, the tombstone check and the stored origin. The rejection message names the repository root and how to proceed. Test: import-session stores the resolved export path as origin (fails against the previous code, which stored the temp path). Fixes the provenance half of F061.
rejectTombstonedSource refused a native-id-less import whenever any tombstoned source of the same kind had a native id (`oldNative != "" || oldOrigin == origin`), so after one `source delete` the default `source import --file F` was permanently broken for that kind in that repository. Without a native id, only an import of the same kind from the same origin is now treated as a resurrection; with a native id, only the same native id is. The conflict message names the origin. Test: after deleting a source imported with a native id, a different file imports without one while re-importing the deleted origin is still refused (the first half fails against the previous rule). Fixes F052.
…s frozen Migration 5 is not released yet; once it is, its checksum is part of every database's ledger and its columns are permanent. Two parts of it had no implementation behind them: - idempotency_keys: no reader or writer anywhere. - sessions.event_cursor / checkpoints.event_cursor: always inserted as 0 and never advanced, so every checkpoint bundle sealed an event_cursor of 0 into its bundle_hash, overstating the evidence it carries. Remove them from migration 5 and from the session insert/show, checkpoint insert and CheckpointBundle contract. A real event cursor can be added in a later migration together with the code that advances it. Part of F056.
- git.UninstallHook (restore the chained original, or remove an Across-only hook) had no command. Expose it as `across hook uninstall REPO_PATH`, sharing the hooks-directory lookup with `hook install` (a non-repository path is now invalid_argument). The E2E hook-chaining test uninstalls and checks the original hook is restored byte-for-byte and the .across-orig sidecar is gone. - Remove code with no callers: the requireFlags annotation helper and the PreRunE wrapper that only served it (commands declare required flags with requiredFlags), and issueMustBelong, changeMustBelong and principalMustExist. Part of F056.
…and handoffs The new contract commands had no tests. Add unit tests that run them through the command tree: - session fork records parent, fork_type and lineage_version, and rejects a missing parent (3) and a parent from another repository (4); - checkpoint bundle writes a bundle whose bundle_hash reseals to the same value, records the identical payload in evidence_bundles, and returns not_found for an unknown checkpoint; - context pack includes only approved memories, marks over-budget items as not included, rejects a zero budget; context show reproduces the sealed manifest and returns not_found for an unknown id; - handoff --format json reseals to its content_hash, is recorded in handoffs, and rejects an unknown format. The round-trip test found that `context show` of a manifest with no items printed `"items": null` while `context pack` hashed `[]`, so the shown manifest no longer matched its content_hash; show now starts from an empty item list. Part of F056.
The cookie set from `/?token=` (Path=/, SameSite=Strict) was accepted as equivalent to the bearer token on every route, including /git/. SameSite compares sites, not ports, so any page served from another localhost port could send credentialed simple GET/POST requests to the API and to git http-backend after the user had opened the console. A cookie now authorizes a request only when Sec-Fetch-Site is absent, `same-origin` or `none`, and any Origin header equals the served host:port. Bearer-token requests are unchanged, so non-browser clients and git with an Authorization header keep working. Test: table test of requestAuthorized for bearer, same-origin fetch, top-level navigation, non-browser client, matching and mismatched Origin, same-site/cross-site fetch metadata, opaque origin and a wrong cookie. Fixes F057.
cmd/across no longer constructs internal/logging (errors are printed by cli.FormatError), so nothing referenced the package, ACROSS_LOG=file did nothing, and EnsureHome still created a logs/ directory that was never written. Delete the package, stop creating logs/ (existing homes keep theirs; backups still skip it), and drop the README/AGENTS lines that described it. Test: EnsureHome does not create logs/. Fixes F059.
Owner's documentation corrections (protocol shells vs parsers vs provider integrations, eight MCP tools, unreleased 0.0.1 source snapshot, phase-0 roadmap) plus the reconciliation the audit asked for once the code was final: - Transcript confinement (F049): the policy is the one the code and E2E test enforce: imports must be inside the registered repository root. AGENTS.md said "provider root", README/SECURITY/CHANGELOG said confinement was not enforced; all now state the repository-root rule, the exit code and message, and how to import a provider export (copy it into an untracked or ignored directory in the working tree). - SECURITY.md/README (F051): describe the implemented controls (marker ownership and chained originals, staged and validated restore with target protection, tombstone identity rules, embedded console assets, same-origin cookie) and the residual gaps precisely, instead of listing them as not implemented. - CHANGELOG (F050, F058): the 0.0.1 entry is restored as originally written, with a note that no v0.0.1 tag or release exists and links to commit 1085a2a; its overclaims are listed under Unreleased → Corrected; the "import-session could never succeed" entry described a bug that never shipped and is replaced by the Security entry for repository-root confinement; Added/Changed/Removed/Security now cover the new commands, --native-id, --force, hook uninstall, typed exit codes, migrations 4-5 and the checksum ledger (F056). - README documents the new commands, exit codes and platform status; STATUS reflects the hardened core. Fixes F049, F050, F051, F058; documentation part of F056.
reports/ and research_notes/ (about 620 KB of OSS comparison and roadmap research with third-party repository snapshots) are working notes, not product documentation, and nothing in the repository links to them. Ignore them instead of deleting the owner's files; conclusions worth publishing can be distilled into docs/ in a separate change. Fixes F060.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Commits the owner's uncommitted phase-zero hardening work (45 modified + new files) in coherent groups, reviewed against the AGENTS.md invariants, followed by one fix commit per audit finding and a final docs reconciliation.
Stacked on #3 (
ci/go-toolchain-gates): the first two commits of this branch are that PR. Review froma3b2d03onward; once #3 merges the diff shrinks to the hardening commits. The release workflow draft is in #4.Owner's work (committed as written)
feat(store): checksummed migration ledger (refuses unknown/future versions, gaps, edited migrations), migration 4 indexes, migration 5 provenance schema; migrations 1-3 unchanged.feat(config),feat(git)×2: symlink-safe managed directories,ResolveRevisionviarev-parse --verify, marker-owned staged hook installs.fix(adapter): the nineacross-agent-*binaries become honest protocol-v1 shells.feat(cli): typed errors/exit codes, argument contracts, transactional mutations, reference validation, import provenance, tombstones,session fork, context/handoff/bundle contracts, truthful MCP inventory.feat(backup),feat(serve),test(e2e).The full suite passes at
facd67c(the owner's state on top of ci: move to Go 1.26.6, pin actions by SHA, and gate Windows as compile-only #3):go test -count=1 ./...all packages ok.Fixes on top (one per finding)
fix(config): resolve the chosen Across home instead of rejecting symlinksfix(backup): never destroy an unrelated directory on restorefix(git): make chained hooks actually run the preserved originalfix(cli): record the caller's export path as the source originfix(cli): tombstones only block the identity that was deletedrefactor(store): drop schema that nothing reads or writes before it is frozenfeat(cli): add hook uninstall; remove helpers nothing callstest(cli): cover session fork, context manifests, checkpoint bundles and handoffscontext showof an empty manifest, which no longer matched its hash)fix(serve): accept the console cookie only for same-origin requestschore: remove the unwired logging package and the unused logs/ directorydocs: reconcile README, SECURITY, CHANGELOG and AGENTS with the codechore: keep local research notes and audit reports out of the repositoryFindings addressed
backup restore --target-homeRemoveAll'd any existing directory. Now: missing/empty target restored; non-empty non-Across directory refused (exit 4) even with--force; an Across home replaced only with the new--force, and kept asDIR.across-old-<UTC timestamp>.exit 0; the wrapper now runs them (with stdin replayed) and propagates rejection; post-commit runs both.idempotency_keysand the always-zeroevent_cursorcolumns (and from the sealed bundle) before migration 5 is frozen; exposedhook uninstall; removed dead helpers; added tests forsession fork,context pack/show,checkpoint bundle, handoff envelopes; CHANGELOG/README document the new commands,--native-id, migrations 4-5 and the checksum ledger.across_tokencookie is honoured only for same-origin requests (Sec-Fetch-Siteabsent/same-origin/none,Originequal to the served host:port); bearer auth unchanged.internal/loggingandACROSS_LOG=fileremoved;logs/no longer created; docs updated.reports/andresearch_notes/(~620 KB of research prose, referenced nowhere) are ignored, not committed and not deleted.manifest.jsonno longer left in a restored home;import-sessionrecords the caller's path assources.origininstead of the deleted temp copy.Findings F047, F055 and the CI half of F247 are in #3; F048 and the release half of F247 are in #4.
Security-invariant decisions (AGENTS.md requires recording them)
ACROSS_HOME,--home, restore--target-homeparent) is now resolved once withEvalSymlinks; symlinks inside the home (managed subdirectories) and symlinked hook files/dirs are still refused. The owner's testsTestEnsureHomeRejectsSymlinkedParentandTestRestoreRejectsSymlinkedTargetParentencoded the reported bug and were replaced by tests of the new rule (resolved location used, previous home kept, symlinked managed dir refused).Not reproduced / deferred
context pack --querydoes not rank/filter items yet; recorded handoffs/manifests/bundles have no list command; issue/change/principal references in collaboration commands are still not validated (the unused helpers were removed rather than wired in untested).Verification
Run on this branch head (
39b6970) on darwin/arm64, go1.26.6:GOWORK=off make checkgo test -race -count=1 ./...; e2e 51.8s, cli 12.5s, all packages ok)GOWORK=off make test-e2eok github.com/graycodeai/across/e2e 111.3s)GOWORK=off go test -race -count=1 ./...GOOS=linux GOARCH=amd64 GOWORK=off go build ./...GOOS=windows GOARCH=amd64 GOWORK=off go build ./...GOWORK=off make cross-checkGOWORK=off go mod tidy -diffGOWORK=off make vulncheck(govulncheck v1.8.0)No vulnerabilities found.a3b2d03to39b6970go build ./...,go vet ./...andgofmt -lclean in a temporary worktree, plus the tests of the packages it touchesoriginal hook did not receive stdin,unrelated import without a native identity was blocked,source origin ".../across-import-.../transcript.jsonl") and pass after the fixLinux CI jobs will run on this PR; Windows is compile-checked only.
Follow-ups
mainafter ci: move to Go 1.26.6, pin actions by SHA, and gate Windows as compile-only #3 merges (the first two commits drop out).~/.local/share/acrossis at migration 3 and upgrades cleanly).context pack --query; add read commands for recorded handoffs/manifests/bundles; validate issue/change/principal references; restore fault-injection tests; Windows qualification.🤖 Generated with Claude Code