@sema-agent/core 7.34.0 → 7.35.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/CHANGELOG.md +123 -3
- package/dist/brain/errors.d.ts +23 -1
- package/dist/brain/errors.js +19 -2
- package/dist/brain/status-sink.d.ts +7 -1
- package/dist/brain/stream-shared.d.ts +3 -1
- package/dist/brain/stream-shared.js +16 -4
- package/dist/core/ask-origin.d.ts +34 -6
- package/dist/core/ask-origin.js +24 -23
- package/dist/core/ask-question.js +2 -1
- package/dist/core/checkpoint-store.d.ts +4 -0
- package/dist/core/gate-fold.js +2 -2
- package/dist/core/gate-lanes.js +5 -4
- package/dist/core/governance-codes.d.ts +17 -1
- package/dist/core/governance-codes.js +11 -1
- package/dist/core/hooks.d.ts +3 -0
- package/dist/core/message-utils.d.ts +2 -1
- package/dist/core/message-utils.js +2 -1
- package/dist/core/mode-policy.d.ts +3 -0
- package/dist/core/mode-policy.js +5 -0
- package/dist/core/permission-rule-model.d.ts +4 -0
- package/dist/core/permission-rule-model.js +4 -0
- package/dist/core/permission-rules.js +21 -1
- package/dist/core/persisted-rule-arms.d.ts +4 -1
- package/dist/core/persisted-rule-arms.js +4 -0
- package/dist/core/retired-keys.js +1 -1
- package/dist/core/runner/active-skill-scope.js +5 -4
- package/dist/core/runner/assemble-result.js +2 -2
- package/dist/core/runner/contracts.d.ts +25 -3
- package/dist/core/runner/denial-limit-arms.d.ts +6 -1
- package/dist/core/runner/denial-limit-arms.js +4 -4
- package/dist/core/runner/file-history-request.d.ts +26 -0
- package/dist/core/runner/file-history-request.js +90 -0
- package/dist/core/runner/permission-rule-lanes.d.ts +2 -1
- package/dist/core/runner/permission-rule-lanes.js +14 -11
- package/dist/core/runner/prepare-ask-lane.d.ts +1 -1
- package/dist/core/runner/prepare-ask-lane.js +2 -2
- package/dist/core/runner/prepare-file-history.d.ts +9 -3
- package/dist/core/runner/prepare-file-history.js +4 -90
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -3
- package/dist/core/runner/prepare-gate-stations.js +15 -11
- package/dist/core/runner/prepare-inherited-gate.d.ts +8 -1
- package/dist/core/runner/prepare-inherited-gate.js +10 -4
- package/dist/core/runner/prepare-park-ask.d.ts +4 -2
- package/dist/core/runner/prepare-park-ask.js +5 -4
- package/dist/core/runner/prepare-policy-chain.d.ts +3 -2
- package/dist/core/runner/prepare-policy-chain.js +171 -175
- package/dist/core/runner/prepare-task.js +7 -4
- package/dist/core/runner/run-brain-sinks.js +3 -1
- package/dist/core/runner/run-leg.js +5 -3
- package/dist/core/runner/run-notification-lane.d.ts +3 -0
- package/dist/core/runner/run-notification-lane.js +2 -1
- package/dist/core/runner/runtask.d.ts +1 -0
- package/dist/core/runner/runtask.js +5 -11
- package/dist/core/runner/session-mode-seats.d.ts +48 -0
- package/dist/core/runner/session-mode-seats.js +73 -0
- package/dist/core/runner/session-rule-policy.js +3 -2
- package/dist/core/secret-env.d.ts +30 -58
- package/dist/core/secret-env.js +149 -586
- package/dist/core/sensitive-path-policy.js +2 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
- package/dist/core/tool-policy.d.ts +31 -6
- package/dist/core/tool-policy.js +35 -16
- package/dist/core/tool-registry.d.ts +36 -0
- package/dist/core/tool-registry.js +22 -1
- package/dist/core/tool-roster.d.ts +17 -2
- package/dist/core/tool-roster.js +11 -1
- package/dist/core/trace.d.ts +11 -0
- package/dist/engine/execution-env/node-execution-env.d.ts +3 -1
- package/dist/engine/execution-env/node-execution-env.js +5 -2
- package/dist/engine/harness/agent-harness.js +2 -1
- package/dist/engine/lsp/node-lsp-manager.js +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/stores/file/index.d.ts +5 -3
- package/dist/stores/file/session-store.d.ts +8 -2
- package/dist/stores/file/session-store.js +5 -5
- package/dist/tools/fs/file-read-check.d.ts +2 -1
- package/dist/tools/fs/file-read-check.js +15 -7
- package/dist/tools/fs/shell-read-check.d.ts +10 -2
- package/dist/tools/fs/shell-read-check.js +12 -8
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +15 -1
- package/wire-changes/core@7.35.0.json +584 -0
package/dist/core/secret-env.js
CHANGED
|
@@ -1,18 +1,72 @@
|
|
|
1
1
|
import { deliverEngineNotice } from "./engine-notice.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
|
|
2
|
+
const GIT_CONFIG_ENV_RE = /^GIT_CONFIG_(?:COUNT|PARAMETERS|(?:KEY|VALUE)_[0-9]+)$/;
|
|
3
|
+
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;
|
|
4
|
+
function isExemptEnvName(name) {
|
|
5
|
+
return GIT_CONFIG_ENV_RE.test(name) || PROXY_ENV_RE.test(name);
|
|
6
|
+
}
|
|
7
|
+
const CREDENTIAL_ENV_NAMES = new Set([
|
|
8
|
+
"ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN", "CLAUDE_CODE_ARTIFACTS_API_TOKEN", "CLAUDE_CODE_MEMORY_API_TOKEN",
|
|
9
|
+
"CLAUDE_CODE_SLACK_TAG_TOKEN", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_FOUNDRY_API_KEY", "ANTHROPIC_FOUNDRY_AUTH_TOKEN",
|
|
10
|
+
"ANTHROPIC_AWS_API_KEY", "ANTHROPIC_CUSTOM_HEADERS", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN",
|
|
11
|
+
"AWS_BEARER_TOKEN_BEDROCK", "GOOGLE_APPLICATION_CREDENTIALS", "GOOGLE_GHA_CREDS_PATH", "AZURE_CLIENT_SECRET",
|
|
12
|
+
"IDENTITY_HEADER", "MSI_SECRET", "AZURE_CLIENT_CERTIFICATE_PATH", "AZURE_CLIENT_CERTIFICATE_PASSWORD",
|
|
13
|
+
"AZURE_PASSWORD", "AZURE_FEDERATED_TOKEN_FILE", "AWS_WEB_IDENTITY_TOKEN_FILE",
|
|
14
|
+
"AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "AWS_CONTAINER_CREDENTIALS_FULL_URI",
|
|
15
|
+
"AWS_CONTAINER_AUTHORIZATION_TOKEN", "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE", "CLOUDSDK_AUTH_ACCESS_TOKEN",
|
|
16
|
+
"GOOGLE_OAUTH_ACCESS_TOKEN", "CLAUDE_CODE_OAUTH_REFRESH_TOKEN", "HF_TOKEN", "HUGGING_FACE_HUB_TOKEN",
|
|
17
|
+
"HUGGINGFACEHUB_API_TOKEN", "NODE_AUTH_TOKEN", "NUGET_AUTH_TOKEN", "CARGO_REGISTRY_TOKEN", "TWINE_PASSWORD",
|
|
18
|
+
"TWINE_USERNAME", "PYPI_TOKEN", "PYPI_API_TOKEN", "UV_PUBLISH_TOKEN", "UV_PUBLISH_PASSWORD", "UV_PUBLISH_USERNAME",
|
|
19
|
+
"FLIT_PASSWORD", "FLIT_USERNAME", "HATCH_INDEX_AUTH", "HATCH_INDEX_USER", "GEM_HOST_API_KEY", "MATURIN_PYPI_TOKEN",
|
|
20
|
+
"MATURIN_PASSWORD", "MATURIN_USERNAME", "CONAN_LOGIN_USERNAME", "CONAN_PASSWORD", "ANACONDA_API_TOKEN",
|
|
21
|
+
"BINSTAR_API_TOKEN", "VAULT_TOKEN", "VAULT_AUTH_TOKEN", "VAULT_ROLE_ID", "VAULT_SECRET_ID", "CONSUL_HTTP_TOKEN",
|
|
22
|
+
"CONSUL_HTTP_AUTH", "NOMAD_TOKEN", "NOMAD_HTTP_AUTH", "CI_REGISTRY_USER", "CI_DEPLOY_USER", "JF_USER",
|
|
23
|
+
"FASTLANE_SESSION", "MATCH_GIT_BASIC_AUTHORIZATION", "SONAR_TOKEN", "SONARQUBE_SCANNER_PARAMS",
|
|
24
|
+
"SONAR_SCANNER_JSON_PARAMS", "SLACK_WEBHOOK_URL", "SLACK_WEBHOOK", "DISCORD_WEBHOOK", "DISCORD_WEBHOOK_URL",
|
|
25
|
+
"TEAMS_WEBHOOK_URL", "MS_TEAMS_WEBHOOK_URI", "ANTHROPIC_IDENTITY_TOKEN", "ANTHROPIC_IDENTITY_TOKEN_FILE",
|
|
26
|
+
"CLOUDSDK_AUTH_ACCESS_TOKEN_FILE", "CLOUDSDK_AUTH_AUTHORIZATION_TOKEN_FILE", "AZURE_AUTH_LOCATION",
|
|
27
|
+
"ACTIONS_ID_TOKEN_REQUEST_TOKEN", "ACTIONS_ID_TOKEN_REQUEST_URL", "ACTIONS_RUNTIME_TOKEN", "ACTIONS_RUNTIME_URL",
|
|
28
|
+
"ALL_INPUTS", "VSS_NUGET_EXTERNAL_FEED_ENDPOINTS", "ARTIFACTS_CREDENTIALPROVIDER_EXTERNAL_FEED_ENDPOINTS",
|
|
29
|
+
"VSS_NUGET_ACCESSTOKEN", "ARTIFACTS_CREDENTIALPROVIDER_ACCESSTOKEN", "COMPOSER_AUTH", "OVERRIDE_GITHUB_TOKEN",
|
|
30
|
+
"DEFAULT_WORKFLOW_TOKEN", "SSH_SIGNING_KEY"
|
|
31
|
+
].flatMap((name) => [name, `INPUT_${name}`]));
|
|
32
|
+
const OTEL_ENV_RE = /^(?:INPUT_)?OTEL_/i;
|
|
33
|
+
const COUNTED_TOKENS = "(?<!(?:^|_)(?:MAX|OUTPUT|INPUT|THINKING|NUM|TOTAL)_)TOKENS";
|
|
34
|
+
const SEMA_NAMESPACE_SECRET_RE = new RegExp(`^SEMA_(?:.*_)?(?:KEY|KEYS|TOKEN|${COUNTED_TOKENS}|SECRET|SECRETS|PASSWORD|PASSWORDS|PASSWD|CREDENTIAL|CREDENTIALS|PRIVATEKEY|APIKEY)(?:_|$)`, "is");
|
|
35
|
+
const LEADING_WORDS = [
|
|
36
|
+
"TOKEN", "SECRET", "PASSWORD", "PASSWD", "PASSPHRASE", "KEY", "AUTH", "COOKIE", "PAT", "DSN", "WEBHOOK", "CREDENTIAL",
|
|
37
|
+
"CREDENTIALS", "CREDS", "APIKEY", "ACCESSKEY", "SECRETKEY", "ACCOUNTKEY", "PRIVATEKEY", "AUTHKEY", "SSHKEY",
|
|
38
|
+
"SIGNINGKEY", "MASTERKEY", "DEPLOYKEY", "ENCRYPTIONKEY", "PGPASSWORD", "SSHPASS",
|
|
39
|
+
];
|
|
40
|
+
const PLURAL_WORDS = ["KEY", "SECRET", "PASSWORD", "CREDENTIAL"];
|
|
41
|
+
const SEPARATED_WORDS = ["PWD", "PASS", "JWT"];
|
|
42
|
+
const EMBEDDED_WORDS = ["TOKEN", "SECRET", "PASSWORD", "PASSWD", "PASSPHRASE"];
|
|
43
|
+
const CREDENTIAL_WORD_RE = new RegExp(`((^|_)(${LEADING_WORDS.join("|")}|(${PLURAL_WORDS.join("|")})S|${COUNTED_TOKENS})|_(${SEPARATED_WORDS.join("|")})|(${EMBEDDED_WORDS.join("|")}))(?=$|[_0-9])`, "i");
|
|
44
|
+
const CONNECTION_STRING_RE = /CONN(ECT(ION)?)?_?STR(ING)?S?(?=$|[_0-9])/i;
|
|
45
|
+
const BUNDLER_SETTINGS = ["BUILD", "LOCAL", "MIRROR", "PATH", "WITH", "WITHOUT", "CACHE", "DISABLE", "IGNORE", "ONLY"];
|
|
46
|
+
const BUNDLER_HOST = "(?:[A-Za-z0-9]+(?:___[A-Za-z0-9]+)*__)+[A-Za-z]{2,}";
|
|
47
|
+
const BUNDLER_CREDENTIAL_RE = new RegExp(`^(?:INPUT_)?BUNDLE_(?!(?:${BUNDLER_SETTINGS.join("|")})__(?!${BUNDLER_HOST}$))\\w*__`, "i");
|
|
48
|
+
const GIT_CONFIG_KEY_NAME_RE = /^GIT_CONFIG_KEY_[0-9][A-Za-z0-9_]*$/;
|
|
49
|
+
const PUBLISHER_PREFIX_RE = /^(?:INPUT_|ORG_GRADLE_PROJECT_|POETRY_PYPI_TOKEN_|POETRY_HTTP_BASIC_|CARGO_REGISTRIES_|CONAN_LOGIN_USERNAME_|CONAN_PASSWORD_)/i;
|
|
50
|
+
const PUBLISHER_LOGIN_PREFIX_RE = /^(?:INPUT_)?(?:POETRY_HTTP_BASIC_|CONAN_LOGIN_USERNAME_)/i;
|
|
51
|
+
const USER_NAME_TAIL_RE = /USER(?:_?NAME)?_?[0-9]*$/i;
|
|
52
|
+
function splitCamelCase(name) {
|
|
53
|
+
return ["OAuth", "NextAuth"]
|
|
54
|
+
.reduce((text, word) => text.replaceAll(word, word[0] + word.slice(1).toLowerCase()), name)
|
|
55
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
56
|
+
.replace(/([A-Z])([A-Z][a-z])/g, "$1_$2");
|
|
57
|
+
}
|
|
58
|
+
function nameShapeHolds(name) {
|
|
59
|
+
const words = name.replace(/^AUTH0_/i, "").replace(/-/g, "_");
|
|
60
|
+
const split = splitCamelCase(words);
|
|
61
|
+
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);
|
|
62
|
+
return shaped && !GIT_CONFIG_KEY_NAME_RE.test(name);
|
|
63
|
+
}
|
|
64
|
+
function nameIsCredentialShaped(name) {
|
|
65
|
+
if (BUNDLER_CREDENTIAL_RE.test(name) || nameShapeHolds(name))
|
|
66
|
+
return true;
|
|
67
|
+
const words = name.replace(/-/g, "_");
|
|
68
|
+
return PUBLISHER_PREFIX_RE.test(words) && (PUBLISHER_LOGIN_PREFIX_RE.test(words) || USER_NAME_TAIL_RE.test(words) || nameShapeHolds(words));
|
|
69
|
+
}
|
|
16
70
|
const NON_SECRET_ENV_EXACT_NAMES = new Set([
|
|
17
71
|
"PARTITION_KEY", "SORT_KEY", "RANGE_KEY", "IDEMPOTENCY_KEY", "CACHE_KEY", "ROUTING_KEY",
|
|
18
72
|
"DB_FOREIGN_KEYS", "JSON_SORT_KEYS",
|
|
@@ -20,593 +74,102 @@ const NON_SECRET_ENV_EXACT_NAMES = new Set([
|
|
|
20
74
|
"MAX_BATCH_PREFILL_TOKENS", "MAX_WAITING_TOKENS", "MAX_TOP_N_TOKENS", "RATE_LIMIT_TOKENS",
|
|
21
75
|
"PROMPT_TOKENS", "CONTEXT_TOKENS",
|
|
22
76
|
]);
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
77
|
+
const VALUE_SCAN_LIMIT = 8192;
|
|
78
|
+
function looksLikeToken(user) {
|
|
79
|
+
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));
|
|
80
|
+
}
|
|
81
|
+
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;
|
|
82
|
+
const HOSTLIKE_RE = /^(?:(?=[a-z0-9._-]*[a-z])[a-z0-9._-]+|\d{1,3}(?:\.\d{1,3}){3}|\[[0-9a-f:.]+\])(?::\d+)?(?:[/?#]|$)/i;
|
|
83
|
+
function isHostPortPathBeforeAt(before, after) {
|
|
84
|
+
const hostPort = /^([a-z0-9_-]+(?:\.[a-z0-9_-]+)*):\d+([/?#].*)$/is.exec(before);
|
|
85
|
+
if (!hostPort)
|
|
30
86
|
return false;
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
const HEADER_LINE_BREAK_RE = /\r\n|\r|\n/;
|
|
34
|
-
const AUTHORIZATION_HEADER_NAME_RE = /^[ \t]*(?:(?:proxy-)?authorization|cookie)[ \t]*:/i;
|
|
35
|
-
const HEADER_CONTENT_RE = /[^ \t\f\v]/;
|
|
36
|
-
function carriesAuthorizationHeader(value) {
|
|
37
|
-
let logical;
|
|
38
|
-
const judge = () => logical !== undefined && AUTHORIZATION_HEADER_NAME_RE.test(logical) && HEADER_CONTENT_RE.test(logical.slice(logical.indexOf(":") + 1));
|
|
39
|
-
for (const physical of value.split(HEADER_LINE_BREAK_RE)) {
|
|
40
|
-
if (logical !== undefined && (physical.startsWith(" ") || physical.startsWith("\t"))) {
|
|
41
|
-
logical += " " + physical;
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
if (judge())
|
|
45
|
-
return true;
|
|
46
|
-
logical = physical;
|
|
47
|
-
}
|
|
48
|
-
return judge();
|
|
49
|
-
}
|
|
50
|
-
const GIT_PROXY_CONFIG_KEY_RE = /^(?:http(?:\..*)?|remote\..*)\.proxy$/is;
|
|
51
|
-
const proxyValueCarriesUserinfo = (value) => value.includes("@") || value.includes("%40");
|
|
52
|
-
const GIT_SECRET_VARIABLE_NAMES = new Set(["pass", "password", "smtppass", "dbpass", "token", "secret"]);
|
|
53
|
-
const GIT_USER_NAMED_VARIABLE_KEY_RE = /^(?:alias|pretty|remotes|pager)\.[^.]*$/is;
|
|
54
|
-
const GIT_EXTRA_HEADER_KEY_RE = /^http\.(?:.*\.)?extraheader$/is;
|
|
55
|
-
const gitKeyHoldsCredential = (key) => (!GIT_USER_NAMED_VARIABLE_KEY_RE.test(key) && GIT_SECRET_VARIABLE_NAMES.has(key.slice(key.lastIndexOf(".") + 1).toLowerCase())) || GIT_EXTRA_HEADER_KEY_RE.test(key);
|
|
56
|
-
const GIT_COMMAND_KEY_RE = /^core\.(?:sshcommand|gitproxy)$/i;
|
|
57
|
-
const GIT_ALIAS_KEY_RE = /^alias\.(?:[^.]*|.*\.command)$/is;
|
|
58
|
-
const GIT_CREDENTIAL_HELPER_KEY_RE = /^credential\.(?:.*\.)?helper$/is;
|
|
59
|
-
function gitShellCommandOf(key, value) {
|
|
60
|
-
if (GIT_COMMAND_KEY_RE.test(key))
|
|
61
|
-
return value;
|
|
62
|
-
if (GIT_CREDENTIAL_HELPER_KEY_RE.test(key))
|
|
63
|
-
return value.startsWith("!") ? value.slice(1) : value;
|
|
64
|
-
if (!GIT_ALIAS_KEY_RE.test(key))
|
|
65
|
-
return undefined;
|
|
66
|
-
return value.startsWith("!") ? value.slice(1) : `git ${value}`;
|
|
67
|
-
}
|
|
68
|
-
const EXPANSION_OPEN = "\u0002";
|
|
69
|
-
const EXPANSION_CLOSE = "\u0003";
|
|
70
|
-
const EXPANSION_RE = /\u0002([^\u0003]*)\u0003/g;
|
|
71
|
-
function decodeCEscapes(text, octal) {
|
|
72
|
-
if (!text.includes("\\"))
|
|
73
|
-
return text;
|
|
74
|
-
const simple = ANSI_C_ESCAPES;
|
|
75
|
-
const run = (re, from, max) => {
|
|
76
|
-
let j = from;
|
|
77
|
-
while (j < text.length && j - from < max && re.test(text.charAt(j)))
|
|
78
|
-
j++;
|
|
79
|
-
return text.slice(from, j);
|
|
80
|
-
};
|
|
81
|
-
let out = "";
|
|
82
|
-
for (let i = 0; i < text.length; i++) {
|
|
83
|
-
const c = text.charAt(i);
|
|
84
|
-
if (c !== "\\" || i + 1 >= text.length) {
|
|
85
|
-
out += c;
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
const e = text.charAt(i + 1);
|
|
89
|
-
let digits;
|
|
90
|
-
if (simple[e] !== undefined) {
|
|
91
|
-
out += simple[e];
|
|
92
|
-
i++;
|
|
93
|
-
}
|
|
94
|
-
else if (e === "c" && i + 2 < text.length) {
|
|
95
|
-
out += String.fromCharCode(text.charCodeAt(i + 2) & 0x1f);
|
|
96
|
-
i += 2;
|
|
97
|
-
}
|
|
98
|
-
else if ((e === "x" || e === "u" || e === "U") && (digits = run(/[0-9A-Fa-f]/, i + 2, e === "x" ? 2 : e === "u" ? 4 : 8)) !== "") {
|
|
99
|
-
out += String.fromCodePoint(Math.min(parseInt(digits, 16), 0x10ffff));
|
|
100
|
-
i += 1 + digits.length;
|
|
101
|
-
}
|
|
102
|
-
else if (/[0-7]/.test(e)) {
|
|
103
|
-
const from = octal === "echo" && e === "0" ? i + 2 : i + 1;
|
|
104
|
-
digits = run(/[0-7]/, from, 3);
|
|
105
|
-
out += digits === "" ? "\0" : String.fromCharCode(parseInt(digits, 8) & 0xff);
|
|
106
|
-
i = from + digits.length - 1;
|
|
107
|
-
}
|
|
108
|
-
else
|
|
109
|
-
out += c;
|
|
110
|
-
}
|
|
111
|
-
return out;
|
|
112
|
-
}
|
|
113
|
-
function markedText(word, source) {
|
|
114
|
-
let out = "";
|
|
115
|
-
for (const piece of word.pieces) {
|
|
116
|
-
if (piece.literal !== undefined)
|
|
117
|
-
out += piece.literal;
|
|
118
|
-
else if (piece.expansion !== undefined) {
|
|
119
|
-
const { kind, name, body = "" } = piece.expansion;
|
|
120
|
-
const written = kind === "parameter" ? /^[A-Za-z_][A-Za-z0-9_]*:?[-=](.+)$/s.exec(body)?.[1] : undefined;
|
|
121
|
-
out += written ?? EXPANSION_OPEN + (kind === "parameter" && name !== undefined && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) ? name : "") + EXPANSION_CLOSE;
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
const text = source.slice(piece.span[0], piece.span[1]);
|
|
125
|
-
out += decodeCEscapes(/^\$'.*'$/s.test(text) ? text.slice(2, -1) : text, "printf");
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return out;
|
|
129
|
-
}
|
|
130
|
-
const SECRET_OPTION_WORDS = new Set(["token", "password", "passwd", "pass", "passphrase", "secret", "pw", "auth", "oauth", "apikey", "credential"]);
|
|
131
|
-
const ASSIGNMENT_RE = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/s;
|
|
132
|
-
const PRINTF_CONVERSION_RE = /%(?:%|[-+ #0']*(\*|[0-9]*)(?:\.(\*|[0-9]*))?[A-Za-z])/g;
|
|
133
|
-
function printfFormatIn(words) {
|
|
134
|
-
const program = words.findIndex((w) => w.slice(w.lastIndexOf("/") + 1) === "printf");
|
|
135
|
-
if (program === -1)
|
|
136
|
-
return undefined;
|
|
137
|
-
let at = program + 1;
|
|
138
|
-
while (words[at] === "-v" || words[at] === "--")
|
|
139
|
-
at += words[at] === "-v" ? 2 : 1;
|
|
140
|
-
const format = words[at];
|
|
141
|
-
if (format === undefined)
|
|
142
|
-
return undefined;
|
|
143
|
-
let count = 0;
|
|
144
|
-
const marked = format.replace(PRINTF_CONVERSION_RE, (conversion, width, precision) => {
|
|
145
|
-
if (conversion === "%%")
|
|
146
|
-
return "%";
|
|
147
|
-
count += (width === "*" ? 1 : 0) + (precision === "*" ? 1 : 0);
|
|
148
|
-
return `${EXPANSION_OPEN}#${count++}${EXPANSION_CLOSE}`;
|
|
149
|
-
});
|
|
150
|
-
return { at, marked: decodeCEscapes(marked, "printf"), count };
|
|
151
|
-
}
|
|
152
|
-
const SHELL_PROGRAMS = new Set(["sh", "bash", "dash", "zsh", "ksh", "mksh", "ash"]);
|
|
153
|
-
const SSH_ARGUMENT_OPTIONS = "BbcDEeFIiJLlmOoPpQRSWw";
|
|
154
|
-
const SSH_COMMAND_OPTION_RE = /^(?:proxy|local|knownhosts|remote)command(?:\s*=\s*|\s+)(.*)$/is;
|
|
155
|
-
function commandCarriesCredential(command, depth = 0, inherited = new Set()) {
|
|
156
|
-
const source = depth === 0 ? command.replace(/[\u0002\u0003]/g, "\ufffd") : command;
|
|
157
|
-
const lexed = lexShell(source);
|
|
158
|
-
if ("parseFailure" in lexed)
|
|
159
|
-
return true;
|
|
160
|
-
const segments = lexed.segments.map((segment) => argvOf(segment).map((word) => markedText(word, source)));
|
|
161
|
-
const formats = segments.map((words) => {
|
|
162
|
-
const format = printfFormatIn(words);
|
|
163
|
-
if (format !== undefined)
|
|
164
|
-
words[format.at] = format.marked;
|
|
165
|
-
return format;
|
|
166
|
-
});
|
|
167
|
-
const bodies = [];
|
|
168
|
-
const lines = [];
|
|
169
|
-
for (const segment of lexed.segments) {
|
|
170
|
-
for (const token of segment.tokens) {
|
|
171
|
-
const word = token.kind === "word" ? token : token.kind === "redirect" ? token.target : undefined;
|
|
172
|
-
for (const piece of word?.expansions ?? [])
|
|
173
|
-
if ((piece.expansion?.kind === "command" || piece.expansion?.kind === "process") && piece.expansion.body !== undefined)
|
|
174
|
-
bodies.push(piece.expansion.body);
|
|
175
|
-
if (token.kind !== "heredoc")
|
|
176
|
-
continue;
|
|
177
|
-
lines.push(...token.body.split(/[\n\r]/));
|
|
178
|
-
if (!token.quoted)
|
|
179
|
-
bodies.push(...(substitutionsInHeredocBody(token.body) ?? []).filter((s) => s.kind === "command").map((s) => s.body));
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
const words = segments.flat();
|
|
183
|
-
const shapeText = (text) => text.replace(EXPANSION_RE, ";").replace(/[\n\r]/g, ";");
|
|
184
|
-
if (carriesSecretShape(`${words.map(shapeText).join(" ")};${lines.map(shapeText).join(";")}`))
|
|
87
|
+
if (/[?#&]/.test(hostPort[2] ?? ""))
|
|
185
88
|
return true;
|
|
186
|
-
|
|
187
|
-
const dependents = new Map();
|
|
188
|
-
const reached = [...inherited];
|
|
189
|
-
for (const word of words) {
|
|
190
|
-
const assigned = ASSIGNMENT_RE.exec(word);
|
|
191
|
-
if (assigned === null)
|
|
192
|
-
continue;
|
|
193
|
-
const [, name = "", value = ""] = assigned;
|
|
194
|
-
if (value.replace(EXPANSION_RE, "") !== "") {
|
|
195
|
-
if (!literalNames.has(name))
|
|
196
|
-
reached.push(name);
|
|
197
|
-
literalNames.add(name);
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
for (const [, ref = ""] of value.matchAll(EXPANSION_RE)) {
|
|
201
|
-
const list = dependents.get(ref);
|
|
202
|
-
if (list === undefined)
|
|
203
|
-
dependents.set(ref, [name]);
|
|
204
|
-
else
|
|
205
|
-
list.push(name);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
for (let q = 0; q < reached.length; q++) {
|
|
210
|
-
for (const name of dependents.get(reached[q] ?? "") ?? []) {
|
|
211
|
-
if (!literalNames.has(name))
|
|
212
|
-
reached.push(name);
|
|
213
|
-
literalNames.add(name);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
const written = (text) => text.replace(EXPANSION_RE, "") !== "" || [...text.matchAll(EXPANSION_RE)].some((m) => literalNames.has(m[1] ?? ""));
|
|
217
|
-
const runs = (line) => depth < 3 && commandCarriesCredential(line, depth + 1, literalNames);
|
|
218
|
-
const secretLine = (line) => {
|
|
219
|
-
const assigned = ASSIGNMENT_RE.exec(line);
|
|
220
|
-
return assigned !== null && written(assigned[2] ?? "") && classifySecretEnvKey(assigned[1] ?? "") !== undefined;
|
|
221
|
-
};
|
|
222
|
-
if (lines.some(secretLine))
|
|
223
|
-
return true;
|
|
224
|
-
const printfCarries = (format, words) => {
|
|
225
|
-
const argument = (n) => words[format.at + 1 + n] ?? "";
|
|
226
|
-
const firstPass = format.marked.replace(EXPANSION_RE, (mark, ref) => {
|
|
227
|
-
const conversion = /^#([0-9]+)$/.exec(ref);
|
|
228
|
-
return conversion === null ? mark : argument(Number(conversion[1]));
|
|
229
|
-
});
|
|
230
|
-
if (firstPass.split(/[\n\r]/).some(secretLine))
|
|
231
|
-
return true;
|
|
232
|
-
for (const line of format.marked.split(/[\n\r]/)) {
|
|
233
|
-
const assigned = ASSIGNMENT_RE.exec(line);
|
|
234
|
-
if (assigned === null || classifySecretEnvKey(assigned[1] ?? "") === undefined)
|
|
235
|
-
continue;
|
|
236
|
-
for (const [, ref = ""] of (assigned[2] ?? "").matchAll(EXPANSION_RE)) {
|
|
237
|
-
const conversion = /^#([0-9]+)$/.exec(ref);
|
|
238
|
-
if (conversion === null)
|
|
239
|
-
continue;
|
|
240
|
-
for (let n = Number(conversion[1]) + format.count; format.count > 0 && format.at + 1 + n < words.length; n += format.count)
|
|
241
|
-
if (written(argument(n)))
|
|
242
|
-
return true;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
89
|
+
if (HOST_RE.test(after))
|
|
245
90
|
return false;
|
|
246
|
-
|
|
247
|
-
if (segments.some((words, i) => formats[i] !== undefined && printfCarries(formats[i], words)))
|
|
91
|
+
if (before.endsWith("/"))
|
|
248
92
|
return true;
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
let shellOptions = false;
|
|
253
|
-
let sshOptions = false;
|
|
254
|
-
let gitOptions = false;
|
|
255
|
-
let pending;
|
|
256
|
-
for (const [position, word] of segmentWords.entries()) {
|
|
257
|
-
const waiting = pending;
|
|
258
|
-
pending = undefined;
|
|
259
|
-
if (waiting === "skip")
|
|
260
|
-
continue;
|
|
261
|
-
if (waiting === "command" && runs(word))
|
|
262
|
-
return true;
|
|
263
|
-
if (waiting === "ssh-option") {
|
|
264
|
-
const run = SSH_COMMAND_OPTION_RE.exec(word);
|
|
265
|
-
if (run !== null && runs(run[1] ?? ""))
|
|
266
|
-
return true;
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
if (waiting === "git-config") {
|
|
270
|
-
const eq = word.indexOf("=");
|
|
271
|
-
if (depth < 3 && eq > 0 && gitConfigEntryCarriesCredential({ key: word.slice(0, eq), value: word.slice(eq + 1) }, depth + 1))
|
|
272
|
-
return true;
|
|
273
|
-
continue;
|
|
274
|
-
}
|
|
275
|
-
if (waiting === "sshpass-password") {
|
|
276
|
-
if (written(word))
|
|
277
|
-
return true;
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
if (waiting === "option-value" && !word.startsWith("-")) {
|
|
281
|
-
if (written(word))
|
|
282
|
-
return true;
|
|
283
|
-
continue;
|
|
284
|
-
}
|
|
285
|
-
for (const view of position === formatAt ? [word] : [word, decodeCEscapes(word, "printf"), decodeCEscapes(word, "echo")]) {
|
|
286
|
-
if (view.split(/[\n\r]/).some(secretLine))
|
|
287
|
-
return true;
|
|
288
|
-
}
|
|
289
|
-
if (sshpassOptions) {
|
|
290
|
-
if (word === "--") {
|
|
291
|
-
sshpassOptions = false;
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
if (word.startsWith("--")) {
|
|
295
|
-
if (word === "--password")
|
|
296
|
-
pending = "sshpass-password";
|
|
297
|
-
else if (word.startsWith("--password=") && written(word.slice("--password=".length)))
|
|
298
|
-
return true;
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
|
-
if (word.startsWith("-") && word.length > 1) {
|
|
302
|
-
for (let k = 1; k < word.length; k++) {
|
|
303
|
-
const letter = word.charAt(k);
|
|
304
|
-
if (letter === "p") {
|
|
305
|
-
if (k === word.length - 1)
|
|
306
|
-
pending = "sshpass-password";
|
|
307
|
-
else if (written(word.slice(k + 1)))
|
|
308
|
-
return true;
|
|
309
|
-
break;
|
|
310
|
-
}
|
|
311
|
-
if (letter === "f" || letter === "d" || letter === "P") {
|
|
312
|
-
if (k === word.length - 1)
|
|
313
|
-
pending = "skip";
|
|
314
|
-
break;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
continue;
|
|
318
|
-
}
|
|
319
|
-
sshpassOptions = false;
|
|
320
|
-
}
|
|
321
|
-
if (shellOptions) {
|
|
322
|
-
if (/^--[A-Za-z-]+$/.test(word))
|
|
323
|
-
continue;
|
|
324
|
-
if (/^[-+][A-Za-z]+$/.test(word)) {
|
|
325
|
-
if (word.startsWith("-") && word.includes("c"))
|
|
326
|
-
pending = "command";
|
|
327
|
-
else if (/o$/.test(word))
|
|
328
|
-
pending = "skip";
|
|
329
|
-
continue;
|
|
330
|
-
}
|
|
331
|
-
shellOptions = false;
|
|
332
|
-
}
|
|
333
|
-
if (gitOptions) {
|
|
334
|
-
if (word === "-c") {
|
|
335
|
-
pending = "git-config";
|
|
336
|
-
continue;
|
|
337
|
-
}
|
|
338
|
-
if (word.startsWith("-")) {
|
|
339
|
-
if (["-C", "--git-dir", "--work-tree", "--namespace", "--exec-path", "--config-env", "--super-prefix", "--list-cmds", "--attr-source"].includes(word))
|
|
340
|
-
pending = "skip";
|
|
341
|
-
continue;
|
|
342
|
-
}
|
|
343
|
-
gitOptions = false;
|
|
344
|
-
}
|
|
345
|
-
if (sshOptions) {
|
|
346
|
-
if (/^-[A-Za-z0-9]+/.test(word) && !word.startsWith("--")) {
|
|
347
|
-
for (let k = 1; k < word.length; k++) {
|
|
348
|
-
const letter = word.charAt(k);
|
|
349
|
-
if (!SSH_ARGUMENT_OPTIONS.includes(letter))
|
|
350
|
-
continue;
|
|
351
|
-
const attached = word.slice(k + 1);
|
|
352
|
-
if (letter === "o") {
|
|
353
|
-
if (attached === "")
|
|
354
|
-
pending = "ssh-option";
|
|
355
|
-
else {
|
|
356
|
-
const run = SSH_COMMAND_OPTION_RE.exec(attached);
|
|
357
|
-
if (run !== null && runs(run[1] ?? ""))
|
|
358
|
-
return true;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
else if (attached === "")
|
|
362
|
-
pending = "skip";
|
|
363
|
-
break;
|
|
364
|
-
}
|
|
365
|
-
continue;
|
|
366
|
-
}
|
|
367
|
-
sshOptions = false;
|
|
368
|
-
}
|
|
369
|
-
const option = /^--?([A-Za-z0-9][A-Za-z0-9_-]*)(?:=(.*))?$/s.exec(word);
|
|
370
|
-
if (option !== null && SECRET_OPTION_WORDS.has((option[1] ?? "").toLowerCase().split(/[-_]/).pop() ?? "")) {
|
|
371
|
-
if (option[2] === undefined)
|
|
372
|
-
pending = "option-value";
|
|
373
|
-
else if (written(option[2]))
|
|
374
|
-
return true;
|
|
375
|
-
}
|
|
376
|
-
const program = word.slice(word.lastIndexOf("/") + 1).replace(/\.exe$/i, "");
|
|
377
|
-
if (program === "sshpass")
|
|
378
|
-
sshpassOptions = true;
|
|
379
|
-
else if (SHELL_PROGRAMS.has(program))
|
|
380
|
-
shellOptions = true;
|
|
381
|
-
else if (program === "ssh")
|
|
382
|
-
sshOptions = true;
|
|
383
|
-
else if (program === "git")
|
|
384
|
-
gitOptions = true;
|
|
385
|
-
else if (program === "eval" || word.toLowerCase() === "-proxycmd")
|
|
386
|
-
pending = "command";
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
return bodies.some((body) => runs(body));
|
|
93
|
+
if (/^(?:v?\d|sha\d*:)/i.test(after))
|
|
94
|
+
return true;
|
|
95
|
+
return !HOSTLIKE_RE.test(after);
|
|
390
96
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const
|
|
394
|
-
|
|
97
|
+
function authorityCarriesCredential(rest) {
|
|
98
|
+
const blank = rest.search(/\s/);
|
|
99
|
+
const word = blank === -1 ? rest : rest.slice(0, blank);
|
|
100
|
+
const at = word.lastIndexOf("@");
|
|
101
|
+
if (at === -1)
|
|
102
|
+
return false;
|
|
103
|
+
const userinfo = word.slice(0, at);
|
|
104
|
+
if (isHostPortPathBeforeAt(userinfo, word.slice(at + 1)))
|
|
105
|
+
return false;
|
|
106
|
+
return userinfo.includes(":") || (!/[/?#]/.test(userinfo) && looksLikeToken(userinfo));
|
|
395
107
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
108
|
+
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;
|
|
109
|
+
function carriesSchemelessUserinfo(text) {
|
|
110
|
+
if (!/^[^\s/@:]+:(?!\/\/)(?![\\/])[^\s@]*@[^\s/@]+/.test(text) || /^[a-z]:[\\/]/i.test(text) || PACKAGE_REFERENCE_RE.test(text))
|
|
111
|
+
return false;
|
|
112
|
+
const at = text.lastIndexOf("@", text.search(/\s|$/));
|
|
113
|
+
return !isHostPortPathBeforeAt(text.slice(0, at), text.slice(at + 1)) && !/^(?:mailto|sips?|xmpp|im|acct):/i.test(text);
|
|
114
|
+
}
|
|
115
|
+
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;
|
|
116
|
+
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;
|
|
117
|
+
const SONAR_LOGIN_RE = /["']?sonar\.login["']?\s*[:=]\s*["']?[^\s"',}]{8,}/;
|
|
118
|
+
const PRIVATE_KEY_HEADER_RE = /-----BEGIN [A-Z ]*PRIVATE KEY(?: BLOCK)?-----/;
|
|
119
|
+
const AUTHORIZATION_HEADER_RE = /\bauthorization\s*[:=]\s*["']?[a-z][a-z0-9_-]*\s+[A-Za-z0-9._~+\/=-]{8,}/i;
|
|
120
|
+
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(?![\/.])/;
|
|
121
|
+
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,}/;
|
|
122
|
+
function valueCarriesCredential(value) {
|
|
123
|
+
const text = value.length > VALUE_SCAN_LIMIT ? value.slice(0, VALUE_SCAN_LIMIT) : value;
|
|
124
|
+
for (let at = text.indexOf("://"); at !== -1; at = text.indexOf("://", at + 3)) {
|
|
125
|
+
if (at > 0 && /[a-z0-9+.-]/i.test(text[at - 1] ?? "") && authorityCarriesCredential(text.slice(at + 3)))
|
|
402
126
|
return true;
|
|
403
127
|
}
|
|
404
|
-
if (
|
|
405
|
-
return false;
|
|
406
|
-
if (carriesAuthorizationHeader(value) || carriesUrlUserinfo(value, value))
|
|
407
|
-
return true;
|
|
408
|
-
if (key === undefined)
|
|
409
|
-
return carriesSecretShape(value);
|
|
410
|
-
if (gitKeyHoldsCredential(key))
|
|
128
|
+
if (text.startsWith("//") && authorityCarriesCredential(text.slice(2)))
|
|
411
129
|
return true;
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
const GIT_COMMAND_ENV_KEYS = new Map([["GIT_SSH_COMMAND", "core.sshCommand"], ["GIT_PROXY_COMMAND", "core.gitProxy"]]);
|
|
421
|
-
const GIT_BLANK = /[ \t\n\r]/;
|
|
422
|
-
export function readGitConfigParameters(text) {
|
|
423
|
-
const entries = [];
|
|
424
|
-
let at = 0;
|
|
425
|
-
const quoted = () => {
|
|
426
|
-
if (text[at] !== "'")
|
|
427
|
-
return undefined;
|
|
428
|
-
let word = "";
|
|
429
|
-
for (let i = at + 1; i < text.length; i++) {
|
|
430
|
-
if (text[i] !== "'")
|
|
431
|
-
word += text[i];
|
|
432
|
-
else if (text[i + 1] === "\\" && (text[i + 2] === "'" || text[i + 2] === "!") && text[i + 3] === "'") {
|
|
433
|
-
word += text[i + 2];
|
|
434
|
-
i += 3;
|
|
435
|
-
}
|
|
436
|
-
else {
|
|
437
|
-
at = i + 1;
|
|
438
|
-
return word;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
return undefined;
|
|
442
|
-
};
|
|
443
|
-
const endsItem = () => at === text.length || GIT_BLANK.test(text.charAt(at));
|
|
444
|
-
while (at < text.length) {
|
|
445
|
-
const key = quoted();
|
|
446
|
-
if (key === undefined)
|
|
447
|
-
return undefined;
|
|
448
|
-
if (endsItem()) {
|
|
449
|
-
const eq = key.indexOf("=");
|
|
450
|
-
entries.push(eq === -1 ? { key } : { key: key.slice(0, eq), value: key.slice(eq + 1) });
|
|
451
|
-
}
|
|
452
|
-
else if (text[at] === "=") {
|
|
453
|
-
at++;
|
|
454
|
-
if (endsItem())
|
|
455
|
-
entries.push({ key });
|
|
456
|
-
else {
|
|
457
|
-
const value = quoted();
|
|
458
|
-
if (value === undefined || !endsItem())
|
|
459
|
-
return undefined;
|
|
460
|
-
entries.push({ key, value });
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
else
|
|
464
|
-
return undefined;
|
|
465
|
-
while (at < text.length && GIT_BLANK.test(text.charAt(at)))
|
|
466
|
-
at++;
|
|
467
|
-
}
|
|
468
|
-
return entries;
|
|
469
|
-
}
|
|
470
|
-
function gitConfigVariableCarriesCredential(upperName, value) {
|
|
471
|
-
if (upperName === "GIT_CONFIG_PARAMETERS")
|
|
472
|
-
return readGitConfigParameters(value)?.some((entry) => gitConfigEntryCarriesCredential(entry)) ?? true;
|
|
473
|
-
const key = PROXY_ENV_NAMES.has(upperName) ? "http.proxy" : GIT_COMMAND_ENV_KEYS.get(upperName);
|
|
474
|
-
return key !== undefined && gitConfigEntryCarriesCredential({ key, value });
|
|
475
|
-
}
|
|
476
|
-
const GIT_CONFIG_COUNT_NAME = "GIT_CONFIG_COUNT";
|
|
477
|
-
const GIT_CONFIG_MEMBER_RE = /^GIT_CONFIG_(KEY|VALUE)_(.*)$/s;
|
|
478
|
-
const GIT_CONFIG_INDEX_RE = /^(?:0|[1-9][0-9]*)$/;
|
|
479
|
-
const GIT_CONFIG_COUNT_RE = /^[ \t\n\v\f\r]*([+-]?)([0-9]+)$/;
|
|
480
|
-
function readGitConfigCount(value, platform) {
|
|
481
|
-
if (value === "")
|
|
482
|
-
return 0n;
|
|
483
|
-
const number = GIT_CONFIG_COUNT_RE.exec(value);
|
|
484
|
-
if (number === null)
|
|
485
|
-
return undefined;
|
|
486
|
-
const [, sign, digits = ""] = number;
|
|
487
|
-
const significant = digits.replace(/^0+/, "");
|
|
488
|
-
const unsignedLongMax = platform === "win32" ? 2n ** 32n - 1n : 2n ** 64n - 1n;
|
|
489
|
-
const magnitude = significant.length > 20 ? unsignedLongMax : BigInt(significant === "" ? "0" : significant);
|
|
490
|
-
const read = magnitude > unsignedLongMax ? unsignedLongMax : sign === "-" ? (unsignedLongMax + 1n - magnitude) % (unsignedLongMax + 1n) : magnitude;
|
|
491
|
-
return read <= 2n ** 31n - 1n ? read : undefined;
|
|
492
|
-
}
|
|
493
|
-
const GIT_CONFIG_PLACEHOLDER = { key: "sema.withheld", value: "" };
|
|
494
|
-
function readGitEnvConfigRecord(env, platform) {
|
|
495
|
-
const withheld = new Map();
|
|
496
|
-
const kept = new Set();
|
|
497
|
-
const countNames = [];
|
|
498
|
-
const indexed = new Map();
|
|
499
|
-
const loose = new Map();
|
|
500
|
-
for (const [name, text] of Object.entries(env)) {
|
|
501
|
-
if (text === undefined)
|
|
502
|
-
continue;
|
|
503
|
-
const upper = name.toUpperCase();
|
|
504
|
-
const lookedUp = platform === "win32" ? upper : name;
|
|
505
|
-
if (lookedUp === GIT_CONFIG_COUNT_NAME) {
|
|
506
|
-
countNames.push(name);
|
|
507
|
-
continue;
|
|
508
|
-
}
|
|
509
|
-
const member = GIT_CONFIG_MEMBER_RE.exec(upper);
|
|
510
|
-
if (member === null)
|
|
511
|
-
continue;
|
|
512
|
-
const [, role, suffix = ""] = member;
|
|
513
|
-
const half = role === "KEY" ? "key" : "value";
|
|
514
|
-
const groups = GIT_CONFIG_MEMBER_RE.test(lookedUp) && GIT_CONFIG_INDEX_RE.test(suffix) ? indexed : loose;
|
|
515
|
-
const group = groups.get(suffix) ?? { members: [], twice: false };
|
|
516
|
-
groups.set(suffix, group);
|
|
517
|
-
group.members.push([name, half]);
|
|
518
|
-
if (group[half] !== undefined)
|
|
519
|
-
group.twice = true;
|
|
520
|
-
group[half] = text;
|
|
521
|
-
}
|
|
522
|
-
const withhold = (g, standIn) => {
|
|
523
|
-
for (const [n, half] of g.members)
|
|
524
|
-
withheld.set(n, standIn ? GIT_CONFIG_PLACEHOLDER[half] : undefined);
|
|
525
|
-
};
|
|
526
|
-
for (const g of loose.values()) {
|
|
527
|
-
if (g.twice || gitConfigEntryCarriesCredential(g))
|
|
528
|
-
withhold(g, false);
|
|
529
|
-
else if (g.members.some(([n]) => classifySecretEnvKey(n) !== undefined)) {
|
|
530
|
-
for (const [n] of g.members)
|
|
531
|
-
if (classifySecretEnvKey(n) === undefined)
|
|
532
|
-
withheld.set(n, undefined);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
const record = [...indexed].map(([suffix, g]) => [BigInt(suffix), g]);
|
|
536
|
-
const [countName, secondCountName] = countNames;
|
|
537
|
-
if (secondCountName !== undefined || record.some(([, g]) => g.twice)) {
|
|
538
|
-
for (const n of countNames)
|
|
539
|
-
withheld.set(n, undefined);
|
|
540
|
-
for (const [, g] of record)
|
|
541
|
-
withhold(g, false);
|
|
542
|
-
return { withheld, kept };
|
|
543
|
-
}
|
|
544
|
-
const count = countName === undefined ? 0n : readGitConfigCount(env[countName] ?? "", platform);
|
|
545
|
-
for (const n of countNames)
|
|
546
|
-
kept.add(n);
|
|
547
|
-
for (const [index, g] of record) {
|
|
548
|
-
if (gitConfigEntryCarriesCredential(g))
|
|
549
|
-
withhold(g, count === undefined || index < count);
|
|
550
|
-
else
|
|
551
|
-
for (const [n] of g.members)
|
|
552
|
-
kept.add(n);
|
|
553
|
-
}
|
|
554
|
-
return { withheld, kept };
|
|
130
|
+
return (carriesSchemelessUserinfo(text) ||
|
|
131
|
+
CREDENTIAL_ASSIGNMENT_RE.test(text) ||
|
|
132
|
+
WEBHOOK_URL_RE.test(text) ||
|
|
133
|
+
SONAR_LOGIN_RE.test(text) ||
|
|
134
|
+
PRIVATE_KEY_HEADER_RE.test(text) ||
|
|
135
|
+
AUTHORIZATION_HEADER_RE.test(text) ||
|
|
136
|
+
TOKEN_WORD_RE.test(text) ||
|
|
137
|
+
BEARER_BASIC_RE.test(text));
|
|
555
138
|
}
|
|
556
139
|
const SECRET_ENV_KIND_CONFIDENCE = {
|
|
557
140
|
"exact-name": "high",
|
|
558
|
-
"
|
|
141
|
+
"prefix-rule": "high",
|
|
142
|
+
"value-rule": "high",
|
|
143
|
+
"name-rule": "medium",
|
|
559
144
|
"namespace-rule": "medium",
|
|
560
|
-
"family-rule": "high",
|
|
561
145
|
};
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
if (credentialNames.has(upper))
|
|
565
|
-
return "credential";
|
|
566
|
-
if (exemptNames.has(upper))
|
|
567
|
-
return "exempt";
|
|
568
|
-
return "unknown";
|
|
569
|
-
}
|
|
570
|
-
function classifySecretEnvKey(key) {
|
|
571
|
-
const byExactName = classifyExactEnvName(key, SECRET_ENV_EXACT_NAMES, NON_SECRET_ENV_EXACT_NAMES);
|
|
572
|
-
if (byExactName === "credential")
|
|
573
|
-
return "exact-name";
|
|
574
|
-
if (byExactName === "exempt")
|
|
575
|
-
return undefined;
|
|
576
|
-
if (SECRET_ENV_RE.test(key))
|
|
577
|
-
return "suffix-rule";
|
|
578
|
-
return SEMA_NAMESPACE_SECRET_RE.test(key) ? "namespace-rule" : undefined;
|
|
579
|
-
}
|
|
580
|
-
function classifyEnvEntry(key, value, record) {
|
|
581
|
-
if (record.withheld.has(key))
|
|
582
|
-
return "family-rule";
|
|
583
|
-
if (record.kept.has(key))
|
|
146
|
+
function classifySecretEnvEntry(name, value) {
|
|
147
|
+
if (isExemptEnvName(name))
|
|
584
148
|
return undefined;
|
|
585
|
-
if (
|
|
586
|
-
return "
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
const
|
|
603
|
-
if (
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
findings
|
|
607
|
-
}
|
|
149
|
+
if (CREDENTIAL_ENV_NAMES.has(name.toUpperCase().replace(/-/g, "_")))
|
|
150
|
+
return "exact-name";
|
|
151
|
+
if (OTEL_ENV_RE.test(name))
|
|
152
|
+
return "prefix-rule";
|
|
153
|
+
if (SEMA_NAMESPACE_SECRET_RE.test(name))
|
|
154
|
+
return "namespace-rule";
|
|
155
|
+
if (!NON_SECRET_ENV_EXACT_NAMES.has(name.toUpperCase()) && nameIsCredentialShaped(name))
|
|
156
|
+
return "name-rule";
|
|
157
|
+
return value !== undefined && valueCarriesCredential(typeof value === "string" ? value : String(value)) ? "value-rule" : undefined;
|
|
158
|
+
}
|
|
159
|
+
export function isSecretEnvKey(key, value) {
|
|
160
|
+
return classifySecretEnvEntry(key, value) !== undefined;
|
|
161
|
+
}
|
|
162
|
+
export function scrubSecretEnv(env, findings) {
|
|
163
|
+
const kept = [];
|
|
164
|
+
for (const key of Object.keys(env)) {
|
|
165
|
+
const value = env[key];
|
|
166
|
+
const kind = classifySecretEnvEntry(key, value);
|
|
167
|
+
if (kind === undefined)
|
|
168
|
+
kept.push([key, value]);
|
|
169
|
+
else
|
|
170
|
+
findings?.push({ key, kind, confidence: SECRET_ENV_KIND_CONFIDENCE[kind], source: "secret-env" });
|
|
608
171
|
}
|
|
609
|
-
return
|
|
172
|
+
return Object.fromEntries(kept);
|
|
610
173
|
}
|
|
611
174
|
export function inheritedHostEnv(policy, findings) {
|
|
612
175
|
if (policy === "all")
|
|
@@ -618,7 +181,7 @@ export function inheritedHostEnv(policy, findings) {
|
|
|
618
181
|
picked[k] = process.env[k];
|
|
619
182
|
return picked;
|
|
620
183
|
}
|
|
621
|
-
return scrubSecretEnv(process.env, findings
|
|
184
|
+
return scrubSecretEnv(process.env, findings);
|
|
622
185
|
}
|
|
623
186
|
export function inheritEnvPolicyProblem(value) {
|
|
624
187
|
if (value === undefined || value === "all" || value === "scrub")
|