@jurislm/coolify-plugin 4.0.27 → 4.0.28
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/.codex-plugin/plugin.json +1 -1
- package/dist/client.js +87 -1
- package/package.json +3 -3
- package/plugin.json +1 -1
package/dist/client.js
CHANGED
|
@@ -1,6 +1,92 @@
|
|
|
1
|
+
import { isAlias, isScalar, parseDocument, stringify, visit } from "yaml";
|
|
1
2
|
import { CoolifyApiError, redactErrorText } from "./errors.js";
|
|
2
3
|
const sensitiveKey = /(^value$|real_?value|private_?key|api_?key|user_?key|webhook_?url|token|secret|password|authorization|cookie)/iu;
|
|
4
|
+
const composeKey = /^(docker_compose(_pr)?(_raw)?|configuration)$/u;
|
|
5
|
+
const composeSecretName = /(password|passwd|secret|token|private|credential|api_?key|access_?key|authorization|cookie)/iu;
|
|
6
|
+
const composeSecretSegment = /(^|[_.-])(key|pass|pwd|auth)($|[_.-])/iu;
|
|
7
|
+
const interpolation = /^\$\{?[A-Za-z_][A-Za-z0-9_]*\}?$/u;
|
|
8
|
+
const urlCredential = /([a-z][a-z0-9+.-]{0,31}:\/\/[^\s:/@]{0,256}):([^\s/?#]{1,256})@/giu;
|
|
9
|
+
const queryParameter = /([?&])([^=&#?\s"']{1,64})=([^&#\s"']+)/gu;
|
|
10
|
+
const querySecretSegments = new Set(["password", "passwd", "pass", "pwd", "secret", "token", "auth", "sig", "signature", "credential", "credentials", "apikey", "key"]);
|
|
11
|
+
const querySecretName = /(password|passwd|secret|token|api_?key|access_?key)/iu;
|
|
12
|
+
const flagValue = /^(true|false|yes|no|on|off|enabled|disabled|none)$/iu;
|
|
13
|
+
const assignment = /^([\w.-]+)=([\s\S]*)$/u;
|
|
14
|
+
const base64Text = /^[A-Za-z0-9+/]+={0,2}$/u;
|
|
15
|
+
function isComposeSecret(name, value) {
|
|
16
|
+
if (!composeSecretName.test(name) && !composeSecretSegment.test(name))
|
|
17
|
+
return false;
|
|
18
|
+
const text = typeof value === "string" ? value : typeof value === "number" ? String(value) : "";
|
|
19
|
+
return text.trim() !== "" && !interpolation.test(text) && !flagValue.test(text.trim());
|
|
20
|
+
}
|
|
21
|
+
function redactUrlCredentials(text) {
|
|
22
|
+
return text
|
|
23
|
+
.replace(urlCredential, (match, prefix, password) => interpolation.test(password) ? match : `${prefix}:[REDACTED]@`)
|
|
24
|
+
.replace(queryParameter, (match, separator, name, value) => value !== "[REDACTED]" && isQuerySecret(name) ? `${separator}${name}=[REDACTED]` : match);
|
|
25
|
+
}
|
|
26
|
+
function isQuerySecret(name) {
|
|
27
|
+
return querySecretName.test(name) || name.toLowerCase().split(/[_.-]/u).some((segment) => querySecretSegments.has(segment));
|
|
28
|
+
}
|
|
29
|
+
const composeAliasLimit = 100;
|
|
30
|
+
function redactComposeValue(value) {
|
|
31
|
+
if (Array.isArray(value))
|
|
32
|
+
return value.map(redactComposeValue);
|
|
33
|
+
if (value && typeof value === "object") {
|
|
34
|
+
return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, isComposeSecret(key, child) ? "[REDACTED]" : redactComposeValue(child)]));
|
|
35
|
+
}
|
|
36
|
+
if (typeof value !== "string")
|
|
37
|
+
return value;
|
|
38
|
+
const match = assignment.exec(value);
|
|
39
|
+
return match && isComposeSecret(match[1], match[2]) ? `${match[1]}=[REDACTED]` : redactUrlCredentials(value);
|
|
40
|
+
}
|
|
41
|
+
function redactComposeYaml(text) {
|
|
42
|
+
if (text.trim() === "" || text === "[REDACTED]")
|
|
43
|
+
return text;
|
|
44
|
+
const document = parseDocument(text, { schema: "failsafe", merge: true });
|
|
45
|
+
if (document.errors.length > 0)
|
|
46
|
+
return "[REDACTED]";
|
|
47
|
+
let parsed;
|
|
48
|
+
let rendered;
|
|
49
|
+
try {
|
|
50
|
+
const secrets = [];
|
|
51
|
+
visit(document, {
|
|
52
|
+
Pair(_key, pair) {
|
|
53
|
+
const key = isAlias(pair.key) ? pair.key.resolve(document) : pair.key;
|
|
54
|
+
const value = isAlias(pair.value) ? pair.value.resolve(document) : pair.value;
|
|
55
|
+
if (isScalar(key) && isScalar(value) && isComposeSecret(String(key.value), value.value))
|
|
56
|
+
secrets.push(value);
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
for (const node of secrets)
|
|
60
|
+
node.value = "[REDACTED]";
|
|
61
|
+
parsed = document.toJS({ maxAliasCount: composeAliasLimit });
|
|
62
|
+
rendered = stringify(redactComposeValue(parsed), { lineWidth: 0 });
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return "[REDACTED]";
|
|
66
|
+
}
|
|
67
|
+
return text.endsWith("\n") ? rendered : rendered.replace(/\n$/u, "");
|
|
68
|
+
}
|
|
69
|
+
function decodeBase64Text(text) {
|
|
70
|
+
if (text.length % 4 !== 0 || !base64Text.test(text))
|
|
71
|
+
return undefined;
|
|
72
|
+
const bytes = Buffer.from(text, "base64");
|
|
73
|
+
if (bytes.toString("base64") !== text)
|
|
74
|
+
return undefined;
|
|
75
|
+
try {
|
|
76
|
+
const decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
77
|
+
return /[\n:]/u.test(decoded) ? decoded : undefined;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function redactComposeText(text) {
|
|
84
|
+
const decoded = decodeBase64Text(text);
|
|
85
|
+
return decoded === undefined ? redactComposeYaml(text) : Buffer.from(redactComposeYaml(decoded), "utf8").toString("base64");
|
|
86
|
+
}
|
|
3
87
|
export function redactSensitive(value) {
|
|
88
|
+
if (typeof value === "string")
|
|
89
|
+
return redactUrlCredentials(value);
|
|
4
90
|
if (Array.isArray(value))
|
|
5
91
|
return value.map(redactSensitive);
|
|
6
92
|
if (!value || typeof value !== "object")
|
|
@@ -8,7 +94,7 @@ export function redactSensitive(value) {
|
|
|
8
94
|
const binary = value.encoding === "base64" && typeof value.contentType === "string" && typeof value.value === "string";
|
|
9
95
|
return Object.fromEntries(Object.entries(value).map(([key, child]) => [
|
|
10
96
|
key,
|
|
11
|
-
sensitiveKey.test(key) && !(binary && key === "value") ? "[REDACTED]" : redactSensitive(child),
|
|
97
|
+
sensitiveKey.test(key) && !(binary && key === "value") ? "[REDACTED]" : composeKey.test(key) && typeof child === "string" ? redactComposeText(child) : redactSensitive(child),
|
|
12
98
|
]));
|
|
13
99
|
}
|
|
14
100
|
function pathValue(value, name) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jurislm/coolify-plugin",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.28",
|
|
4
4
|
"description": "Portable local stdio MCP plugin for Coolify",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
50
|
+
"yaml": "2.9.1",
|
|
50
51
|
"zod": "4.6.5"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
@@ -57,8 +58,7 @@
|
|
|
57
58
|
"json-schema-to-zod": "2.8.1",
|
|
58
59
|
"openapi-typescript": "7.13.0",
|
|
59
60
|
"typescript": "5.9.3",
|
|
60
|
-
"typescript-eslint": "8.70.1"
|
|
61
|
-
"yaml": "2.9.1"
|
|
61
|
+
"typescript-eslint": "8.70.1"
|
|
62
62
|
},
|
|
63
63
|
"packageManager": "bun@1.3.14"
|
|
64
64
|
}
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "coolify-plugin",
|
|
4
|
-
"version": "4.0.
|
|
4
|
+
"version": "4.0.28",
|
|
5
5
|
"description": "Portable Coolify MCP plugin with local stdio tools generated from the official OpenAPI contract.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "JurisLM",
|