Skip to content

Releases: stevepridemore/graph-memory

v0.3.1 — Rule subtype + Windows CRLF fix

Choose a tag to compare

@stevepridemore stevepridemore released this 17 May 20:07
b7b44af

Patch release.

What's new

Feature: Rule subtype for permanent preferences (#37)

Adds a subtype: 'rule' semantic on Preference (or any) nodes for permanent constraints — "never X" / "always Y" with no expected sunset.

Rule-subtype nodes and their anchor edges are exempt from:

  • Time-based decay (node confidence and edges touching them)
  • Low-confidence pruning
  • Orphan pruning (a stranded rule should be reconnected, not deleted)
  • Edge-weight pruning when either endpoint is a rule

The anchor edge (typically Person -[PREFERS]-> Rule) is held at weight 1.0 and exempt from edge decay so the rule never drifts loose.

Extractor prompts (dream + graph-capture) updated to recognize hard "never/always" statements and emit them as Rules rather than soft Preferences.

Backward compatible. All existing nodes have subtype = null and are unaffected — the new filters are no-ops for them. Existing tenants need no migration. To convert an existing Preference into a Rule on your own tenant, run:

MATCH (s:Entity {tenant_id: '<your-tenant>', id: 'steve'})-[r:PREFERS]->(n:Entity {id: '<rule-id>'})
SET n.subtype = 'rule', n.confidence = 1.0, n.last_seen = datetime(),
    r.weight = 1.0, r.last_confirmed = datetime()

Bug fix: Force LF on shell scripts and Dockerfile (#38)

Adds a .gitattributes that pins *.sh, Dockerfile, and *.cypher to LF regardless of core.autocrlf settings.

Why: Windows clients with core.autocrlf=true (Git for Windows default) convert *.sh to CRLF on checkout. The entrypoint shim then ships into the Linux container with #!/usr/bin/env bash\r, producing /usr/bin/env: 'bash\r': No such file or directory and a 502 from the MCP. Anyone doing a fresh Windows clone + build would hit this. Now fixed at the repo level.

Image

  • ghcr.io/stevepridemore/graph-memory-mcp:v0.3.1
  • ghcr.io/stevepridemore/graph-memory-mcp:latest (same digest)

Upgrade

docker compose pull
docker compose up -d --force-recreate graph-memory-mcp

Notes for future

The release workflow emitted a deprecation warning: docker/build-push-action@v6, docker/login-action@v3, and docker/setup-buildx-action@v3 still run on Node.js 20, which is being forced to Node.js 24 by GitHub Actions on June 2, 2026 and removed September 16, 2026. Worth bumping these action versions in the next chore PR.

🤖 Generated with Claude Code

v0.3.0 — curl-pipeable installers + pre-built GHCR image

Choose a tag to compare

@stevepridemore stevepridemore released this 11 May 03:05
f285354

First release that ships without requiring users to clone the repo or build from source. The new install paths are documented in the README.

Highlights

  • Primary device install (machine running the containers):

    curl -fsSL https://raw.githubusercontent.com/stevepridemore/graph-memory/v0.3.0/scripts/install-primary.sh | bash -s v0.3.0

    PowerShell mirror at scripts/install-primary.ps1.

  • Secondary device install (anything pointing at the primary over Cloudflare Tunnel):

    curl -fsSL https://raw.githubusercontent.com/stevepridemore/graph-memory/v0.3.0/scripts/install-secondary.sh | bash -s v0.3.0 <tunnel-host>

    PowerShell mirror at scripts/install-secondary.ps1.

  • Developer install unchanged: clone + docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d.

What's in the box

  • Pre-built MCP image at ghcr.io/stevepridemore/graph-memory-mcp:v0.3.0 (also tagged :latest). Multi-stage build means no host-side npm install needed.
  • 12 graph-memory slash commands vendored under skills/ and installed to ~/.claude/skills/ by the installer.
  • Container entrypoint auto-seeds the prompts directory and generates a self-signed TLS cert on first run.
  • New .github/workflows/release.yml publishes the GHCR image on every v* tag.
  • scripts/sync-dream-skill.py now does platform-aware path substitution (--os auto|windows|unix).

Bug fixes

  • Slash commands ingest, ingest-audio, and graph-dream were referencing ~/.claude/graph-memory/ for the ingest queue, but the actual data dir is ~/graph-memory/. Fixed.
  • MCP server now generates a self-signed cert on first run instead of crashing with ENOENT: server.crt.

Tested

  • Tier 1 local: install dry-run on a WSL clone + multi-stage docker build + docker compose up against the locally-tagged image. Both containers healthy, /health returns 200 over HTTPS.
  • Tier 2 live: anonymous docker pull from GHCR + installer running against the published branch artifacts. Same green result.

🤖 Generated with Claude Code

v0.2.1 — STRIDE threat model fully closed (16/16)

Choose a tag to compare

@stevepridemore stevepridemore released this 09 May 21:23

Closes the remaining three STRIDE findings deferred at the v0.2.0 OAuth hardening pass. Internal threat model is now fully resolved (16 of 16 findings).

Included PRs

  • #23OAUTH_ALLOWED_EMAILS allowlist (closes S-2). Optional second-layer guard on top of Cloudflare Access at /oauth/authorize. Supports exact emails and *@domain wildcards. Re-validated on grant_type=refresh_token so removing an email invalidates refresh tokens within seconds.
  • #24readBody size caps (closes D-3). 64 KB on OAuth routes, 4 MB on /mcp. Two-layer defense (fast reject via Content-Length, streaming reject via byte counter). Hardcoded named constants.
  • #25 — Drop confidential-client support (closes I-1). Removes a misadvertised path: the server had been generating and storing plaintext client_secrets but never verifying them. Discovery metadata now advertises ["none"] only; /oauth/register rejects confidential auth methods. Future-enhancements note added to docs/REMOTE.md covering the deferred server-to-server / client_credentials case.

Compatibility

All real clients (claude.ai web, Claude Desktop, Claude Code) use PKCE + token_endpoint_auth_method: "none", which became mandatory in #18. No on-disk migration required. Production deployed at 182204a and smoke-tested on the wire.

v0.2.0 — OAuth 2.1 security hardening

Choose a tag to compare

@stevepridemore stevepridemore released this 09 May 06:02

v0.2.0 — OAuth 2.1 security hardening pass

Closes 12 of 16 findings from an end-to-end STRIDE threat model of the
public deployment at https://your-host.example/mcp.

  • POST /oauth/revoke (RFC 7009) — token revocation with persistent
    deny-list at $GRAPH_MEMORY_HOME/oauth/revoked.json (auto-prunes
    naturally-expired entries). revocation_endpoint advertised in
    authorization-server metadata.

  • Every issued access/refresh token now carries a unique jti claim for
    individual revocation lookup.

  • redirect_uri hostname allowlist on POST /oauth/register
    (claude.ai, *.claude.ai, claude.com, *.claude.com, localhost,
    127.0.0.1 by default; suffix-match-safe wildcard). Configurable via
    the new OAUTH_REDIRECT_URI_HOSTS env var.

  • Registration cap via OAUTH_MAX_CLIENTS (default 100; 429 once full).

  • clients.json now written with mode 0o600.

  • Structured OAuth event logging to
    $GRAPH_MEMORY_HOME/logs/oauth-events.jsonl: register / register_fail /
    authorize_ok / authorize_fail / token_issue / token_refresh /
    token_consume_fail / token_pkce_fail / token_refresh_fail /
    client_deregistered / revoke_ok / revoke_noop / bearer_verify_fail.
    Each event carries client_id, email, jti, redirect_uri_host, reason,
    and source_ip as applicable. Best-effort; never throws from the
    request path.

  • Refresh-token TTL: 90 days → 30 days. Existing tokens keep their
    original expiry; new tokens issued after deploy use the shorter TTL.

  • PKCE-S256 is now required for any client registered with
    token_endpoint_auth_method: "none" (i.e. public clients including the
    claude.ai connector). code_challenge_method=plain is rejected.
    Discovery metadata advertises ["S256"] only. Existing claude.ai
    connectors already send S256 — no flow change.

  • Refresh-grant rejects tokens whose client_id is no longer in
    clients.json (closes E-4).

  • 401 response bodies for bearer-verify failures now use a constant
    string; verbose jose reasons live in oauth-events.jsonl (closes I-3).

  • BearerVerifyError extends TenantAuthError, carrying .reason
    separately from the public .message.

  • revokeToken signature: Promise →
    Promise<{ revoked: boolean; jti?: string }>.

  • New module src/shared/oauth-events.ts (logger, OAuthEvent interface,
    pickClientIp helper).

  • config.ts now honors GRAPH_MEMORY_HOME env var (was hardcoded to
    homedir()/graph-memory).

  • docker-compose pins GRAPH_MEMORY_HOME=/root/graph-memory in the
    container's environment block to override the host-side value
    injected via env_file.

ID Severity Status
S-1 HIGH closed
T-1 LOW closed
D-1 MED closed
E-1 HIGH closed
E-2 MED closed
E-3 HIGH (chain) closed (subsumed by S-1 fix)
E-4 LOW closed
R-1 MED closed
I-3 LOW closed
S-3, T-2, I-2, D-2 informational no action needed
S-2 MED open (deferred)
I-1 LOW open (deferred — code path currently unused)
D-3 LOW–MED open (verification step)

47 OAuth unit tests + 12 OAuth-event-log tests + 46 Neo4j integration
tests + 2 PKCE tests = 100/100 passing against a throwaway Neo4j
side-car. CI runs against a fresh service container per build.

npm audit: 5 vulnerabilities (1 high, 4 moderate) → 0. All in
transitive deps fixable by patch/minor bumps; none reachable from
runtime paths we exercise.

2026-05-09.

v0.1.1 — Decay correctness + test coverage

Choose a tag to compare

@stevepridemore stevepridemore released this 08 May 03:11

⚠️ Operational note before upgrading

graph_decay was previously dropping the months component of a normalized duration, so any backdating beyond ~28 days only applied a fraction of expected decay. After this release, decay matches the documented per-type half-lives (Preferences ~693d, Events ~99d, etc.).

First run on an existing graph may show large catch-up decay. Recommended:

graph_export { label: "pre-v0.1.1-decay-fix" }   # back up first
graph_decay  { dry_run: true }                   # preview the impact
graph_decay  {}                                  # apply when ready

What's new

  • Fixed decay-formula bug (#13, commit 45edc8d) — duration.between().days returned only the days component of a normalized year/month/day duration. Switched to duration.inDays().days which forces an all-days representation.
  • 15 new integration tests covering decay math, bi-temporal queries, contradiction edge cases (#13)
  • Test suite is data-safe by construction (#12) — per-tenant isolation plus a startup guard that refuses to run against graphs holding non-test data. Catches the footgun where the test defaults match the production docker-compose Neo4j.
  • graph_merge tests added; stale test signatures repaired (#10)
  • CI runs typecheck + integration suite against a Neo4j 5.20 service container on every PR (#9, #10, #11)

New internal APIs

Not user-facing MCP tools — useful for ops/maintenance scripts:

  • Neo4jClient.clearTenant(tenantId) / countNodesOutsideTenantPrefix(prefix)
  • Neo4jClient.setNodeLastSeen(tenantId, id, daysAgo) / setEdgeLastConfirmed(...)
  • Neo4jClient.setEdgeInvalidAt(tenantId, fromId, toId, relation, isoString) — closes a documented-but-unimplemented gap; supersession was in the schema but had no public API before this.

Other

  • README badge cache-busted (#14)
  • GitHub Actions bumped to v5 (Node 24 internals) (#11)

Full changelog: v0.1.0...v0.1.1

v0.1.0

Choose a tag to compare

@stevepridemore stevepridemore released this 07 May 18:58

What's Changed

New Contributors

Full Changelog: https://github.com/stevepridemore/graph-memory/commits/v0.1.0