150 lines
5.8 KiB
JavaScript
150 lines
5.8 KiB
JavaScript
// lib/exporters/field-allowlist.mjs
|
|
// CWE-212 (Improper Cross-boundary Removal of Sensitive Data) mitigation:
|
|
// inline static allowlist that strips PII / high-cardinality fields before
|
|
// records reach the OTel exporter.
|
|
//
|
|
// The allowlist is INLINE STATIC at module-scope (Object.freeze). NEVER read
|
|
// runtime from tests/fixtures/ — fixture file is AUTHORING reference only
|
|
// (Source: tests/fixtures/jsonl-schemas.md per Step 1 audit).
|
|
//
|
|
// Per-schema allowlists drop:
|
|
// - command_excerpt (post-bash-stats: arbitrary Bash slice, CWE-212)
|
|
// - session_id (UUID, high-cardinality, log-only)
|
|
// - task / question / project_dir / brief_path / plan / project (PII-ish prose, paths)
|
|
// - payload (event-emit: open-ended object, allowlist payload-keys instead)
|
|
// - phase_models (structured object, summarize via `profile` label instead)
|
|
// - counts (review nested object, flatten to per-severity metrics elsewhere)
|
|
|
|
// ---- Per-schema allowlists (Source: tests/fixtures/jsonl-schemas.md) -------
|
|
|
|
// Source: tests/fixtures/jsonl-schemas.md row 1 (trekbrief)
|
|
const TREKBRIEF_ALLOWED = Object.freeze(new Set([
|
|
'ts', 'slug', 'mode', 'interview_turns', 'review_iterations',
|
|
'brief_quality', 'research_topics', 'auto_research', 'auto_result',
|
|
'profile', 'profile_source',
|
|
]));
|
|
|
|
// Source: tests/fixtures/jsonl-schemas.md row 2 (trekresearch)
|
|
const TREKRESEARCH_ALLOWED = Object.freeze(new Set([
|
|
'ts', 'slug', 'mode', 'scope', 'dimensions', 'agents_local',
|
|
'agents_external', 'gemini_used', 'confidence', 'contradictions',
|
|
'open_questions', 'profile', 'parallel_agents',
|
|
'external_research_enabled', 'profile_source',
|
|
]));
|
|
|
|
// Source: tests/fixtures/jsonl-schemas.md row 3 (trekplan)
|
|
const TREKPLAN_ALLOWED = Object.freeze(new Set([
|
|
'ts', 'slug', 'mode', 'codebase_size', 'codebase_files',
|
|
'agents_deployed', 'deep_dives', 'research_briefs_used',
|
|
'research_scout_used', 'critic_verdict', 'guardian_verdict', 'outcome',
|
|
'profile', 'parallel_agents', 'profile_source',
|
|
]));
|
|
|
|
// Source: tests/fixtures/jsonl-schemas.md row 4 (trekexecute Phase 9 record)
|
|
const TREKEXECUTE_ALLOWED = Object.freeze(new Set([
|
|
'ts', 'plan_type', 'mode', 'result', 'steps_total', 'steps_passed',
|
|
'steps_failed', 'steps_skipped', 'failed_at_step',
|
|
'profile', 'profile_source',
|
|
]));
|
|
|
|
// Source: tests/fixtures/jsonl-schemas.md row 5 (trekexecute autonomy events)
|
|
const EVENT_EMIT_ALLOWED = Object.freeze(new Set([
|
|
'ts', 'event', 'known_event',
|
|
]));
|
|
|
|
// Source: tests/fixtures/jsonl-schemas.md row 5 (event-emit payload sub-allowlist)
|
|
const EVENT_EMIT_PAYLOAD_ALLOWED = Object.freeze(new Set([
|
|
'profile', 'phase_models', 'parallel_agents',
|
|
'external_research_enabled', 'profile_source',
|
|
'brief_quality', 'plan_grade',
|
|
]));
|
|
|
|
// Source: tests/fixtures/jsonl-schemas.md row 6 (post-bash-stats — PII-flag)
|
|
// CWE-212: command_excerpt + session_id MUST be stripped.
|
|
const POST_BASH_STATS_ALLOWED = Object.freeze(new Set([
|
|
'ts', 'duration_ms', 'success',
|
|
]));
|
|
|
|
// Source: tests/fixtures/jsonl-schemas.md row 7 (trekreview)
|
|
const TREKREVIEW_ALLOWED = Object.freeze(new Set([
|
|
'ts', 'slug', 'verdict', 'reviewed_files_count', 'mode',
|
|
'duration_ms', 'profile', 'profile_source',
|
|
]));
|
|
|
|
// Source: tests/fixtures/jsonl-schemas.md row 8 (trekcontinue)
|
|
const TREKCONTINUE_ALLOWED = Object.freeze(new Set([
|
|
'ts', 'next_session_label', 'status', 'profile', 'profile_source',
|
|
]));
|
|
|
|
// Source: tests/fixtures/jsonl-schemas.md row 9 (token-usage — SKAL-2)
|
|
// CWE-212: numeric + low-cardinality-label fields ONLY. DENY BY OMISSION:
|
|
// session_id (UUID), transcript_path (filesystem path), cwd (filesystem path)
|
|
// are written into the jsonl for upsert keying but MUST NOT reach the exporter.
|
|
const TOKEN_USAGE_ALLOWED = Object.freeze(new Set([
|
|
'ts', 'scope', 'model',
|
|
'tokens_input', 'tokens_output', 'tokens_cache_creation', 'tokens_cache_read',
|
|
'cost_usd', 'is_estimate', 'price_table_version',
|
|
]));
|
|
|
|
// Schema-id → allowlist set
|
|
const SCHEMA_ALLOWLISTS = Object.freeze({
|
|
'trekbrief': TREKBRIEF_ALLOWED,
|
|
'trekresearch': TREKRESEARCH_ALLOWED,
|
|
'trekplan': TREKPLAN_ALLOWED,
|
|
'trekexecute': TREKEXECUTE_ALLOWED,
|
|
'event-emit': EVENT_EMIT_ALLOWED,
|
|
'post-bash-stats': POST_BASH_STATS_ALLOWED,
|
|
'post_bash_stats': POST_BASH_STATS_ALLOWED, // common alt-spelling
|
|
'trekreview': TREKREVIEW_ALLOWED,
|
|
'trekcontinue': TREKCONTINUE_ALLOWED,
|
|
'token-usage': TOKEN_USAGE_ALLOWED,
|
|
});
|
|
|
|
/**
|
|
* Apply field-allowlist to a record. Drops any field not in the schema's
|
|
* allowlist. Returns a new object (does not mutate input).
|
|
*
|
|
* Always preserves `_schema_id` (caller-supplied identifier used by exporters
|
|
* to group records by source).
|
|
*
|
|
* @param {object} record
|
|
* @param {string} schemaType Schema-id (e.g. 'trekplan', 'post-bash-stats')
|
|
* @returns {object} Redacted record with only allowlisted fields.
|
|
*/
|
|
export function applyFieldAllowlist(record, schemaType) {
|
|
if (!record || typeof record !== 'object') return {};
|
|
const allowed = SCHEMA_ALLOWLISTS[schemaType];
|
|
if (!allowed) {
|
|
// Unknown schema-type: be conservative — keep ts only + _schema_id.
|
|
return record.ts ? { _schema_id: schemaType, ts: record.ts } : { _schema_id: schemaType };
|
|
}
|
|
const out = { _schema_id: schemaType };
|
|
for (const k of allowed) {
|
|
if (k in record) out[k] = record[k];
|
|
}
|
|
|
|
// Special: event-emit records have a `payload` sub-object that needs its
|
|
// own allowlist applied recursively.
|
|
if (schemaType === 'event-emit' && record.payload && typeof record.payload === 'object') {
|
|
const subOut = {};
|
|
for (const k of EVENT_EMIT_PAYLOAD_ALLOWED) {
|
|
if (k in record.payload) subOut[k] = record.payload[k];
|
|
}
|
|
out.payload = subOut;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
export {
|
|
SCHEMA_ALLOWLISTS,
|
|
EVENT_EMIT_PAYLOAD_ALLOWED,
|
|
POST_BASH_STATS_ALLOWED,
|
|
TREKBRIEF_ALLOWED,
|
|
TREKRESEARCH_ALLOWED,
|
|
TREKPLAN_ALLOWED,
|
|
TREKEXECUTE_ALLOWED,
|
|
TREKREVIEW_ALLOWED,
|
|
TREKCONTINUE_ALLOWED,
|
|
TOKEN_USAGE_ALLOWED,
|
|
};
|