imperium-crawl 1.5.2 → 2.0.0
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/README.md +370 -257
- package/dist/constants.d.ts +2 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +3 -1
- package/dist/constants.js.map +1 -1
- package/dist/network/interceptor.d.ts +19 -0
- package/dist/network/interceptor.d.ts.map +1 -0
- package/dist/network/interceptor.js +82 -0
- package/dist/network/interceptor.js.map +1 -0
- package/dist/network/types.d.ts +27 -0
- package/dist/network/types.d.ts.map +1 -0
- package/dist/network/types.js +2 -0
- package/dist/network/types.js.map +1 -0
- package/dist/security/action-policy.d.ts +26 -0
- package/dist/security/action-policy.d.ts.map +1 -0
- package/dist/security/action-policy.js +136 -0
- package/dist/security/action-policy.js.map +1 -0
- package/dist/security/auth-vault.d.ts +49 -0
- package/dist/security/auth-vault.d.ts.map +1 -0
- package/dist/security/auth-vault.js +133 -0
- package/dist/security/auth-vault.js.map +1 -0
- package/dist/security/domain-filter.d.ts +19 -0
- package/dist/security/domain-filter.d.ts.map +1 -0
- package/dist/security/domain-filter.js +114 -0
- package/dist/security/domain-filter.js.map +1 -0
- package/dist/security/types.d.ts +19 -0
- package/dist/security/types.d.ts.map +1 -0
- package/dist/security/types.js +2 -0
- package/dist/security/types.js.map +1 -0
- package/dist/sessions/encryption.d.ts +37 -0
- package/dist/sessions/encryption.d.ts.map +1 -0
- package/dist/sessions/encryption.js +108 -0
- package/dist/sessions/encryption.js.map +1 -0
- package/dist/sessions/index.d.ts +1 -0
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/index.js +1 -0
- package/dist/sessions/index.js.map +1 -1
- package/dist/sessions/manager.d.ts +3 -0
- package/dist/sessions/manager.d.ts.map +1 -1
- package/dist/sessions/manager.js +28 -2
- package/dist/sessions/manager.js.map +1 -1
- package/dist/snapshot/annotator.d.ts +21 -0
- package/dist/snapshot/annotator.d.ts.map +1 -0
- package/dist/snapshot/annotator.js +152 -0
- package/dist/snapshot/annotator.js.map +1 -0
- package/dist/snapshot/boundary.d.ts +7 -0
- package/dist/snapshot/boundary.d.ts.map +1 -0
- package/dist/snapshot/boundary.js +12 -0
- package/dist/snapshot/boundary.js.map +1 -0
- package/dist/snapshot/differ.d.ts +40 -0
- package/dist/snapshot/differ.d.ts.map +1 -0
- package/dist/snapshot/differ.js +194 -0
- package/dist/snapshot/differ.js.map +1 -0
- package/dist/snapshot/extractor.d.ts +27 -0
- package/dist/snapshot/extractor.d.ts.map +1 -0
- package/dist/snapshot/extractor.js +265 -0
- package/dist/snapshot/extractor.js.map +1 -0
- package/dist/snapshot/index.d.ts +8 -0
- package/dist/snapshot/index.d.ts.map +1 -0
- package/dist/snapshot/index.js +6 -0
- package/dist/snapshot/index.js.map +1 -0
- package/dist/snapshot/store.d.ts +28 -0
- package/dist/snapshot/store.d.ts.map +1 -0
- package/dist/snapshot/store.js +65 -0
- package/dist/snapshot/store.js.map +1 -0
- package/dist/snapshot/types.d.ts +42 -0
- package/dist/snapshot/types.d.ts.map +1 -0
- package/dist/snapshot/types.js +2 -0
- package/dist/snapshot/types.js.map +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/interact.d.ts +194 -5
- package/dist/tools/interact.d.ts.map +1 -1
- package/dist/tools/interact.js +355 -20
- package/dist/tools/interact.js.map +1 -1
- package/dist/tools/snapshot.d.ts +53 -0
- package/dist/tools/snapshot.d.ts.map +1 -0
- package/dist/tools/snapshot.js +160 -0
- package/dist/tools/snapshot.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain Filter — Security sandbox for browser actions.
|
|
3
|
+
*
|
|
4
|
+
* Blocks requests to non-allowed domains via page.route().
|
|
5
|
+
* Also patches WebSocket/EventSource/sendBeacon to prevent data exfiltration.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Check if a hostname matches any of the allowed patterns.
|
|
9
|
+
* Supports exact match and wildcard (*.example.com).
|
|
10
|
+
*/
|
|
11
|
+
export function isDomainAllowed(hostname, patterns) {
|
|
12
|
+
const lower = hostname.toLowerCase();
|
|
13
|
+
for (const pattern of patterns) {
|
|
14
|
+
const p = pattern.toLowerCase();
|
|
15
|
+
if (p === lower)
|
|
16
|
+
return true;
|
|
17
|
+
if (p.startsWith("*.")) {
|
|
18
|
+
const suffix = p.slice(2);
|
|
19
|
+
if (lower === suffix || lower.endsWith(`.${suffix}`))
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Install domain filter on a browser context.
|
|
27
|
+
* Blocks all requests to non-allowed domains + patches WebSocket/EventSource.
|
|
28
|
+
*/
|
|
29
|
+
export async function installDomainFilter(context, allowedDomains) {
|
|
30
|
+
if (!allowedDomains.length)
|
|
31
|
+
return;
|
|
32
|
+
// Block HTTP requests to non-allowed domains
|
|
33
|
+
await context.route("**/*", (route) => {
|
|
34
|
+
try {
|
|
35
|
+
const url = new URL(route.request().url());
|
|
36
|
+
if (isDomainAllowed(url.hostname, allowedDomains)) {
|
|
37
|
+
route.continue();
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
route.abort("blockedbyclient");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Invalid URL — allow (probably internal)
|
|
45
|
+
route.continue();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
// Patch WebSocket/EventSource/sendBeacon in page context
|
|
49
|
+
const initScript = `
|
|
50
|
+
(function() {
|
|
51
|
+
const allowedDomains = ${JSON.stringify(allowedDomains)};
|
|
52
|
+
|
|
53
|
+
function isDomainAllowed(hostname) {
|
|
54
|
+
const lower = hostname.toLowerCase();
|
|
55
|
+
for (const pattern of allowedDomains) {
|
|
56
|
+
const p = pattern.toLowerCase();
|
|
57
|
+
if (p === lower) return true;
|
|
58
|
+
if (p.startsWith('*.')) {
|
|
59
|
+
const suffix = p.slice(2);
|
|
60
|
+
if (lower === suffix || lower.endsWith('.' + suffix)) return true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function checkUrl(urlStr) {
|
|
67
|
+
try {
|
|
68
|
+
const url = new URL(urlStr, window.location.href);
|
|
69
|
+
return isDomainAllowed(url.hostname);
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Patch WebSocket
|
|
76
|
+
const OrigWebSocket = window.WebSocket;
|
|
77
|
+
window.WebSocket = function(url, protocols) {
|
|
78
|
+
if (!checkUrl(url)) {
|
|
79
|
+
console.warn('[imperium-crawl] Blocked WebSocket to:', url);
|
|
80
|
+
throw new DOMException('Blocked by domain filter', 'SecurityError');
|
|
81
|
+
}
|
|
82
|
+
return new OrigWebSocket(url, protocols);
|
|
83
|
+
};
|
|
84
|
+
window.WebSocket.prototype = OrigWebSocket.prototype;
|
|
85
|
+
|
|
86
|
+
// Patch EventSource
|
|
87
|
+
const OrigEventSource = window.EventSource;
|
|
88
|
+
if (OrigEventSource) {
|
|
89
|
+
window.EventSource = function(url, opts) {
|
|
90
|
+
if (!checkUrl(url)) {
|
|
91
|
+
console.warn('[imperium-crawl] Blocked EventSource to:', url);
|
|
92
|
+
throw new DOMException('Blocked by domain filter', 'SecurityError');
|
|
93
|
+
}
|
|
94
|
+
return new OrigEventSource(url, opts);
|
|
95
|
+
};
|
|
96
|
+
window.EventSource.prototype = OrigEventSource.prototype;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Patch sendBeacon
|
|
100
|
+
const origSendBeacon = navigator.sendBeacon?.bind(navigator);
|
|
101
|
+
if (origSendBeacon) {
|
|
102
|
+
navigator.sendBeacon = function(url, data) {
|
|
103
|
+
if (!checkUrl(url)) {
|
|
104
|
+
console.warn('[imperium-crawl] Blocked sendBeacon to:', url);
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return origSendBeacon(url, data);
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
})();
|
|
111
|
+
`;
|
|
112
|
+
await context.addInitScript(initScript);
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=domain-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain-filter.js","sourceRoot":"","sources":["../../src/security/domain-filter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB,EAAE,QAAkB;IAClE,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;IAErC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,CAAC,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;QAChC,IAAI,CAAC,KAAK,KAAK;YAAE,OAAO,IAAI,CAAC;QAC7B,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC1B,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,MAAM,EAAE,CAAC;gBAAE,OAAO,IAAI,CAAC;QACpE,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAAuB,EACvB,cAAwB;IAExB,IAAI,CAAC,cAAc,CAAC,MAAM;QAAE,OAAO;IAEnC,6CAA6C;IAC7C,MAAM,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;QACpC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;YAC3C,IAAI,eAAe,CAAC,GAAG,CAAC,QAAQ,EAAE,cAAc,CAAC,EAAE,CAAC;gBAClD,KAAK,CAAC,QAAQ,EAAE,CAAC;YACnB,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,0CAA0C;YAC1C,KAAK,CAAC,QAAQ,EAAE,CAAC;QACnB,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,yDAAyD;IACzD,MAAM,UAAU,GAAG;;+BAEU,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4D1D,CAAC;IAEF,MAAM,OAAO,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Action policy decision */
|
|
2
|
+
export type PolicyDecision = "allow" | "deny" | "confirm";
|
|
3
|
+
/** Action policy configuration */
|
|
4
|
+
export interface ActionPolicyConfig {
|
|
5
|
+
/** Default decision for uncategorized actions */
|
|
6
|
+
default: "allow" | "deny";
|
|
7
|
+
/** Allowed categories override default */
|
|
8
|
+
allow?: string[];
|
|
9
|
+
/** Denied categories override default and allow */
|
|
10
|
+
deny?: string[];
|
|
11
|
+
/** Categories requiring explicit confirmation */
|
|
12
|
+
confirm?: string[];
|
|
13
|
+
}
|
|
14
|
+
/** Domain filter patterns */
|
|
15
|
+
export interface DomainFilterConfig {
|
|
16
|
+
/** Allowed domain patterns (exact or wildcard *.example.com) */
|
|
17
|
+
allowed_domains: string[];
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/security/types.ts"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1D,kCAAkC;AAClC,MAAM,WAAW,kBAAkB;IACjC,iDAAiD;IACjD,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,mDAAmD;IACnD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,6BAA6B;AAC7B,MAAM,WAAW,kBAAkB;IACjC,gEAAgE;IAChE,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/security/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Encryption — AES-256-GCM with scrypt key derivation.
|
|
3
|
+
*
|
|
4
|
+
* Auto-encrypts session files when SESSION_ENCRYPTION_KEY env var is set.
|
|
5
|
+
* Uses Node.js crypto module only, zero deps.
|
|
6
|
+
*/
|
|
7
|
+
export interface EncryptedPayload {
|
|
8
|
+
version: 1;
|
|
9
|
+
algorithm: "aes-256-gcm";
|
|
10
|
+
iv: string;
|
|
11
|
+
authTag: string;
|
|
12
|
+
ciphertext: string;
|
|
13
|
+
salt: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Encrypt plaintext with AES-256-GCM.
|
|
17
|
+
*/
|
|
18
|
+
export declare function encryptData(plaintext: string, userKey: string): EncryptedPayload;
|
|
19
|
+
/**
|
|
20
|
+
* Decrypt an encrypted payload.
|
|
21
|
+
*/
|
|
22
|
+
export declare function decryptData(payload: EncryptedPayload, userKey: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Check if an object looks like an encrypted payload.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isEncryptedPayload(obj: unknown): obj is EncryptedPayload;
|
|
27
|
+
/**
|
|
28
|
+
* Get encryption key from env var or auto-generated key file.
|
|
29
|
+
* Returns undefined if no key is configured and no key file exists.
|
|
30
|
+
*/
|
|
31
|
+
export declare function ensureEncryptionKey(): Promise<string | undefined>;
|
|
32
|
+
/**
|
|
33
|
+
* Generate a new encryption key and save to file.
|
|
34
|
+
* Only call this explicitly (e.g. from setup wizard).
|
|
35
|
+
*/
|
|
36
|
+
export declare function generateEncryptionKey(): Promise<string>;
|
|
37
|
+
//# sourceMappingURL=encryption.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encryption.d.ts","sourceRoot":"","sources":["../../src/sessions/encryption.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAoBH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,CAAC,CAAC;IACX,SAAS,EAAE,aAAa,CAAC;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AASD;;GAEG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,gBAAgB,CAoBhF;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAc9E;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,gBAAgB,CAWxE;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAcvE;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,MAAM,CAAC,CAS7D"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Encryption — AES-256-GCM with scrypt key derivation.
|
|
3
|
+
*
|
|
4
|
+
* Auto-encrypts session files when SESSION_ENCRYPTION_KEY env var is set.
|
|
5
|
+
* Uses Node.js crypto module only, zero deps.
|
|
6
|
+
*/
|
|
7
|
+
import { randomBytes, createCipheriv, createDecipheriv, scryptSync, } from "node:crypto";
|
|
8
|
+
import fs from "node:fs/promises";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import os from "node:os";
|
|
11
|
+
import { SKILLS_DIR_NAME } from "../constants.js";
|
|
12
|
+
const ALGORITHM = "aes-256-gcm";
|
|
13
|
+
const KEY_LENGTH = 32;
|
|
14
|
+
const IV_LENGTH = 12;
|
|
15
|
+
const SALT_LENGTH = 16;
|
|
16
|
+
const AUTH_TAG_LENGTH = 16;
|
|
17
|
+
const ENCRYPTION_KEY_FILENAME = "encryption.key";
|
|
18
|
+
/**
|
|
19
|
+
* Derive a 256-bit key from user password using scrypt.
|
|
20
|
+
*/
|
|
21
|
+
function deriveKey(userKey, salt) {
|
|
22
|
+
return scryptSync(userKey, salt, KEY_LENGTH);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Encrypt plaintext with AES-256-GCM.
|
|
26
|
+
*/
|
|
27
|
+
export function encryptData(plaintext, userKey) {
|
|
28
|
+
const salt = randomBytes(SALT_LENGTH);
|
|
29
|
+
const key = deriveKey(userKey, salt);
|
|
30
|
+
const iv = randomBytes(IV_LENGTH);
|
|
31
|
+
const cipher = createCipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
|
|
32
|
+
const encrypted = Buffer.concat([
|
|
33
|
+
cipher.update(plaintext, "utf-8"),
|
|
34
|
+
cipher.final(),
|
|
35
|
+
]);
|
|
36
|
+
const authTag = cipher.getAuthTag();
|
|
37
|
+
return {
|
|
38
|
+
version: 1,
|
|
39
|
+
algorithm: ALGORITHM,
|
|
40
|
+
iv: iv.toString("hex"),
|
|
41
|
+
authTag: authTag.toString("hex"),
|
|
42
|
+
ciphertext: encrypted.toString("hex"),
|
|
43
|
+
salt: salt.toString("hex"),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Decrypt an encrypted payload.
|
|
48
|
+
*/
|
|
49
|
+
export function decryptData(payload, userKey) {
|
|
50
|
+
const salt = Buffer.from(payload.salt, "hex");
|
|
51
|
+
const key = deriveKey(userKey, salt);
|
|
52
|
+
const iv = Buffer.from(payload.iv, "hex");
|
|
53
|
+
const authTag = Buffer.from(payload.authTag, "hex");
|
|
54
|
+
const ciphertext = Buffer.from(payload.ciphertext, "hex");
|
|
55
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
|
|
56
|
+
decipher.setAuthTag(authTag);
|
|
57
|
+
return Buffer.concat([
|
|
58
|
+
decipher.update(ciphertext),
|
|
59
|
+
decipher.final(),
|
|
60
|
+
]).toString("utf-8");
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Check if an object looks like an encrypted payload.
|
|
64
|
+
*/
|
|
65
|
+
export function isEncryptedPayload(obj) {
|
|
66
|
+
if (!obj || typeof obj !== "object")
|
|
67
|
+
return false;
|
|
68
|
+
const o = obj;
|
|
69
|
+
return (o.version === 1 &&
|
|
70
|
+
o.algorithm === ALGORITHM &&
|
|
71
|
+
typeof o.iv === "string" &&
|
|
72
|
+
typeof o.authTag === "string" &&
|
|
73
|
+
typeof o.ciphertext === "string" &&
|
|
74
|
+
typeof o.salt === "string");
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get encryption key from env var or auto-generated key file.
|
|
78
|
+
* Returns undefined if no key is configured and no key file exists.
|
|
79
|
+
*/
|
|
80
|
+
export async function ensureEncryptionKey() {
|
|
81
|
+
// Env var takes priority
|
|
82
|
+
const envKey = process.env.SESSION_ENCRYPTION_KEY?.trim();
|
|
83
|
+
if (envKey)
|
|
84
|
+
return envKey;
|
|
85
|
+
// Check for existing key file
|
|
86
|
+
const keyPath = path.join(os.homedir(), SKILLS_DIR_NAME, ENCRYPTION_KEY_FILENAME);
|
|
87
|
+
try {
|
|
88
|
+
const key = await fs.readFile(keyPath, "utf-8");
|
|
89
|
+
return key.trim();
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// No key file — return undefined (encryption not configured)
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Generate a new encryption key and save to file.
|
|
98
|
+
* Only call this explicitly (e.g. from setup wizard).
|
|
99
|
+
*/
|
|
100
|
+
export async function generateEncryptionKey() {
|
|
101
|
+
const key = randomBytes(32).toString("hex");
|
|
102
|
+
const dir = path.join(os.homedir(), SKILLS_DIR_NAME);
|
|
103
|
+
const keyPath = path.join(dir, ENCRYPTION_KEY_FILENAME);
|
|
104
|
+
await fs.mkdir(dir, { recursive: true });
|
|
105
|
+
await fs.writeFile(keyPath, key, { encoding: "utf-8", mode: 0o600 });
|
|
106
|
+
return key;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=encryption.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encryption.js","sourceRoot":"","sources":["../../src/sessions/encryption.ts"],"names":[],"mappings":"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"}
|
package/dist/sessions/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export type { StoredSession, StoredCookie } from "./types.js";
|
|
2
2
|
export { SessionManager, getSessionManager, resetSessionManager } from "./manager.js";
|
|
3
|
+
export { encryptData, decryptData, isEncryptedPayload, ensureEncryptionKey, generateEncryptionKey, } from "./encryption.js";
|
|
3
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sessions/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sessions/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,EACL,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC"}
|
package/dist/sessions/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sessions/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sessions/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,EACL,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC"}
|
|
@@ -2,7 +2,10 @@ import type { StoredSession, StoredCookie } from "./types.js";
|
|
|
2
2
|
export declare class SessionManager {
|
|
3
3
|
private cache;
|
|
4
4
|
private dir;
|
|
5
|
+
private encryptionKey;
|
|
6
|
+
private keyLoaded;
|
|
5
7
|
constructor(dir?: string);
|
|
8
|
+
private getEncryptionKey;
|
|
6
9
|
private sessionPath;
|
|
7
10
|
save(id: string, cookies: StoredCookie[], url: string): Promise<void>;
|
|
8
11
|
load(id: string): Promise<StoredSession | null>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/sessions/manager.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/sessions/manager.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG9D,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,SAAS,CAAS;gBAEd,GAAG,CAAC,EAAE,MAAM;YAIV,gBAAgB;IAQ9B,OAAO,CAAC,WAAW;IAMb,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2BrE,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAqC/C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASjC,IAAI,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;CAUhC;AAMD,wBAAgB,iBAAiB,IAAI,cAAc,CAKlD;AAED,oCAAoC;AACpC,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C"}
|
package/dist/sessions/manager.js
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { getSessionsDir } from "../config.js";
|
|
4
|
+
import { encryptData, decryptData, isEncryptedPayload, ensureEncryptionKey } from "./encryption.js";
|
|
4
5
|
export class SessionManager {
|
|
5
6
|
cache = new Map();
|
|
6
7
|
dir;
|
|
8
|
+
encryptionKey;
|
|
9
|
+
keyLoaded = false;
|
|
7
10
|
constructor(dir) {
|
|
8
11
|
this.dir = dir ?? getSessionsDir();
|
|
9
12
|
}
|
|
13
|
+
async getEncryptionKey() {
|
|
14
|
+
if (!this.keyLoaded) {
|
|
15
|
+
this.encryptionKey = await ensureEncryptionKey();
|
|
16
|
+
this.keyLoaded = true;
|
|
17
|
+
}
|
|
18
|
+
return this.encryptionKey;
|
|
19
|
+
}
|
|
10
20
|
sessionPath(id) {
|
|
11
21
|
// Sanitize id to prevent path traversal
|
|
12
22
|
const safe = id.replace(/[^a-zA-Z0-9_\-]/g, "_");
|
|
@@ -26,7 +36,11 @@ export class SessionManager {
|
|
|
26
36
|
await fs.mkdir(this.dir, { recursive: true });
|
|
27
37
|
const filePath = this.sessionPath(id);
|
|
28
38
|
const tmpPath = filePath + ".tmp";
|
|
29
|
-
await
|
|
39
|
+
const key = await this.getEncryptionKey();
|
|
40
|
+
const content = key
|
|
41
|
+
? JSON.stringify(encryptData(JSON.stringify(session), key), null, 2)
|
|
42
|
+
: JSON.stringify(session, null, 2);
|
|
43
|
+
await fs.writeFile(tmpPath, content, "utf-8");
|
|
30
44
|
await fs.rename(tmpPath, filePath);
|
|
31
45
|
}
|
|
32
46
|
async load(id) {
|
|
@@ -34,7 +48,19 @@ export class SessionManager {
|
|
|
34
48
|
return this.cache.get(id);
|
|
35
49
|
try {
|
|
36
50
|
const data = await fs.readFile(this.sessionPath(id), "utf-8");
|
|
37
|
-
const
|
|
51
|
+
const parsed = JSON.parse(data);
|
|
52
|
+
let session;
|
|
53
|
+
if (isEncryptedPayload(parsed)) {
|
|
54
|
+
const key = await this.getEncryptionKey();
|
|
55
|
+
if (!key) {
|
|
56
|
+
console.error("[sessions] Encrypted session found but no encryption key configured");
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
session = JSON.parse(decryptData(parsed, key));
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
session = parsed;
|
|
63
|
+
}
|
|
38
64
|
this.cache.set(id, session);
|
|
39
65
|
return session;
|
|
40
66
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/sessions/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/sessions/manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Annotated Screenshots — Inject visual overlay badges on interactive elements.
|
|
3
|
+
*
|
|
4
|
+
* Zero external deps — uses DOM injection via page.evaluate(),
|
|
5
|
+
* takes screenshot, then cleans up injected elements.
|
|
6
|
+
*
|
|
7
|
+
* Color coding:
|
|
8
|
+
* - Red (#e53e3e) = button, switch
|
|
9
|
+
* - Blue (#3182ce) = link, tab
|
|
10
|
+
* - Green (#38a169) = textbox, searchbox, combobox, listbox
|
|
11
|
+
* - Orange (#dd6b20) = other interactive (checkbox, radio, slider, etc.)
|
|
12
|
+
*/
|
|
13
|
+
import type { RefMap } from "./types.js";
|
|
14
|
+
type Page = import("rebrowser-playwright").Page;
|
|
15
|
+
/**
|
|
16
|
+
* Inject annotation overlays on interactive elements, take screenshot, cleanup.
|
|
17
|
+
* Returns PNG buffer.
|
|
18
|
+
*/
|
|
19
|
+
export declare function annotateScreenshot(page: Page, refs: RefMap): Promise<Buffer>;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=annotator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"annotator.d.ts","sourceRoot":"","sources":["../../src/snapshot/annotator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC,KAAK,IAAI,GAAG,OAAO,sBAAsB,EAAE,IAAI,CAAC;AAiBhD;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAoIlF"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Annotated Screenshots — Inject visual overlay badges on interactive elements.
|
|
3
|
+
*
|
|
4
|
+
* Zero external deps — uses DOM injection via page.evaluate(),
|
|
5
|
+
* takes screenshot, then cleans up injected elements.
|
|
6
|
+
*
|
|
7
|
+
* Color coding:
|
|
8
|
+
* - Red (#e53e3e) = button, switch
|
|
9
|
+
* - Blue (#3182ce) = link, tab
|
|
10
|
+
* - Green (#38a169) = textbox, searchbox, combobox, listbox
|
|
11
|
+
* - Orange (#dd6b20) = other interactive (checkbox, radio, slider, etc.)
|
|
12
|
+
*/
|
|
13
|
+
const ROLE_COLORS = {
|
|
14
|
+
button: "#e53e3e",
|
|
15
|
+
switch: "#e53e3e",
|
|
16
|
+
link: "#3182ce",
|
|
17
|
+
tab: "#3182ce",
|
|
18
|
+
textbox: "#38a169",
|
|
19
|
+
searchbox: "#38a169",
|
|
20
|
+
combobox: "#38a169",
|
|
21
|
+
listbox: "#38a169",
|
|
22
|
+
};
|
|
23
|
+
const DEFAULT_COLOR = "#dd6b20";
|
|
24
|
+
const ANNOTATION_ATTR = "data-imperium-annotation";
|
|
25
|
+
/**
|
|
26
|
+
* Inject annotation overlays on interactive elements, take screenshot, cleanup.
|
|
27
|
+
* Returns PNG buffer.
|
|
28
|
+
*/
|
|
29
|
+
export async function annotateScreenshot(page, refs) {
|
|
30
|
+
// Build annotation data for injection
|
|
31
|
+
const annotations = Object.entries(refs).map(([ref, entry]) => ({
|
|
32
|
+
ref,
|
|
33
|
+
role: entry.role,
|
|
34
|
+
name: entry.name,
|
|
35
|
+
nth: entry.nth,
|
|
36
|
+
color: ROLE_COLORS[entry.role] ?? DEFAULT_COLOR,
|
|
37
|
+
}));
|
|
38
|
+
// Inject overlays via page.evaluate
|
|
39
|
+
await page.evaluate((data) => {
|
|
40
|
+
const ATTR = "data-imperium-annotation";
|
|
41
|
+
for (const { ref, role, name, nth, color } of data) {
|
|
42
|
+
// Find the element using ARIA queries
|
|
43
|
+
let elements;
|
|
44
|
+
try {
|
|
45
|
+
// Try aria role query
|
|
46
|
+
const selector = name
|
|
47
|
+
? `[role="${role}"]`
|
|
48
|
+
: `[role="${role}"]`;
|
|
49
|
+
elements = Array.from(document.querySelectorAll(selector));
|
|
50
|
+
// Filter by accessible name if provided
|
|
51
|
+
if (name) {
|
|
52
|
+
elements = elements.filter((el) => {
|
|
53
|
+
const accName = el.getAttribute("aria-label") ||
|
|
54
|
+
el.getAttribute("title") ||
|
|
55
|
+
(el.textContent ?? "").trim();
|
|
56
|
+
return accName.includes(name) || name.includes(accName.slice(0, 80));
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// Handle nth
|
|
60
|
+
if (nth !== undefined && elements[nth]) {
|
|
61
|
+
elements = [elements[nth]];
|
|
62
|
+
}
|
|
63
|
+
else if (elements.length > 0) {
|
|
64
|
+
elements = [elements[0]];
|
|
65
|
+
}
|
|
66
|
+
// Fallback: try matching by tag for common roles
|
|
67
|
+
if (elements.length === 0) {
|
|
68
|
+
const tagMap = {
|
|
69
|
+
button: "button",
|
|
70
|
+
link: "a",
|
|
71
|
+
textbox: "input,textarea",
|
|
72
|
+
searchbox: "input[type=search]",
|
|
73
|
+
checkbox: "input[type=checkbox]",
|
|
74
|
+
radio: "input[type=radio]",
|
|
75
|
+
};
|
|
76
|
+
if (tagMap[role]) {
|
|
77
|
+
const tagEls = document.querySelectorAll(tagMap[role]);
|
|
78
|
+
for (const el of tagEls) {
|
|
79
|
+
const accName = el.getAttribute("aria-label") ||
|
|
80
|
+
el.getAttribute("title") ||
|
|
81
|
+
(el.textContent ?? "").trim();
|
|
82
|
+
if (!name || accName.includes(name) || name.includes(accName.slice(0, 80))) {
|
|
83
|
+
elements = [el];
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
elements = [];
|
|
92
|
+
}
|
|
93
|
+
if (elements.length === 0)
|
|
94
|
+
continue;
|
|
95
|
+
const el = elements[0];
|
|
96
|
+
const rect = el.getBoundingClientRect();
|
|
97
|
+
if (rect.width === 0 || rect.height === 0)
|
|
98
|
+
continue;
|
|
99
|
+
// Create border overlay
|
|
100
|
+
const border = document.createElement("div");
|
|
101
|
+
border.setAttribute(ATTR, ref);
|
|
102
|
+
Object.assign(border.style, {
|
|
103
|
+
position: "absolute",
|
|
104
|
+
top: `${rect.top + window.scrollY}px`,
|
|
105
|
+
left: `${rect.left + window.scrollX}px`,
|
|
106
|
+
width: `${rect.width}px`,
|
|
107
|
+
height: `${rect.height}px`,
|
|
108
|
+
border: `2px solid ${color}`,
|
|
109
|
+
borderRadius: "3px",
|
|
110
|
+
pointerEvents: "none",
|
|
111
|
+
zIndex: "2147483647",
|
|
112
|
+
boxSizing: "border-box",
|
|
113
|
+
});
|
|
114
|
+
// Create badge
|
|
115
|
+
const badge = document.createElement("div");
|
|
116
|
+
badge.setAttribute(ATTR, `${ref}-badge`);
|
|
117
|
+
const refNum = ref.replace("e", "");
|
|
118
|
+
badge.textContent = refNum;
|
|
119
|
+
Object.assign(badge.style, {
|
|
120
|
+
position: "absolute",
|
|
121
|
+
top: `${rect.top + window.scrollY - 10}px`,
|
|
122
|
+
left: `${rect.left + window.scrollX - 10}px`,
|
|
123
|
+
width: "20px",
|
|
124
|
+
height: "20px",
|
|
125
|
+
borderRadius: "50%",
|
|
126
|
+
backgroundColor: color,
|
|
127
|
+
color: "white",
|
|
128
|
+
fontSize: "11px",
|
|
129
|
+
fontWeight: "bold",
|
|
130
|
+
fontFamily: "Arial, sans-serif",
|
|
131
|
+
display: "flex",
|
|
132
|
+
alignItems: "center",
|
|
133
|
+
justifyContent: "center",
|
|
134
|
+
pointerEvents: "none",
|
|
135
|
+
zIndex: "2147483647",
|
|
136
|
+
lineHeight: "1",
|
|
137
|
+
});
|
|
138
|
+
document.body.appendChild(border);
|
|
139
|
+
document.body.appendChild(badge);
|
|
140
|
+
}
|
|
141
|
+
}, annotations);
|
|
142
|
+
// Take screenshot with annotations
|
|
143
|
+
const screenshot = await page.screenshot({ fullPage: false });
|
|
144
|
+
// Cleanup injected elements
|
|
145
|
+
await page.evaluate((attr) => {
|
|
146
|
+
const els = document.querySelectorAll(`[${attr}]`);
|
|
147
|
+
for (const el of els)
|
|
148
|
+
el.remove();
|
|
149
|
+
}, ANNOTATION_ATTR);
|
|
150
|
+
return screenshot;
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=annotator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"annotator.js","sourceRoot":"","sources":["../../src/snapshot/annotator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content boundaries — unique markers to wrap content sections.
|
|
3
|
+
* Prevents LLM confusion about where content starts/ends.
|
|
4
|
+
*/
|
|
5
|
+
export declare function generateBoundary(): string;
|
|
6
|
+
export declare function wrapContent(content: string, boundary: string): string;
|
|
7
|
+
//# sourceMappingURL=boundary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boundary.d.ts","sourceRoot":"","sources":["../../src/snapshot/boundary.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAErE"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content boundaries — unique markers to wrap content sections.
|
|
3
|
+
* Prevents LLM confusion about where content starts/ends.
|
|
4
|
+
*/
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
export function generateBoundary() {
|
|
7
|
+
return randomBytes(8).toString("hex");
|
|
8
|
+
}
|
|
9
|
+
export function wrapContent(content, boundary) {
|
|
10
|
+
return `<imperium-boundary:${boundary}>\n${content}\n</imperium-boundary:${boundary}>`;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=boundary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boundary.js","sourceRoot":"","sources":["../../src/snapshot/boundary.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,UAAU,gBAAgB;IAC9B,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,QAAgB;IAC3D,OAAO,sBAAsB,QAAQ,MAAM,OAAO,yBAAyB,QAAQ,GAAG,CAAC;AACzF,CAAC"}
|