@patrickdk77/script-defuse 0.0.0-stage → 0.1.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.
@@ -0,0 +1,12 @@
1
+ import type { ScriptContext, ScriptInspection, ScriptOptions } from './types';
2
+ export interface InspectScriptOptions extends ScriptOptions {
3
+ /** Where the script runs, for the findings' locations. */
4
+ context?: ScriptContext;
5
+ /** Global names scripts that ran before this one defined and kept, which this one may use. */
6
+ knownGlobals?: Iterable<string>;
7
+ }
8
+ /**
9
+ * Inspect one script, in JavaScript or, when the context says so, in FormCalc. The verdict is 'form' when no finding strips it: every call and property it uses formats,
10
+ * checks or calculates form fields, and nothing in it hides what it does.
11
+ */
12
+ export declare function inspectScript(text: string, options?: InspectScriptOptions): ScriptInspection;
package/dist/engine.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.inspectScript = inspectScript;
4
+ const analyze_1 = require("./analyze");
5
+ const formcalc_1 = require("./formcalc");
6
+ const score_1 = require("./score");
7
+ /**
8
+ * Inspect one script, in JavaScript or, when the context says so, in FormCalc. The verdict is 'form' when no finding strips it: every call and property it uses formats,
9
+ * checks or calculates form fields, and nothing in it hides what it does.
10
+ */
11
+ function inspectScript(text, options = {}) {
12
+ const { findings, definedGlobals } = options.context?.language === 'formcalc'
13
+ ? (0, formcalc_1.analyzeFormCalc)({ text, context: options.context, options })
14
+ : (0, analyze_1.analyzeScript)({ text, context: options.context, options, known: new Set(options.knownGlobals ?? []) });
15
+ const counted = findings.filter(f => f.action === 'strip');
16
+ const { score, risk } = (0, score_1.scoreFindings)(counted, options);
17
+ return { verdict: counted.length ? 'unsafe' : 'form', score, risk, findings, definedGlobals };
18
+ }
@@ -0,0 +1,19 @@
1
+ import { ScriptCategory as C, ScriptDetail as D, type ScriptActionOverride, type ScriptFinding, type ScriptFindingAction } from './types';
2
+ interface Spec {
3
+ action: ScriptFindingAction;
4
+ /** May contain <key> placeholders filled from data. A filled value has control characters replaced by "?" and is cut to 60 characters. */
5
+ description: string;
6
+ weight: number;
7
+ }
8
+ export declare function findingSpec(c: string, d: string): Spec | undefined;
9
+ /** A finding with its default action, after the caller's overrides. */
10
+ export declare function makeFinding(c: C, d: D, overrides?: ScriptActionOverride[], location?: string, data?: Record<string, string | number>): ScriptFinding;
11
+ /** Every finding kind this package can report, with its default action, weight and description. */
12
+ export declare function allFindingSpecs(): Array<{
13
+ category: C;
14
+ detail: D;
15
+ action: ScriptFindingAction;
16
+ weight: number;
17
+ description: string;
18
+ }>;
19
+ export {};
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findingSpec = findingSpec;
4
+ exports.makeFinding = makeFinding;
5
+ exports.allFindingSpecs = allFindingSpecs;
6
+ const types_1 = require("./types");
7
+ const specs = new Map();
8
+ function def(c, d, action, weight, description) {
9
+ specs.set(`${c}/${d}`, { action, weight, description });
10
+ }
11
+ // FORM
12
+ def(types_1.ScriptCategory.Form, types_1.ScriptDetail.Computation, 'info', 0, 'The script only formats, checks or calculates form fields');
13
+ // EXTERNAL
14
+ def(types_1.ScriptCategory.External, types_1.ScriptDetail.Url, 'strip', 60, '<api> opens a web address');
15
+ def(types_1.ScriptCategory.External, types_1.ScriptDetail.Submit, 'strip', 50, '<api> sends the form data somewhere');
16
+ def(types_1.ScriptCategory.External, types_1.ScriptDetail.Mail, 'strip', 50, '<api> sends mail');
17
+ def(types_1.ScriptCategory.External, types_1.ScriptDetail.OpenDocument, 'strip', 60, '<api> opens or creates another document');
18
+ def(types_1.ScriptCategory.External, types_1.ScriptDetail.DataObject, 'strip', 70, '<api> reads, writes or opens a file attached to the document');
19
+ def(types_1.ScriptCategory.External, types_1.ScriptDetail.ImportExport, 'strip', 50, '<api> reads or writes form data or files outside the document');
20
+ def(types_1.ScriptCategory.External, types_1.ScriptDetail.Network, 'strip', 70, '<api> talks to a network service');
21
+ def(types_1.ScriptCategory.External, types_1.ScriptDetail.MenuCommand, 'strip', 60, '<api> runs a viewer menu command');
22
+ def(types_1.ScriptCategory.External, types_1.ScriptDetail.Print, 'strip', 20, '<api> prints');
23
+ def(types_1.ScriptCategory.External, types_1.ScriptDetail.Save, 'strip', 30, '<api> saves or closes the document');
24
+ // DYNAMIC_CODE
25
+ def(types_1.ScriptCategory.DynamicCode, types_1.ScriptDetail.Eval, 'strip', 80, '<api> runs code built from a string');
26
+ def(types_1.ScriptCategory.DynamicCode, types_1.ScriptDetail.Timer, 'strip', 70, '<api> runs code later, from a string');
27
+ def(types_1.ScriptCategory.DynamicCode, types_1.ScriptDetail.InstallScript, 'strip', 70, '<api> installs another script or action');
28
+ def(types_1.ScriptCategory.DynamicCode, types_1.ScriptDetail.Privilege, 'strip', 80, '<api> asks for privileged execution');
29
+ def(types_1.ScriptCategory.DynamicCode, types_1.ScriptDetail.ComputedAccess, 'strip', 40, 'A property is read by a name computed at run time, so what it reaches cannot be told');
30
+ def(types_1.ScriptCategory.DynamicCode, types_1.ScriptDetail.Prototype, 'strip', 60, '<api> reaches the language machinery behind objects and functions');
31
+ def(types_1.ScriptCategory.DynamicCode, types_1.ScriptDetail.With, 'strip', 40, 'A with statement changes what names refer to');
32
+ def(types_1.ScriptCategory.DynamicCode, types_1.ScriptDetail.SelfReference, 'strip', 60, '<api> reads the source of the running code, the way self-decrypting scripts do');
33
+ // OBFUSCATION
34
+ def(types_1.ScriptCategory.Obfuscation, types_1.ScriptDetail.CharCodes, 'strip', 50, 'Text is built from character codes');
35
+ def(types_1.ScriptCategory.Obfuscation, types_1.ScriptDetail.Decode, 'strip', 60, '<api> decodes hidden text');
36
+ def(types_1.ScriptCategory.Obfuscation, types_1.ScriptDetail.Concatenation, 'strip', 40, 'A string is joined from many short pieces');
37
+ def(types_1.ScriptCategory.Obfuscation, types_1.ScriptDetail.Escapes, 'strip', 40, 'A string writes ordinary letters as escape sequences');
38
+ def(types_1.ScriptCategory.Obfuscation, types_1.ScriptDetail.EncodedString, 'strip', 50, 'A string holds encoded data');
39
+ def(types_1.ScriptCategory.Obfuscation, types_1.ScriptDetail.LongString, 'strip', 40, 'A string far longer than form text');
40
+ def(types_1.ScriptCategory.Obfuscation, types_1.ScriptDetail.NumberExpression, 'strip', 30, 'A number is written as arithmetic on hex constants');
41
+ def(types_1.ScriptCategory.Obfuscation, types_1.ScriptDetail.BracketName, 'strip', 30, '<api> is reached by a quoted name where a form script names it plainly');
42
+ // API
43
+ def(types_1.ScriptCategory.Api, types_1.ScriptDetail.Exploited, 'strip', 80, '<api> is a viewer API known from exploits');
44
+ def(types_1.ScriptCategory.Api, types_1.ScriptDetail.Unknown, 'strip', 30, '<api> is not a form calculation, format or check');
45
+ def(types_1.ScriptCategory.Api, types_1.ScriptDetail.PasswordPrompt, 'strip', 50, '<api> asks for a password in a prompt, as a fake sign-in does. A form takes a password in a field of its own');
46
+ // SYNTAX
47
+ def(types_1.ScriptCategory.Syntax, types_1.ScriptDetail.Unparseable, 'strip', 50, 'The script is not valid JavaScript, so what it does cannot be told');
48
+ // LIMIT
49
+ def(types_1.ScriptCategory.Limit, types_1.ScriptDetail.Size, 'strip', 30, 'The script is longer than limits.scriptSize');
50
+ def(types_1.ScriptCategory.Limit, types_1.ScriptDetail.Depth, 'strip', 30, 'The script nests deeper than limits.depth');
51
+ function findingSpec(c, d) {
52
+ return specs.get(`${c}/${d}`);
53
+ }
54
+ const clip = (v) => String(v)
55
+ .replace(/[\p{Cc}\p{Cf}]/gu, '?')
56
+ .slice(0, 60);
57
+ /** A finding with its default action, after the caller's overrides. */
58
+ function makeFinding(c, d, overrides = [], location, data) {
59
+ const spec = specs.get(`${c}/${d}`);
60
+ if (!spec)
61
+ throw new Error(`no finding spec for ${c}/${d}`);
62
+ const exact = overrides.find(o => o.category === c && o.detail === d);
63
+ const byCategory = overrides.find(o => o.category === c && o.detail === undefined);
64
+ const action = exact?.action ?? byCategory?.action ?? spec.action;
65
+ const description = spec.description.replace(/<(\w+)>/g, (_m, key) => (data?.[key] === undefined ? key : clip(data[key])));
66
+ const f = { category: c, detail: d, description, action, weight: spec.weight };
67
+ if (location)
68
+ f.location = location;
69
+ if (data)
70
+ f.data = data;
71
+ return f;
72
+ }
73
+ /** Every finding kind this package can report, with its default action, weight and description. */
74
+ function allFindingSpecs() {
75
+ return [...specs].map(([key, spec]) => {
76
+ const [category, detail] = key.split('/');
77
+ return { category, detail, ...spec };
78
+ });
79
+ }
@@ -0,0 +1,10 @@
1
+ import { type ScriptContext, type ScriptFinding, type ScriptOptions } from './types';
2
+ export interface FormCalcInput {
3
+ text: string;
4
+ context?: ScriptContext;
5
+ options?: ScriptOptions;
6
+ }
7
+ export declare function analyzeFormCalc(input: FormCalcInput): {
8
+ findings: ScriptFinding[];
9
+ definedGlobals: string[];
10
+ };