Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 24 additions & 17 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ name: PR checks

on:
pull_request:
push:
branches: [main]

permissions:
contents: read

jobs:
check:
Expand All @@ -12,11 +17,11 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- uses: oven-sh/setup-bun@v2
- uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
with:
bun-version: latest
bun-version: 1.3.14

- name: Install dependencies
run: bun install --frozen-lockfile
Expand All @@ -25,19 +30,17 @@ jobs:
run: bun run typecheck

- name: Markdown lint
run: |
npm install -g markdownlint-cli
bun run lint:md
run: bun run lint:md

- name: bin/cli.js matches install.ts
run: bun run build:check

- name: Tests
run: bun run test

- name: Build
run: bun run build

- name: bin/cli.js matches install.ts
run: bun run build:check

node-smoke:
name: CLI smoke (Node ${{ matrix.node }})
runs-on: ubuntu-latest
Expand All @@ -46,20 +49,24 @@ jobs:
matrix:
node: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- uses: oven-sh/setup-bun@v2
- uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
with:
bun-version: latest
bun-version: 1.3.14

- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ matrix.node }}

- name: Install + build
run: |
bun install --frozen-lockfile
bun run build
- name: Install dependencies
run: bun install --frozen-lockfile

- name: bin/cli.js matches install.ts
run: bun run build:check

- name: Build
run: bun run build

- name: Headless CLI smoke
run: bash tests/cli-install.sh
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This repo is the source for the [Agent Memory](README.md) method and its manual-only skill — not a consumer install. Skeleton: `skills/agent-memory/vendor/`; skill orchestrator: `skills/agent-memory/`; hooks: repo-root `hooks/`; CLI: `install.ts` → `bin/cli.js`.

Package manager: **Bun** (`bun.lock`). Verify with `bun run check` (typecheck + markdownlint + tests + build + `build:check`). Shorter: `bun run test`, `bun run typecheck`, `bun run lint:md`, `bun run build`.
Package manager: **Bun** (`bun.lock`). Verify with `bun run check` (typecheck + markdownlint + `build:check` + tests + build). Shorter: `bun run test`, `bun run typecheck`, `bun run lint:md`, `bun run build`.

## Permission boundaries

