koppler 0.0.0-stage → 0.0.2

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.
Files changed (225) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +101 -2
  3. package/bin/run-logged +16 -0
  4. package/bin/secret-list +7 -0
  5. package/bin/secret-psql +41 -0
  6. package/bin/secret-set +17 -0
  7. package/bin/secret-store +82 -0
  8. package/bin/secret-test +10 -0
  9. package/bin/with-secrets +25 -0
  10. package/dist/.built +1 -0
  11. package/dist/cli/agent.js +144 -0
  12. package/dist/cli/agent.js.map +1 -0
  13. package/dist/cli/doctor.js +173 -0
  14. package/dist/cli/doctor.js.map +1 -0
  15. package/dist/cli/mcp.js +204 -0
  16. package/dist/cli/mcp.js.map +1 -0
  17. package/dist/cli/output.js +37 -0
  18. package/dist/cli/output.js.map +1 -0
  19. package/dist/cli/records.js +121 -0
  20. package/dist/cli/records.js.map +1 -0
  21. package/dist/cli/running.js +270 -0
  22. package/dist/cli/running.js.map +1 -0
  23. package/dist/cli/shell.js +43 -0
  24. package/dist/cli/shell.js.map +1 -0
  25. package/dist/cli/wait.js +59 -0
  26. package/dist/cli/wait.js.map +1 -0
  27. package/dist/core/addresses.js +55 -0
  28. package/dist/core/addresses.js.map +1 -0
  29. package/dist/core/arrange.js +329 -0
  30. package/dist/core/arrange.js.map +1 -0
  31. package/dist/core/cables.js +158 -0
  32. package/dist/core/cables.js.map +1 -0
  33. package/dist/core/expressions.js +72 -0
  34. package/dist/core/expressions.js.map +1 -0
  35. package/dist/core/frameworks.js +13 -0
  36. package/dist/core/frameworks.js.map +1 -0
  37. package/dist/core/kinds.js +244 -0
  38. package/dist/core/kinds.js.map +1 -0
  39. package/dist/core/launch.js +181 -0
  40. package/dist/core/launch.js.map +1 -0
  41. package/dist/core/logfmt.js +144 -0
  42. package/dist/core/logfmt.js.map +1 -0
  43. package/dist/core/mock.js +27 -0
  44. package/dist/core/mock.js.map +1 -0
  45. package/dist/core/plan.js +147 -0
  46. package/dist/core/plan.js.map +1 -0
  47. package/dist/core/ports.js +155 -0
  48. package/dist/core/ports.js.map +1 -0
  49. package/dist/core/presets.js +186 -0
  50. package/dist/core/presets.js.map +1 -0
  51. package/dist/core/secret-types.js +3 -0
  52. package/dist/core/secret-types.js.map +1 -0
  53. package/dist/core/sign-in.js +30 -0
  54. package/dist/core/sign-in.js.map +1 -0
  55. package/dist/core/stack-path.js +64 -0
  56. package/dist/core/stack-path.js.map +1 -0
  57. package/dist/core/stack.js +509 -0
  58. package/dist/core/stack.js.map +1 -0
  59. package/dist/core/terraform.js +393 -0
  60. package/dist/core/terraform.js.map +1 -0
  61. package/dist/data.js +37 -0
  62. package/dist/data.js.map +1 -0
  63. package/dist/engine/actions.js +99 -0
  64. package/dist/engine/actions.js.map +1 -0
  65. package/dist/engine/app-commands.js +106 -0
  66. package/dist/engine/app-commands.js.map +1 -0
  67. package/dist/engine/app-start.js +167 -0
  68. package/dist/engine/app-start.js.map +1 -0
  69. package/dist/engine/app-state.js +43 -0
  70. package/dist/engine/app-state.js.map +1 -0
  71. package/dist/engine/boards.js +219 -0
  72. package/dist/engine/boards.js.map +1 -0
  73. package/dist/engine/branch.js +194 -0
  74. package/dist/engine/branch.js.map +1 -0
  75. package/dist/engine/browser-console.js +45 -0
  76. package/dist/engine/browser-console.js.map +1 -0
  77. package/dist/engine/calls.js +97 -0
  78. package/dist/engine/calls.js.map +1 -0
  79. package/dist/engine/checks.js +270 -0
  80. package/dist/engine/checks.js.map +1 -0
  81. package/dist/engine/clones.js +67 -0
  82. package/dist/engine/clones.js.map +1 -0
  83. package/dist/engine/config.js +26 -0
  84. package/dist/engine/config.js.map +1 -0
  85. package/dist/engine/deployed.js +488 -0
  86. package/dist/engine/deployed.js.map +1 -0
  87. package/dist/engine/devices.js +81 -0
  88. package/dist/engine/devices.js.map +1 -0
  89. package/dist/engine/discover.js +236 -0
  90. package/dist/engine/discover.js.map +1 -0
  91. package/dist/engine/down.js +253 -0
  92. package/dist/engine/down.js.map +1 -0
  93. package/dist/engine/history.js +215 -0
  94. package/dist/engine/history.js.map +1 -0
  95. package/dist/engine/investigate.js +278 -0
  96. package/dist/engine/investigate.js.map +1 -0
  97. package/dist/engine/libraries.js +129 -0
  98. package/dist/engine/libraries.js.map +1 -0
  99. package/dist/engine/linked.js +146 -0
  100. package/dist/engine/linked.js.map +1 -0
  101. package/dist/engine/listen.js +223 -0
  102. package/dist/engine/listen.js.map +1 -0
  103. package/dist/engine/locks.js +38 -0
  104. package/dist/engine/locks.js.map +1 -0
  105. package/dist/engine/messages.js +177 -0
  106. package/dist/engine/messages.js.map +1 -0
  107. package/dist/engine/needs.js +363 -0
  108. package/dist/engine/needs.js.map +1 -0
  109. package/dist/engine/processes.js +350 -0
  110. package/dist/engine/processes.js.map +1 -0
  111. package/dist/engine/query.js +241 -0
  112. package/dist/engine/query.js.map +1 -0
  113. package/dist/engine/queues.js +100 -0
  114. package/dist/engine/queues.js.map +1 -0
  115. package/dist/engine/recipes.js +248 -0
  116. package/dist/engine/recipes.js.map +1 -0
  117. package/dist/engine/reload.js +32 -0
  118. package/dist/engine/reload.js.map +1 -0
  119. package/dist/engine/repos.js +214 -0
  120. package/dist/engine/repos.js.map +1 -0
  121. package/dist/engine/research.js +134 -0
  122. package/dist/engine/research.js.map +1 -0
  123. package/dist/engine/run-logs.js +79 -0
  124. package/dist/engine/run-logs.js.map +1 -0
  125. package/dist/engine/run.js +54 -0
  126. package/dist/engine/run.js.map +1 -0
  127. package/dist/engine/runs.js +223 -0
  128. package/dist/engine/runs.js.map +1 -0
  129. package/dist/engine/session.js +130 -0
  130. package/dist/engine/session.js.map +1 -0
  131. package/dist/engine/share.js +124 -0
  132. package/dist/engine/share.js.map +1 -0
  133. package/dist/engine/stack-file.js +206 -0
  134. package/dist/engine/stack-file.js.map +1 -0
  135. package/dist/engine/stack-share.js +86 -0
  136. package/dist/engine/stack-share.js.map +1 -0
  137. package/dist/engine/stacks.js +143 -0
  138. package/dist/engine/stacks.js.map +1 -0
  139. package/dist/engine/sync.js +214 -0
  140. package/dist/engine/sync.js.map +1 -0
  141. package/dist/engine/traces.js +65 -0
  142. package/dist/engine/traces.js.map +1 -0
  143. package/dist/engine/up.js +621 -0
  144. package/dist/engine/up.js.map +1 -0
  145. package/dist/engine/watch.js +71 -0
  146. package/dist/engine/watch.js.map +1 -0
  147. package/dist/engine/wire.js +120 -0
  148. package/dist/engine/wire.js.map +1 -0
  149. package/dist/failure.js +16 -0
  150. package/dist/failure.js.map +1 -0
  151. package/dist/github.js +95 -0
  152. package/dist/github.js.map +1 -0
  153. package/dist/ids.js +6 -0
  154. package/dist/ids.js.map +1 -0
  155. package/dist/json-files.js +36 -0
  156. package/dist/json-files.js.map +1 -0
  157. package/dist/koppler.mjs +840 -0
  158. package/dist/koppler.mjs.map +1 -0
  159. package/dist/lambda-local.mjs +250 -0
  160. package/dist/lambda-local.mjs.map +1 -0
  161. package/dist/lambda-secrets-proxy.go +31 -0
  162. package/dist/mock-server.mjs +23 -0
  163. package/dist/mock-server.mjs.map +1 -0
  164. package/dist/page/assets/JetBrainsMono-cyrillic-D2jMJ_bk.woff2 +0 -0
  165. package/dist/page/assets/JetBrainsMono-greek-D2nYnAec.woff2 +0 -0
  166. package/dist/page/assets/JetBrainsMono-latin-Db4Uuiha.woff2 +0 -0
  167. package/dist/page/assets/JetBrainsMono-latin-ext-Bnd2h4Br.woff2 +0 -0
  168. package/dist/page/assets/JetBrainsMono-vietnamese-CNlsqbWb.woff2 +0 -0
  169. package/dist/page/assets/SchibstedGrotesk-latin-DxefFeRw.woff2 +0 -0
  170. package/dist/page/assets/SchibstedGrotesk-latin-ext-CnpJE18X.woff2 +0 -0
  171. package/dist/page/assets/index-Bkj_pX35.js +42 -0
  172. package/dist/page/assets/index-Dq45jswH.css +1 -0
  173. package/dist/page/index.html +14 -0
  174. package/dist/page/theme.js +8 -0
  175. package/dist/paths.js +19 -0
  176. package/dist/paths.js.map +1 -0
  177. package/dist/safe-env.js +73 -0
  178. package/dist/safe-env.js.map +1 -0
  179. package/dist/sam-local.mjs +135 -0
  180. package/dist/sam-local.mjs.map +1 -0
  181. package/dist/scrub.js +87 -0
  182. package/dist/scrub.js.map +1 -0
  183. package/dist/secret-check.mjs +262 -0
  184. package/dist/secret-check.mjs.map +1 -0
  185. package/dist/server/browser-console.js +117 -0
  186. package/dist/server/browser-console.js.map +1 -0
  187. package/dist/server/cable-test.js +365 -0
  188. package/dist/server/cable-test.js.map +1 -0
  189. package/dist/server/choices.js +51 -0
  190. package/dist/server/choices.js.map +1 -0
  191. package/dist/server/files.js +107 -0
  192. package/dist/server/files.js.map +1 -0
  193. package/dist/server/gate.js +128 -0
  194. package/dist/server/gate.js.map +1 -0
  195. package/dist/server/logs.js +76 -0
  196. package/dist/server/logs.js.map +1 -0
  197. package/dist/server/mobile.js +113 -0
  198. package/dist/server/mobile.js.map +1 -0
  199. package/dist/server/routes.js +326 -0
  200. package/dist/server/routes.js.map +1 -0
  201. package/dist/server/runs.js +111 -0
  202. package/dist/server/runs.js.map +1 -0
  203. package/dist/server/sanitize.js +242 -0
  204. package/dist/server/sanitize.js.map +1 -0
  205. package/dist/server/secrets.js +111 -0
  206. package/dist/server/secrets.js.map +1 -0
  207. package/dist/server/status.js +321 -0
  208. package/dist/server/status.js.map +1 -0
  209. package/dist/server/token.js +38 -0
  210. package/dist/server/token.js.map +1 -0
  211. package/dist/server/waiting.js +79 -0
  212. package/dist/server/waiting.js.map +1 -0
  213. package/dist/status-watch.mjs +63 -0
  214. package/dist/status-watch.mjs.map +1 -0
  215. package/package.json +74 -4
  216. package/skills/koppler-run/SKILL.md +169 -0
  217. package/skills/koppler-run/VERSION +1 -0
  218. package/skills/koppler-run/references/action-log.md +50 -0
  219. package/skills/koppler-run/references/permissions.md +25 -0
  220. package/skills/koppler-run/references/preparing-nodes.md +62 -0
  221. package/skills/koppler-run/references/reporting-bugs.md +76 -0
  222. package/skills/koppler-run/references/research.md +40 -0
  223. package/skills/koppler-run/references/secrets.md +56 -0
  224. package/skills/koppler-run/references/stack.md +197 -0
  225. package/skills/koppler-run/references/status.md +92 -0
