@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.
Files changed (83) hide show
  1. package/CHANGELOG.md +123 -3
  2. package/dist/brain/errors.d.ts +23 -1
  3. package/dist/brain/errors.js +19 -2
  4. package/dist/brain/status-sink.d.ts +7 -1
  5. package/dist/brain/stream-shared.d.ts +3 -1
  6. package/dist/brain/stream-shared.js +16 -4
  7. package/dist/core/ask-origin.d.ts +34 -6
  8. package/dist/core/ask-origin.js +24 -23
  9. package/dist/core/ask-question.js +2 -1
  10. package/dist/core/checkpoint-store.d.ts +4 -0
  11. package/dist/core/gate-fold.js +2 -2
  12. package/dist/core/gate-lanes.js +5 -4
  13. package/dist/core/governance-codes.d.ts +17 -1
  14. package/dist/core/governance-codes.js +11 -1
  15. package/dist/core/hooks.d.ts +3 -0
  16. package/dist/core/message-utils.d.ts +2 -1
  17. package/dist/core/message-utils.js +2 -1
  18. package/dist/core/mode-policy.d.ts +3 -0
  19. package/dist/core/mode-policy.js +5 -0
  20. package/dist/core/permission-rule-model.d.ts +4 -0
  21. package/dist/core/permission-rule-model.js +4 -0
  22. package/dist/core/permission-rules.js +21 -1
  23. package/dist/core/persisted-rule-arms.d.ts +4 -1
  24. package/dist/core/persisted-rule-arms.js +4 -0
  25. package/dist/core/retired-keys.js +1 -1
  26. package/dist/core/runner/active-skill-scope.js +5 -4
  27. package/dist/core/runner/assemble-result.js +2 -2
  28. package/dist/core/runner/contracts.d.ts +25 -3
  29. package/dist/core/runner/denial-limit-arms.d.ts +6 -1
  30. package/dist/core/runner/denial-limit-arms.js +4 -4
  31. package/dist/core/runner/file-history-request.d.ts +26 -0
  32. package/dist/core/runner/file-history-request.js +90 -0
  33. package/dist/core/runner/permission-rule-lanes.d.ts +2 -1
  34. package/dist/core/runner/permission-rule-lanes.js +14 -11
  35. package/dist/core/runner/prepare-ask-lane.d.ts +1 -1
  36. package/dist/core/runner/prepare-ask-lane.js +2 -2
  37. package/dist/core/runner/prepare-file-history.d.ts +9 -3
  38. package/dist/core/runner/prepare-file-history.js +4 -90
  39. package/dist/core/runner/prepare-gate-stations.d.ts +4 -3
  40. package/dist/core/runner/prepare-gate-stations.js +15 -11
  41. package/dist/core/runner/prepare-inherited-gate.d.ts +8 -1
  42. package/dist/core/runner/prepare-inherited-gate.js +10 -4
  43. package/dist/core/runner/prepare-park-ask.d.ts +4 -2
  44. package/dist/core/runner/prepare-park-ask.js +5 -4
  45. package/dist/core/runner/prepare-policy-chain.d.ts +3 -2
  46. package/dist/core/runner/prepare-policy-chain.js +171 -175
  47. package/dist/core/runner/prepare-task.js +7 -4
  48. package/dist/core/runner/run-brain-sinks.js +3 -1
  49. package/dist/core/runner/run-leg.js +5 -3
  50. package/dist/core/runner/run-notification-lane.d.ts +3 -0
  51. package/dist/core/runner/run-notification-lane.js +2 -1
  52. package/dist/core/runner/runtask.d.ts +1 -0
  53. package/dist/core/runner/runtask.js +5 -11
  54. package/dist/core/runner/session-mode-seats.d.ts +48 -0
  55. package/dist/core/runner/session-mode-seats.js +73 -0
  56. package/dist/core/runner/session-rule-policy.js +3 -2
  57. package/dist/core/secret-env.d.ts +30 -58
  58. package/dist/core/secret-env.js +149 -586
  59. package/dist/core/sensitive-path-policy.js +2 -1
  60. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
  61. package/dist/core/tool-policy.d.ts +31 -6
  62. package/dist/core/tool-policy.js +35 -16
  63. package/dist/core/tool-registry.d.ts +36 -0
  64. package/dist/core/tool-registry.js +22 -1
  65. package/dist/core/tool-roster.d.ts +17 -2
  66. package/dist/core/tool-roster.js +11 -1
  67. package/dist/core/trace.d.ts +11 -0
  68. package/dist/engine/execution-env/node-execution-env.d.ts +3 -1
  69. package/dist/engine/execution-env/node-execution-env.js +5 -2
  70. package/dist/engine/harness/agent-harness.js +2 -1
  71. package/dist/engine/lsp/node-lsp-manager.js +1 -1
  72. package/dist/index.d.ts +4 -4
  73. package/dist/index.js +4 -4
  74. package/dist/stores/file/index.d.ts +5 -3
  75. package/dist/stores/file/session-store.d.ts +8 -2
  76. package/dist/stores/file/session-store.js +5 -5
  77. package/dist/tools/fs/file-read-check.d.ts +2 -1
  78. package/dist/tools/fs/file-read-check.js +15 -7
  79. package/dist/tools/fs/shell-read-check.d.ts +10 -2
  80. package/dist/tools/fs/shell-read-check.js +12 -8
  81. package/package.json +1 -1
  82. package/test/export-surface.snapshot.json +15 -1
  83. package/wire-changes/core@7.35.0.json +584 -0