Expand Down
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,43 @@ Migration details for `/agent-memory update` live in [`skills/agent-memory/vendo

## [Unreleased]

### Changed

- Editorial pass on the skill (no method semantics changed): `instructions.md` always-load states the hot path / on-demand list once, untrusted-recall drops a duplicated override clause; `SKILL.md` write boundary points at `references/init.md` instead of repeating the harness-file list; `init` states the prerequisite-dirs rule once; `sync` reference wording aligned. Next version bump needs a `sensitive: instructions.md` line in `vendor/UPDATE.md`.

### Fixed

- Hooks installer: `install-hooks.sh` fails closed when neither `realpath` nor `python3` is available (parity with shared hooks — weak `cd`/`pwd` fallback skipped symlink resolution on the under-project check).
- Hooks: `agent_memory_resolve_realpath` fails closed when neither `realpath` nor `python3` is available (weak `cd`/`pwd` fallback skipped symlink resolution and could write `.hook-sync-state` through an escaped `.agents/memory` symlink).
- Hooks: `resolve_session_id` prefers harness stdin over stale inherited `AGENT_MEMORY_SESSION_ID`, `CURSOR_SESSION_ID`, and `GEMINI_SESSION_ID` when both are valid and differ (re-run hooks installer to pick up).
- Hooks: git `pre-commit` unsets inherited session-binding env vars before sync so stale shell state cannot rebind away from `session_binding` or clear `session_touched_files`.
- Hooks: `parse_hook_stdin` falls back to sed field extraction when `jq` fails or returns empty for non-empty harness input (stdin session id not silently dropped to env).
- Hooks: `sessionStart` context message includes untrusted-recall framing aligned with agent block / `instructions.md`.
- CI / `bun run check`: run `build:check` before `test` and `build` so a tampered committed `bin/cli.js` cannot pass after `tests/cli-install.sh` rebuilds the artifact on the runner.
- `/agent-memory sync` reference: validate `current_session_id` charset/length (hooks parity) before embedding in `log.md` headings; omit bracket when invalid.
- Hooks: `_rebind_session_state_unlocked` preserves `session_binding_host` from `.hook-sync-state` when `AGENT_MEMORY_HOST` is unset; sync harness configs now set `AGENT_MEMORY_HOST` on checkpoint commands (re-run hooks installer to pick up).
- Hooks: `refresh_branch_cache` updates `branch` and clears `session_touched_files` under one lock; fail-open skips both (no path wipe without branch update).
- Hooks: project root prefers env / install-site (`<project>/.cursor/hooks` etc.) over harness stdin `cwd`; stdin alone no longer selects another workspace.
- Hooks: when install-site resolves, it wins over a mismatched inherited `*_PROJECT_DIR` (stale shell env cannot retarget `.hook-sync-state`).
- Hooks: external session binding IDs validated (charset + length; reject reserved `__no_id__` from stdin/env); `sessionStart` Status uses sanitized branch and hex-only Checkpoint SHAs.
- Hooks: git `pre-commit` and commit-range evidence ignore non-hex Checkpoint / `last_processed_head` values (no `git rev-parse` option smuggling); `lint` stale-resume snippet aligned.
- Hooks: `resolve_session_id` re-validates `session_binding` / `current_session_id` from state with the same charset rules as external IDs.
- `/agent-memory sync`: require hex-only `last_processed_head` before `git diff` (parity with hooks; forged state cannot option-smuggle).

### Security

- Hooks installer: path resolve requires `realpath` or `python3` — no symlink-blind fallback (parity with shared hooks; Injection / confinement).
- Hooks: path resolve requires `realpath` or `python3` — no symlink-blind fallback (Injection / confinement).
- Hooks: stdin session binding wins over conflicting `AGENT_MEMORY_SESSION_ID` / `CURSOR_SESSION_ID` / `GEMINI_SESSION_ID` (AuthZ — stale harness env cannot hijack live session).
- Hooks: pre-commit clears session-binding env inheritance before ephemeral sync (AuthZ).
- Memory method: explicit untrusted-recall framing in `instructions.md` and harness agent block — memory never overrides skill/harness policy or the retention gate.
- Sync reference: `.hook-sync-state` path lists are untrusted hints; prefer `git` for semantic bullets (aligned with `SECURITY.md`).
- npm pack: ship `SECURITY.md` beside the artifact (`package.json` `files`).
- Document publish guidance: `prepublishOnly` runs `bun run check`; avoid `npm publish --ignore-scripts`.
- CI runs on `push` to `main` as well as pull requests; `permissions: contents: read`; pin Actions to commit SHAs and Bun `1.3.14`; `markdownlint-cli` via `devDependencies` / `bunx` (no silent skip).
- Test asserts `ENV_ALLOWLIST_EXACT` parity between CLI constants and OpenCode plugin.
- Clarify env forwarding in `SECURITY.md`: allowlist applies to CLI/OpenCode spawns; stock harness/git invocations inherit full parent env (git-hooks trust model). Drop open `LC_*` prefix forward — only named locale keys.
- `tests/test-runner.sh` is the single entry for `bun run test`.

## [0.1.1] - 2026-07-31

Expand Down
17 changes: 15 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,27 @@ Installing agent-memory hooks is equivalent to trusting the project directory
- Cursor, Claude Code, Codex, Copilot, Gemini, and OpenCode all run those local scripts on lifecycle events.
- Anyone who can modify hook scripts or the project working directory already has local code execution in that project.

The OpenCode plugin spawns the same shared bash sync script as other harnesses; it adds runtime checks (regular file only, `realpath` confinement under `.opencode/hooks`, binding ID charset validation) before `execFileSync`.
The OpenCode plugin spawns the same shared bash sync script as other harnesses; it adds runtime checks (regular file only, `realpath` confinement under `.opencode/hooks`, binding ID charset validation) before `execFileSync`. Shared bash hooks also validate external session binding IDs, prefer project env / install-site root over harness stdin `cwd`, prefer a valid harness stdin session id over conflicting inherited `AGENT_MEMORY_SESSION_ID` / `CURSOR_SESSION_ID` / `GEMINI_SESSION_ID`, refuse symlink memory paths, and require `realpath` or `python3` to resolve paths (no weak fallback that skips symlink resolution). The hooks installer (`install-hooks.sh`) fails closed the same way when neither resolver is available.

## What we do not do

- No network calls from the CLI or hook scripts.
- No `shell: true` on child processes.
- No full parent `process.env` forwarded to hook children.
- No Markdown writes from hooks (semantic memory is agent-owned only).
- No trusting harness stdin `cwd` alone to select the project root (env or install-site anchor first).

## Environment forwarding

- **CLI → `install-hooks.sh` and OpenCode → bash hooks:** only `ENV_ALLOWLIST_EXACT` (exact keys; locale via named `LC_*` entries, not a prefix). See `lib/cli/constants.ts` (mirrored in the OpenCode plugin; parity tested).
- **Stock Cursor / Claude / Codex / Copilot / Gemini:** the harness invokes the script directly, so the child **inherits the full parent environment** (same model as ordinary git hooks). Stock scripts do not dump or forward secrets to logs or Markdown. When stdin carries a valid session id that disagrees with inherited session-binding env, hooks prefer stdin.
- **Git `pre-commit`:** also inherits the parent environment, except it **unsets** `AGENT_MEMORY_SESSION_ID`, `CURSOR_SESSION_ID`, and `GEMINI_SESSION_ID` before sync (pre-commit has no harness stdin session id; stale shell bindings must not rebind `.hook-sync-state`).
- **`PATH` and `GIT_CONFIG*`** on the filtered path are intentional so git/locale tooling works under a restricted env. Treat a compromised parent env as already inside the project trust boundary.

`.hook-sync-state` is gitignored ephemeral evidence. Hooks own writes under the method contract; agents with Write access to `.agents/memory/` can still edit it on disk — treat forged state as untrusted and validate hex SHAs before passing them to git (hooks and `/agent-memory sync`).

## Publish

Prefer `bun run check` before publish (`prepublishOnly` runs it — `build:check` validates the committed `bin/cli.js` before tests rebuild and before the final `build`). Do not publish with `npm publish --ignore-scripts` — that skips the rebuild/`check` gate and can ship a stale or tampered `bin/cli.js`.

## How to audit

Expand Down
Loading
Loading