@develler/remediation-agent 1.0.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/dist/commands/connect.d.ts +17 -0
- package/dist/commands/connect.d.ts.map +1 -0
- package/dist/commands/connect.js +138 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/contracts/agentConnection.d.ts +28 -0
- package/dist/contracts/agentConnection.d.ts.map +1 -0
- package/dist/contracts/agentConnection.js +3 -0
- package/dist/contracts/agentConnection.js.map +1 -0
- package/dist/contracts/lifecycleInterceptor.d.ts +22 -0
- package/dist/contracts/lifecycleInterceptor.d.ts.map +1 -0
- package/dist/contracts/lifecycleInterceptor.js +3 -0
- package/dist/contracts/lifecycleInterceptor.js.map +1 -0
- package/dist/controllers/extractionController.d.ts +12 -0
- package/dist/controllers/extractionController.d.ts.map +1 -0
- package/dist/controllers/extractionController.js +91 -0
- package/dist/controllers/extractionController.js.map +1 -0
- package/dist/controllers/webhookController.d.ts +16 -0
- package/dist/controllers/webhookController.d.ts.map +1 -0
- package/dist/controllers/webhookController.js +74 -0
- package/dist/controllers/webhookController.js.map +1 -0
- package/dist/exceptions/ReplayError.d.ts +4 -0
- package/dist/exceptions/ReplayError.d.ts.map +1 -0
- package/dist/exceptions/ReplayError.js +11 -0
- package/dist/exceptions/ReplayError.js.map +1 -0
- package/dist/exceptions/SignatureError.d.ts +4 -0
- package/dist/exceptions/SignatureError.d.ts.map +1 -0
- package/dist/exceptions/SignatureError.js +11 -0
- package/dist/exceptions/SignatureError.js.map +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +98 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +7 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +26 -0
- package/dist/logger.js.map +1 -0
- package/dist/middleware/remediationInterceptor.d.ts +42 -0
- package/dist/middleware/remediationInterceptor.d.ts.map +1 -0
- package/dist/middleware/remediationInterceptor.js +144 -0
- package/dist/middleware/remediationInterceptor.js.map +1 -0
- package/dist/services/AgentConnectionService.d.ts +37 -0
- package/dist/services/AgentConnectionService.d.ts.map +1 -0
- package/dist/services/AgentConnectionService.js +186 -0
- package/dist/services/AgentConnectionService.js.map +1 -0
- package/dist/services/AstExtractorService.d.ts +18 -0
- package/dist/services/AstExtractorService.d.ts.map +1 -0
- package/dist/services/AstExtractorService.js +166 -0
- package/dist/services/AstExtractorService.js.map +1 -0
- package/dist/services/CircuitBreakerService.d.ts +29 -0
- package/dist/services/CircuitBreakerService.d.ts.map +1 -0
- package/dist/services/CircuitBreakerService.js +93 -0
- package/dist/services/CircuitBreakerService.js.map +1 -0
- package/dist/services/InstructionCacheService.d.ts +28 -0
- package/dist/services/InstructionCacheService.d.ts.map +1 -0
- package/dist/services/InstructionCacheService.js +79 -0
- package/dist/services/InstructionCacheService.js.map +1 -0
- package/dist/services/MaskingEngine.d.ts +27 -0
- package/dist/services/MaskingEngine.d.ts.map +1 -0
- package/dist/services/MaskingEngine.js +88 -0
- package/dist/services/MaskingEngine.js.map +1 -0
- package/dist/types/extraction.d.ts +27 -0
- package/dist/types/extraction.d.ts.map +1 -0
- package/dist/types/extraction.js +16 -0
- package/dist/types/extraction.js.map +1 -0
- package/dist/types/instructions.d.ts +58 -0
- package/dist/types/instructions.d.ts.map +1 -0
- package/dist/types/instructions.js +203 -0
- package/dist/types/instructions.js.map +1 -0
- package/dist/types/wireProtocol.d.ts +117 -0
- package/dist/types/wireProtocol.d.ts.map +1 -0
- package/dist/types/wireProtocol.js +8 -0
- package/dist/types/wireProtocol.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AstExtractorService = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const logger_js_1 = require("../logger.js");
|
|
39
|
+
/**
|
|
40
|
+
* AST-based extractor for TypeScript/JavaScript source files.
|
|
41
|
+
*
|
|
42
|
+
* Uses the TypeScript compiler API (the same `typescript` package that's
|
|
43
|
+
* already present in any TS project) to locate and extract symbol source.
|
|
44
|
+
* Falls back to a regex-based line scanner when the TS compiler is not
|
|
45
|
+
* available (plain JS projects or environments without `typescript`).
|
|
46
|
+
*/
|
|
47
|
+
class AstExtractorService {
|
|
48
|
+
extract(absoluteFilePath, target) {
|
|
49
|
+
if (!fs.existsSync(absoluteFilePath)) {
|
|
50
|
+
logger_js_1.logger.warn('AstExtractor: file not found.', { filePath: absoluteFilePath });
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
return this.extractWithTs(absoluteFilePath, target);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
logger_js_1.logger.warn('AstExtractor: TS compiler unavailable, falling back to line scanner.', {
|
|
58
|
+
error: err instanceof Error ? err.message : String(err),
|
|
59
|
+
});
|
|
60
|
+
return this.extractWithLineScan(absoluteFilePath, target);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// TypeScript Compiler API extraction
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
extractWithTs(filePath, target) {
|
|
67
|
+
// Dynamic require — typescript is a peer/devDep, not a runtime dep.
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
69
|
+
const ts = require('typescript');
|
|
70
|
+
const sourceText = fs.readFileSync(filePath, 'utf8');
|
|
71
|
+
const sourceFile = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true);
|
|
72
|
+
const lines = sourceText.split('\n');
|
|
73
|
+
const found = this.findNode(ts, sourceFile, target);
|
|
74
|
+
if (!found)
|
|
75
|
+
return null;
|
|
76
|
+
const { node, startLine, endLine } = found;
|
|
77
|
+
const sourceCode = lines.slice(startLine - 1, endLine).join('\n');
|
|
78
|
+
const docBlock = this.extractDocBlock(sourceText, node, ts, sourceFile);
|
|
79
|
+
return {
|
|
80
|
+
filePath,
|
|
81
|
+
startLine,
|
|
82
|
+
endLine,
|
|
83
|
+
sourceCode,
|
|
84
|
+
symbolName: target.symbolName,
|
|
85
|
+
symbolType: target.symbolType,
|
|
86
|
+
docBlock,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
findNode(ts, sourceFile, target) {
|
|
90
|
+
let result = null;
|
|
91
|
+
const visit = (node) => {
|
|
92
|
+
if (result)
|
|
93
|
+
return;
|
|
94
|
+
if (target.symbolType === 'function' || target.symbolType === 'method') {
|
|
95
|
+
if ((ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) || ts.isArrowFunction(node)) &&
|
|
96
|
+
ts.isIdentifier(node.name ?? {}) &&
|
|
97
|
+
node.name.text === target.symbolName) {
|
|
98
|
+
result = this.nodePosition(ts, sourceFile, node);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (target.symbolType === 'class') {
|
|
103
|
+
if (ts.isClassDeclaration(node) &&
|
|
104
|
+
node.name?.text === target.symbolName) {
|
|
105
|
+
result = this.nodePosition(ts, sourceFile, node);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
ts.forEachChild(node, visit);
|
|
110
|
+
};
|
|
111
|
+
ts.forEachChild(sourceFile, visit);
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
nodePosition(ts, sourceFile, node) {
|
|
115
|
+
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
116
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
117
|
+
return {
|
|
118
|
+
node,
|
|
119
|
+
startLine: start.line + 1,
|
|
120
|
+
endLine: end.line + 1,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
extractDocBlock(sourceText, node, ts, sourceFile) {
|
|
124
|
+
const nodeStart = node.getFullStart();
|
|
125
|
+
const preceding = sourceText.slice(Math.max(0, nodeStart - 500), nodeStart);
|
|
126
|
+
const match = preceding.match(/\/\*\*[\s\S]*?\*\/\s*$/);
|
|
127
|
+
return match ? match[0].trim() : null;
|
|
128
|
+
}
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// Fallback: line scanner (regex-based, no AST)
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
extractWithLineScan(filePath, target) {
|
|
133
|
+
const lines = fs.readFileSync(filePath, 'utf8').split('\n');
|
|
134
|
+
// Find the first line matching the symbol name.
|
|
135
|
+
const startIdx = lines.findIndex((line) => line.includes(target.symbolName) &&
|
|
136
|
+
(line.includes('function') || line.includes('class') || line.includes('=>') || line.includes('(')));
|
|
137
|
+
if (startIdx === -1)
|
|
138
|
+
return null;
|
|
139
|
+
// Walk forward to find the closing brace using a simple depth counter.
|
|
140
|
+
let depth = 0;
|
|
141
|
+
let endIdx = startIdx;
|
|
142
|
+
for (let i = startIdx; i < lines.length; i++) {
|
|
143
|
+
for (const ch of lines[i] ?? '') {
|
|
144
|
+
if (ch === '{')
|
|
145
|
+
depth++;
|
|
146
|
+
else if (ch === '}')
|
|
147
|
+
depth--;
|
|
148
|
+
}
|
|
149
|
+
endIdx = i;
|
|
150
|
+
if (depth === 0 && i > startIdx)
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
const sourceCode = lines.slice(startIdx, endIdx + 1).join('\n');
|
|
154
|
+
return {
|
|
155
|
+
filePath,
|
|
156
|
+
startLine: startIdx + 1,
|
|
157
|
+
endLine: endIdx + 1,
|
|
158
|
+
sourceCode,
|
|
159
|
+
symbolName: target.symbolName,
|
|
160
|
+
symbolType: target.symbolType,
|
|
161
|
+
docBlock: null,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
exports.AstExtractorService = AstExtractorService;
|
|
166
|
+
//# sourceMappingURL=AstExtractorService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AstExtractorService.js","sourceRoot":"","sources":["../../src/services/AstExtractorService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Redis } from 'ioredis';
|
|
2
|
+
/**
|
|
3
|
+
* Three-state circuit breaker: CLOSED → OPEN → HALF-OPEN → CLOSED.
|
|
4
|
+
*
|
|
5
|
+
* Exact behavioural equivalent of the PHP CircuitBreakerService.
|
|
6
|
+
* State is stored in Redis with a configurable TTL so it auto-resets.
|
|
7
|
+
*
|
|
8
|
+
* Node.js-specific: when Redis itself is unavailable we fall back to a
|
|
9
|
+
* module-level in-process flag cleared via setTimeout (single-threaded
|
|
10
|
+
* event loop — no locking needed, unlike PHP-FPM per-request processes).
|
|
11
|
+
*/
|
|
12
|
+
export declare class CircuitBreakerService {
|
|
13
|
+
private readonly redis;
|
|
14
|
+
private readonly clientId;
|
|
15
|
+
private readonly redisKey;
|
|
16
|
+
private readonly ttlSeconds;
|
|
17
|
+
private static inProcessOpen;
|
|
18
|
+
private static resetTimer;
|
|
19
|
+
constructor(redis: Redis, clientId: string, ttlSeconds: number);
|
|
20
|
+
/**
|
|
21
|
+
* Returns true when the breaker is OPEN — all interception must be skipped.
|
|
22
|
+
* If Redis is down, trips in-process and returns true.
|
|
23
|
+
*/
|
|
24
|
+
isOpen(): Promise<boolean>;
|
|
25
|
+
trip(reason: Error): Promise<void>;
|
|
26
|
+
reset(): Promise<void>;
|
|
27
|
+
private tripInProcess;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=CircuitBreakerService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CircuitBreakerService.d.ts","sourceRoot":"","sources":["../../src/services/CircuitBreakerService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGrC;;;;;;;;;GASG;AACH,qBAAa,qBAAqB;IAS9B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAT3B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAGpC,OAAO,CAAC,MAAM,CAAC,aAAa,CAAS;IACrC,OAAO,CAAC,MAAM,CAAC,UAAU,CAA8C;gBAGpD,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,MAAM,EACjC,UAAU,EAAE,MAAM;IAMpB;;;OAGG;IACG,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAa1B,IAAI,CAAC,MAAM,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBlC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAe5B,OAAO,CAAC,aAAa;CAgBtB"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CircuitBreakerService = void 0;
|
|
4
|
+
const logger_js_1 = require("../logger.js");
|
|
5
|
+
/**
|
|
6
|
+
* Three-state circuit breaker: CLOSED → OPEN → HALF-OPEN → CLOSED.
|
|
7
|
+
*
|
|
8
|
+
* Exact behavioural equivalent of the PHP CircuitBreakerService.
|
|
9
|
+
* State is stored in Redis with a configurable TTL so it auto-resets.
|
|
10
|
+
*
|
|
11
|
+
* Node.js-specific: when Redis itself is unavailable we fall back to a
|
|
12
|
+
* module-level in-process flag cleared via setTimeout (single-threaded
|
|
13
|
+
* event loop — no locking needed, unlike PHP-FPM per-request processes).
|
|
14
|
+
*/
|
|
15
|
+
class CircuitBreakerService {
|
|
16
|
+
redis;
|
|
17
|
+
clientId;
|
|
18
|
+
redisKey;
|
|
19
|
+
ttlSeconds;
|
|
20
|
+
// Module-level state survives across requests in a single Node.js process.
|
|
21
|
+
static inProcessOpen = false;
|
|
22
|
+
static resetTimer = null;
|
|
23
|
+
constructor(redis, clientId, ttlSeconds) {
|
|
24
|
+
this.redis = redis;
|
|
25
|
+
this.clientId = clientId;
|
|
26
|
+
this.redisKey = `remediation:circuit_breaker:${clientId}`;
|
|
27
|
+
this.ttlSeconds = ttlSeconds;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Returns true when the breaker is OPEN — all interception must be skipped.
|
|
31
|
+
* If Redis is down, trips in-process and returns true.
|
|
32
|
+
*/
|
|
33
|
+
async isOpen() {
|
|
34
|
+
if (CircuitBreakerService.inProcessOpen)
|
|
35
|
+
return true;
|
|
36
|
+
try {
|
|
37
|
+
const exists = await this.redis.exists(this.redisKey);
|
|
38
|
+
return exists === 1;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Redis unavailable — open in-process and protect production.
|
|
42
|
+
this.tripInProcess(new Error('Redis unavailable during isOpen check'));
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async trip(reason) {
|
|
47
|
+
logger_js_1.logger.error('RemediationEngine circuit breaker tripped.', {
|
|
48
|
+
reason: reason.message,
|
|
49
|
+
name: reason.name,
|
|
50
|
+
});
|
|
51
|
+
const payload = JSON.stringify({
|
|
52
|
+
state: 'open',
|
|
53
|
+
tripped_at: Math.floor(Date.now() / 1000),
|
|
54
|
+
reason: reason.message,
|
|
55
|
+
});
|
|
56
|
+
try {
|
|
57
|
+
await this.redis.set(this.redisKey, payload, 'EX', this.ttlSeconds);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Redis is the problem — fall back to in-process.
|
|
61
|
+
this.tripInProcess(reason);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async reset() {
|
|
65
|
+
CircuitBreakerService.inProcessOpen = false;
|
|
66
|
+
if (CircuitBreakerService.resetTimer !== null) {
|
|
67
|
+
clearTimeout(CircuitBreakerService.resetTimer);
|
|
68
|
+
CircuitBreakerService.resetTimer = null;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
await this.redis.del(this.redisKey);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Ignore — key will expire on its own.
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
tripInProcess(reason) {
|
|
79
|
+
CircuitBreakerService.inProcessOpen = true;
|
|
80
|
+
logger_js_1.logger.warn('RemediationEngine: using in-process circuit breaker fallback.', {
|
|
81
|
+
reason: reason.message,
|
|
82
|
+
});
|
|
83
|
+
if (CircuitBreakerService.resetTimer !== null) {
|
|
84
|
+
clearTimeout(CircuitBreakerService.resetTimer);
|
|
85
|
+
}
|
|
86
|
+
CircuitBreakerService.resetTimer = setTimeout(() => {
|
|
87
|
+
CircuitBreakerService.inProcessOpen = false;
|
|
88
|
+
CircuitBreakerService.resetTimer = null;
|
|
89
|
+
}, this.ttlSeconds * 1000);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
exports.CircuitBreakerService = CircuitBreakerService;
|
|
93
|
+
//# sourceMappingURL=CircuitBreakerService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CircuitBreakerService.js","sourceRoot":"","sources":["../../src/services/CircuitBreakerService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Redis } from 'ioredis';
|
|
2
|
+
import { InstructionCollection, RuntimeInstruction } from '../types/instructions.js';
|
|
3
|
+
/**
|
|
4
|
+
* Reads and writes RuntimeInstructions from/to Redis.
|
|
5
|
+
*
|
|
6
|
+
* Exact equivalent of the PHP InstructionCacheService.
|
|
7
|
+
* Key strategy: one STRING key per instruction with its own TTL, plus a SET
|
|
8
|
+
* index so getActive() can SMEMBERS → MGET in two roundtrips.
|
|
9
|
+
*
|
|
10
|
+
* Namespace:
|
|
11
|
+
* remediation:instruction:{clientId}:{instructionId} → JSON string
|
|
12
|
+
* remediation:active:{clientId} → SET of instruction IDs
|
|
13
|
+
*/
|
|
14
|
+
export declare class InstructionCacheService {
|
|
15
|
+
private readonly redis;
|
|
16
|
+
private readonly clientId;
|
|
17
|
+
private readonly indexKey;
|
|
18
|
+
constructor(redis: Redis, clientId: string);
|
|
19
|
+
store(instruction: RuntimeInstruction): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Return all currently active instructions.
|
|
22
|
+
* Two roundtrips: SMEMBERS → MGET. <1ms on a local socket.
|
|
23
|
+
*/
|
|
24
|
+
getActive(): Promise<InstructionCollection>;
|
|
25
|
+
evict(instructionId: string): Promise<void>;
|
|
26
|
+
private instructionKey;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=InstructionCacheService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InstructionCacheService.d.ts","sourceRoot":"","sources":["../../src/services/InstructionCacheService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAErF;;;;;;;;;;GAUG;AACH,qBAAa,uBAAuB;IAIhC,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAJ3B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAGf,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,MAAM;IAK7B,KAAK,CAAC,WAAW,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAc3D;;;OAGG;IACG,SAAS,IAAI,OAAO,CAAC,qBAAqB,CAAC;IAqC3C,KAAK,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjD,OAAO,CAAC,cAAc;CAGvB"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InstructionCacheService = void 0;
|
|
4
|
+
const instructions_js_1 = require("../types/instructions.js");
|
|
5
|
+
/**
|
|
6
|
+
* Reads and writes RuntimeInstructions from/to Redis.
|
|
7
|
+
*
|
|
8
|
+
* Exact equivalent of the PHP InstructionCacheService.
|
|
9
|
+
* Key strategy: one STRING key per instruction with its own TTL, plus a SET
|
|
10
|
+
* index so getActive() can SMEMBERS → MGET in two roundtrips.
|
|
11
|
+
*
|
|
12
|
+
* Namespace:
|
|
13
|
+
* remediation:instruction:{clientId}:{instructionId} → JSON string
|
|
14
|
+
* remediation:active:{clientId} → SET of instruction IDs
|
|
15
|
+
*/
|
|
16
|
+
class InstructionCacheService {
|
|
17
|
+
redis;
|
|
18
|
+
clientId;
|
|
19
|
+
indexKey;
|
|
20
|
+
constructor(redis, clientId) {
|
|
21
|
+
this.redis = redis;
|
|
22
|
+
this.clientId = clientId;
|
|
23
|
+
this.indexKey = `remediation:active:${clientId}`;
|
|
24
|
+
}
|
|
25
|
+
async store(instruction) {
|
|
26
|
+
const ttl = Math.max(1, instruction.expiresAt - Math.floor(Date.now() / 1000));
|
|
27
|
+
const key = this.instructionKey(instruction.instructionId);
|
|
28
|
+
await this.redis.set(key, JSON.stringify(instruction.toJSON()), 'EX', ttl);
|
|
29
|
+
await this.redis.sadd(this.indexKey, instruction.instructionId);
|
|
30
|
+
// Extend index TTL to at least match the instruction's TTL.
|
|
31
|
+
const currentTtl = await this.redis.ttl(this.indexKey);
|
|
32
|
+
if (currentTtl < ttl) {
|
|
33
|
+
await this.redis.expire(this.indexKey, ttl + 60);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Return all currently active instructions.
|
|
38
|
+
* Two roundtrips: SMEMBERS → MGET. <1ms on a local socket.
|
|
39
|
+
*/
|
|
40
|
+
async getActive() {
|
|
41
|
+
const ids = await this.redis.smembers(this.indexKey);
|
|
42
|
+
if (ids.length === 0)
|
|
43
|
+
return instructions_js_1.InstructionCollection.empty();
|
|
44
|
+
const keys = ids.map((id) => this.instructionKey(id));
|
|
45
|
+
const values = await this.redis.mget(...keys);
|
|
46
|
+
const instructions = [];
|
|
47
|
+
const staleIds = [];
|
|
48
|
+
for (let i = 0; i < ids.length; i++) {
|
|
49
|
+
const json = values[i];
|
|
50
|
+
if (json == null) {
|
|
51
|
+
staleIds.push(ids[i]);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const raw = JSON.parse(json);
|
|
56
|
+
const instruction = instructions_js_1.RuntimeInstruction.fromJSON(raw);
|
|
57
|
+
if (instruction.isEffective()) {
|
|
58
|
+
instructions.push(instruction);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
staleIds.push(ids[i]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (staleIds.length > 0) {
|
|
66
|
+
await this.redis.srem(this.indexKey, ...staleIds);
|
|
67
|
+
}
|
|
68
|
+
return new instructions_js_1.InstructionCollection(instructions);
|
|
69
|
+
}
|
|
70
|
+
async evict(instructionId) {
|
|
71
|
+
await this.redis.del(this.instructionKey(instructionId));
|
|
72
|
+
await this.redis.srem(this.indexKey, instructionId);
|
|
73
|
+
}
|
|
74
|
+
instructionKey(instructionId) {
|
|
75
|
+
return `remediation:instruction:${this.clientId}:${instructionId}`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.InstructionCacheService = InstructionCacheService;
|
|
79
|
+
//# sourceMappingURL=InstructionCacheService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InstructionCacheService.js","sourceRoot":"","sources":["../../src/services/InstructionCacheService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { MaskRule } from '../types/instructions.js';
|
|
2
|
+
/**
|
|
3
|
+
* Pure stateless masking engine — no I/O, no Redis, no logging.
|
|
4
|
+
* Byte-for-byte logic equivalent of the PHP MaskingEngine.
|
|
5
|
+
*
|
|
6
|
+
* Applies PII masking rules via deep recursive traversal of an arbitrary
|
|
7
|
+
* data structure. All mutations happen on a deep clone (JSON round-trip);
|
|
8
|
+
* the original is never touched until the full masked copy is ready.
|
|
9
|
+
*
|
|
10
|
+
* Supported strategies:
|
|
11
|
+
* partial_last4 — keeps last 4 digits, masks the rest (phone numbers)
|
|
12
|
+
* email_domain_only — masks local part, keeps @domain
|
|
13
|
+
* last4_only — keeps last 4 digits only (credit cards)
|
|
14
|
+
* full_redact — replaces value with '[REDACTED]'
|
|
15
|
+
* hash_stable — deterministic SHA-256 truncated to 16 hex chars
|
|
16
|
+
*/
|
|
17
|
+
export declare class MaskingEngine {
|
|
18
|
+
private static readonly MAX_DEPTH;
|
|
19
|
+
apply(data: unknown, rules: MaskRule[]): unknown;
|
|
20
|
+
private traverse;
|
|
21
|
+
private findMatchingRule;
|
|
22
|
+
private maskValue;
|
|
23
|
+
private maskPartialLast4;
|
|
24
|
+
private maskEmailDomainOnly;
|
|
25
|
+
private maskLast4Only;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=MaskingEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MaskingEngine.d.ts","sourceRoot":"","sources":["../../src/services/MaskingEngine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAGzD;;;;;;;;;;;;;;GAcG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAM;IAEvC,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,OAAO;IAQhD,OAAO,CAAC,QAAQ;IA0BhB,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,mBAAmB;IAM3B,OAAO,CAAC,aAAa;CAKtB"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MaskingEngine = void 0;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
/**
|
|
6
|
+
* Pure stateless masking engine — no I/O, no Redis, no logging.
|
|
7
|
+
* Byte-for-byte logic equivalent of the PHP MaskingEngine.
|
|
8
|
+
*
|
|
9
|
+
* Applies PII masking rules via deep recursive traversal of an arbitrary
|
|
10
|
+
* data structure. All mutations happen on a deep clone (JSON round-trip);
|
|
11
|
+
* the original is never touched until the full masked copy is ready.
|
|
12
|
+
*
|
|
13
|
+
* Supported strategies:
|
|
14
|
+
* partial_last4 — keeps last 4 digits, masks the rest (phone numbers)
|
|
15
|
+
* email_domain_only — masks local part, keeps @domain
|
|
16
|
+
* last4_only — keeps last 4 digits only (credit cards)
|
|
17
|
+
* full_redact — replaces value with '[REDACTED]'
|
|
18
|
+
* hash_stable — deterministic SHA-256 truncated to 16 hex chars
|
|
19
|
+
*/
|
|
20
|
+
class MaskingEngine {
|
|
21
|
+
static MAX_DEPTH = 20;
|
|
22
|
+
apply(data, rules) {
|
|
23
|
+
if (rules.length === 0)
|
|
24
|
+
return data;
|
|
25
|
+
// Deep clone via JSON round-trip — matches PHP behaviour exactly.
|
|
26
|
+
const clone = JSON.parse(JSON.stringify(data));
|
|
27
|
+
return this.traverse(clone, rules, 0);
|
|
28
|
+
}
|
|
29
|
+
traverse(data, rules, depth) {
|
|
30
|
+
if (depth > MaskingEngine.MAX_DEPTH)
|
|
31
|
+
return data;
|
|
32
|
+
if (typeof data !== 'object' || data === null || Array.isArray(data)) {
|
|
33
|
+
// Arrays: recurse into elements without key matching.
|
|
34
|
+
if (Array.isArray(data)) {
|
|
35
|
+
return data.map((item) => this.traverse(item, rules, depth + 1));
|
|
36
|
+
}
|
|
37
|
+
return data;
|
|
38
|
+
}
|
|
39
|
+
const obj = data;
|
|
40
|
+
for (const key of Object.keys(obj)) {
|
|
41
|
+
const value = obj[key];
|
|
42
|
+
const matchedRule = this.findMatchingRule(key, rules);
|
|
43
|
+
if (matchedRule !== null && value !== null && typeof value !== 'object') {
|
|
44
|
+
obj[key] = this.maskValue(String(value), matchedRule);
|
|
45
|
+
}
|
|
46
|
+
else if (typeof value === 'object' && value !== null) {
|
|
47
|
+
obj[key] = this.traverse(value, rules, depth + 1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return obj;
|
|
51
|
+
}
|
|
52
|
+
findMatchingRule(key, rules) {
|
|
53
|
+
for (const rule of rules) {
|
|
54
|
+
if (rule.matchesKey(key))
|
|
55
|
+
return rule;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
maskValue(value, rule) {
|
|
60
|
+
switch (rule.maskStrategy) {
|
|
61
|
+
case 'partial_last4': return this.maskPartialLast4(value, rule.maskChar, rule.separator);
|
|
62
|
+
case 'email_domain_only': return this.maskEmailDomainOnly(value);
|
|
63
|
+
case 'last4_only': return this.maskLast4Only(value, rule.maskChar);
|
|
64
|
+
case 'full_redact': return '[REDACTED]';
|
|
65
|
+
case 'hash_stable': return (0, crypto_1.createHash)('sha256').update(value).digest('hex').slice(0, 16);
|
|
66
|
+
default: return '[REDACTED]';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
maskPartialLast4(value, maskChar, separator) {
|
|
70
|
+
const digits = value.replace(/\D/g, '');
|
|
71
|
+
const last4 = digits.length >= 4 ? digits.slice(-4) : digits.padStart(4, maskChar);
|
|
72
|
+
return `${maskChar.repeat(4)}${separator}${maskChar.repeat(4)}${separator}${last4}`;
|
|
73
|
+
}
|
|
74
|
+
maskEmailDomainOnly(value) {
|
|
75
|
+
const atIndex = value.indexOf('@');
|
|
76
|
+
if (atIndex === -1)
|
|
77
|
+
return '****';
|
|
78
|
+
return `****${value.slice(atIndex)}`;
|
|
79
|
+
}
|
|
80
|
+
maskLast4Only(value, maskChar) {
|
|
81
|
+
const digits = value.replace(/\D/g, '');
|
|
82
|
+
if (digits.length <= 4)
|
|
83
|
+
return digits;
|
|
84
|
+
return maskChar.repeat(digits.length - 4) + digits.slice(-4);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.MaskingEngine = MaskingEngine;
|
|
88
|
+
//# sourceMappingURL=MaskingEngine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MaskingEngine.js","sourceRoot":"","sources":["../../src/services/MaskingEngine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type SymbolType = 'method' | 'function' | 'class' | 'route';
|
|
2
|
+
export interface ExtractionTarget {
|
|
3
|
+
readonly filePath: string;
|
|
4
|
+
readonly symbolName: string;
|
|
5
|
+
readonly symbolType: SymbolType;
|
|
6
|
+
}
|
|
7
|
+
export interface ExtractionResult {
|
|
8
|
+
readonly filePath: string;
|
|
9
|
+
readonly startLine: number;
|
|
10
|
+
readonly endLine: number;
|
|
11
|
+
readonly sourceCode: string;
|
|
12
|
+
readonly symbolName: string;
|
|
13
|
+
readonly symbolType: SymbolType;
|
|
14
|
+
readonly docBlock: string | null;
|
|
15
|
+
}
|
|
16
|
+
export interface ExtractionRequestPayload {
|
|
17
|
+
readonly extraction_id: string;
|
|
18
|
+
readonly extraction_type: string;
|
|
19
|
+
readonly targets: readonly ExtractionTarget[];
|
|
20
|
+
readonly callback_url: string;
|
|
21
|
+
readonly include_ast: boolean;
|
|
22
|
+
readonly include_docblock: boolean;
|
|
23
|
+
readonly include_line_numbers: boolean;
|
|
24
|
+
readonly max_lines_per_symbol: number;
|
|
25
|
+
}
|
|
26
|
+
export declare function parseExtractionRequestPayload(payload: Record<string, unknown>): ExtractionRequestPayload;
|
|
27
|
+
//# sourceMappingURL=extraction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extraction.d.ts","sourceRoot":"","sources":["../../src/types/extraction.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC;AAEnE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;CACjC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC9C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;CACvC;AAED,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,wBAAwB,CAWxG"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseExtractionRequestPayload = parseExtractionRequestPayload;
|
|
4
|
+
function parseExtractionRequestPayload(payload) {
|
|
5
|
+
return {
|
|
6
|
+
extraction_id: String(payload['extraction_id'] ?? ''),
|
|
7
|
+
extraction_type: String(payload['extraction_type'] ?? 'generic'),
|
|
8
|
+
targets: (Array.isArray(payload['targets']) ? payload['targets'] : []),
|
|
9
|
+
callback_url: String(payload['callback_url'] ?? ''),
|
|
10
|
+
include_ast: Boolean(payload['include_ast'] ?? true),
|
|
11
|
+
include_docblock: Boolean(payload['include_docblock'] ?? true),
|
|
12
|
+
include_line_numbers: Boolean(payload['include_line_numbers'] ?? true),
|
|
13
|
+
max_lines_per_symbol: Number(payload['max_lines_per_symbol'] ?? 500),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=extraction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extraction.js","sourceRoot":"","sources":["../../src/types/extraction.ts"],"names":[],"mappings":";;AA6BA,sEAWC;AAXD,SAAgB,6BAA6B,CAAC,OAAgC;IAC5E,OAAO;QACL,aAAa,EAAS,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,IAAW,EAAE,CAAC;QACnE,eAAe,EAAO,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,IAAS,SAAS,CAAC;QAC1E,OAAO,EAAe,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAuB;QACzG,YAAY,EAAU,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,IAAY,EAAE,CAAC;QACnE,WAAW,EAAW,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,IAAY,IAAI,CAAC;QACrE,gBAAgB,EAAM,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,IAAO,IAAI,CAAC;QACrE,oBAAoB,EAAE,OAAO,CAAC,OAAO,CAAC,sBAAsB,CAAC,IAAI,IAAI,CAAC;QACtE,oBAAoB,EAAE,MAAM,CAAC,OAAO,CAAC,sBAAsB,CAAC,IAAI,GAAG,CAAC;KACrE,CAAC;AACJ,CAAC"}
|