@@ -1,18 +1,72 @@
1
1
  import { deliverEngineNotice } from "./engine-notice.js";
2
- import { redactSecrets } from "./untrusted-egress.js";
3
- import { ANSI_C_ESCAPES, argvOf, lexShell, substitutionsInHeredocBody } from "../shell/lex.js";
4
- const TOKEN_COUNT_WORDS = ["MAX", "OUTPUT", "INPUT", "THINKING", "NUM", "TOTAL"];
5
- const CREDENTIAL_WORD = `KEY|KEYS|TOKEN|(?<!(?:^|_)(?:${TOKEN_COUNT_WORDS.join("|")})_)TOKENS|SECRET|SECRETS|PASSWORD|PASSWORDS|PASSWD|CREDENTIAL|CREDENTIALS|PRIVATEKEY|APIKEY`;
6
- const SECRET_ENV_RE = new RegExp(`(^|_)(${CREDENTIAL_WORD})(?:_(?:\\d+|ID))?$`, "i");
7
- const SEMA_NAMESPACE_SECRET_RE = new RegExp(`^SEMA_(?:.*_)?(?:${CREDENTIAL_WORD})(?:_|$)`, "is");
8
- const SECRET_ENV_EXACT_NAMES = new Set([
9
- "DATABASE_URL", "CONNECTION_STRING", "DSN", "NPM_AUTH",
10
- "PGPASSWORD", "MYSQL_PWD", "SSHPASS", "GPG_PASSPHRASE", "GH_PAT", "GITHUB_PAT",
11
- "NPM_CONFIG__AUTH", "NPM_CONFIG__AUTHTOKEN",
12
- "OTEL_EXPORTER_OTLP_HEADERS", "OTEL_EXPORTER_OTLP_TRACES_HEADERS", "OTEL_EXPORTER_OTLP_METRICS_HEADERS",
13
- "OTEL_EXPORTER_OTLP_LOGS_HEADERS", "OTEL_EXPORTER_OTLP_PROFILES_HEADERS",
14
- "OTEL_EXPORTER_OTLP_SPAN_HEADERS", "OTEL_EXPORTER_OTLP_METRIC_HEADERS",
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 SSH_LOGIN_ONLY_URL_RE = /^(?:ssh|git\+ssh|git):\/\/[A-Za-z0-9._-]+@[^@]*$/;
24
- const GIT_KEY_SECTION_RE = /^[A-Za-z0-9-]+\./;
25
- function carriesUrlUserinfo(text, url) {
26
- const scheme = text.indexOf(":/");
27
- if (scheme === -1)
28
- return false;
29
- if (text.indexOf("@", scheme + 2) === -1 && text.indexOf("%40", scheme + 2) === -1)
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
- return url === undefined || url.includes("%40") || !SSH_LOGIN_ONLY_URL_RE.test(url);
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
- const literalNames = new Set(inherited);
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
- for (const [segment, segmentWords] of segments.entries()) {
250
- const formatAt = formats[segment]?.at;
251
- let sshpassOptions = false;
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
- const GIT_REF_VALUE_KEY_RE = /^(?:remote\..*\.(?:fetch|push)|branch\..*\.merge)$/is;
392
- function carriesSecretShape(value) {
393
- const view = value.replaceAll(":", "\u0001");
394
- return redactSecrets(view) !== view;
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
- function gitConfigEntryCarriesCredential(entry, depth = 0) {
397
- const key = entry.key?.trim();
398
- const value = entry.value;
399
- if (key !== undefined) {
400
- const section = GIT_KEY_SECTION_RE.exec(key);
401
- if (carriesUrlUserinfo(key, section === null ? undefined : key.slice(section[0].length)))
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 (value === undefined || value === "")
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
- const command = gitShellCommandOf(key, value);
413
- if (command !== undefined)
414
- return commandCarriesCredential(command, depth);
415
- if (!GIT_REF_VALUE_KEY_RE.test(key) && carriesSecretShape(value))
416
- return true;
417
- return GIT_PROXY_CONFIG_KEY_RE.test(key) && proxyValueCarriesUserinfo(value);
418
- }
419
- const PROXY_ENV_NAMES = new Set(["HTTP_PROXY", "HTTPS_PROXY", "FTP_PROXY", "ALL_PROXY", "NPM_CONFIG_PROXY", "NPM_CONFIG_HTTPS_PROXY"]);
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
- "suffix-rule": "medium",
141
+ "prefix-rule": "high",
142
+ "value-rule": "high",
143
+ "name-rule": "medium",
559
144
  "namespace-rule": "medium",
560
- "family-rule": "high",
561
145
  };
562
- export function classifyExactEnvName(key, credentialNames, exemptNames) {
563
- const upper = key.toUpperCase();
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 (value !== undefined && gitConfigVariableCarriesCredential(key.toUpperCase(), value))
586
- return "family-rule";
587
- return classifySecretEnvKey(key);
588
- }
589
- export function isSecretEnvKey(key, value, env, platform = process.platform) {
590
- const record = readGitEnvConfigRecord(value === undefined ? {} : { ...env, [key]: value }, platform);
591
- return classifyEnvEntry(key, value, record) !== undefined;
592
- }
593
- export function scrubSecretEnv(env, findings, platform = process.platform) {
594
- const record = readGitEnvConfigRecord(env, platform);
595
- const out = {};
596
- for (const k of Object.keys(env)) {
597
- const kind = classifyEnvEntry(k, env[k], record);
598
- if (kind === undefined) {
599
- out[k] = env[k];
600
- continue;
601
- }
602
- const standIn = record.withheld.get(k);
603
- if (standIn !== undefined)
604
- out[k] = standIn;
605
- if (findings !== undefined) {
606
- findings.push({ key: k, kind, confidence: SECRET_ENV_KIND_CONFIDENCE[kind], source: "secret-env" });
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 out;
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, process.platform);
184
+ return scrubSecretEnv(process.env, findings);
622
185
  }
623
186
  export function inheritEnvPolicyProblem(value) {
624
187
  if (value === undefined || value === "all" || value === "scrub")