@jurislm/coolify-plugin 4.1.0 → 4.2.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/.codex-plugin/plugin.json +1 -1
- package/dist/client.js +2 -129
- package/package.json +1 -1
- package/plugin.json +1 -1
package/dist/client.js
CHANGED
|
@@ -1,25 +1,13 @@
|
|
|
1
|
-
import { isAlias, isScalar, parseDocument, stringify, visit } from "yaml";
|
|
2
1
|
import { CoolifyApiError, redactErrorText } from "./errors.js";
|
|
3
2
|
const sensitiveKey = /(^value$|real_?value|private_?key|api_?key|user_?key|webhook_?url|token|secret|password|authorization|cookie)/iu;
|
|
4
3
|
const envValueKey = /^(value|real_value)$/u;
|
|
5
4
|
const envPath = /\/envs(\/|$)/u;
|
|
6
|
-
const
|
|
7
|
-
const composeSecretName = /(password|passwd|secret|token|private|credential|api_?key|access_?key|root_?user|authorization|cookie)/iu;
|
|
8
|
-
const composeSecretSegment = /(^|[_.-])(key|pass|pwd|auth)($|[_.-])/iu;
|
|
5
|
+
const renderedComposeKey = "docker_compose";
|
|
9
6
|
const interpolation = /^\$\{?[A-Za-z_][A-Za-z0-9_]*\}?$/u;
|
|
10
7
|
const urlCredential = /([a-z][a-z0-9+.-]{0,31}:\/\/[^\s:/@]{0,256}):([^\s/?#]{1,256})@/giu;
|
|
11
8
|
const queryParameter = /([?&])([^=&#?\s"']{1,64})=([^&#\s"']+)/gu;
|
|
12
9
|
const querySecretSegments = new Set(["password", "passwd", "pass", "pwd", "secret", "token", "auth", "sig", "signature", "credential", "credentials", "apikey", "key"]);
|
|
13
10
|
const querySecretName = /(password|passwd|secret|token|api_?key|access_?key)/iu;
|
|
14
|
-
const flagValue = /^(true|false|yes|no|on|off|enabled|disabled|none)$/iu;
|
|
15
|
-
const assignment = /^([\w.-]+)=([\s\S]*)$/u;
|
|
16
|
-
const base64Text = /^[A-Za-z0-9+/]+={0,2}$/u;
|
|
17
|
-
function isComposeSecret(name, value) {
|
|
18
|
-
if (!composeSecretName.test(name) && !composeSecretSegment.test(name))
|
|
19
|
-
return false;
|
|
20
|
-
const text = typeof value === "string" ? value : typeof value === "number" ? String(value) : "";
|
|
21
|
-
return text.trim() !== "" && !interpolation.test(text) && !flagValue.test(text.trim());
|
|
22
|
-
}
|
|
23
11
|
function redactUrlCredentials(text) {
|
|
24
12
|
return text
|
|
25
13
|
.replace(urlCredential, (match, prefix, password) => interpolation.test(password) ? match : `${prefix}:[REDACTED]@`)
|
|
@@ -28,117 +16,6 @@ function redactUrlCredentials(text) {
|
|
|
28
16
|
function isQuerySecret(name) {
|
|
29
17
|
return querySecretName.test(name) || name.toLowerCase().split(/[_.-]/u).some((segment) => querySecretSegments.has(segment));
|
|
30
18
|
}
|
|
31
|
-
const commandKey = /^(command|entrypoint|test)$/u;
|
|
32
|
-
const secretFlagName = /(requirepass|masterauth)/iu;
|
|
33
|
-
const portValue = /^[\d.:[\]]+(\/(tcp|udp|sctp))?$/iu;
|
|
34
|
-
const envOption = /(^|\s)(-e|--env)(=|\s+)(?:"([A-Za-z_][\w.]{0,127})=([^"\n]{0,4096})"|'([A-Za-z_][\w.]{0,127})=([^'\n]{0,4096})'|([A-Za-z_][\w.]{0,127})=("[^"\n]{0,4096}"|'[^'\n]{0,4096}'|[^\s"']*))/gu;
|
|
35
|
-
const flagEquals = /(^|\s)(--?[A-Za-z][\w-]{0,63})=("[^"\n]{0,4096}"|'[^'\n]{0,4096}'|[^\s"']+)/gu;
|
|
36
|
-
const flagSpace = /(^|\s)(--?[A-Za-z][\w-]{0,63})(\s+)("[^"\n]{0,4096}"|'[^'\n]{0,4096}'|[^\s"'-][^\s"']*)/gu;
|
|
37
|
-
function unquote(value) {
|
|
38
|
-
return value.replace(/^(["'])([\s\S]*)\1$/u, "$2");
|
|
39
|
-
}
|
|
40
|
-
function quoteOf(value) {
|
|
41
|
-
return value.startsWith("\"") || value.startsWith("'") ? value[0] : "";
|
|
42
|
-
}
|
|
43
|
-
function isSecretFlag(flag) {
|
|
44
|
-
if (flag === "-p")
|
|
45
|
-
return true;
|
|
46
|
-
if (!flag.startsWith("--"))
|
|
47
|
-
return false;
|
|
48
|
-
const name = flag.slice(2).replace(/-/gu, "_");
|
|
49
|
-
return composeSecretName.test(name) || composeSecretSegment.test(name) || secretFlagName.test(name);
|
|
50
|
-
}
|
|
51
|
-
function isSecretFlagValue(flag, value) {
|
|
52
|
-
const bare = unquote(value);
|
|
53
|
-
if (bare === "" || bare === "[REDACTED]" || interpolation.test(bare) || flagValue.test(bare))
|
|
54
|
-
return false;
|
|
55
|
-
return flag !== "-p" || !portValue.test(bare);
|
|
56
|
-
}
|
|
57
|
-
function redactCommandLine(text) {
|
|
58
|
-
return text
|
|
59
|
-
.replace(envOption, (match, lead, flag, separator, doubleKey, _doubleValue, singleKey, _singleValue, key, value) => {
|
|
60
|
-
if (doubleKey !== undefined)
|
|
61
|
-
return isComposeSecret(doubleKey, _doubleValue) ? `${lead}${flag}${separator}"${doubleKey}=[REDACTED]"` : match;
|
|
62
|
-
if (singleKey !== undefined)
|
|
63
|
-
return isComposeSecret(singleKey, _singleValue) ? `${lead}${flag}${separator}'${singleKey}=[REDACTED]'` : match;
|
|
64
|
-
if (key === undefined || value === undefined)
|
|
65
|
-
return match;
|
|
66
|
-
const quote = quoteOf(value);
|
|
67
|
-
return isComposeSecret(key, unquote(value)) ? `${lead}${flag}${separator}${key}=${quote}[REDACTED]${quote}` : match;
|
|
68
|
-
})
|
|
69
|
-
.replace(flagEquals, (match, lead, flag, value) => isSecretFlag(flag) && isSecretFlagValue(flag, value) ? `${lead}${flag}=${quoteOf(value)}[REDACTED]${quoteOf(value)}` : match)
|
|
70
|
-
.replace(flagSpace, (match, lead, flag, space, value) => isSecretFlag(flag) && isSecretFlagValue(flag, value) ? `${lead}${flag}${space}${quoteOf(value)}[REDACTED]${quoteOf(value)}` : match);
|
|
71
|
-
}
|
|
72
|
-
function redactCommand(value) {
|
|
73
|
-
if (typeof value === "string")
|
|
74
|
-
return redactCommandLine(value);
|
|
75
|
-
if (!Array.isArray(value))
|
|
76
|
-
return value;
|
|
77
|
-
return value.map((token, index) => {
|
|
78
|
-
if (typeof token !== "string")
|
|
79
|
-
return token;
|
|
80
|
-
const flag = value[index - 1];
|
|
81
|
-
return typeof flag === "string" && !flag.includes("=") && isSecretFlag(flag) && isSecretFlagValue(flag, token) ? "[REDACTED]" : redactCommandLine(token);
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
const composeAliasLimit = 100;
|
|
85
|
-
function redactComposeValue(value) {
|
|
86
|
-
if (Array.isArray(value))
|
|
87
|
-
return value.map(redactComposeValue);
|
|
88
|
-
if (value && typeof value === "object") {
|
|
89
|
-
return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, isComposeSecret(key, child) ? "[REDACTED]" : redactComposeValue(commandKey.test(key) ? redactCommand(child) : child)]));
|
|
90
|
-
}
|
|
91
|
-
if (typeof value !== "string")
|
|
92
|
-
return value;
|
|
93
|
-
const match = assignment.exec(value);
|
|
94
|
-
return match && isComposeSecret(match[1], match[2]) ? `${match[1]}=[REDACTED]` : redactUrlCredentials(value);
|
|
95
|
-
}
|
|
96
|
-
function redactComposeYaml(text) {
|
|
97
|
-
if (text.trim() === "" || text === "[REDACTED]")
|
|
98
|
-
return text;
|
|
99
|
-
const document = parseDocument(text, { schema: "failsafe", merge: true });
|
|
100
|
-
if (document.errors.length > 0)
|
|
101
|
-
return "[REDACTED]";
|
|
102
|
-
let parsed;
|
|
103
|
-
let rendered;
|
|
104
|
-
try {
|
|
105
|
-
const secrets = [];
|
|
106
|
-
visit(document, {
|
|
107
|
-
Pair(_key, pair) {
|
|
108
|
-
const key = isAlias(pair.key) ? pair.key.resolve(document) : pair.key;
|
|
109
|
-
const value = isAlias(pair.value) ? pair.value.resolve(document) : pair.value;
|
|
110
|
-
if (isScalar(key) && isScalar(value) && isComposeSecret(String(key.value), value.value))
|
|
111
|
-
secrets.push(value);
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
for (const node of secrets)
|
|
115
|
-
node.value = "[REDACTED]";
|
|
116
|
-
parsed = document.toJS({ maxAliasCount: composeAliasLimit });
|
|
117
|
-
rendered = stringify(redactComposeValue(parsed), { lineWidth: 0 });
|
|
118
|
-
}
|
|
119
|
-
catch {
|
|
120
|
-
return "[REDACTED]";
|
|
121
|
-
}
|
|
122
|
-
return text.endsWith("\n") ? rendered : rendered.replace(/\n$/u, "");
|
|
123
|
-
}
|
|
124
|
-
function decodeBase64Text(text) {
|
|
125
|
-
if (text.length % 4 !== 0 || !base64Text.test(text))
|
|
126
|
-
return undefined;
|
|
127
|
-
const bytes = Buffer.from(text, "base64");
|
|
128
|
-
if (bytes.toString("base64") !== text)
|
|
129
|
-
return undefined;
|
|
130
|
-
try {
|
|
131
|
-
const decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
132
|
-
return /[\n:]/u.test(decoded) ? decoded : undefined;
|
|
133
|
-
}
|
|
134
|
-
catch {
|
|
135
|
-
return undefined;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
function redactComposeText(text) {
|
|
139
|
-
const decoded = decodeBase64Text(text);
|
|
140
|
-
return decoded === undefined ? redactComposeYaml(text) : Buffer.from(redactComposeYaml(decoded), "utf8").toString("base64");
|
|
141
|
-
}
|
|
142
19
|
function withoutTokens(text, tokens) {
|
|
143
20
|
return tokens.reduce((redacted, token) => token ? redacted.replaceAll(token, "[REDACTED]") : redacted, text);
|
|
144
21
|
}
|
|
@@ -153,7 +30,7 @@ export function redactSensitive(value, tokens = [], envResponse = false) {
|
|
|
153
30
|
if (!value || typeof value !== "object")
|
|
154
31
|
return value;
|
|
155
32
|
const binary = value.encoding === "base64" && typeof value.contentType === "string" && typeof value.value === "string";
|
|
156
|
-
return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, redactField(key, child, tokens, envResponse, binary)]));
|
|
33
|
+
return Object.fromEntries(Object.entries(value).filter(([key]) => key !== renderedComposeKey).map(([key, child]) => [key, redactField(key, child, tokens, envResponse, binary)]));
|
|
157
34
|
}
|
|
158
35
|
function redactField(key, child, tokens, envResponse, binary) {
|
|
159
36
|
if (binary && key === "value")
|
|
@@ -162,10 +39,6 @@ function redactField(key, child, tokens, envResponse, binary) {
|
|
|
162
39
|
return typeof child === "string" ? withoutTokens(child, tokens) : child;
|
|
163
40
|
if (sensitiveKey.test(key))
|
|
164
41
|
return "[REDACTED]";
|
|
165
|
-
if (composeKey.test(key) && typeof child === "string")
|
|
166
|
-
return withoutTokens(redactComposeText(child), tokens);
|
|
167
|
-
if (key === "custom_docker_run_options" && typeof child === "string")
|
|
168
|
-
return withoutTokens(redactUrlCredentials(redactCommandLine(child)), tokens);
|
|
169
42
|
return redactSensitive(child, tokens, envResponse);
|
|
170
43
|
}
|
|
171
44
|
function pathValue(value, name) {
|
package/package.json
CHANGED
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.
|
|
4
|
+
"version": "4.2.0",
|
|
5
5
|
"description": "Portable Coolify MCP plugin with local stdio tools generated from the official OpenAPI contract.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "JurisLM",
|