fix(security): CCR cross-tenant IDOR — scope store per-principal + bound memory (#3859)

Integrated into release/v3.8.25 — fix(security): CCR cross-tenant IDOR (scope store per-principal + bound memory).
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-14 18:02:36 -03:00
committed by GitHub
parent aa8fc4157d
commit cf5898205a
7 changed files with 401 additions and 48 deletions

View File

@@ -15,7 +15,7 @@
"open-sse/executors/muse-spark-web.ts": 1284,
"open-sse/executors/perplexity-web.ts": 868,
"open-sse/handlers/audioSpeech.ts": 952,
"open-sse/handlers/chatCore.ts": 5811,
"open-sse/handlers/chatCore.ts": 5812,
"open-sse/handlers/imageGeneration.ts": 3777,
"open-sse/handlers/responseSanitizer.ts": 1103,
"open-sse/handlers/search.ts": 1442,

View File

@@ -2626,6 +2626,7 @@ export async function handleChatCore({
const result = await applyCompressionAsync(compressionInputBody, mode, {
model: effectiveModel,
config,
principalId: apiKeyInfo?.id ? String(apiKeyInfo.id) : undefined,
});
if (result.stats) {
if (result.compressed) {

View File

@@ -243,6 +243,7 @@ import {
compressionComboStatsInput,
} from "../schemas/tools.ts";
import { handleCcrRetrieve } from "../../services/compression/engines/ccr/index.ts";
import { resolveCallerScopeContext } from "../scopeEnforcement.ts";
const ccrRetrieveInput = z.object({
hash: z
@@ -351,6 +352,11 @@ export const compressionTools = {
"Scope: read:compression. Always available (sticky-on).",
scopes: ["read:compression"],
inputSchema: ccrRetrieveInput,
handler: (args: z.infer<typeof ccrRetrieveInput>) => handleCcrRetrieve(args),
handler: (args: z.infer<typeof ccrRetrieveInput>, extra?: McpToolExtraLike) => {
// Derive caller identity from MCP auth context so the retrieve is scoped to the
// same principal that stored the block. This closes the cross-tenant IDOR (HIGH).
const { callerId } = resolveCallerScopeContext(extra, ["read:compression"]);
return handleCcrRetrieve(args, callerId === "anonymous" ? undefined : callerId);
},
},
};

View File

@@ -4,21 +4,30 @@
* Replaces large contiguous blocks of text with a content-addressed
* retrieve marker: `[CCR retrieve hash=<24hex> chars=<N>]`
*
* The verbatim block is stored in an in-module content-addressed store
* (keyed by 24-hex content hash). The `retrieve` MCP tool (or the
* `handleCcrRetrieve` helper exported here) returns the block on demand.
* The verbatim block is stored in a principal-scoped, bounded in-module store.
* The store key is `${principalId ?? "__anon__"} ${contentHash}` so that one
* principal cannot read another's stored blocks (IDOR protection).
*
* The `retrieve` MCP tool (or the `handleCcrRetrieve` helper exported here)
* returns the block on demand when called with the matching callerId.
*
* Algorithm:
* - Scan non-system messages; for each `type:"text"` part or string content,
* find contiguous text blocks ≥ minChars characters.
* - Replace the block with `[CCR retrieve hash=<24hex> chars=<N>]` only if
* the marker is shorter than the original block.
* - Store the original block keyed by hash in the CCR store.
* - Store the original block keyed by (principalId, hash) in the CCR store.
*
* Feedback:
* - `recordRetrieval(hash)` increments a retrieval counter for that hash.
* - `shouldSkipCompression(hash)` returns true once the counter reaches
* RETRIEVAL_THRESHOLD, signalling "do not compress this block again".
* Feedback (scoped by principal):
* - `recordRetrieval(hash, principalId)` increments a retrieval counter for
* that (principalId, hash) pair.
* - `shouldSkipCompression(hash, principalId)` returns true once the counter
* reaches RETRIEVAL_THRESHOLD for that principal — one principal's behaviour
* does not affect another's (cross-tenant state drift protection).
*
* Memory bound:
* - Both `ccrStore` and `retrievalCounts` are capped at MAX_CCR_ENTRIES
* entries using FIFO eviction (Map insertion-order guarantees).
*
* Conservative guards:
* - Never touch `role: "system"`.
@@ -42,57 +51,95 @@ import type { CompressionResult } from "../../types.ts";
const ENGINE_ID = "ccr";
/** Default minimum character count for a block to be a CCR candidate. */
const DEFAULT_MIN_CHARS = 600;
/** Number of retrievals before a block is flagged "do-not-compress". */
/** Number of retrievals before a block is flagged "do-not-compress" for that principal. */
const RETRIEVAL_THRESHOLD = 3;
/** Regex to match CCR markers for reconstruction. */
const MARKER_RE = /\[CCR retrieve hash=([0-9a-f]{24}) chars=\d+\]/g;
/**
* Maximum number of entries in each bounded store.
* When inserting beyond this cap, the oldest entry (Map insertion order) is evicted.
* 5 000 entries × ~2 KB average ≈ 10 MB upper bound for each map.
*/
export const MAX_CCR_ENTRIES = 5_000;
// ─── content-addressed store ──────────────────────────────────────────────────
// ─── principal-scoped, bounded content store ──────────────────────────────────
/** Map from 24-hex hash → verbatim block text. */
/**
* Store key = `${principalId ?? "__anon__"} ${contentHash}`.
* Using a compound key scopes data to the principal that stored it.
*/
const ccrStore = new Map<string, string>();
/** Map from 24-hex hash → retrieval count (feedback signal). */
/** Retrieval counter store — same scoping as ccrStore. */
const retrievalCounts = new Map<string, number>();
/** Sentinel used when no principalId is provided. */
const ANON = "__anon__";
function buildStoreKey(hash: string, principalId?: string): string {
return `${principalId ?? ANON} ${hash}`;
}
/**
* Insert a value into a bounded Map, evicting the oldest entry when over the cap.
*/
function boundedSet<V>(map: Map<string, V>, key: string, value: V): void {
if (!map.has(key) && map.size >= MAX_CCR_ENTRIES) {
// Map preserves insertion order — the first iterator result is the oldest entry.
const firstKey = map.keys().next().value;
if (firstKey !== undefined) {
map.delete(firstKey);
}
}
map.set(key, value);
}
/**
* Compute a 24-hex content hash for a text block (SHA-256 prefix).
* This is the hash embedded in the marker; principal scoping is internal to
* the store key and is NOT part of the marker itself.
*/
function hashContent(text: string): string {
return crypto.createHash("sha256").update(text).digest("hex").slice(0, 24);
}
/**
* Store a block in the CCR store, returning its hash.
* Store a block in the CCR store under the given principal.
* Returns the 24-hex content hash (for embedding in the marker).
*/
function storeBlock(text: string): string {
export function storeBlock(text: string, principalId?: string): string {
const hash = hashContent(text);
if (!ccrStore.has(hash)) {
ccrStore.set(hash, text);
const key = buildStoreKey(hash, principalId);
if (!ccrStore.has(key)) {
boundedSet(ccrStore, key, text);
}
return hash;
}
/**
* Retrieve the verbatim block for a given hash.
* Returns null if not found.
* Retrieve the verbatim block for a given hash and principal.
* Returns null if not found or if the principal does not match the stored key.
*/
export function retrieveBlock(hash: string): string | null {
return ccrStore.get(hash) ?? null;
export function retrieveBlock(hash: string, principalId?: string): string | null {
const key = buildStoreKey(hash, principalId);
return ccrStore.get(key) ?? null;
}
/**
* Record a retrieval event for a given hash (feedback signal).
* Record a retrieval event for a given (hash, principal) pair (feedback signal).
*/
export function recordRetrieval(hash: string): void {
retrievalCounts.set(hash, (retrievalCounts.get(hash) ?? 0) + 1);
export function recordRetrieval(hash: string, principalId?: string): void {
const key = buildStoreKey(hash, principalId);
boundedSet(retrievalCounts, key, (retrievalCounts.get(key) ?? 0) + 1);
}
/**
* Returns true if the block has been retrieved often enough that it
* should be excluded from compression in future requests.
* Returns true if the block has been retrieved often enough by this principal
* that it should be excluded from compression in future requests.
* Each principal's feedback is isolated from other principals.
*/
export function shouldSkipCompression(hash: string): boolean {
return (retrievalCounts.get(hash) ?? 0) >= RETRIEVAL_THRESHOLD;
export function shouldSkipCompression(hash: string, principalId?: string): boolean {
const key = buildStoreKey(hash, principalId);
return (retrievalCounts.get(key) ?? 0) >= RETRIEVAL_THRESHOLD;
}
/**
@@ -107,21 +154,29 @@ export function resetCcrStore(): void {
/**
* Handler for the `omniroute_ccr_retrieve` MCP tool.
*
* The `callerId` parameter must be the authenticated principal id derived from
* the MCP `extra` context (see compressionTools.ts). Only the principal that
* stored the block can retrieve it.
*
* Returns the verbatim block for the given hash, or an error object.
*/
export function handleCcrRetrieve(args: { hash: string }): { content: string } | { error: string } {
export function handleCcrRetrieve(
args: { hash: string },
callerId?: string
): { content: string } | { error: string } {
if (!args.hash || typeof args.hash !== "string") {
return { error: "hash parameter is required and must be a string" };
}
const block = retrieveBlock(args.hash);
const block = retrieveBlock(args.hash, callerId);
if (block === null) {
return {
error: `CCR block not found for hash=${args.hash}. The block may have expired or the hash is invalid.`,
};
}
recordRetrieval(args.hash);
recordRetrieval(args.hash, callerId);
return { content: block };
}
@@ -146,7 +201,8 @@ function buildMarker(hash: string, charCount: number): string {
*/
function maybeCcrReplace(
text: string,
minChars: number
minChars: number,
principalId?: string
): { text: string; replaced: boolean; hash: string | null } {
if (text.length < minChars) {
return { text, replaced: false, hash: null };
@@ -154,8 +210,8 @@ function maybeCcrReplace(
const hash = hashContent(text);
// Skip if this hash is flagged as do-not-compress
if (shouldSkipCompression(hash)) {
// Skip if this (principal, hash) pair is flagged as do-not-compress
if (shouldSkipCompression(hash, principalId)) {
return { text, replaced: false, hash: null };
}
@@ -166,7 +222,7 @@ function maybeCcrReplace(
return { text, replaced: false, hash: null };
}
storeBlock(text);
storeBlock(text, principalId);
return { text: marker, replaced: true, hash };
}
@@ -175,7 +231,8 @@ function maybeCcrReplace(
*/
function processMessages(
messages: MessageLike[],
minChars: number
minChars: number,
principalId?: string
): { messages: MessageLike[]; replacedCount: number } {
let replacedCount = 0;
@@ -183,7 +240,7 @@ function processMessages(
if (msg.role === "system") return { ...msg };
if (typeof msg.content === "string") {
const { text, replaced } = maybeCcrReplace(msg.content, minChars);
const { text, replaced } = maybeCcrReplace(msg.content, minChars, principalId);
if (replaced) {
replacedCount++;
return { ...msg, content: text };
@@ -195,7 +252,7 @@ function processMessages(
let changed = false;
const newContent = msg.content.map((part) => {
if (part["type"] !== "text" || typeof part["text"] !== "string") return part;
const { text, replaced } = maybeCcrReplace(part["text"] as string, minChars);
const { text, replaced } = maybeCcrReplace(part["text"] as string, minChars, principalId);
if (replaced) {
changed = true;
replacedCount++;
@@ -253,11 +310,15 @@ function validateCcrConfig(config: Record<string, unknown>): EngineValidationRes
/**
* Reconstruct a body by replacing all `[CCR retrieve hash=<24hex> chars=N]`
* markers with the stored verbatim blocks.
* markers with the stored verbatim blocks for the given principal.
*
* Returns a new body object with markers restored to their original text.
* Markers that do not belong to the given principal remain unchanged.
*/
export function reconstructCcr(body: Record<string, unknown>): Record<string, unknown> {
export function reconstructCcr(
body: Record<string, unknown>,
principalId?: string
): Record<string, unknown> {
const messages = body["messages"];
if (!Array.isArray(messages)) return body;
@@ -272,7 +333,7 @@ export function reconstructCcr(body: Record<string, unknown>): Record<string, un
if (typeof content === "string") {
const reconstructed = content.replace(MARKER_RE, (_m, hash: string) => {
return ccrStore.get(hash) ?? _m;
return retrieveBlock(hash, principalId) ?? _m;
});
return reconstructed !== content ? { ...msg, content: reconstructed } : { ...msg };
}
@@ -282,7 +343,7 @@ export function reconstructCcr(body: Record<string, unknown>): Record<string, un
const newContent = content.map((part) => {
if (part["type"] !== "text" || typeof part["text"] !== "string") return part;
const reconstructed = (part["text"] as string).replace(MARKER_RE, (_m, hash: string) => {
return ccrStore.get(hash) ?? _m;
return retrieveBlock(hash, principalId) ?? _m;
});
if (reconstructed !== part["text"]) {
changed = true;
@@ -307,7 +368,8 @@ export const ccrEngine: CompressionEngine = {
description:
"Replaces large blocks of text with content-addressed retrieve markers " +
"`[CCR retrieve hash=<24hex> chars=N]`. The original block is stored and " +
"retrievable via the `omniroute_ccr_retrieve` MCP tool (H4).",
"retrievable via the `omniroute_ccr_retrieve` MCP tool (H4). " +
"Store is principal-scoped: only the storing principal can retrieve their blocks.",
icon: "archive",
targets: ["messages"],
stackable: true,
@@ -319,7 +381,7 @@ export const ccrEngine: CompressionEngine = {
name: "CCR (Content-Compression-Retrieve)",
description:
"Reversible compression: large blocks → retrieve marker. " +
"Original retrievable via MCP tool (H4).",
"Original retrievable via MCP tool (H4). Principal-scoped for tenant isolation.",
inputScope: "messages",
targetLatencyMs: 1,
supportsPreview: true,
@@ -346,7 +408,8 @@ export const ccrEngine: CompressionEngine = {
const start = performance.now();
const { messages: newMessages, replacedCount } = processMessages(
messages as MessageLike[],
minChars
minChars,
options?.principalId
);
if (replacedCount === 0) {

View File

@@ -35,6 +35,8 @@ export interface CompressionEngineApplyOptions {
config?: CompressionConfig;
compressionComboId?: string | null;
stepConfig?: Record<string, unknown>;
/** Authenticated principal (API key id) making the request. Used by CCR to scope its store. */
principalId?: string;
}
export interface CompressionEngine {

View File

@@ -70,7 +70,12 @@ export function selectCompressionStrategy(
export function applyCompression(
body: Record<string, unknown>,
mode: CompressionMode,
options?: { model?: string; supportsVision?: boolean | null; config?: CompressionConfig }
options?: {
model?: string;
supportsVision?: boolean | null;
config?: CompressionConfig;
principalId?: string;
}
): CompressionResult {
if (mode === "off") {
return { body, compressed: false, stats: null };
@@ -190,7 +195,12 @@ export function applyCompression(
export async function applyCompressionAsync(
body: Record<string, unknown>,
mode: CompressionMode,
options?: { model?: string; supportsVision?: boolean | null; config?: CompressionConfig }
options?: {
model?: string;
supportsVision?: boolean | null;
config?: CompressionConfig;
principalId?: string;
}
): Promise<CompressionResult> {
if (mode === "stacked") {
const adapter = adaptBodyForCompression(body);
@@ -231,6 +241,8 @@ interface StackOptions {
compressionComboId?: string | null;
/** TV1 bail-out discipline (opt-in, default disabled). */
bailout?: BailoutConfig;
/** Authenticated principal id — threaded through to CCR engine for store scoping. */
principalId?: string;
}
/** Accumulates per-step telemetry across a stacked run (shared sync/async). */
@@ -274,6 +286,7 @@ function buildStepOptions(
return {
...options,
compressionComboId: options?.compressionComboId ?? options?.config?.compressionComboId,
principalId: options?.principalId,
stepConfig: {
...(step.config ?? {}),
...(step.intensity ? { intensity: step.intensity } : {}),

View File

@@ -0,0 +1,268 @@
/**
* TDD security tests: CCR cross-tenant IDOR + bounded memory + scoped feedback.
* Run: node --import tsx/esm --test tests/unit/compression/ccr-cross-tenant.test.ts
*
* RED → GREEN: these tests define the security contract and must drive the fix.
*
* Security assertions:
* 1. Cross-tenant IDOR blocked: principal B cannot retrieve a block stored by A.
* 2. Anonymous principal cannot retrieve a block stored by a named principal.
* 3. Memory bound: store never exceeds MAX_CCR_ENTRIES (FIFO eviction).
* 4. Scoped feedback: A's retrievals do NOT flip B's shouldSkipCompression.
* 5. handleCcrRetrieve end-to-end: cross-tenant retrieve returns not-found error.
*/
import crypto from "node:crypto";
import { describe, it, beforeEach } from "node:test";
import assert from "node:assert/strict";
import {
storeBlock,
retrieveBlock,
recordRetrieval,
shouldSkipCompression,
resetCcrStore,
handleCcrRetrieve,
ccrEngine,
MAX_CCR_ENTRIES,
} from "../../../open-sse/services/compression/engines/ccr/index.ts";
// ─── helpers ──────────────────────────────────────────────────────────────────
function makeText(seed: string, length: number = 700): string {
return seed.repeat(Math.ceil(length / seed.length)).slice(0, length);
}
const TEXT_A = makeText("content for principal A — confidential data");
const TEXT_B = makeText("content for principal B — different tenant");
function contentHash(text: string): string {
return crypto.createHash("sha256").update(text).digest("hex").slice(0, 24);
}
// ─── cross-tenant IDOR isolation ─────────────────────────────────────────────
describe("ccr security: cross-tenant IDOR isolation", () => {
beforeEach(() => resetCcrStore());
it("[HIGH] principal B cannot retrieve a block stored by principal A", () => {
const hash = storeBlock(TEXT_A, "principalA");
// Same principal can retrieve
const resultA = retrieveBlock(hash, "principalA");
assert.equal(resultA, TEXT_A, "principal A must be able to retrieve its own block");
// Different principal is blocked — THE CORE SECURITY ASSERTION
const resultB = retrieveBlock(hash, "principalB");
assert.equal(
resultB,
null,
"[HIGH IDOR] principal B must NOT be able to retrieve principal A's block"
);
});
it("[HIGH] anonymous principal cannot retrieve a block stored by a named principal", () => {
const hash = storeBlock(TEXT_A, "principalA");
const resultAnon = retrieveBlock(hash, undefined);
assert.equal(
resultAnon,
null,
"[HIGH IDOR] anonymous principal must NOT retrieve a named principal's block"
);
});
it("named principal cannot retrieve a block stored by anonymous", () => {
const hash = storeBlock(TEXT_B, undefined);
const resultA = retrieveBlock(hash, "principalA");
assert.equal(resultA, null, "principal A must NOT retrieve an anonymous block");
// The anonymous store owner can retrieve
const resultAnon = retrieveBlock(hash, undefined);
assert.equal(resultAnon, TEXT_B, "anonymous can retrieve its own block");
});
it("principal with no stored block cannot retrieve via another principal's hash", () => {
// Store TEXT_A only under principalA — principalB never stores anything.
const hashA = storeBlock(TEXT_A, "principalA");
// principalA can retrieve their own block
assert.equal(retrieveBlock(hashA, "principalA"), TEXT_A, "A can retrieve its own block");
// principalB never stored anything — cannot retrieve even with the correct hash
assert.equal(
retrieveBlock(hashA, "principalB"),
null,
"[HIGH IDOR] B (never stored) cannot retrieve A's block by reusing A's hash"
);
// anonymous similarly cannot
assert.equal(
retrieveBlock(hashA, undefined),
null,
"[HIGH IDOR] anonymous cannot retrieve A's block"
);
});
});
// ─── handleCcrRetrieve end-to-end isolation ───────────────────────────────────
describe("ccr security: handleCcrRetrieve end-to-end isolation", () => {
beforeEach(() => resetCcrStore());
it("handleCcrRetrieve with correct callerId returns content", () => {
const hash = storeBlock(TEXT_A, "callerA");
const result = handleCcrRetrieve({ hash }, "callerA");
assert.ok("content" in result, "same-caller retrieve must return content");
assert.equal((result as { content: string }).content, TEXT_A);
});
it("[HIGH] handleCcrRetrieve with wrong callerId returns not-found error", () => {
const hash = storeBlock(TEXT_A, "callerA");
const result = handleCcrRetrieve({ hash }, "callerB");
assert.ok(
"error" in result,
"[HIGH IDOR] cross-tenant retrieve via handleCcrRetrieve must return error, not content"
);
assert.ok(!("content" in result), "[HIGH IDOR] cross-tenant retrieve must NOT return content");
});
it("handleCcrRetrieve without callerId cannot access named-principal block", () => {
const hash = storeBlock(TEXT_A, "callerA");
const result = handleCcrRetrieve({ hash }, undefined);
assert.ok("error" in result, "anonymous retrieve of named-principal block must return error");
});
it("handleCcrRetrieve returns error for completely unknown hash", () => {
const result = handleCcrRetrieve({ hash: "000000000000000000000000" }, "anyPrincipal");
assert.ok("error" in result, "unknown hash must return error");
assert.ok(typeof (result as { error: string }).error === "string");
});
it("handleCcrRetrieve returns error for missing hash parameter", () => {
// @ts-expect-error — intentional wrong call to verify guard
const result = handleCcrRetrieve({}, "caller");
assert.ok("error" in result, "missing hash must return error");
});
});
// ─── scoped retrieval feedback ────────────────────────────────────────────────
describe("ccr security: [MEDIUM] scoped retrieval feedback (shouldSkipCompression)", () => {
beforeEach(() => resetCcrStore());
it("A's retrievals do NOT flip B's shouldSkipCompression", () => {
const hash = storeBlock(TEXT_A, "principalA");
storeBlock(TEXT_A, "principalB"); // same content stored under B
// Record retrievals for A above the threshold (default 3)
recordRetrieval(hash, "principalA");
recordRetrieval(hash, "principalA");
recordRetrieval(hash, "principalA");
// A's compression should be skipped
assert.equal(
shouldSkipCompression(hash, "principalA"),
true,
"A's compression should be skipped after threshold retrievals"
);
// B's compression must NOT be affected
assert.equal(
shouldSkipCompression(hash, "principalB"),
false,
"[MEDIUM] A's retrievals must NOT flip B's shouldSkipCompression"
);
});
it("shouldSkipCompression returns false for a principal with no retrievals", () => {
const hash = storeBlock(TEXT_A, "principalA");
assert.equal(shouldSkipCompression(hash, "principalA"), false);
assert.equal(shouldSkipCompression(hash, "principalB"), false);
assert.equal(shouldSkipCompression(hash, undefined), false);
});
});
// ─── bounded memory (FIFO eviction) ──────────────────────────────────────────
describe("ccr security: [MEDIUM] bounded memory (FIFO eviction)", () => {
beforeEach(() => resetCcrStore());
it("MAX_CCR_ENTRIES is exported and positive", () => {
assert.ok(typeof MAX_CCR_ENTRIES === "number", "MAX_CCR_ENTRIES must be a number");
assert.ok(MAX_CCR_ENTRIES > 0, "MAX_CCR_ENTRIES must be positive");
});
it("FIFO eviction: oldest entry is evicted when cap is reached", () => {
const principal = "evictionTest";
// Fill to exactly cap with unique blocks
const firstText = `eviction block 0 ${"y".repeat(30)}`;
const firstHash = storeBlock(firstText, principal);
for (let i = 1; i < MAX_CCR_ENTRIES; i++) {
storeBlock(`eviction block ${i} ${"y".repeat(30)}`, principal);
}
// Confirm first block is present before overflow
assert.equal(
retrieveBlock(firstHash, principal),
firstText,
"first block must be present at exactly-cap state"
);
// Insert one more unique block — should evict the first (oldest) entry
storeBlock(`eviction overflow block ${"z".repeat(50)}`, principal);
// First block should now be gone
assert.equal(
retrieveBlock(firstHash, principal),
null,
"[MEDIUM] FIFO eviction: oldest block must be evicted when cap is reached"
);
});
it("store does not grow unboundedly beyond MAX_CCR_ENTRIES distinct principals", () => {
// Insert MAX+5 distinct (principal, content) pairs and verify the overall store
// did not go unbounded by checking that the first inserted block is evicted.
const firstText = `overflow test block 0 ${"a".repeat(40)}`;
const firstPrincipal = "overflow-p-0";
const firstHash = storeBlock(firstText, firstPrincipal);
for (let i = 1; i < MAX_CCR_ENTRIES + 5; i++) {
storeBlock(`overflow test block ${i} ${"a".repeat(40)}`, `overflow-p-${i}`);
}
// The first inserted store-key should have been evicted by FIFO
assert.equal(
retrieveBlock(firstHash, firstPrincipal),
null,
"[MEDIUM] unbounded-growth prevented: first inserted entry must be evicted after MAX+5 inserts"
);
});
});
// ─── engine-level scoping: apply() threads principalId end-to-end ─────────────
describe("ccr security: [HIGH] ccrEngine.apply scopes the stored block to the principal", () => {
beforeEach(() => resetCcrStore());
const bigBlock = makeText("a large block that CCR would normally compress ", 5000);
const makeBody = () => ({ messages: [{ role: "user", content: bigBlock }] });
it("apply with a principalId stores the block retrievable ONLY by that principal", () => {
const result = ccrEngine.apply(makeBody(), {
principalId: "principalA",
stepConfig: { minChars: 100 },
});
assert.equal(result.compressed, true, "CCR compresses the large block");
const hash = contentHash(bigBlock);
assert.equal(retrieveBlock(hash, "principalA"), bigBlock, "owner principal A can retrieve");
assert.equal(
retrieveBlock(hash, "principalB"),
null,
"[HIGH IDOR] principal B must NOT retrieve principal A's apply()-stored block"
);
});
});