@massu/core 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +71 -0
- package/README.md +2 -2
- package/dist/hooks/cost-tracker.js +149 -11527
- package/dist/hooks/post-edit-context.js +127 -11493
- package/dist/hooks/post-tool-use.js +169 -11550
- package/dist/hooks/pre-compact.js +149 -11530
- package/dist/hooks/pre-delete-check.js +144 -11523
- package/dist/hooks/quality-event.js +149 -11527
- package/dist/hooks/session-end.js +188 -11570
- package/dist/hooks/session-start.js +159 -11534
- package/dist/hooks/user-prompt.js +149 -11530
- package/package.json +14 -19
- package/src/adr-generator.ts +292 -0
- package/src/analytics.ts +373 -0
- package/src/audit-trail.ts +450 -0
- package/src/backfill-sessions.ts +180 -0
- package/src/cli.ts +105 -0
- package/src/cloud-sync.ts +190 -0
- package/src/commands/doctor.ts +300 -0
- package/src/commands/init.ts +395 -0
- package/src/commands/install-hooks.ts +26 -0
- package/src/config.ts +357 -0
- package/src/cost-tracker.ts +355 -0
- package/src/db.ts +233 -0
- package/src/dependency-scorer.ts +337 -0
- package/src/docs-map.json +100 -0
- package/src/docs-tools.ts +517 -0
- package/src/domains.ts +181 -0
- package/src/hooks/cost-tracker.ts +66 -0
- package/src/hooks/intent-suggester.ts +131 -0
- package/src/hooks/post-edit-context.ts +91 -0
- package/src/hooks/post-tool-use.ts +175 -0
- package/src/hooks/pre-compact.ts +146 -0
- package/src/hooks/pre-delete-check.ts +153 -0
- package/src/hooks/quality-event.ts +127 -0
- package/src/hooks/security-gate.ts +121 -0
- package/src/hooks/session-end.ts +467 -0
- package/src/hooks/session-start.ts +210 -0
- package/src/hooks/user-prompt.ts +91 -0
- package/src/import-resolver.ts +224 -0
- package/src/memory-db.ts +1376 -0
- package/src/memory-tools.ts +391 -0
- package/src/middleware-tree.ts +70 -0
- package/src/observability-tools.ts +343 -0
- package/src/observation-extractor.ts +411 -0
- package/src/page-deps.ts +283 -0
- package/src/prompt-analyzer.ts +332 -0
- package/src/regression-detector.ts +319 -0
- package/src/rules.ts +57 -0
- package/src/schema-mapper.ts +232 -0
- package/src/security-scorer.ts +405 -0
- package/src/security-utils.ts +133 -0
- package/src/sentinel-db.ts +578 -0
- package/src/sentinel-scanner.ts +405 -0
- package/src/sentinel-tools.ts +512 -0
- package/src/sentinel-types.ts +140 -0
- package/src/server.ts +189 -0
- package/src/session-archiver.ts +112 -0
- package/src/session-state-generator.ts +174 -0
- package/src/team-knowledge.ts +407 -0
- package/src/tools.ts +847 -0
- package/src/transcript-parser.ts +458 -0
- package/src/trpc-index.ts +214 -0
- package/src/validate-features-runner.ts +106 -0
- package/src/validation-engine.ts +358 -0
- package/dist/cli.js +0 -7890
- package/dist/server.js +0 -7008
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Copyright (c) 2026 Massu. All rights reserved.
|
|
2
|
+
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
|
+
|
|
4
|
+
import { resolve, normalize } from 'path';
|
|
5
|
+
|
|
6
|
+
// ============================================================
|
|
7
|
+
// Shared Security Utilities
|
|
8
|
+
// ============================================================
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Ensure a resolved file path stays within the project root.
|
|
12
|
+
* Prevents path traversal attacks via `../../etc/passwd` style inputs.
|
|
13
|
+
*
|
|
14
|
+
* @returns The resolved absolute path if safe
|
|
15
|
+
* @throws Error if the path escapes the project root
|
|
16
|
+
*/
|
|
17
|
+
export function ensureWithinRoot(filePath: string, projectRoot: string): string {
|
|
18
|
+
const resolvedRoot = resolve(projectRoot);
|
|
19
|
+
const resolvedPath = resolve(resolvedRoot, filePath);
|
|
20
|
+
const normalizedPath = normalize(resolvedPath);
|
|
21
|
+
const normalizedRoot = normalize(resolvedRoot);
|
|
22
|
+
|
|
23
|
+
if (!normalizedPath.startsWith(normalizedRoot + '/') && normalizedPath !== normalizedRoot) {
|
|
24
|
+
throw new Error(`Path traversal blocked: "${filePath}" resolves outside project root`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return normalizedPath;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Escape regex special characters in a string.
|
|
32
|
+
* Use when building RegExp from user/config-provided strings.
|
|
33
|
+
*/
|
|
34
|
+
export function escapeRegex(str: string): string {
|
|
35
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validate that a regex pattern is safe to compile.
|
|
40
|
+
* Rejects patterns likely to cause catastrophic backtracking (ReDoS).
|
|
41
|
+
*
|
|
42
|
+
* Checks:
|
|
43
|
+
* 1. Pattern length limit (prevents memory exhaustion)
|
|
44
|
+
* 2. Nested quantifiers (e.g., `(a+)+`, `(a*)*`, `(a+)*`)
|
|
45
|
+
* 3. Tries compilation with a timeout guard
|
|
46
|
+
*
|
|
47
|
+
* @returns The compiled RegExp if safe, or null if rejected
|
|
48
|
+
*/
|
|
49
|
+
export function safeRegex(pattern: string, flags?: string): RegExp | null {
|
|
50
|
+
// Length guard
|
|
51
|
+
if (pattern.length > 500) return null;
|
|
52
|
+
|
|
53
|
+
// Detect nested quantifiers - the primary cause of ReDoS
|
|
54
|
+
// Matches patterns like (X+)+, (X*)+, (X+)*, (X{n,})+, etc.
|
|
55
|
+
if (/(\([^)]*[+*}][^)]*\))[+*{]/.test(pattern)) return null;
|
|
56
|
+
|
|
57
|
+
// Detect alternation with overlapping repetition: (a|a)+
|
|
58
|
+
// This is harder to detect perfectly, so we use a conservative heuristic
|
|
59
|
+
if (/\([^)]*\|[^)]*\)[+*]{1,2}/.test(pattern) && pattern.length > 100) return null;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
return new RegExp(pattern, flags);
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Convert a glob pattern to a safe regex.
|
|
70
|
+
* Unlike naive star-replacement, this escapes all other special chars
|
|
71
|
+
* first and uses non-greedy matching to prevent backtracking.
|
|
72
|
+
*/
|
|
73
|
+
export function globToSafeRegex(glob: string): RegExp {
|
|
74
|
+
// Escape everything except * and **
|
|
75
|
+
const escaped = glob
|
|
76
|
+
.split('**')
|
|
77
|
+
.map(segment =>
|
|
78
|
+
segment
|
|
79
|
+
.split('*')
|
|
80
|
+
.map(part => escapeRegex(part))
|
|
81
|
+
.join('[^/]*')
|
|
82
|
+
)
|
|
83
|
+
.join('.*');
|
|
84
|
+
|
|
85
|
+
return new RegExp(`^${escaped}$`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Redact sensitive patterns from text before storage.
|
|
90
|
+
* Replaces URLs, email addresses, API keys, tokens, and file paths
|
|
91
|
+
* with redaction placeholders.
|
|
92
|
+
*/
|
|
93
|
+
export function redactSensitiveContent(text: string): string {
|
|
94
|
+
return text
|
|
95
|
+
// API keys / tokens (sk-..., ghp_..., xoxb-..., etc.)
|
|
96
|
+
.replace(/\b(sk-|ghp_|gho_|xoxb-|xoxp-|AKIA)[A-Za-z0-9_-]{10,}\b/g, '[REDACTED_KEY]')
|
|
97
|
+
// Bearer tokens
|
|
98
|
+
.replace(/Bearer\s+[A-Za-z0-9._~+/=-]{20,}/gi, 'Bearer [REDACTED_TOKEN]')
|
|
99
|
+
// Connection strings with passwords (must run BEFORE email to avoid partial match)
|
|
100
|
+
.replace(/:\/\/[^:]+:[^@\s]+@/g, '://[REDACTED_CREDENTIALS]@')
|
|
101
|
+
// URLs with auth tokens in query params
|
|
102
|
+
.replace(/(https?:\/\/[^\s]+[?&](?:token|key|secret|password|auth)=)[^\s&]*/gi, '$1[REDACTED]')
|
|
103
|
+
// Email addresses
|
|
104
|
+
.replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, '[REDACTED_EMAIL]')
|
|
105
|
+
// Absolute file paths (preserve relative)
|
|
106
|
+
.replace(/(?:\/Users\/|\/home\/|C:\\Users\\)[^\s"'`]+/g, '[REDACTED_PATH]');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Minimum severity weight floors to prevent disabling security scoring via config. */
|
|
110
|
+
export const MINIMUM_SEVERITY_WEIGHTS: Record<string, number> = {
|
|
111
|
+
critical: 10,
|
|
112
|
+
high: 5,
|
|
113
|
+
medium: 2,
|
|
114
|
+
low: 1,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Merge config severity weights with minimum floors.
|
|
119
|
+
* Prevents configs from zeroing out severity weights to disable scoring.
|
|
120
|
+
*/
|
|
121
|
+
export function enforceSeverityFloors(
|
|
122
|
+
configWeights: Record<string, number>,
|
|
123
|
+
defaults: Record<string, number>
|
|
124
|
+
): Record<string, number> {
|
|
125
|
+
const result: Record<string, number> = { ...defaults };
|
|
126
|
+
|
|
127
|
+
for (const [severity, configValue] of Object.entries(configWeights)) {
|
|
128
|
+
const floor = MINIMUM_SEVERITY_WEIGHTS[severity] ?? 1;
|
|
129
|
+
result[severity] = Math.max(configValue, floor);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return result;
|
|
133
|
+
}
|