fix(mcp): disable audience verification in resolve_mcp_jwt
All checks were successful
CVE Scan & Docker Build / security-scan (push) Successful in 50s
CVE Scan & Docker Build / build-and-push (push) Successful in 2m16s

Team JWTs include `aud=mnemosyne` while per-turn JWTs omit `aud`
entirely. Since `iss` + `typ` already partition the two token
populations, explicitly skip audience verification to avoid rejecting
valid tokens.

Also expand test coverage for the MCP auth surface to exercise all
three credential types (opaque MCPToken, per-turn JWT, team JWT),
including replay cache behavior and Neo4j-backed library resolution
via mocked cypher queries.
This commit is contained in:
2026-05-10 12:32:58 -04:00
parent 16fb7ff4dc
commit 6a4fecf488
7 changed files with 1394 additions and 4 deletions

127
test-postgres.sh Executable file
View File

@@ -0,0 +1,127 @@
#!/bin/bash
# Run the Mnemosyne Django test suite against an ephemeral Postgres + Neo4j.
#
# Pattern borrowed from spelunker/test-postgres.sh. Both databases run as
# throwaway Docker containers on a private bridge network; each invocation
# gets a fresh pair. The Django test runner applies migrations to the
# empty Postgres and tears the whole network down on exit.
#
# Neo4j is included because a handful of library/tests/* modules touch
# neomodel at import time even though the assertions themselves stub the
# cypher layer; having a reachable bolt endpoint keeps startup probes
# from crashing.
#
# Usage:
# ./test-postgres.sh # run everything
# ./test-postgres.sh mcp_server # scope to one app
# ./test-postgres.sh mcp_server.tests.test_auth
# ./test-postgres.sh mcp_server -v 2 --failfast
set -euo pipefail
# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
NET="mnemosyne-test-$$"
PG_CONTAINER="mnemosyne-test-pg-$$"
NEO_CONTAINER="mnemosyne-test-neo4j-$$"
PG_USER="mnemosyne"
PG_PASS="mnemosyne"
PG_DB="mnemosyne_test"
NEO_PASS="mnemosynetestpw" # Neo4j rejects short or obvious secrets.
IMAGE="git.helu.ca/r/mnemosyne:latest"
# Colours (skipped when not a TTY).
if [ -t 1 ]; then
GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'; NC='\033[0m'
else
GREEN=''; RED=''; YELLOW=''; NC=''
fi
say() { printf "${GREEN}==> %s${NC}\n" "$*"; }
warn() { printf "${YELLOW}[!] %s${NC}\n" "$*"; }
die() { printf "${RED}[✗] %s${NC}\n" "$*" >&2; exit 1; }
# ---------------------------------------------------------------------------
# Cleanup
# ---------------------------------------------------------------------------
cleanup() {
say "Cleaning up test containers"
docker rm -f "$PG_CONTAINER" >/dev/null 2>&1 || true
docker rm -f "$NEO_CONTAINER" >/dev/null 2>&1 || true
docker network rm "$NET" >/dev/null 2>&1 || true
}
trap cleanup EXIT
# ---------------------------------------------------------------------------
# Start the support services
# ---------------------------------------------------------------------------
say "Creating Docker network $NET"
docker network create "$NET" >/dev/null
say "Starting Postgres ($PG_CONTAINER)"
docker run -d --rm \
--name "$PG_CONTAINER" \
--network "$NET" \
-e POSTGRES_USER="$PG_USER" \
-e POSTGRES_PASSWORD="$PG_PASS" \
-e POSTGRES_DB="$PG_DB" \
postgres:16-alpine \
>/dev/null
say "Starting Neo4j ($NEO_CONTAINER)"
docker run -d --rm \
--name "$NEO_CONTAINER" \
--network "$NET" \
-e NEO4J_AUTH="neo4j/$NEO_PASS" \
-e NEO4J_PLUGINS='["apoc"]' \
neo4j:5-community \
>/dev/null
# Wait for Postgres to accept connections.
say "Waiting for Postgres to become ready"
for i in $(seq 1 30); do
if docker exec "$PG_CONTAINER" pg_isready -U "$PG_USER" -d "$PG_DB" >/dev/null 2>&1; then
break
fi
sleep 1
done
docker exec "$PG_CONTAINER" pg_isready -U "$PG_USER" -d "$PG_DB" >/dev/null \
|| die "Postgres never became ready"
# Wait for Neo4j's HTTP endpoint — bolt comes up just after it.
say "Waiting for Neo4j to become ready"
for i in $(seq 1 60); do
if docker exec "$NEO_CONTAINER" wget -qO- http://localhost:7474/ >/dev/null 2>&1; then
break
fi
sleep 1
done
# ---------------------------------------------------------------------------
# Run the test command
# ---------------------------------------------------------------------------
SOURCE_DIR="$(cd "$(dirname "$0")" && pwd)/mnemosyne"
say "Running Django test suite"
docker run --rm \
--network "$NET" \
-v "$SOURCE_DIR":/app \
-w /app \
-e DJANGO_SETTINGS_MODULE=mnemosyne.settings \
-e APP_DB_NAME="$PG_DB" \
-e APP_DB_USER="$PG_USER" \
-e APP_DB_PASSWORD="$PG_PASS" \
-e DB_HOST="$PG_CONTAINER" \
-e DB_PORT=5432 \
-e NEOMODEL_NEO4J_BOLT_URL="bolt://neo4j:$NEO_PASS@$NEO_CONTAINER:7687" \
-e SECRET_KEY=test-only-not-a-real-secret \
-e DEBUG=0 \
-e MCP_REQUIRE_AUTH=True \
"$IMAGE" \
python manage.py test "$@"