Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

57 lines · 2.3 KBRaw History
1/**
2 * Which secrets a job uses. As on GitHub, a runner gets only the secrets the
3 * job's expressions name, so a compromised action or tool in one job can't read
4 * every secret out of the runner's memory or environment.
5 */
6
7/** Secrets the runner reads itself: they turn on debug logging. */
8export const RUNNER_SECRETS = ["ACTIONS_STEP_DEBUG", "ACTIONS_RUNNER_DEBUG"];
9
10const EXPRESSION = /\$\{\{([^]*?)\}\}/g;
11const SECRETS = /\bsecrets\b/gi;
12const NAMED = /^\s*(?:\.\s*([A-Za-z_][A-Za-z0-9_]*)|\[\s*(['"])([A-Za-z_][A-Za-z0-9_]*)\2\s*\])/;
13
14/** Secret names (upper case) referenced in `expr`, or "all" when it reads `secrets` as a whole (toJSON, a computed index). */
15function scan(expr: string, into: Set<string>): boolean {
16 for (const m of expr.matchAll(SECRETS)) {
17 // `steps.secrets.outputs.x` is a property named secrets, not the context.
18 if (/\.\s*$/.test(expr.slice(0, m.index))) continue;
19 const named = NAMED.exec(expr.slice(m.index + m[0].length));
20 if (!named) return false;
21 into.add((named[1] ?? named[3])!.toUpperCase());
22 }
23 return true;
24}
25
26/**
27 * The secret names `parts` (a job definition, the workflow's `env` and `defaults`) reference,
28 * upper case; or "all" when an expression uses `secrets` in a way that can't be narrowed.
29 * `if:` values are expressions without `${{ }}`.
30 */
31export function referencedSecrets(...parts: unknown[]): Set<string> | "all" {
32 const names = new Set<string>();
33 let all = false;
34 const walk = (v: unknown, key: string | null): void => {
35 if (all) return;
36 if (typeof v === "string") {
37 if (key === "if" && !v.includes("${{")) {
38 if (!scan(v, names)) all = true;
39 return;
40 }
41 for (const m of v.matchAll(EXPRESSION)) if (!scan(m[1]!, names)) all = true;
42 return;
43 }
44 if (Array.isArray(v)) for (const x of v) walk(x, null);
45 else if (v && typeof v === "object") for (const [k, x] of Object.entries(v)) walk(x, k);
46 };
47 for (const p of parts) walk(p, null);
48 return all ? "all" : names;
49}
50
51/** `secrets` narrowed to what the job references. */
52export function secretsFor(secrets: Record<string, string>, used: Set<string> | "all"): Record<string, string> {
53 if (used === "all") return secrets;
54 const out: Record<string, string> = {};
55 for (const [name, value] of Object.entries(secrets)) if (used.has(name.toUpperCase())) out[name] = value;
56 return out;
57}