@sema-agent/core
Version:
Stateless, task-oriented AI agent core
213 lines (212 loc) • 13.4 kB
JavaScript
import { deliverEngineNotice } from "./engine-notice.js";
const GIT_CONFIG_ENV_RE = /^GIT_CONFIG_(?:COUNT|PARAMETERS|(?:KEY|VALUE)_[0-9]+)$/;
const PROXY_ENV_RE = /^(?:(?:https?|ftp|all|no)_proxy|npm_config_(?:https?_)?proxy|npm_config_noproxy|yarn_proxy|(?:yarn|global_agent|docker|claude_code)_(?:https?|no)_proxy|cloudsdk_proxy_[a-z]+|electron_get_use_proxy)$/i;
function isExemptEnvName(name) {
return GIT_CONFIG_ENV_RE.test(name) || PROXY_ENV_RE.test(name);
}
const CREDENTIAL_ENV_NAMES = new Set([
"ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN", "CLAUDE_CODE_ARTIFACTS_API_TOKEN", "CLAUDE_CODE_MEMORY_API_TOKEN",
"CLAUDE_CODE_SLACK_TAG_TOKEN", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_FOUNDRY_API_KEY", "ANTHROPIC_FOUNDRY_AUTH_TOKEN",
"ANTHROPIC_AWS_API_KEY", "ANTHROPIC_CUSTOM_HEADERS", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN",
"AWS_BEARER_TOKEN_BEDROCK", "GOOGLE_APPLICATION_CREDENTIALS", "GOOGLE_GHA_CREDS_PATH", "AZURE_CLIENT_SECRET",
"IDENTITY_HEADER", "MSI_SECRET", "AZURE_CLIENT_CERTIFICATE_PATH", "AZURE_CLIENT_CERTIFICATE_PASSWORD",
"AZURE_PASSWORD", "AZURE_FEDERATED_TOKEN_FILE", "AWS_WEB_IDENTITY_TOKEN_FILE",
"AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "AWS_CONTAINER_CREDENTIALS_FULL_URI",
"AWS_CONTAINER_AUTHORIZATION_TOKEN", "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE", "CLOUDSDK_AUTH_ACCESS_TOKEN",
"GOOGLE_OAUTH_ACCESS_TOKEN", "CLAUDE_CODE_OAUTH_REFRESH_TOKEN", "HF_TOKEN", "HUGGING_FACE_HUB_TOKEN",
"HUGGINGFACEHUB_API_TOKEN", "NODE_AUTH_TOKEN", "NUGET_AUTH_TOKEN", "CARGO_REGISTRY_TOKEN", "TWINE_PASSWORD",
"TWINE_USERNAME", "PYPI_TOKEN", "PYPI_API_TOKEN", "UV_PUBLISH_TOKEN", "UV_PUBLISH_PASSWORD", "UV_PUBLISH_USERNAME",
"FLIT_PASSWORD", "FLIT_USERNAME", "HATCH_INDEX_AUTH", "HATCH_INDEX_USER", "GEM_HOST_API_KEY", "MATURIN_PYPI_TOKEN",
"MATURIN_PASSWORD", "MATURIN_USERNAME", "CONAN_LOGIN_USERNAME", "CONAN_PASSWORD", "ANACONDA_API_TOKEN",
"BINSTAR_API_TOKEN", "VAULT_TOKEN", "VAULT_AUTH_TOKEN", "VAULT_ROLE_ID", "VAULT_SECRET_ID", "CONSUL_HTTP_TOKEN",
"CONSUL_HTTP_AUTH", "NOMAD_TOKEN", "NOMAD_HTTP_AUTH", "CI_REGISTRY_USER", "CI_DEPLOY_USER", "JF_USER",
"FASTLANE_SESSION", "MATCH_GIT_BASIC_AUTHORIZATION", "SONAR_TOKEN", "SONARQUBE_SCANNER_PARAMS",
"SONAR_SCANNER_JSON_PARAMS", "SLACK_WEBHOOK_URL", "SLACK_WEBHOOK", "DISCORD_WEBHOOK", "DISCORD_WEBHOOK_URL",
"TEAMS_WEBHOOK_URL", "MS_TEAMS_WEBHOOK_URI", "ANTHROPIC_IDENTITY_TOKEN", "ANTHROPIC_IDENTITY_TOKEN_FILE",
"CLOUDSDK_AUTH_ACCESS_TOKEN_FILE", "CLOUDSDK_AUTH_AUTHORIZATION_TOKEN_FILE", "AZURE_AUTH_LOCATION",
"ACTIONS_ID_TOKEN_REQUEST_TOKEN", "ACTIONS_ID_TOKEN_REQUEST_URL", "ACTIONS_RUNTIME_TOKEN", "ACTIONS_RUNTIME_URL",
"ALL_INPUTS", "VSS_NUGET_EXTERNAL_FEED_ENDPOINTS", "ARTIFACTS_CREDENTIALPROVIDER_EXTERNAL_FEED_ENDPOINTS",
"VSS_NUGET_ACCESSTOKEN", "ARTIFACTS_CREDENTIALPROVIDER_ACCESSTOKEN", "COMPOSER_AUTH", "OVERRIDE_GITHUB_TOKEN",
"DEFAULT_WORKFLOW_TOKEN", "SSH_SIGNING_KEY"
].flatMap((name) => [name, `INPUT_${name}`]));
const OTEL_ENV_RE = /^(?:INPUT_)?OTEL_/i;
const COUNTED_TOKENS = "(?<!(?:^|_)(?:MAX|OUTPUT|INPUT|THINKING|NUM|TOTAL)_)TOKENS";
const SEMA_NAMESPACE_SECRET_RE = new RegExp(`^SEMA_(?:.*_)?(?:KEY|KEYS|TOKEN|${COUNTED_TOKENS}|SECRET|SECRETS|PASSWORD|PASSWORDS|PASSWD|CREDENTIAL|CREDENTIALS|PRIVATEKEY|APIKEY)(?:_|$)`, "is");
const LEADING_WORDS = [
"TOKEN", "SECRET", "PASSWORD", "PASSWD", "PASSPHRASE", "KEY", "AUTH", "COOKIE", "PAT", "DSN", "WEBHOOK", "CREDENTIAL",
"CREDENTIALS", "CREDS", "APIKEY", "ACCESSKEY", "SECRETKEY", "ACCOUNTKEY", "PRIVATEKEY", "AUTHKEY", "SSHKEY",
"SIGNINGKEY", "MASTERKEY", "DEPLOYKEY", "ENCRYPTIONKEY", "PGPASSWORD", "SSHPASS",
];
const PLURAL_WORDS = ["KEY", "SECRET", "PASSWORD", "CREDENTIAL"];
const SEPARATED_WORDS = ["PWD", "PASS", "JWT"];
const EMBEDDED_WORDS = ["TOKEN", "SECRET", "PASSWORD", "PASSWD", "PASSPHRASE"];
const CREDENTIAL_WORD_RE = new RegExp(`((^|_)(${LEADING_WORDS.join("|")}|(${PLURAL_WORDS.join("|")})S|${COUNTED_TOKENS})|_(${SEPARATED_WORDS.join("|")})|(${EMBEDDED_WORDS.join("|")}))(?=$|[_0-9])`, "i");
const CONNECTION_STRING_RE = /CONN(ECT(ION)?)?_?STR(ING)?S?(?=$|[_0-9])/i;
const BUNDLER_SETTINGS = ["BUILD", "LOCAL", "MIRROR", "PATH", "WITH", "WITHOUT", "CACHE", "DISABLE", "IGNORE", "ONLY"];
const BUNDLER_HOST = "(?:[A-Za-z0-9]+(?:___[A-Za-z0-9]+)*__)+[A-Za-z]{2,}";
const BUNDLER_CREDENTIAL_RE = new RegExp(`^(?:INPUT_)?BUNDLE_(?!(?:${BUNDLER_SETTINGS.join("|")})__(?!${BUNDLER_HOST}$))\\w*__`, "i");
const GIT_CONFIG_KEY_NAME_RE = /^GIT_CONFIG_KEY_[0-9][A-Za-z0-9_]*$/;
const PUBLISHER_PREFIX_RE = /^(?:INPUT_|ORG_GRADLE_PROJECT_|POETRY_PYPI_TOKEN_|POETRY_HTTP_BASIC_|CARGO_REGISTRIES_|CONAN_LOGIN_USERNAME_|CONAN_PASSWORD_)/i;
const PUBLISHER_LOGIN_PREFIX_RE = /^(?:INPUT_)?(?:POETRY_HTTP_BASIC_|CONAN_LOGIN_USERNAME_)/i;
const USER_NAME_TAIL_RE = /USER(?:_?NAME)?_?[0-9]*$/i;
function splitCamelCase(name) {
return ["OAuth", "NextAuth"]
.reduce((text, word) => text.replaceAll(word, word[0] + word.slice(1).toLowerCase()), name)
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
.replace(/([A-Z])([A-Z][a-z])/g, "$1_$2");
}
function nameShapeHolds(name) {
const words = name.replace(/^AUTH0_/i, "").replace(/-/g, "_");
const split = splitCamelCase(words);
const shaped = CREDENTIAL_WORD_RE.test(words) || CREDENTIAL_WORD_RE.test(split) || CONNECTION_STRING_RE.test(words) || CONNECTION_STRING_RE.test(split) || BUNDLER_CREDENTIAL_RE.test(words);
return shaped && !GIT_CONFIG_KEY_NAME_RE.test(name);
}
function nameIsCredentialShaped(name) {
if (BUNDLER_CREDENTIAL_RE.test(name) || nameShapeHolds(name))
return true;
const words = name.replace(/-/g, "_");
return PUBLISHER_PREFIX_RE.test(words) && (PUBLISHER_LOGIN_PREFIX_RE.test(words) || USER_NAME_TAIL_RE.test(words) || nameShapeHolds(words));
}
const NON_SECRET_ENV_EXACT_NAMES = new Set([
"PARTITION_KEY", "SORT_KEY", "RANGE_KEY", "IDEMPOTENCY_KEY", "CACHE_KEY", "ROUTING_KEY",
"DB_FOREIGN_KEYS", "JSON_SORT_KEYS",
"MAX_NEW_TOKENS", "MAX_COMPLETION_TOKENS", "AIDER_MAP_TOKENS", "AIDER_MAX_CHAT_HISTORY_TOKENS",
"MAX_BATCH_PREFILL_TOKENS", "MAX_WAITING_TOKENS", "MAX_TOP_N_TOKENS", "RATE_LIMIT_TOKENS",
"PROMPT_TOKENS", "CONTEXT_TOKENS",
]);
const VALUE_SCAN_LIMIT = 8192;
function looksLikeToken(user) {
return /^(?:gh[opusr]_|github_pat_|glpat-|xox[abpr]-|sk-|pk-|AKIA|eyJ|ya29\.|npm_)/.test(user) || (user.length >= 20 && /[0-9]/.test(user) && /[a-z]/i.test(user));
}
const HOST_RE = /^(?:localhost|[a-z0-9_-]+(?:\.[a-z0-9_-]+)*\.(?=[a-z0-9-]*[a-z])[a-z0-9-]+|[a-z0-9_.-]+(?=:\d+(?:[/?#]|$))|\d{1,3}(?:\.\d{1,3}){3}|\[[0-9a-f:.]+\])(?::\d+)?(?:[/?#]|$)/i;
const HOSTLIKE_RE = /^(?:(?=[a-z0-9._-]*[a-z])[a-z0-9._-]+|\d{1,3}(?:\.\d{1,3}){3}|\[[0-9a-f:.]+\])(?::\d+)?(?:[/?#]|$)/i;
function isHostPortPathBeforeAt(before, after) {
const hostPort = /^([a-z0-9_-]+(?:\.[a-z0-9_-]+)*):\d+([/?#].*)$/is.exec(before);
if (!hostPort)
return false;
if (/[?#&]/.test(hostPort[2] ?? ""))
return true;
if (HOST_RE.test(after))
return false;
if (before.endsWith("/"))
return true;
if (/^(?:v?\d|sha\d*:)/i.test(after))
return true;
return !HOSTLIKE_RE.test(after);
}
function authorityCarriesCredential(rest) {
const blank = rest.search(/\s/);
const word = blank === -1 ? rest : rest.slice(0, blank);
const at = word.lastIndexOf("@");
if (at === -1)
return false;
const userinfo = word.slice(0, at);
if (isHostPortPathBeforeAt(userinfo, word.slice(at + 1)))
return false;
return userinfo.includes(":") || (!/[/?#]/.test(userinfo) && looksLikeToken(userinfo));
}
const PACKAGE_REFERENCE_RE = /^(?:[\w.-]+(?::[\w.-]+)?@sha(?:256|384|512):[0-9a-f]{32,}|npm:(?:@[\w.-]+\/)?[\w.-]+@[\w.^~<>=*|+-]*|[a-z_][\w.-]*:[\w.-]+:v?\d[\w.+\[\](),-]*@(?:jar|war|ear|aar|apk|aab|pom|zip|tar|tgz|module|klib|exe|dll|so|dylib)?)$/i;
function carriesSchemelessUserinfo(text) {
if (!/^[^\s/@:]+:(?!\/\/)(?![\\/])[^\s@]*@[^\s/@]+/.test(text) || /^[a-z]:[\\/]/i.test(text) || PACKAGE_REFERENCE_RE.test(text))
return false;
const at = text.lastIndexOf("@", text.search(/\s|$/));
return !isHostPortPathBeforeAt(text.slice(0, at), text.slice(at + 1)) && !/^(?:mailto|sips?|xmpp|im|acct):/i.test(text);
}
const CREDENTIAL_ASSIGNMENT_RE = /(?:(?:^|[;&?#,{]|\/:)\s*|\s)["']?(?!-*jobserver-auth\s*[=:])(?:[a-z0-9_.-]{0,64}(?:password|passwd|pwd|secret|token|(?:account|access|api|private|subscription)[-_]?key|signature|sig|credential)|[a-z0-9_.-]{0,63}[_.-]auth)["']?\s*[=:]\s*["']?(?!(?:true|false|none|null|yes|no|on|off|enabled|disabled|required|optional)(?:$|[;&,\s"']))[^;&,\s"']+/i;
const WEBHOOK_URL_RE = /https:\/\/(?:hooks\.slack\.com\/(?:services|workflows|triggers)\/|(?:ptb\.|canary\.)?discord(?:app)?\.com\/api\/webhooks\/\d+\/|[\w.-]+\.webhook\.office\.com\/webhookb2\/)[\w\/@.~-]{16,}/i;
const SONAR_LOGIN_RE = /["']?sonar\.login["']?\s*[:=]\s*["']?[^\s"',}]{8,}/;
const PRIVATE_KEY_HEADER_RE = /-----BEGIN [A-Z ]*PRIVATE KEY(?: BLOCK)?-----/;
const AUTHORIZATION_HEADER_RE = /\bauthorization\s*[:=]\s*["']?[a-z][a-z0-9_-]*\s+[A-Za-z0-9._~+\/=-]{8,}/i;
const TOKEN_WORD_RE = /\b(?:[Tt]oken|TOKEN)\s+(?=[A-Za-z0-9_~+=-]*[0-9])(?=[A-Za-z0-9_~+=-]*[A-Z])(?=([A-Za-z0-9_~+=-]{20,}))\1(?![\/.])/;
const BEARER_BASIC_RE = /\b(?:Bearer|Basic)\s+(?=[A-Za-z._~+\/=-]*[0-9]|(?:[A-Za-z0-9._~+\/=-]*?[a-z][A-Z](?![a-z])){2}|[A-Za-z0-9.-]*[_~+\/=])[A-Za-z0-9._~+\/=-]{8,}/;
function valueCarriesCredential(value) {
const text = value.length > VALUE_SCAN_LIMIT ? value.slice(0, VALUE_SCAN_LIMIT) : value;
for (let at = text.indexOf("://"); at !== -1; at = text.indexOf("://", at + 3)) {
if (at > 0 && /[a-z0-9+.-]/i.test(text[at - 1] ?? "") && authorityCarriesCredential(text.slice(at + 3)))
return true;
}
if (text.startsWith("//") && authorityCarriesCredential(text.slice(2)))
return true;
return (carriesSchemelessUserinfo(text) ||
CREDENTIAL_ASSIGNMENT_RE.test(text) ||
WEBHOOK_URL_RE.test(text) ||
SONAR_LOGIN_RE.test(text) ||
PRIVATE_KEY_HEADER_RE.test(text) ||
AUTHORIZATION_HEADER_RE.test(text) ||
TOKEN_WORD_RE.test(text) ||
BEARER_BASIC_RE.test(text));
}
const SECRET_ENV_KIND_CONFIDENCE = {
"exact-name": "high",
"prefix-rule": "high",
"value-rule": "high",
"name-rule": "medium",
"namespace-rule": "medium",
};
function classifySecretEnvEntry(name, value) {
if (isExemptEnvName(name))
return undefined;
if (CREDENTIAL_ENV_NAMES.has(name.toUpperCase().replace(/-/g, "_")))
return "exact-name";
if (OTEL_ENV_RE.test(name))
return "prefix-rule";
if (SEMA_NAMESPACE_SECRET_RE.test(name))
return "namespace-rule";
if (!NON_SECRET_ENV_EXACT_NAMES.has(name.toUpperCase()) && nameIsCredentialShaped(name))
return "name-rule";
return value !== undefined && valueCarriesCredential(typeof value === "string" ? value : String(value)) ? "value-rule" : undefined;
}
export function isSecretEnvKey(key, value) {
return classifySecretEnvEntry(key, value) !== undefined;
}
export function scrubSecretEnv(env, findings) {
const kept = [];
for (const key of Object.keys(env)) {
const value = env[key];
const kind = classifySecretEnvEntry(key, value);
if (kind === undefined)
kept.push([key, value]);
else
findings?.push({ key, kind, confidence: SECRET_ENV_KIND_CONFIDENCE[kind], source: "secret-env" });
}
return Object.fromEntries(kept);
}
export function inheritedHostEnv(policy, findings) {
if (policy === "all")
return { ...process.env };
if (Array.isArray(policy)) {
const picked = {};
for (const k of policy)
if (process.env[k] !== undefined)
picked[k] = process.env[k];
return picked;
}
return scrubSecretEnv(process.env, findings);
}
export function inheritEnvPolicyProblem(value) {
if (value === undefined || value === "all" || value === "scrub")
return undefined;
if (Array.isArray(value)) {
const bad = value.findIndex((k) => typeof k !== "string" || k.length === 0 || /[=\0]/.test(k));
if (bad === -1)
return undefined;
const k = value[bad];
return `inheritEnv[${bad}] is not a variable name (got ${typeof k !== "string" ? typeof k : k === "" ? "an empty string" : `a name holding ${k.includes("=") ? "\"=\"" : "a NUL"}`})`;
}
return `inheritEnv must be "scrub", "all" or a list of variable names (got ${typeof value === "string" ? JSON.stringify(value) : value === null ? "null" : typeof value})`;
}
let secretEnvScrubNoticeAnnounced = false;
export function __resetSecretEnvScrubNoticeAnnouncement() {
secretEnvScrubNoticeAnnounced = false;
}
export function announceSecretEnvScrub(onNotice, findings) {
if (findings.length === 0 || secretEnvScrubNoticeAnnounced)
return;
secretEnvScrubNoticeAnnounced = true;
const names = findings.map((f) => f.key);
deliverEngineNotice(onNotice, {
code: "config.secret_env_scrubbed",
message: `secret_env_scrubbed: ${findings.length} env key(s) withheld from a spawned child's environment (${names.join(", ")}) — once per process; wire onNotice for the structured form`,
detail: { names },
});
}