package/dist/scrub.js ADDED
@@ -0,0 +1,87 @@
1
+ // Text made safe to show or post. `redact` masks secrets, for anything that reaches the page or an agent. `scrub` goes
2
+ // further, for a public issue: everything that names the user, their company or their stack (apps, repos, hosts,
3
+ // queues, paths, ticket ids, accounts) becomes a stable placeholder, so a log keeps its shape.
4
+ import os from 'node:os';
5
+ const SECRET_SETTING = /(password|passwd|pwd|secret|token|api[_-]?key|access[_-]?key|account[_-]?key)(\\?["']?\s*[=:]\s*)(?:(\\?["'])[^\n]*?\3|[^;"'\s]+)/gi;
6
+ /** Masks what logs echo on errors: connection strings, tokens, keys. Terminal colours go too. */
7
+ export const redact = (text) => text
8
+ .replace(/\x1b\[[0-9;]*[A-Za-z]/g, '')
9
+ .replace(SECRET_SETTING, (_all, key, separator, quote = '') => `${key}${separator}${String(quote)}***${String(quote)}`)
10
+ .replace(/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g, '***private key***')
11
+ .replace(/\b([a-z][a-z0-9+.-]*:\/\/)[^\s/:@]+:[^\s@]+@/gi, '$1***:***@')
12
+ .replace(/\b([a-z][a-z0-9+.-]*:\/\/)[^\s/:@]{8,}@/gi, '$1***@')
13
+ .replace(/([?&;]sig=)[^&\s;"']+/gi, '$1***')
14
+ .replace(/\b(Basic\s+)[A-Za-z0-9+/=]{8,}/g, '$1***')
15
+ .replace(/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{5,}/g, '***jwt***')
16
+ .replace(/\b(Bearer\s+)[A-Za-z0-9._~+/=-]{8,}/g, '$1***')
17
+ .replace(/\b(gh[pousr]_|github_pat_|glpat-|npm_|xox[abpsr]-|sk-|[sr]k_(?:live|test)_)[A-Za-z0-9_-]{10,}/g, '***token***')
18
+ .replace(/\b(AKIA|ASIA)[A-Z0-9]{16}\b/g, '***aws-key***');
19
+ const HOST = /\b(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z][a-z0-9-]*[a-z0-9]\b/gi;
20
+ const GENERIC = new Set(['localhost', 'example.com', 'github.com', 'host.docker.internal']);
21
+ const FILE_NAME = /\.(json|js|mjs|ya?ml|toml|cs|dart|md)$/i;
22
+ const escaped = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
23
+ // Not glued to a word or a dash on either side.
24
+ const whole = (text) => new RegExp(`(?<![\\w-])${escaped(text)}(?![\\w-])`, 'gi');
25
+ const text = (value) => (typeof value === 'string' ? value : '');
26
+ /** Every name the stacks give away, each with its placeholder; a log can come from a stack used before this one. */
27
+ export function namesOf(registries) {
28
+ const names = new Map();
29
+ const add = (value, as) => {
30
+ const term = text(value).trim();
31
+ if (term.length > 2 && !names.has(term.toLowerCase()))
32
+ names.set(term.toLowerCase(), { term, as });
33
+ };
34
+ // A name as written, and as code writes it ("Orders API" and "OrdersAPI").
35
+ const named = (name, as) => {
36
+ add(name, as);
37
+ add(text(name).replace(/[\s_-]+/g, ''), as.replace(/\s+/g, ''));
38
+ };
39
+ let apps = 0;
40
+ let queues = 0;
41
+ let stacks = 0;
42
+ let boards = 0;
43
+ for (const registry of [registries ?? []].flat().filter(Boolean)) {
44
+ named(registry.name, `my-stack-${String(++stacks)}`);
45
+ for (const [id, app] of Object.entries(registry.apps ?? {})) {
46
+ const n = String(++apps);
47
+ named(app.name, `App ${n}`);
48
+ add(id, `app-${n}`);
49
+ for (const part of text(app.slug).split('/').slice(-2))
50
+ add(part, `repo-${n}`);
51
+ }
52
+ for (const preset of registry.presets ?? [])
53
+ named(preset?.name, `Board ${String(++boards)}`);
54
+ for (const [id, queue] of Object.entries(registry.queues ?? {})) {
55
+ const n = String(++queues);
56
+ named(queue.name, `Queue ${n}`);
57
+ add(id, `queue-${n}`);
58
+ add(queue.queue, `queue-${n}`);
59
+ for (const overlay of Object.values(queue.envs ?? {}))
60
+ add(overlay.queue, `queue-${n}`);
61
+ }
62
+ }
63
+ const hosts = new Set(JSON.stringify(registries ?? {}).match(HOST) ?? []);
64
+ [...hosts]
65
+ .filter(host => !GENERIC.has(host.toLowerCase()) && !FILE_NAME.test(host))
66
+ .forEach((host, at) => {
67
+ add(host, `host-${String(at + 1)}.example`);
68
+ });
69
+ return names;
70
+ }
71
+ export function scrub(given, registries, { home = os.homedir(), user = os.userInfo().username } = {}) {
72
+ let out = redact(typeof given === 'string' ? given : String(given));
73
+ if (home)
74
+ out = out.split(home).join('~');
75
+ // Longest first, so "orders-api.acme.dev" goes before "orders-api".
76
+ const byLength = [...namesOf(registries).values()].sort((a, b) => b.term.length - a.term.length);
77
+ // A long name is replaced even when glued to other text (".NETOrders API"); a short one only as a whole word.
78
+ for (const { term, as } of byLength)
79
+ out = out.replace(term.length >= 8 ? new RegExp(escaped(term), 'gi') : whole(term), as);
80
+ return out
81
+ .replace(whole(user), 'user')
82
+ .replace(/[\w.+-]+@[\w-]+(?:\.[\w-]+)+/g, 'someone@example.com')
83
+ .replace(/\b[A-Z][A-Z0-9]+-\d+\b/g, 'TICKET-1')
84
+ .replace(/\b\d{12}\b/g, '<account>')
85
+ .replace(/\b(?!127\.|0\.0\.0\.0)(?:\d{1,3}\.){3}\d{1,3}\b/g, '<ip>');
86
+ }
87
+ //# sourceMappingURL=scrub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scrub.js","sourceRoot":"","sources":["../src/scrub.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,262 @@
1
+ // Checks that a stored credential really works, by connecting to the address inside the value itself.
2
+ // Says only the outcome: never the value, and never the server's own message (it can echo names from the value).
3
+ // A value is only ever sent to the host it names; nothing in stack.json can point a test somewhere else.
4
+ import { execFile, execFileSync } from 'node:child_process';
5
+ import net from 'node:net';
6
+ import tls from 'node:tls';
7
+ import { SECRET_TYPES } from "./core/secret-types.js";
8
+ import { clientEnv, findClient } from "./safe-env.js";
9
+ export const TYPES = SECRET_TYPES;
10
+ const PORTS = { postgres: 5432, mysql: 3306, redis: 6379 };
11
+ /** "Key=Value;Key=Value" as .NET writes it; a quoted value may hold a semicolon. */
12
+ export function keyValues(value) {
13
+ const out = {};
14
+ for (const [, key = '', written = ''] of value.matchAll(/\s*([^=;]+?)\s*=\s*("(?:[^"]|"")*"|'(?:[^']|'')*'|[^;]*)\s*(?:;|$)/g)) {
15
+ const inside = written.slice(1, -1);
16
+ out[key.toLowerCase()] = /^".*"$/.test(written)
17
+ ? inside.replaceAll('""', '"')
18
+ : /^'.*'$/.test(written)
19
+ ? inside.replaceAll("''", "'")
20
+ : written;
21
+ }
22
+ return out;
23
+ }
24
+ const pick = (pairs, ...names) => names.map(name => pairs[name]).find(value => value !== undefined && value !== '');
25
+ /** A StackExchange.Redis value: "host:port,password=...,ssl=true". */
26
+ function redisOptions(written) {
27
+ const options = written
28
+ .split(',')
29
+ .filter(Boolean)
30
+ .map((option) => {
31
+ const at = option.indexOf('=');
32
+ return at < 0 ? [option.toLowerCase(), 'true'] : [option.slice(0, at).trim().toLowerCase(), option.slice(at + 1)];
33
+ });
34
+ return Object.fromEntries(options);
35
+ }
36
+ /**
37
+ * Where the value points and how to sign in, or null when it is not a connection Koppler reads. `type` comes from the
38
+ * stack, else from the value's shape.
39
+ */
40
+ export function parse(value, type) {
41
+ const text = value.trim();
42
+ const scheme = /^(postgres(?:ql)?|mysql|rediss?):\/\//i.exec(text)?.[1]?.toLowerCase();
43
+ if (scheme) {
44
+ let url;
45
+ try {
46
+ url = new URL(text);
47
+ }
48
+ catch {
49
+ return null;
50
+ }
51
+ const kind = scheme.startsWith('postgres') ? 'postgres' : scheme.startsWith('redis') ? 'redis' : 'mysql';
52
+ return {
53
+ type: type ?? kind,
54
+ host: url.hostname,
55
+ port: Number(url.port) || PORTS[kind],
56
+ user: decodeURIComponent(url.username),
57
+ password: decodeURIComponent(url.password),
58
+ database: decodeURIComponent(url.pathname.slice(1)),
59
+ tls: scheme === 'rediss',
60
+ };
61
+ }
62
+ const [, redisHost, redisPort, redisRest] = /^([\w.-]+)(?::(\d+))?((?:,[^,]*)*)$/.exec(text) ?? [];
63
+ if (redisHost !== undefined && (type === 'redis' || (!type && (redisPort || redisRest) && !text.includes(';')))) {
64
+ const options = redisOptions(redisRest ?? '');
65
+ return {
66
+ type: 'redis',
67
+ host: redisHost,
68
+ port: Number(redisPort) || PORTS.redis,
69
+ user: options['user'] ?? '',
70
+ password: options['password'] ?? '',
71
+ tls: /^true$/i.test(options['ssl'] ?? ''),
72
+ };
73
+ }
74
+ const pairs = keyValues(text);
75
+ const host = pick(pairs, 'host', 'server', 'data source', 'datasource', 'address');
76
+ const user = pick(pairs, 'username', 'user id', 'userid', 'user', 'uid');
77
+ if (!host || !user)
78
+ return null;
79
+ const port = Number(pick(pairs, 'port')) || null;
80
+ // "Server=" is written for both; without a type the port decides.
81
+ const postgresWords = pairs['host'] !== undefined || pairs['username'] !== undefined;
82
+ const byShape = postgresWords
83
+ ? 'postgres'
84
+ : port === 3306
85
+ ? 'mysql'
86
+ : port === 5432
87
+ ? 'postgres'
88
+ : pairs['uid'] !== undefined
89
+ ? 'mysql'
90
+ : null;
91
+ const kind = type ?? byShape;
92
+ if (!kind)
93
+ return { type: null };
94
+ return {
95
+ type: kind,
96
+ host: host.split(',')[0] ?? '',
97
+ port: port ?? PORTS[kind],
98
+ user,
99
+ password: pick(pairs, 'password', 'pwd') ?? '',
100
+ database: pick(pairs, 'database', 'db', 'initial catalog') ?? '',
101
+ };
102
+ }
103
+ const run = (command, args, env) => new Promise(resolve => {
104
+ execFile(command, args, { env: clientEnv(env), timeout: 20000, maxBuffer: 256 * 1024 }, (error, stdout, stderr) => {
105
+ resolve({ ok: !error, text: `${stdout}${stderr}` });
106
+ });
107
+ });
108
+ const REFUSED = /password authentication failed|access denied|no pg_hba\.conf entry|WRONGPASS|NOAUTH|invalid username-password|invalid password/i;
109
+ const UNREACHED = /connection refused|could not connect|can't connect|timeout|timed out|server closed the connection|could not translate|unknown (mysql )?server host|ECONNREFUSED|ENOTFOUND|ECONNRESET|EHOSTUNREACH/i;
110
+ const REJECTED = 'failed: it rejected the connection';
111
+ /** The server's message decides the wording; the message itself is never passed on. */
112
+ const reason = (text) => {
113
+ if (REFUSED.test(text))
114
+ return 'failed: it refused the user name or password';
115
+ if (UNREACHED.test(text))
116
+ return 'failed: nothing answers at its address (is the tunnel up?)';
117
+ if (/does not exist|unknown database/i.test(text))
118
+ return 'failed: the database or user does not exist on that server';
119
+ return REJECTED;
120
+ };
121
+ const outcomeOf = (ran) => ran.ok ? { code: 0, message: 'connected' } : { code: 1, message: reason(ran.text) };
122
+ async function postgres(to) {
123
+ const psql = findClient('psql', 'libpq');
124
+ if (!psql)
125
+ return { code: 2, message: 'cannot test: psql is not installed (brew install libpq)' };
126
+ const args = [
127
+ '-h',
128
+ to.host,
129
+ '-p',
130
+ String(to.port),
131
+ '-U',
132
+ to.user,
133
+ '-d',
134
+ to.database || 'postgres',
135
+ '-XAtc',
136
+ 'select 1',
137
+ ];
138
+ return outcomeOf(await run(psql, args, { PGPASSWORD: to.password, PGCONNECT_TIMEOUT: '10' }));
139
+ }
140
+ async function mysql(to) {
141
+ const client = findClient('mysql', 'mysql-client');
142
+ if (!client)
143
+ return { code: 2, message: 'cannot test: the mysql client is not installed (brew install mysql-client)' };
144
+ const database = to.database ? ['-D', to.database] : [];
145
+ const args = [
146
+ '--connect-timeout=10',
147
+ '-h',
148
+ to.host,
149
+ '-P',
150
+ String(to.port),
151
+ '-u',
152
+ to.user,
153
+ ...database,
154
+ '-N',
155
+ '-e',
156
+ 'select 1',
157
+ ];
158
+ return outcomeOf(await run(client, args, { MYSQL_PWD: to.password }));
159
+ }
160
+ /** Redis and Valkey speak a plain line protocol, so no client is needed: sign in if there is a password, then PING. */
161
+ function redis(to) {
162
+ return new Promise(resolve => {
163
+ const options = { host: to.host, port: to.port, timeout: 10000 };
164
+ const socket = to.tls
165
+ ? tls.connect({ ...options, ...(!net.isIP(to.host) && { servername: to.host }) })
166
+ : net.connect(options);
167
+ const done = (code, message) => {
168
+ socket.destroy();
169
+ resolve({ code, message });
170
+ };
171
+ const send = (...parts) => {
172
+ const sized = parts.map(part => `$${String(Buffer.byteLength(part))}\r\n${part}\r\n`);
173
+ socket.write(`*${String(parts.length)}\r\n${sized.join('')}`);
174
+ };
175
+ let waiting = to.password ? 'auth' : 'ping';
176
+ let seen = '';
177
+ socket.once(to.tls ? 'secureConnect' : 'connect', () => {
178
+ if (waiting === 'auth')
179
+ send('AUTH', ...(to.user ? [to.user] : []), to.password);
180
+ else
181
+ send('PING');
182
+ });
183
+ socket.on('data', (chunk) => {
184
+ seen += chunk.toString();
185
+ if (!seen.includes('\r\n'))
186
+ return;
187
+ const line = seen.split('\r\n')[0] ?? '';
188
+ seen = '';
189
+ if (line.startsWith('-'))
190
+ done(1, reason(/NOAUTH|WRONGPASS|invalid/i.test(line) ? line : 'rejected'));
191
+ else if (waiting === 'auth') {
192
+ waiting = 'ping';
193
+ send('PING');
194
+ }
195
+ else if (line === '+PONG')
196
+ done(0, 'connected');
197
+ else
198
+ done(1, REJECTED);
199
+ });
200
+ socket.on('timeout', () => {
201
+ done(1, reason('timeout'));
202
+ });
203
+ socket.on('error', (error) => {
204
+ done(1, reason(error.code ?? error.message));
205
+ });
206
+ });
207
+ }
208
+ const TESTS = { postgres, mysql, redis };
209
+ /** Tries the connection a value holds. */
210
+ export async function check(value, type) {
211
+ if (type && !TYPES.includes(type))
212
+ return {
213
+ code: 2,
214
+ message: `cannot test: Koppler has no test for type "${type}" (it tests ${TYPES.join(', ')})`,
215
+ };
216
+ const found = parse(value, type);
217
+ if (!found)
218
+ return { code: 2, message: 'cannot test: not a database or cache connection Koppler reads' };
219
+ if (found.type === null)
220
+ return {
221
+ code: 2,
222
+ message: 'cannot test: say which database it is with "type" on this secret in stack.json (postgres or mysql)',
223
+ };
224
+ if (!found.host)
225
+ return { code: 2, message: 'cannot test: the value names no host' };
226
+ const test = TESTS[found.type];
227
+ if (!test)
228
+ throw new TypeError(`no test for ${found.type}`);
229
+ return test(found);
230
+ }
231
+ /** secret-test <app or app@env> <NAME> [--type <type>] */
232
+ export async function main(args, here) {
233
+ const [store = '', name = ''] = args;
234
+ const typeAt = args.indexOf('--type');
235
+ const type = typeAt >= 0 ? args[typeAt + 1] : undefined;
236
+ const fail = (message) => {
237
+ console.error(message);
238
+ process.exit(2);
239
+ };
240
+ if (!/^[a-z0-9-]+(@[a-z0-9-]+)?$/.test(store))
241
+ fail(`invalid app id: ${store}`);
242
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))
243
+ fail(`invalid env name: ${name}`);
244
+ let value = '';
245
+ try {
246
+ value = execFileSync(`${here}/secret-store`, ['value', store, name], {
247
+ encoding: 'utf8',
248
+ stdio: ['ignore', 'pipe', 'ignore'],
249
+ });
250
+ }
251
+ catch {
252
+ // Not stored, or the store refused: both read as not stored.
253
+ }
254
+ if (!value) {
255
+ console.log('not stored');
256
+ process.exit(2);
257
+ }
258
+ const { code, message } = await check(value, type);
259
+ console.log(message);
260
+ process.exit(code);
261
+ }
262
+ //# sourceMappingURL=secret-check.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-check.mjs","sourceRoot":"","sources":["../src/secret-check.mts"],"names":[],"mappings":"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"}
@@ -0,0 +1,117 @@
1
+ // A web app's browser console on its card: the page loads a small script from this server that forwards what it
2
+ // writes to the console, and its uncaught errors, into the app's log. Only with the app's own key, and only lines.
3
+ import crypto from 'node:crypto';
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import { APP_ID } from "../ids.js";
7
+ import { writeJson } from "../json-files.js";
8
+ import { logsDir, tempDir } from "../paths.js";
9
+ const keysFile = () => path.join(tempDir(), 'console-keys.json');
10
+ const MAX_BODY = 64 * 1024;
11
+ const LEVELS = ['log', 'info', 'warn', 'error', 'debug'];
12
+ const keys = () => {
13
+ try {
14
+ return JSON.parse(fs.readFileSync(keysFile(), 'utf8'));
15
+ }
16
+ catch {
17
+ return {};
18
+ }
19
+ };
20
+ /** The app's key: made once, kept on this Mac. */
21
+ export function consoleKey(app) {
22
+ const all = keys();
23
+ const key = all[app] ?? crypto.randomBytes(16).toString('hex');
24
+ if (!all[app])
25
+ writeJson(keysFile(), { ...all, [app]: key });
26
+ return key;
27
+ }
28
+ /** The script the page loads: it keeps the console as it is and sends a copy of each line here, in batches. */
29
+ const script = (to) => `(() => {
30
+ const to = ${JSON.stringify(to)};
31
+ const queue = [];
32
+ let timer = 0;
33
+ const send = () => {
34
+ timer = 0;
35
+ const lines = queue.splice(0, 50);
36
+ if (lines.length) fetch(to, { method: 'POST', mode: 'no-cors', keepalive: true, body: JSON.stringify(lines) }).catch(() => {});
37
+ };
38
+ const add = (level, text) => {
39
+ queue.push({ level, text: String(text).slice(0, 2000) });
40
+ if (!timer) timer = setTimeout(send, 300);
41
+ };
42
+ const said = value => {
43
+ if (typeof value === 'string') return value;
44
+ if (value instanceof Error) return value.stack || value.message;
45
+ try { return JSON.stringify(value); } catch { return String(value); }
46
+ };
47
+ for (const level of ${JSON.stringify(LEVELS)}) {
48
+ const own = console[level];
49
+ console[level] = (...args) => {
50
+ own.apply(console, args);
51
+ add(level, args.map(said).join(' '));
52
+ };
53
+ }
54
+ addEventListener('error', event => add('error', (event.error && event.error.stack) || event.message));
55
+ addEventListener('unhandledrejection', event => add('error', 'Unhandled rejection: ' + said(event.reason)));
56
+ })();
57
+ `;
58
+ /** Answers /console.js and /console for a page of another origin, the app's: its key decides, not the token. */
59
+ export function serveConsole(req, res, url) {
60
+ const app = url.searchParams.get('app') ?? '';
61
+ const given = Buffer.from(url.searchParams.get('key') ?? '');
62
+ const own = Buffer.from(APP_ID.test(app) ? (keys()[app] ?? '') : '');
63
+ if (!own.length || given.length !== own.length || !crypto.timingSafeEqual(given, own)) {
64
+ req.resume();
65
+ res.writeHead(404).end();
66
+ return;
67
+ }
68
+ if (req.method === 'GET' && url.pathname === '/console.js') {
69
+ // Loaded by the app's page, which is another origin.
70
+ res.setHeader('cross-origin-resource-policy', 'cross-origin');
71
+ res.writeHead(200, { 'content-type': 'text/javascript; charset=utf-8', 'cache-control': 'no-store' });
72
+ res.end(script(`http://${req.headers.host ?? '127.0.0.1'}/console?app=${app}&key=${url.searchParams.get('key') ?? ''}`));
73
+ return;
74
+ }
75
+ if (req.method !== 'POST') {
76
+ req.resume();
77
+ res.writeHead(404).end();
78
+ return;
79
+ }
80
+ let size = 0;
81
+ const chunks = [];
82
+ req.on('data', (chunk) => {
83
+ size += chunk.length;
84
+ if (size <= MAX_BODY)
85
+ chunks.push(chunk);
86
+ });
87
+ req.on('end', () => {
88
+ let lines;
89
+ try {
90
+ lines = size <= MAX_BODY ? JSON.parse(Buffer.concat(chunks).toString('utf8')) : null;
91
+ }
92
+ catch {
93
+ lines = null;
94
+ }
95
+ const list = (Array.isArray(lines) ? lines : []).slice(0, 50).flatMap(line => {
96
+ const { level, text } = (line ?? {});
97
+ if (typeof text !== 'string' || typeof level !== 'string' || !LEVELS.includes(level))
98
+ return [];
99
+ const time = new Date().toTimeString().slice(0, 8);
100
+ return text
101
+ .slice(0, 2000)
102
+ .split('\n')
103
+ .map(one => `${time} browser ${level}: ${one}\n`);
104
+ });
105
+ try {
106
+ if (list.length) {
107
+ fs.mkdirSync(logsDir(), { recursive: true, mode: 0o700 });
108
+ fs.appendFileSync(path.join(logsDir(), `${app}.log`), list.join(''), { mode: 0o600 });
109
+ }
110
+ res.writeHead(204).end();
111
+ }
112
+ catch {
113
+ res.writeHead(500).end();
114
+ }
115
+ });
116
+ }
117
+ //# sourceMappingURL=browser-console.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-console.js","sourceRoot":"","sources":["../../src/server/browser-console.ts"],"names":[],"mappings":"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"}