@xagents_in/unmask 0.1.2 → 0.1.4
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 +301 -85
- package/dist/cli/annotations.mjs +145 -0
- package/dist/cli/audit.mjs +223 -0
- package/dist/cli/batch.mjs +218 -0
- package/dist/cli/cache.mjs +488 -0
- package/dist/cli/compare.mjs +261 -0
- package/dist/cli/config.mjs +141 -0
- package/dist/cli/discovery.mjs +372 -0
- package/dist/cli/doctor.mjs +354 -0
- package/dist/cli/exceptions.mjs +170 -0
- package/dist/cli/file.mjs +271 -0
- package/dist/cli/files.mjs +141 -0
- package/dist/cli/history.mjs +232 -0
- package/dist/cli/hosted.mjs +251 -43
- package/dist/cli/info.mjs +125 -0
- package/dist/cli/init.mjs +57 -0
- package/dist/cli/install-guard.mjs +896 -0
- package/dist/cli/lockfile.mjs +330 -0
- package/dist/cli/main.mjs +101 -14
- package/dist/cli/preflight.mjs +388 -0
- package/dist/cli/sarif.mjs +100 -0
- package/dist/cli/scan.mjs +128 -0
- package/dist/cli/shell-integration.mjs +122 -0
- package/dist/cli/terminal.mjs +396 -0
- package/dist/cli/watch.mjs +233 -0
- package/dist/cli/why.mjs +256 -0
- package/dist/core/advisories.mjs +101 -24
- package/dist/core/advisory-cache.mjs +295 -0
- package/dist/core/behavior-signals.mjs +1088 -0
- package/dist/core/dependency-graph.mjs +332 -0
- package/dist/core/evidence-exports.mjs +469 -0
- package/dist/core/guard-config.mjs +463 -0
- package/dist/core/guard-policy.mjs +308 -0
- package/dist/core/package-details.mjs +42 -6
- package/dist/core/registry-facts.mjs +342 -0
- package/dist/core/report-exploration.mjs +328 -0
- package/dist/core/report.mjs +7 -0
- package/dist/core/sarif.mjs +556 -0
- package/dist/core/syntax.mjs +296 -15
- package/dist/model/client.mjs +204 -56
- package/dist/model/diff-evidence.mjs +177 -0
- package/dist/model/summary.mjs +183 -0
- package/dist/server/advisory-result.mjs +137 -0
- package/dist/server/app.mjs +66 -2
- package/dist/server/dependency-screening.mjs +386 -0
- package/dist/server/exports.mjs +63 -0
- package/dist/server/guard-decision.mjs +37 -0
- package/dist/server/main.mjs +25 -7
- package/dist/server/queue.mjs +95 -53
- package/dist/server/registry-facts.mjs +118 -0
- package/dist/server/report-exploration.mjs +87 -0
- package/dist/server/store.mjs +21 -1
- package/dist/version.json +1 -1
- package/dist/viewer/render.mjs +10 -2
- package/dist/viewer/shell.mjs +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { parsePackageSpec, AcquisitionError } from "../core/acquire.mjs";
|
|
2
|
+
import { LocalInputError } from "../core/pipeline.mjs";
|
|
3
|
+
import { checkHosted } from "./hosted.mjs";
|
|
4
|
+
|
|
5
|
+
const DEPENDENCY_FIELDS = [
|
|
6
|
+
"dependencies",
|
|
7
|
+
"optionalDependencies",
|
|
8
|
+
"peerDependencies",
|
|
9
|
+
];
|
|
10
|
+
const LIFECYCLE = new Set(["preinstall", "install", "postinstall", "prepare"]);
|
|
11
|
+
const MAX_LISTED = 40;
|
|
12
|
+
const usage = () =>
|
|
13
|
+
new LocalInputError(
|
|
14
|
+
"usage",
|
|
15
|
+
"Use compare OLD_NAME@VERSION NEW_NAME@VERSION [--json]",
|
|
16
|
+
);
|
|
17
|
+
function exactSpec(spec) {
|
|
18
|
+
try {
|
|
19
|
+
return parsePackageSpec(spec);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (error instanceof AcquisitionError)
|
|
22
|
+
throw new LocalInputError(error.code, error.message);
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function parseCompareArgs(argv) {
|
|
27
|
+
const positional = [];
|
|
28
|
+
let json = false;
|
|
29
|
+
for (const value of argv) {
|
|
30
|
+
if (value === "--json" && !json) json = true;
|
|
31
|
+
else if (value.startsWith("--")) throw usage();
|
|
32
|
+
else positional.push(value);
|
|
33
|
+
}
|
|
34
|
+
if (positional.length !== 2) throw usage();
|
|
35
|
+
const [older, newer] = positional.map(exactSpec);
|
|
36
|
+
if (older.name !== newer.name)
|
|
37
|
+
throw new LocalInputError(
|
|
38
|
+
"unrelated-packages",
|
|
39
|
+
`Refusing to compare different package names (${older.name} vs ${newer.name}); renamed or forked packages are not treated as versions of one package.`,
|
|
40
|
+
);
|
|
41
|
+
if (older.version === newer.version)
|
|
42
|
+
throw new LocalInputError(
|
|
43
|
+
"same-version",
|
|
44
|
+
"Compare requires two different exact versions",
|
|
45
|
+
);
|
|
46
|
+
return { specs: positional, json };
|
|
47
|
+
}
|
|
48
|
+
const manifestOf = (report) =>
|
|
49
|
+
report.manifests.artifact.status === "usable"
|
|
50
|
+
? report.manifests.artifact.value
|
|
51
|
+
: null;
|
|
52
|
+
const plainObject = (value) =>
|
|
53
|
+
value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
54
|
+
const kind = (value) =>
|
|
55
|
+
value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
|
|
56
|
+
function entry(map, key, label) {
|
|
57
|
+
if (!Object.hasOwn(map, key)) return {};
|
|
58
|
+
const value = map[key];
|
|
59
|
+
if (typeof value === "string") return { [label]: value };
|
|
60
|
+
return {
|
|
61
|
+
[label]: JSON.stringify(value) ?? kind(value),
|
|
62
|
+
[`${label}Type`]: kind(value),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function diffMap(before, after) {
|
|
66
|
+
const changes = [];
|
|
67
|
+
const a = plainObject(before);
|
|
68
|
+
const b = plainObject(after);
|
|
69
|
+
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
70
|
+
for (const key of [...keys].sort()) {
|
|
71
|
+
const from = entry(a, key, "from");
|
|
72
|
+
const to = entry(b, key, "to");
|
|
73
|
+
if (
|
|
74
|
+
Object.hasOwn(a, key) &&
|
|
75
|
+
Object.hasOwn(b, key) &&
|
|
76
|
+
from.from === to.to &&
|
|
77
|
+
from.fromType === to.toType
|
|
78
|
+
)
|
|
79
|
+
continue;
|
|
80
|
+
changes.push({
|
|
81
|
+
key,
|
|
82
|
+
change: !Object.hasOwn(a, key)
|
|
83
|
+
? "added"
|
|
84
|
+
: !Object.hasOwn(b, key)
|
|
85
|
+
? "removed"
|
|
86
|
+
: "changed",
|
|
87
|
+
...from,
|
|
88
|
+
...to,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return changes;
|
|
92
|
+
}
|
|
93
|
+
function filesByPath(report) {
|
|
94
|
+
return new Map(
|
|
95
|
+
report.files.map((file) => [file.path ?? file.originalPath, file]),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
function diffFiles(older, newer) {
|
|
99
|
+
const before = filesByPath(older);
|
|
100
|
+
const after = filesByPath(newer);
|
|
101
|
+
const result = { added: [], removed: [], modified: [], unchanged: 0 };
|
|
102
|
+
for (const [key, file] of after) {
|
|
103
|
+
const previous = before.get(key);
|
|
104
|
+
const entry = { path: key, size: file.size, state: file.state };
|
|
105
|
+
if (!previous) result.added.push(entry);
|
|
106
|
+
else if (previous.artifactSha256 !== file.artifactSha256)
|
|
107
|
+
result.modified.push({ ...entry, previousSize: previous.size });
|
|
108
|
+
else result.unchanged++;
|
|
109
|
+
}
|
|
110
|
+
for (const [key, file] of before)
|
|
111
|
+
if (!after.has(key))
|
|
112
|
+
result.removed.push({ path: key, size: file.size, state: file.state });
|
|
113
|
+
for (const list of [result.added, result.removed, result.modified])
|
|
114
|
+
list.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
function side(report) {
|
|
118
|
+
return {
|
|
119
|
+
name: report.inputs.claim.name,
|
|
120
|
+
version: report.inputs.claim.version,
|
|
121
|
+
exitCode: report.policy.exitCode,
|
|
122
|
+
findings: report.policy.findings.map((finding) => finding.ruleId),
|
|
123
|
+
coverageComplete: report.coverage.complete,
|
|
124
|
+
sourceStatus: report.source.status,
|
|
125
|
+
failures: report.failures.map((failure) => failure.code),
|
|
126
|
+
manifestStatus: report.manifests.artifact.status,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
export function aggregateExit(...reports) {
|
|
130
|
+
const codes = reports.map((report) => report.policy.exitCode);
|
|
131
|
+
return codes.includes(2) ? 2 : codes.includes(3) ? 3 : 0;
|
|
132
|
+
}
|
|
133
|
+
export function compareReports(older, newer) {
|
|
134
|
+
const a = manifestOf(older);
|
|
135
|
+
const b = manifestOf(newer);
|
|
136
|
+
const scripts = diffMap(a?.scripts, b?.scripts).map((change) => ({
|
|
137
|
+
...change,
|
|
138
|
+
lifecycle: LIFECYCLE.has(change.key),
|
|
139
|
+
}));
|
|
140
|
+
const dependencies = Object.fromEntries(
|
|
141
|
+
DEPENDENCY_FIELDS.map((field) => [field, diffMap(a?.[field], b?.[field])]),
|
|
142
|
+
);
|
|
143
|
+
const caveats = [];
|
|
144
|
+
for (const [label, report] of [
|
|
145
|
+
["old", older],
|
|
146
|
+
["new", newer],
|
|
147
|
+
]) {
|
|
148
|
+
const version = report.inputs.claim.version;
|
|
149
|
+
if (report.manifests.artifact.status !== "usable")
|
|
150
|
+
caveats.push(
|
|
151
|
+
`${label} ${version}: package manifest is ${report.manifests.artifact.status}; script and dependency changes are incomplete.`,
|
|
152
|
+
);
|
|
153
|
+
if (!report.coverage.complete)
|
|
154
|
+
caveats.push(
|
|
155
|
+
`${label} ${version}: file inventory or source comparison is incomplete.`,
|
|
156
|
+
);
|
|
157
|
+
if (report.failures.length)
|
|
158
|
+
caveats.push(
|
|
159
|
+
`${label} ${version}: deterministic failures ${report.failures.map((failure) => failure.code).join(", ")}.`,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
if (
|
|
163
|
+
[scripts, ...Object.values(dependencies)].some((changes) =>
|
|
164
|
+
changes.some((change) => change.fromType || change.toType),
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
caveats.push(
|
|
168
|
+
"Some changed script or dependency values are not strings; they are shown as JSON with their type and may be malformed.",
|
|
169
|
+
);
|
|
170
|
+
if (scripts.some((change) => change.lifecycle && change.change !== "removed"))
|
|
171
|
+
caveats.push(
|
|
172
|
+
"A lifecycle script was added or changed; installs may run it automatically.",
|
|
173
|
+
);
|
|
174
|
+
caveats.push(
|
|
175
|
+
"A version without current findings is not proven safe; known malware may be missed when advisories lag or versions were removed.",
|
|
176
|
+
);
|
|
177
|
+
return {
|
|
178
|
+
package: older.inputs.claim.name,
|
|
179
|
+
old: side(older),
|
|
180
|
+
new: side(newer),
|
|
181
|
+
scripts,
|
|
182
|
+
dependencies,
|
|
183
|
+
files: diffFiles(older, newer),
|
|
184
|
+
caveats,
|
|
185
|
+
exitCode: aggregateExit(older, newer),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const UNSAFE =
|
|
189
|
+
/[\u0000-\u001f\u007f-\u009f\u200e\u200f\u202a-\u202e\u2066-\u2069]/gu;
|
|
190
|
+
const JSON_UNSAFE = /[\u007f-\u009f\u200e\u200f\u202a-\u202e\u2066-\u2069]/gu;
|
|
191
|
+
const escapeUnsafe = (text, pattern = UNSAFE) =>
|
|
192
|
+
text.replace(
|
|
193
|
+
pattern,
|
|
194
|
+
(char) => "\\u" + char.charCodeAt(0).toString(16).padStart(4, "0"),
|
|
195
|
+
);
|
|
196
|
+
const display = (change, side) =>
|
|
197
|
+
(change[`${side}Type`] ? `<${change[`${side}Type`]}> ` : "") +
|
|
198
|
+
JSON.stringify(change[side]).slice(0, 200);
|
|
199
|
+
function listed(lines, items, format) {
|
|
200
|
+
for (const item of items.slice(0, MAX_LISTED)) lines.push(format(item));
|
|
201
|
+
if (items.length > MAX_LISTED)
|
|
202
|
+
lines.push(` ... ${items.length - MAX_LISTED} more`);
|
|
203
|
+
}
|
|
204
|
+
function mapLines(lines, title, changes) {
|
|
205
|
+
lines.push(`${title}: ${changes.length ? "" : "no changes"}`.trimEnd());
|
|
206
|
+
listed(lines, changes, (change) => {
|
|
207
|
+
const mark = { added: "+", removed: "-", changed: "~" }[change.change];
|
|
208
|
+
const flag = change.lifecycle ? " [lifecycle]" : "";
|
|
209
|
+
if (change.change === "changed")
|
|
210
|
+
return ` ${mark} ${change.key}${flag}: ${display(change, "from")} -> ${display(change, "to")}`;
|
|
211
|
+
return ` ${mark} ${change.key}${flag}: ${display(change, change.change === "added" ? "to" : "from")}`;
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
export function renderComparison(result) {
|
|
215
|
+
const lines = [
|
|
216
|
+
`Compare ${result.package} ${result.old.version} -> ${result.new.version}`,
|
|
217
|
+
`Aggregate exit ${result.exitCode}: 2 if either side is incomplete or failed, else 3 if either side has findings, else 0`,
|
|
218
|
+
];
|
|
219
|
+
for (const label of ["old", "new"]) {
|
|
220
|
+
const item = result[label];
|
|
221
|
+
lines.push(
|
|
222
|
+
`${label.padEnd(3)} ${item.version}: exit ${item.exitCode}, findings ${item.findings.join(", ") || "none"}, coverage ${item.coverageComplete ? "complete" : "incomplete"}, source ${item.sourceStatus}`,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
mapLines(lines, "Scripts", result.scripts);
|
|
226
|
+
for (const field of DEPENDENCY_FIELDS)
|
|
227
|
+
mapLines(lines, field, result.dependencies[field]);
|
|
228
|
+
const files = result.files;
|
|
229
|
+
lines.push(
|
|
230
|
+
`Files: ${files.added.length} added, ${files.removed.length} removed, ${files.modified.length} modified, ${files.unchanged} unchanged`,
|
|
231
|
+
);
|
|
232
|
+
listed(lines, files.added, (file) => ` + ${file.path} (${file.size} B)`);
|
|
233
|
+
listed(lines, files.removed, (file) => ` - ${file.path} (${file.size} B)`);
|
|
234
|
+
listed(
|
|
235
|
+
lines,
|
|
236
|
+
files.modified,
|
|
237
|
+
(file) => ` ~ ${file.path} (${file.previousSize} B -> ${file.size} B)`,
|
|
238
|
+
);
|
|
239
|
+
lines.push("Caveats:");
|
|
240
|
+
for (const caveat of result.caveats) lines.push(` ! ${caveat}`);
|
|
241
|
+
return lines.map((line) => escapeUnsafe(line)).join("\n") + "\n";
|
|
242
|
+
}
|
|
243
|
+
export async function runCompare(argv, runtime = {}) {
|
|
244
|
+
const { specs, json } = parseCompareArgs(argv);
|
|
245
|
+
const check = runtime.checkHosted ?? checkHosted;
|
|
246
|
+
const options = {
|
|
247
|
+
serverUrl: (runtime.env ?? process.env).UNMASK_SERVER_URL,
|
|
248
|
+
fetchImpl: runtime.fetchImpl,
|
|
249
|
+
signal: runtime.signal,
|
|
250
|
+
limits: runtime.hostedLimits,
|
|
251
|
+
};
|
|
252
|
+
const older = await check(specs[0], options);
|
|
253
|
+
const newer = await check(specs[1], options);
|
|
254
|
+
const result = compareReports(older, newer);
|
|
255
|
+
(runtime.stdout ?? process.stdout).write(
|
|
256
|
+
json
|
|
257
|
+
? escapeUnsafe(JSON.stringify(result, null, 2), JSON_UNSAFE) + "\n"
|
|
258
|
+
: renderComparison(result),
|
|
259
|
+
);
|
|
260
|
+
return result.exitCode;
|
|
261
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GUARD_SEMANTICS,
|
|
3
|
+
GuardConfigError,
|
|
4
|
+
PROFILES,
|
|
5
|
+
effectiveGuardSettings,
|
|
6
|
+
readGuardConfig,
|
|
7
|
+
validateSetting,
|
|
8
|
+
writeGuardConfig,
|
|
9
|
+
} from "../core/guard-config.mjs";
|
|
10
|
+
|
|
11
|
+
const USAGE =
|
|
12
|
+
"Use config show|profiles [--json] [--project DIR] or config set profile strict|balanced|audit [--project DIR]";
|
|
13
|
+
const STRENGTH = { strict: 2, balanced: 1, audit: 0 };
|
|
14
|
+
|
|
15
|
+
export function parseCommandOptions(
|
|
16
|
+
args,
|
|
17
|
+
flags = ["--json"],
|
|
18
|
+
values = ["--project"],
|
|
19
|
+
) {
|
|
20
|
+
const positional = [];
|
|
21
|
+
const options = { json: false };
|
|
22
|
+
for (let index = 0; index < args.length; index++) {
|
|
23
|
+
const arg = args[index];
|
|
24
|
+
const name = arg.slice(2);
|
|
25
|
+
if (values.includes(arg)) {
|
|
26
|
+
const value = args[++index];
|
|
27
|
+
if (
|
|
28
|
+
options[name] !== undefined ||
|
|
29
|
+
value === undefined ||
|
|
30
|
+
!value ||
|
|
31
|
+
value.startsWith("--")
|
|
32
|
+
)
|
|
33
|
+
throw new GuardConfigError(
|
|
34
|
+
"missing-value",
|
|
35
|
+
`${arg} requires exactly one value`,
|
|
36
|
+
);
|
|
37
|
+
options[name] = value;
|
|
38
|
+
} else if (flags.includes(arg)) {
|
|
39
|
+
if (options[name] === true)
|
|
40
|
+
throw new GuardConfigError(
|
|
41
|
+
"duplicate-option",
|
|
42
|
+
"Each option may be supplied only once",
|
|
43
|
+
);
|
|
44
|
+
options[name] = true;
|
|
45
|
+
} else if (arg.startsWith("--"))
|
|
46
|
+
throw new GuardConfigError("unknown-option", "Unsupported option");
|
|
47
|
+
else positional.push(arg);
|
|
48
|
+
}
|
|
49
|
+
return { positional, options };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function describe(loaded) {
|
|
53
|
+
const { settings, warnings } = effectiveGuardSettings(loaded.config);
|
|
54
|
+
return {
|
|
55
|
+
schemaVersion: loaded.config.schemaVersion,
|
|
56
|
+
path: loaded.file,
|
|
57
|
+
source: loaded.source,
|
|
58
|
+
settings,
|
|
59
|
+
warnings,
|
|
60
|
+
scope:
|
|
61
|
+
"Applies to explicit UNMASK guard decisions only; check report policy and exit codes are unchanged.",
|
|
62
|
+
semantics: GUARD_SEMANTICS,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function showText(view) {
|
|
67
|
+
const lines = [
|
|
68
|
+
`Guard configuration: ${view.path} (${view.source === "file" ? "file" : "defaults, no file"})`,
|
|
69
|
+
];
|
|
70
|
+
for (const [key, value] of Object.entries(view.settings))
|
|
71
|
+
lines.push(` ${key}: ${value}`);
|
|
72
|
+
lines.push(view.scope, ...view.semantics);
|
|
73
|
+
return lines.join("\n") + "\n";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function runConfig(args, runtime = {}) {
|
|
77
|
+
const stdout = runtime.stdout ?? process.stdout;
|
|
78
|
+
const stderr = runtime.stderr ?? process.stderr;
|
|
79
|
+
const now = runtime.now ? runtime.now() : new Date();
|
|
80
|
+
try {
|
|
81
|
+
const { positional, options } = parseCommandOptions(args);
|
|
82
|
+
const [action, key, value, ...extra] = positional;
|
|
83
|
+
const project = options.project ?? runtime.cwd ?? process.cwd();
|
|
84
|
+
if (action === "profiles" && !key) {
|
|
85
|
+
stdout.write(
|
|
86
|
+
options.json
|
|
87
|
+
? JSON.stringify(
|
|
88
|
+
{ profiles: PROFILES, semantics: GUARD_SEMANTICS },
|
|
89
|
+
null,
|
|
90
|
+
2,
|
|
91
|
+
) + "\n"
|
|
92
|
+
: Object.entries(PROFILES)
|
|
93
|
+
.map(
|
|
94
|
+
([name, settings]) =>
|
|
95
|
+
`${name}: ${Object.entries(settings)
|
|
96
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
97
|
+
.join(" ")}`,
|
|
98
|
+
)
|
|
99
|
+
.concat(GUARD_SEMANTICS)
|
|
100
|
+
.join("\n") + "\n",
|
|
101
|
+
);
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
if (action === "show" && !key) {
|
|
105
|
+
const view = describe(await readGuardConfig(project, { now }));
|
|
106
|
+
for (const warning of view.warnings)
|
|
107
|
+
stderr.write(`warning: ${warning}\n`);
|
|
108
|
+
stdout.write(
|
|
109
|
+
options.json ? JSON.stringify(view, null, 2) + "\n" : showText(view),
|
|
110
|
+
);
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
if (options.json) throw new GuardConfigError("usage", USAGE);
|
|
114
|
+
if (action === "set" && key && value !== undefined && !extra.length) {
|
|
115
|
+
validateSetting(key, value);
|
|
116
|
+
const loaded = await readGuardConfig(project, { now });
|
|
117
|
+
const before = loaded.config.profile;
|
|
118
|
+
const saved = await writeGuardConfig(
|
|
119
|
+
project,
|
|
120
|
+
{ ...loaded.config, profile: value },
|
|
121
|
+
{ now },
|
|
122
|
+
);
|
|
123
|
+
stdout.write(`Updated profile: ${before} -> ${value} (${saved.file})\n`);
|
|
124
|
+
if (STRENGTH[value] < STRENGTH[before])
|
|
125
|
+
stderr.write(
|
|
126
|
+
`warning: profile ${value} is less restrictive than ${before}\n`,
|
|
127
|
+
);
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
throw new GuardConfigError("usage", USAGE);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (error instanceof GuardConfigError) {
|
|
133
|
+
stderr.write(`${error.code}: ${error.message}\n`);
|
|
134
|
+
return 2;
|
|
135
|
+
}
|
|
136
|
+
stderr.write(
|
|
137
|
+
"unreadable-config: Guard configuration error; no change is claimed.\n",
|
|
138
|
+
);
|
|
139
|
+
return 1;
|
|
140
|
+
}
|
|
141
|
+
}
|