Files
shockbot/utils/secrets.ts
T
Colin McDonnell 6f108237d4 Deployment protection bypass (#298)
* test preview bypass 2

Co-authored-by: Cursor <cursoragent@cursor.com>

* add apiFetch wrapper with Vercel bypass via query param + header

the template workflow was missing VERCEL_AUTOMATION_BYPASS_SECRET,
so all action API calls to preview deployments hit Vercel's
deployment protection without bypass. this also consolidates the
bypass logic into a single fetch wrapper that applies the secret
as both a query parameter (matching server-side forwarding) and
a header for belt-and-suspenders reliability.

Co-authored-by: Cursor <cursoragent@cursor.com>

* security hardening for Vercel bypass

- redact bypass token from webhook forwarder logs and response body
- remove dead x-preview-api-forward header
- refactor getAllSecrets() to use SENSITIVE_PATTERNS instead of hardcoded list
- enforce https:// on API_URL (localhost exempt for local dev)

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-13 20:01:48 +00:00

85 lines
2.3 KiB
TypeScript

/**
* Secret detection and redaction utilities
* Redacts actual secret values rather than using pattern matching
*/
import { getGitHubInstallationToken } from "./token.ts";
// patterns for sensitive env var names
export const SENSITIVE_PATTERNS = [
/_KEY$/i,
/_SECRET$/i,
/_TOKEN$/i,
/_PASSWORD$/i,
/_CREDENTIAL$/i,
];
export function isSensitiveEnvName(key: string): boolean {
return SENSITIVE_PATTERNS.some((p) => p.test(key));
}
/** filter env vars, removing sensitive values (tokens, keys, secrets) */
export function filterEnv(): Record<string, string> {
const filtered: Record<string, string> = {};
for (const [key, value] of Object.entries(process.env)) {
if (value === undefined) continue;
if (isSensitiveEnvName(key)) continue;
filtered[key] = value;
}
return filtered;
}
export type EnvMode = "restricted" | "inherit" | Record<string, string>;
/**
* resolve env mode to actual env object
* - "restricted" (default): filterEnv() to prevent secret leakage
* - "inherit": full process.env
* - object: custom env merged with restricted base
*/
export function resolveEnv(mode: EnvMode | undefined): Record<string, string | undefined> {
if (mode === "inherit") {
return process.env;
}
if (mode === "restricted" || mode === undefined) {
return filterEnv();
}
// custom env object - merge with restricted base
return { ...filterEnv(), ...mode };
}
function getAllSecrets(): string[] {
const secrets: string[] = [];
// collect all env var values matching SENSITIVE_PATTERNS
for (const [key, value] of Object.entries(process.env)) {
if (value && isSensitiveEnvName(key)) {
secrets.push(value);
}
}
// add GitHub installation token (stored in memory, not in env)
try {
const token = getGitHubInstallationToken();
if (token) {
secrets.push(token);
}
} catch {
// token not set yet, ignore
}
return secrets;
}
export function redactSecrets(content: string, secrets?: string[]): string {
const secretsToRedact = [...(secrets ?? []), ...getAllSecrets()];
let redacted = content;
for (const secret of secretsToRedact) {
if (secret) {
const escaped = secret.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
redacted = redacted.replaceAll(new RegExp(escaped, "g"), "[REDACTED_SECRET]");
}
}
return redacted;
}