@volter/teams 0.2.9 → 0.2.11

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/checkout.mjs ADDED
@@ -0,0 +1,29 @@
1
+ // What a checkout on this machine is, read with git alone (no machine state): its repository identity, root, branch and
2
+ // whether it is a linked worktree. The machine daemon answers it for another machine; a process asking about a path on
3
+ // its own machine reads it itself, with the file access that process has (a launchd daemon may have none on a volume
4
+ // macOS guards, where the session that named the path does).
5
+ import { realpathSync } from 'node:fs';
6
+ import { resolve } from 'node:path';
7
+ import { spawnSync } from 'node:child_process';
8
+
9
+ const git = (path, args, optional = false) => {
10
+ const r = spawnSync('git', ['-C', path, ...args], { encoding: 'utf8', timeout: 30_000 });
11
+ if (r.status !== 0) { if (optional) return null; throw new Error(`git ${args[0]} in ${path}: ${(r.stderr || r.error?.message || 'failed').trim()}`); }
12
+ return r.stdout.trim();
13
+ };
14
+ export function repositoryId(value) {
15
+ let s = String(value ?? '').trim().replace(/\.git\/?$/, '').replace(/\/$/, '');
16
+ if (/^[^/@]+@[^:]+:/.test(s)) s = s.replace(/^[^@]+@/, '').replace(':', '/');
17
+ else if (/^[a-z]+:\/\//i.test(s)) { const u = new URL(s); s = u.host + u.pathname; }
18
+ s = s.replace(/^github\.com\//i, '');
19
+ if (!/^[\w.-]+(?:\/[\w.-]+)+$/.test(s) || s.split('/').some(p => p === '.' || p === '..')) throw new Error('repository must be a git remote identity, for example volter-ai/editor; not a machine path');
20
+ return s;
21
+ }
22
+ export function inspectCheckout(path) {
23
+ const raw = git(path, ['rev-parse', '--path-format=absolute', '--show-toplevel', '--git-dir', '--git-common-dir']);
24
+ const [root, dir, common] = raw.split('\n').map(p => realpathSync(resolve(path, p)));
25
+ // The repository the checkout declares (remote.origin.url), not where a URL rewrite (insteadOf: a mirror, a proxy, a
26
+ // World's twin) sends its transport.
27
+ const remote = git(root, ['config', '--get', 'remote.origin.url'], true);
28
+ return { path: realpathSync(path), root, linked: dir !== common, repository: remote ? repositoryId(remote) : null, branch: git(root, ['branch', '--show-current']) || null };
29
+ }
package/http-client.d.ts CHANGED
@@ -83,8 +83,10 @@ export interface TeamClient {
83
83
  catalogue(options?: RequestOptions): Promise<Result<{kinds: string[]}>>;
84
84
  createSource(body: {name: string}, options?: RequestOptions): Promise<Result<{id: string; name: string}>>;
85
85
  publish(source: string, body: unknown, options?: RequestOptions): Promise<Result<OntologyEvent>>;
86
+ purge(ids: string[], options?: RequestOptions): Promise<Result<{purged: string[]; unknown: string[]}>>;
86
87
  follow(query?: Query, options?: StreamOptions): AsyncIterable<PublishedEventFrame>;
87
88
  };
89
+ purgeSessionText(sessions: string[], values: string[], options?: RequestOptions): Promise<Result<{sessions: Record<string, {messages: number; natives: number; artifacts_removed: number; artifacts_left: number; chunks_removed: number; shared_chunks_kept: number; shared_chunks_holding_value: number}>}>>;
88
90
  sessions: {
89
91
  list(query?: Query, options?: RequestOptions): Promise<Page>;
90
92
  search(query: Query, options?: RequestOptions): Promise<unknown>;
package/http-client.mjs CHANGED
@@ -13,12 +13,17 @@ export function callerName(env=globalThis.process?.env,argv=globalThis.process?.
13
13
  return ([script,...words].join(' ')+(session?` in ${session}`:'')).replace(/[^\x20-\x7e]/g,'?').slice(0,120);
14
14
  }
15
15
  const CALLER=callerName();
16
+ // Every write to a Teams server is redacted (@volter/teams/redact), but a sign-in or credential exchange, whose body is
17
+ // the credential it hands over.
18
+ const SIGN_IN=/\/(?:login|apps\/token|invitations\/accept|credentials|me\/identities)(?:\/|$)/;
19
+ // Node only (it reads custody files); a browser sends what its person typed, and a bundler must not pull it in.
20
+ const { redactValue } = globalThis.process?.versions?.node ? await import(`./redact${'.mjs'}`) : { redactValue: (value) => value };
16
21
  export class TeamsClient {
17
22
  constructor({server,credential,fetch:fetchImpl=(...args)=>globalThis.fetch(...args)}){this.server=trimOrigin(server);this.credential=credential;this.fetch=fetchImpl;if(typeof fetchImpl!=='function')throw new TypeError('fetch is required');}
18
23
  async request(method,path,{body,signal,idempotencyKey,ifMatch}={}) {
19
24
  const key=idempotencyKey??(method==='GET'?null:randomKey());let delay=250,last;
20
25
  for(let attempt=0;attempt<5;attempt++) { try {
21
- const response=await this.fetch(this.server+path,{method,signal,redirect:'manual',headers:{Accept:'application/json',Authorization:`Bearer ${this.credential}`,...(CALLER?{'X-Supercode-Caller':CALLER}:{}),...(body!==undefined?{'Content-Type':'application/json'}:{}),...(key?{'Idempotency-Key':key}:{}),...(ifMatch!==undefined?{'If-Match':String(ifMatch)}:{})},body:body===undefined?undefined:JSON.stringify(body)});
26
+ const response=await this.fetch(this.server+path,{method,signal,redirect:'manual',headers:{Accept:'application/json',Authorization:`Bearer ${this.credential}`,...(CALLER?{'X-Supercode-Caller':CALLER}:{}),...(body!==undefined?{'Content-Type':'application/json'}:{}),...(key?{'Idempotency-Key':key}:{}),...(ifMatch!==undefined?{'If-Match':String(ifMatch)}:{})},body:body===undefined?undefined:JSON.stringify(SIGN_IN.test(path)?body:redactValue(body))});
22
27
  if(response.type==='opaqueredirect'||(response.status>=300&&response.status<400))throw Object.assign(new Error('cross-origin redirects are refused'),{code:'invalid_request',status:400,retryable:false});
23
28
  const value=response.status===204?null:await response.json();if(!response.ok)throw Object.assign(new Error(value?.error?.message??`HTTP ${response.status}`),value?.error,{status:response.status});return value;
24
29
  } catch(error) { last=error;if(method!=='GET'||signal?.aborted||!(!error.status||error.retryable)||attempt===4)throw error;await retryWait(delay,signal);delay=Math.min(delay*2,5000); } }
@@ -84,7 +89,9 @@ class TeamClient {
84
89
  follow: (q = {},o = {}) => events(root,base+'/events'+query(q),o),
85
90
  createSource: (body,o) => post('/event-sources',body,o),
86
91
  publish: (source,body,o) => post(resource('/event-sources',source)+'/events',body,o),
92
+ purge: (ids,o) => post('/events/purge',{events:ids},o),
87
93
  };
94
+ this.purgeSessionText = (sessions, values, o) => post('/sessions/purge', { sessions, values }, o);
88
95
  this.apps = {
89
96
  catalogue: o => get('/apps/catalogue', o),
90
97
  list: o => get('/apps', o), get: (id, o) => get(resource('/apps', id), o),
package/machine-door.mjs CHANGED
@@ -1,13 +1,19 @@
1
1
  // Which door a machine verb goes through: without a machine name, this OS
2
2
  // user's own daemon over its local socket; with one, the Teams server of the
3
3
  // selected context, which forwards the channel to that enrolled machine.
4
- import { resolve } from 'node:path';
4
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
5
+ import { hostname } from 'node:os';
6
+ import { dirname, join, resolve } from 'node:path';
5
7
  import { openLocalMachine, openRemoteMachine } from './client.mjs';
6
8
  import { WorkspaceContexts, fromContext } from './context.mjs';
7
9
  import { teamsHome, paths } from './home.mjs';
8
10
 
9
11
  export async function openMachine({ machine = null, context = null, env = process.env, home = teamsHome(env) } = {}) {
10
12
  if (!machine) return openLocalMachine(paths(home).socket);
13
+ // This machine named by its own name is its own daemon, reached over its socket: through the Teams server it waited
14
+ // on the org's machine list and a server channel, and a launch on this box failed whenever the server was rate
15
+ // limited or out of reach (two review resumes, 17:03Z and 17:34Z, retired as never issued).
16
+ if (!machine.startsWith('mach_') && normalMachineName(machine) === normalMachineName(hostname()) && existsSync(paths(home).socket)) return openLocalMachine(paths(home).socket);
11
17
  const supercodeHome = env.SUPERCODE_HOME ?? resolve(home, '..');
12
18
  const contexts = new WorkspaceContexts(supercodeHome);
13
19
  const name = context ?? env.SUPERCODE_CONTEXT ?? contexts.read().current;
@@ -15,7 +21,14 @@ export async function openMachine({ machine = null, context = null, env = proces
15
21
  const { client, context: selected } = fromContext({ supercodeHome, name, fetch: globalThis.fetch });
16
22
  const credential = contexts.credential(selected.credential_ref);
17
23
  let machineId = machine;
18
- if (!machine.startsWith('mach_')) {
24
+ // A machine's name resolves to its id once per ten minutes, kept beside the contexts: every mail to another machine
25
+ // listed the org's machines on the shared credential's allowance (`teams mail <machine>` at 18 a minute).
26
+ const cacheFile = join(supercodeHome, 'cache', 'machine-ids.json');
27
+ const cacheKey = `${selected.origin}|${selected.team_id}|${normalMachineName(machine)}`;
28
+ let cache = {};
29
+ try { cache = JSON.parse(readFileSync(cacheFile, 'utf8')); } catch { /* none yet */ }
30
+ if (!machine.startsWith('mach_') && cache[cacheKey] && Date.now() - cache[cacheKey].at < 600_000) machineId = cache[cacheKey].id;
31
+ else if (!machine.startsWith('mach_')) {
19
32
  const listed = (await client.team(selected.team_id).machines.list()).items;
20
33
  let matches = listed.filter((item) => item.name === machine);
21
34
  // A mail address names a machine by its short, lowercased host name
@@ -24,6 +37,13 @@ export async function openMachine({ machine = null, context = null, env = proces
24
37
  if (!matches.length) matches = listed.filter((item) => normalMachineName(item.name) === normalMachineName(machine));
25
38
  if (matches.length !== 1) throw Object.assign(new Error(matches.length ? `machine name ${machine} is ambiguous; use its id` : `machine ${machine} is not accessible`), { code: matches.length ? 'ambiguous_reference' : 'not_found' });
26
39
  machineId = matches[0].id;
40
+ try {
41
+ mkdirSync(dirname(cacheFile), { recursive: true });
42
+ const fresh = Object.fromEntries(Object.entries(cache).filter(([, entry]) => Date.now() - entry.at < 600_000));
43
+ const temporary = `${cacheFile}.${process.pid}.tmp`;
44
+ writeFileSync(temporary, JSON.stringify({ ...fresh, [cacheKey]: { id: machineId, at: Date.now() } }));
45
+ renameSync(temporary, cacheFile);
46
+ } catch { /* an unwritable cache only costs the next lookup */ }
27
47
  }
28
48
  const channel = await openRemoteMachine({ origin: selected.origin, teamId: selected.team_id, machineId, token: credential.token });
29
49
  channel.machineName = machine;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/teams",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "type": "module",
5
5
  "description": "Volter Teams, the neutral team plane: machine identity and daemon core, the local door and server link, admission from machine grants, product doors, and the Teams server's neutral routes (company RFC 0008).",
6
6
  "license": "MIT",
@@ -49,7 +49,11 @@
49
49
  "./machine-door": "./machine-door.mjs",
50
50
  "./sender-identity": "./sender-identity.mjs",
51
51
  "./board-process-guard": "./board-process-guard.mjs",
52
- "./server/agent-authority": "./server/agent-authority.mjs"
52
+ "./server/agent-authority": "./server/agent-authority.mjs",
53
+ "./redact": {
54
+ "default": "./redact.mjs"
55
+ },
56
+ "./checkout": "./checkout.mjs"
53
57
  },
54
58
  "files": [
55
59
  "LICENSE-MIT",
@@ -60,7 +64,9 @@
60
64
  "viewer",
61
65
  "README.md",
62
66
  "http-client.d.ts",
63
- "identity-record.d.ts"
67
+ "identity-record.d.ts",
68
+ "redact.mjs",
69
+ "redact/"
64
70
  ],
65
71
  "engines": {
66
72
  "node": ">=22"
@@ -0,0 +1 @@
1
+ {"source": "gitleaks v8.30.1 config/gitleaks.toml", "sha256": "e163e53b9e7e8a8511e77271e2b323ed057759542a6d988258afe3a1fa329caf", "rules": [{"id": "1password-secret-key", "regex": "\\bA3-[A-Z0-9]{6}-(?:(?:[A-Z0-9]{11})|(?:[A-Z0-9]{6}-[A-Z0-9]{5}))-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}\\b", "keywords": ["a3-"], "entropy": 3.8}, {"id": "1password-service-account-token", "regex": "ops_eyJ[a-zA-Z0-9+/]{250,}={0,3}", "keywords": ["ops_"], "entropy": 4}, {"id": "adafruit-api-key", "regex": "(?i)[\\w.-]{0,50}?(?:adafruit)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9_-]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["adafruit"]}, {"id": "adobe-client-id", "regex": "(?i)[\\w.-]{0,50}?(?:adobe)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-f0-9]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["adobe"], "entropy": 2}, {"id": "adobe-client-secret", "regex": "\\b(p8e-(?i)[a-z0-9]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["p8e-"], "entropy": 2}, {"id": "age-secret-key", "regex": "AGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L]{58}", "keywords": ["age-secret-key-1"]}, {"id": "airtable-api-key", "regex": "(?i)[\\w.-]{0,50}?(?:airtable)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{17})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["airtable"]}, {"id": "airtable-personnal-access-token", "regex": "\\b(pat[[:alnum:]]{14}\\.[a-f0-9]{64})\\b", "keywords": ["airtable"]}, {"id": "algolia-api-key", "regex": "(?i)[\\w.-]{0,50}?(?:algolia)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["algolia"]}, {"id": "alibaba-access-key-id", "regex": "\\b(LTAI(?i)[a-z0-9]{20})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["ltai"], "entropy": 2}, {"id": "alibaba-secret-key", "regex": "(?i)[\\w.-]{0,50}?(?:alibaba)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{30})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["alibaba"], "entropy": 2}, {"id": "anthropic-admin-api-key", "regex": "\\b(sk-ant-admin01-[a-zA-Z0-9_\\-]{93}AA)(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["sk-ant-admin01"]}, {"id": "anthropic-api-key", "regex": "\\b(sk-ant-api03-[a-zA-Z0-9_\\-]{93}AA)(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["sk-ant-api03"]}, {"id": "artifactory-api-key", "regex": "\\bAKCp[A-Za-z0-9]{69}\\b", "keywords": ["akcp"], "entropy": 4.5}, {"id": "artifactory-reference-token", "regex": "\\bcmVmd[A-Za-z0-9]{59}\\b", "keywords": ["cmvmd"], "entropy": 4.5}, {"id": "asana-client-id", "regex": "(?i)[\\w.-]{0,50}?(?:asana)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([0-9]{16})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["asana"]}, {"id": "asana-client-secret", "regex": "(?i)[\\w.-]{0,50}?(?:asana)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["asana"]}, {"id": "atlassian-api-token", "regex": "(?i)[\\w.-]{0,50}?(?:(?-i:ATLASSIAN|[Aa]tlassian)|(?-i:CONFLUENCE|[Cc]onfluence)|(?-i:JIRA|[Jj]ira))(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{20}[a-f0-9]{4})(?:[\\x60'\"\\s;]|\\\\[nr]|$)|\\b(ATATT3[A-Za-z0-9_\\-=]{186})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["atlassian", "confluence", "jira", "atatt3"], "entropy": 3.5}, {"id": "authress-service-client-access-key", "regex": "\\b((?:sc|ext|scauth|authress)_(?i)[a-z0-9]{5,30}\\.[a-z0-9]{4,6}\\.(?-i:acc)[_-][a-z0-9-]{10,32}\\.[a-z0-9+/_=-]{30,120})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["sc_", "ext_", "scauth_", "authress_"], "entropy": 2}, {"id": "aws-access-token", "regex": "\\b((?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z2-7]{16})\\b", "keywords": ["a3t", "akia", "asia", "abia", "acca"], "entropy": 3}, {"id": "aws-amazon-bedrock-api-key-long-lived", "regex": "\\b(ABSK[A-Za-z0-9+/]{109,269}={0,2})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["absk"], "entropy": 3}, {"id": "aws-amazon-bedrock-api-key-short-lived", "regex": "bedrock-api-key-YmVkcm9jay5hbWF6b25hd3MuY29t", "keywords": ["bedrock-api-key-"], "entropy": 3}, {"id": "azure-ad-client-secret", "regex": "(?:^|[\\\\'\"\\x60\\s>=:(,)])([a-zA-Z0-9_~.]{3}\\dQ~[a-zA-Z0-9_~.-]{31,34})(?:$|[\\\\'\"\\x60\\s<),])", "keywords": ["q~"], "entropy": 3}, {"id": "beamer-api-token", "regex": "(?i)[\\w.-]{0,50}?(?:beamer)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}(b_[a-z0-9=_\\-]{44})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["beamer"]}, {"id": "bitbucket-client-id", "regex": "(?i)[\\w.-]{0,50}?(?:bitbucket)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["bitbucket"]}, {"id": "bitbucket-client-secret", "regex": "(?i)[\\w.-]{0,50}?(?:bitbucket)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9=_\\-]{64})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["bitbucket"]}, {"id": "bittrex-access-key", "regex": "(?i)[\\w.-]{0,50}?(?:bittrex)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["bittrex"]}, {"id": "bittrex-secret-key", "regex": "(?i)[\\w.-]{0,50}?(?:bittrex)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["bittrex"]}, {"id": "cisco-meraki-api-key", "regex": "[\\w.-]{0,50}?(?i:[\\w.-]{0,50}?(?:(?-i:[Mm]eraki|MERAKI))(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3})(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([0-9a-f]{40})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["meraki"], "entropy": 3}, {"id": "clickhouse-cloud-api-secret-key", "regex": "\\b(4b1d[A-Za-z0-9]{38})\\b", "keywords": ["4b1d"], "entropy": 3}, {"id": "clojars-api-token", "regex": "(?i)CLOJARS_[a-z0-9]{60}", "keywords": ["clojars_"], "entropy": 2}, {"id": "cloudflare-api-key", "regex": "(?i)[\\w.-]{0,50}?(?:cloudflare)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9_-]{40})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["cloudflare"], "entropy": 2}, {"id": "cloudflare-global-api-key", "regex": "(?i)[\\w.-]{0,50}?(?:cloudflare)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-f0-9]{37})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["cloudflare"], "entropy": 2}, {"id": "cloudflare-origin-ca-key", "regex": "\\b(v1\\.0-[a-f0-9]{24}-[a-f0-9]{146})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["cloudflare", "v1.0-"], "entropy": 2}, {"id": "codecov-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:codecov)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["codecov"]}, {"id": "cohere-api-token", "regex": "[\\w.-]{0,50}?(?i:[\\w.-]{0,50}?(?:cohere|CO_API_KEY)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3})(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-zA-Z0-9]{40})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["cohere", "co_api_key"], "entropy": 4}, {"id": "coinbase-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:coinbase)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9_-]{64})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["coinbase"]}, {"id": "confluent-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:confluent)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{16})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["confluent"]}, {"id": "confluent-secret-key", "regex": "(?i)[\\w.-]{0,50}?(?:confluent)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{64})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["confluent"]}, {"id": "contentful-delivery-api-token", "regex": "(?i)[\\w.-]{0,50}?(?:contentful)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9=_\\-]{43})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["contentful"]}, {"id": "curl-auth-header", "regex": "\\bcurl\\b(?:.*?|.*?(?:[\\r\\n]{1,2}.*?){1,5})[ \\t\\n\\r](?:-H|--header)(?:=|[ \\t]{0,5})(?:\"(?i)(?:Authorization:[ \\t]{0,5}(?:Basic[ \\t]([a-z0-9+/]{8,}={0,3})|(?:Bearer|(?:Api-)?Token)[ \\t]([\\w=~@.+/-]{8,})|([\\w=~@.+/-]{8,}))|(?:(?:X-(?:[a-z]+-)?)?(?:Api-?)?(?:Key|Token)):[ \\t]{0,5}([\\w=~@.+/-]{8,}))\"|'(?i)(?:Authorization:[ \\t]{0,5}(?:Basic[ \\t]([a-z0-9+/]{8,}={0,3})|(?:Bearer|(?:Api-)?Token)[ \\t]([\\w=~@.+/-]{8,})|([\\w=~@.+/-]{8,}))|(?:(?:X-(?:[a-z]+-)?)?(?:Api-?)?(?:Key|Token)):[ \\t]{0,5}([\\w=~@.+/-]{8,}))')(?:\\B|\\s|\\z)", "keywords": ["curl"], "entropy": 2.75}, {"id": "curl-auth-user", "regex": "\\bcurl\\b(?:.*|.*(?:[\\r\\n]{1,2}.*){1,5})[ \\t\\n\\r](?:-u|--user)(?:=|[ \\t]{0,5})(\"(:[^\"]{3,}|[^:\"]{3,}:|[^:\"]{3,}:[^\"]{3,})\"|'([^:']{3,}:[^']{3,})'|((?:\"[^\"]{3,}\"|'[^']{3,}'|[\\w$@.-]+):(?:\"[^\"]{3,}\"|'[^']{3,}'|[\\w${}@.-]+)))(?:\\s|\\z)", "keywords": ["curl"], "entropy": 2}, {"id": "databricks-api-token", "regex": "\\b(dapi[a-f0-9]{32}(?:-\\d)?)(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["dapi"], "entropy": 3}, {"id": "datadog-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:datadog)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{40})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["datadog"]}, {"id": "defined-networking-api-token", "regex": "(?i)[\\w.-]{0,50}?(?:dnkey)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}(dnkey-[a-z0-9=_\\-]{26}-[a-z0-9=_\\-]{52})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["dnkey"]}, {"id": "digitalocean-access-token", "regex": "\\b(doo_v1_[a-f0-9]{64})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["doo_v1_"], "entropy": 3}, {"id": "digitalocean-pat", "regex": "\\b(dop_v1_[a-f0-9]{64})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["dop_v1_"], "entropy": 3}, {"id": "digitalocean-refresh-token", "regex": "(?i)\\b(dor_v1_[a-f0-9]{64})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["dor_v1_"]}, {"id": "discord-api-token", "regex": "(?i)[\\w.-]{0,50}?(?:discord)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-f0-9]{64})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["discord"]}, {"id": "discord-client-id", "regex": "(?i)[\\w.-]{0,50}?(?:discord)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([0-9]{18})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["discord"], "entropy": 2}, {"id": "discord-client-secret", "regex": "(?i)[\\w.-]{0,50}?(?:discord)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9=_\\-]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["discord"], "entropy": 2}, {"id": "doppler-api-token", "regex": "dp\\.pt\\.(?i)[a-z0-9]{43}", "keywords": ["dp.pt."], "entropy": 2}, {"id": "droneci-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:droneci)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["droneci"]}, {"id": "dropbox-api-token", "regex": "(?i)[\\w.-]{0,50}?(?:dropbox)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{15})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["dropbox"]}, {"id": "dropbox-long-lived-api-token", "regex": "(?i)[\\w.-]{0,50}?(?:dropbox)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{11}(AAAAAAAAAA)[a-z0-9\\-_=]{43})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["dropbox"]}, {"id": "dropbox-short-lived-api-token", "regex": "(?i)[\\w.-]{0,50}?(?:dropbox)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}(sl\\.[a-z0-9\\-=_]{135})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["dropbox"]}, {"id": "duffel-api-token", "regex": "duffel_(?:test|live)_(?i)[a-z0-9_\\-=]{43}", "keywords": ["duffel_"], "entropy": 2}, {"id": "dynatrace-api-token", "regex": "dt0c01\\.(?i)[a-z0-9]{24}\\.[a-z0-9]{64}", "keywords": ["dt0c01."], "entropy": 4}, {"id": "easypost-api-token", "regex": "\\bEZAK(?i)[a-z0-9]{54}\\b", "keywords": ["ezak"], "entropy": 2}, {"id": "easypost-test-api-token", "regex": "\\bEZTK(?i)[a-z0-9]{54}\\b", "keywords": ["eztk"], "entropy": 2}, {"id": "etsy-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:(?-i:ETSY|[Ee]tsy))(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{24})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["etsy"], "entropy": 3}, {"id": "facebook-access-token", "regex": "(?i)\\b(\\d{15,16}(\\||%)[0-9a-z\\-_]{27,40})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["facebook"], "entropy": 3}, {"id": "facebook-page-access-token", "regex": "\\b(EAA[MC](?i)[a-z0-9]{100,})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["eaam", "eaac"], "entropy": 4}, {"id": "facebook-secret", "regex": "(?i)[\\w.-]{0,50}?(?:facebook)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-f0-9]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["facebook"], "entropy": 3}, {"id": "fastly-api-token", "regex": "(?i)[\\w.-]{0,50}?(?:fastly)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9=_\\-]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["fastly"]}, {"id": "finicity-api-token", "regex": "(?i)[\\w.-]{0,50}?(?:finicity)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-f0-9]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["finicity"]}, {"id": "finicity-client-secret", "regex": "(?i)[\\w.-]{0,50}?(?:finicity)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{20})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["finicity"]}, {"id": "finnhub-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:finnhub)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{20})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["finnhub"]}, {"id": "flickr-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:flickr)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["flickr"]}, {"id": "flutterwave-encryption-key", "regex": "FLWSECK_TEST-(?i)[a-h0-9]{12}", "keywords": ["flwseck_test"], "entropy": 2}, {"id": "flutterwave-public-key", "regex": "FLWPUBK_TEST-(?i)[a-h0-9]{32}-X", "keywords": ["flwpubk_test"], "entropy": 2}, {"id": "flutterwave-secret-key", "regex": "FLWSECK_TEST-(?i)[a-h0-9]{32}-X", "keywords": ["flwseck_test"], "entropy": 2}, {"id": "flyio-access-token", "regex": "\\b((?:fo1_[\\w-]{43}|fm1[ar]_[a-zA-Z0-9+\\/]{100,}={0,3}|fm2_[a-zA-Z0-9+\\/]{100,}={0,3}))(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["fo1_", "fm1", "fm2_"], "entropy": 4}, {"id": "frameio-api-token", "regex": "fio-u-(?i)[a-z0-9\\-_=]{64}", "keywords": ["fio-u-"]}, {"id": "freemius-secret-key", "regex": "(?i)[\"']secret_key[\"']\\s*=>\\s*[\"'](sk_[\\S]{29})[\"']", "keywords": ["secret_key"]}, {"id": "freshbooks-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:freshbooks)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{64})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["freshbooks"]}, {"id": "gcp-api-key", "regex": "\\b(AIza[\\w-]{35})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["aiza"], "entropy": 4}, {"id": "generic-api-key", "regex": "(?i)[\\w.-]{0,50}?(?:access|auth|(?-i:[Aa]pi|API)|credential|creds|key|passw(?:or)?d|secret|token)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([\\w.=-]{10,150}|[a-z0-9][a-z0-9+/]{11,}={0,3})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["access", "api", "auth", "key", "credential", "creds", "passwd", "password", "secret", "token"], "entropy": 3.5}, {"id": "github-app-token", "regex": "(?:ghu|ghs)_[0-9a-zA-Z]{36}", "keywords": ["ghu_", "ghs_"], "entropy": 3}, {"id": "github-fine-grained-pat", "regex": "github_pat_\\w{82}", "keywords": ["github_pat_"], "entropy": 3}, {"id": "github-oauth", "regex": "gho_[0-9a-zA-Z]{36}", "keywords": ["gho_"], "entropy": 3}, {"id": "github-pat", "regex": "ghp_[0-9a-zA-Z]{36}", "keywords": ["ghp_"], "entropy": 3}, {"id": "github-refresh-token", "regex": "ghr_[0-9a-zA-Z]{36}", "keywords": ["ghr_"], "entropy": 3}, {"id": "gitlab-cicd-job-token", "regex": "glcbt-[0-9a-zA-Z]{1,5}_[0-9a-zA-Z_-]{20}", "keywords": ["glcbt-"], "entropy": 3}, {"id": "gitlab-deploy-token", "regex": "gldt-[0-9a-zA-Z_\\-]{20}", "keywords": ["gldt-"], "entropy": 3}, {"id": "gitlab-feature-flag-client-token", "regex": "glffct-[0-9a-zA-Z_\\-]{20}", "keywords": ["glffct-"], "entropy": 3}, {"id": "gitlab-feed-token", "regex": "glft-[0-9a-zA-Z_\\-]{20}", "keywords": ["glft-"], "entropy": 3}, {"id": "gitlab-incoming-mail-token", "regex": "glimt-[0-9a-zA-Z_\\-]{25}", "keywords": ["glimt-"], "entropy": 3}, {"id": "gitlab-kubernetes-agent-token", "regex": "glagent-[0-9a-zA-Z_\\-]{50}", "keywords": ["glagent-"], "entropy": 3}, {"id": "gitlab-oauth-app-secret", "regex": "gloas-[0-9a-zA-Z_\\-]{64}", "keywords": ["gloas-"], "entropy": 3}, {"id": "gitlab-pat", "regex": "glpat-[\\w-]{20}", "keywords": ["glpat-"], "entropy": 3}, {"id": "gitlab-pat-routable", "regex": "\\bglpat-[0-9a-zA-Z_-]{27,300}\\.[0-9a-z]{2}[0-9a-z]{7}\\b", "keywords": ["glpat-"], "entropy": 4}, {"id": "gitlab-ptt", "regex": "glptt-[0-9a-f]{40}", "keywords": ["glptt-"], "entropy": 3}, {"id": "gitlab-rrt", "regex": "GR1348941[\\w-]{20}", "keywords": ["gr1348941"], "entropy": 3}, {"id": "gitlab-runner-authentication-token", "regex": "glrt-[0-9a-zA-Z_\\-]{20}", "keywords": ["glrt-"], "entropy": 3}, {"id": "gitlab-runner-authentication-token-routable", "regex": "\\bglrt-t\\d_[0-9a-zA-Z_\\-]{27,300}\\.[0-9a-z]{2}[0-9a-z]{7}\\b", "keywords": ["glrt-"], "entropy": 4}, {"id": "gitlab-scim-token", "regex": "glsoat-[0-9a-zA-Z_\\-]{20}", "keywords": ["glsoat-"], "entropy": 3}, {"id": "gitlab-session-cookie", "regex": "_gitlab_session=[0-9a-z]{32}", "keywords": ["_gitlab_session="], "entropy": 3}, {"id": "gitter-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:gitter)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9_-]{40})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["gitter"]}, {"id": "gocardless-api-token", "regex": "(?i)[\\w.-]{0,50}?(?:gocardless)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}(live_(?i)[a-z0-9\\-_=]{40})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["live_", "gocardless"]}, {"id": "grafana-api-key", "regex": "(?i)\\b(eyJrIjoi[A-Za-z0-9]{70,400}={0,3})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["eyjrijoi"], "entropy": 3}, {"id": "grafana-cloud-api-token", "regex": "(?i)\\b(glc_[A-Za-z0-9+/]{32,400}={0,3})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["glc_"], "entropy": 3}, {"id": "grafana-service-account-token", "regex": "(?i)\\b(glsa_[A-Za-z0-9]{32}_[A-Fa-f0-9]{8})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["glsa_"], "entropy": 3}, {"id": "harness-api-key", "regex": "(?:pat|sat)\\.[a-zA-Z0-9_-]{22}\\.[a-zA-Z0-9]{24}\\.[a-zA-Z0-9]{20}", "keywords": ["pat.", "sat."]}, {"id": "hashicorp-tf-api-token", "regex": "(?i)[a-z0-9]{14}\\.(?-i:atlasv1)\\.[a-z0-9\\-_=]{60,70}", "keywords": ["atlasv1"], "entropy": 3.5}, {"id": "hashicorp-tf-password", "regex": "(?i)[\\w.-]{0,50}?(?:administrator_login_password|password)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}(\"[a-z0-9=_\\-]{8,20}\")(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["administrator_login_password", "password"], "entropy": 2}, {"id": "heroku-api-key", "regex": "(?i)[\\w.-]{0,50}?(?:heroku)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["heroku"]}, {"id": "heroku-api-key-v2", "regex": "\\b((HRKU-AA[0-9a-zA-Z_-]{58}))(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["hrku-aa"], "entropy": 4}, {"id": "hubspot-api-key", "regex": "(?i)[\\w.-]{0,50}?(?:hubspot)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["hubspot"]}, {"id": "huggingface-access-token", "regex": "\\b(hf_(?i:[a-z]{34}))(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["hf_"], "entropy": 2}, {"id": "huggingface-organization-api-token", "regex": "\\b(api_org_(?i:[a-z]{34}))(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["api_org_"], "entropy": 2}, {"id": "infracost-api-token", "regex": "\\b(ico-[a-zA-Z0-9]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["ico-"], "entropy": 3}, {"id": "intercom-api-key", "regex": "(?i)[\\w.-]{0,50}?(?:intercom)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9=_\\-]{60})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["intercom"]}, {"id": "intra42-client-secret", "regex": "\\b(s-s4t2(?:ud|af)-(?i)[abcdef0123456789]{64})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["intra", "s-s4t2ud-", "s-s4t2af-"], "entropy": 3}, {"id": "jfrog-api-key", "regex": "(?i)[\\w.-]{0,50}?(?:jfrog|artifactory|bintray|xray)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{73})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["jfrog", "artifactory", "bintray", "xray"]}, {"id": "jfrog-identity-token", "regex": "(?i)[\\w.-]{0,50}?(?:jfrog|artifactory|bintray|xray)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{64})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["jfrog", "artifactory", "bintray", "xray"]}, {"id": "jwt", "regex": "\\b(ey[a-zA-Z0-9]{17,}\\.ey[a-zA-Z0-9\\/\\\\_-]{17,}\\.(?:[a-zA-Z0-9\\/\\\\_-]{10,}={0,2})?)(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["ey"], "entropy": 3}, {"id": "jwt-base64", "regex": "\\bZXlK(?:(?P<alg>aGJHY2lPaU)|(?P<apu>aGNIVWlPaU)|(?P<apv>aGNIWWlPaU)|(?P<aud>aGRXUWlPaU)|(?P<b64>aU5qUWlP)|(?P<crit>amNtbDBJanBi)|(?P<cty>amRIa2lPaU)|(?P<epk>bGNHc2lPbn)|(?P<enc>bGJtTWlPaU)|(?P<jku>cWEzVWlPaU)|(?P<jwk>cWQyc2lPb)|(?P<iss>cGMzTWlPaU)|(?P<iv>cGRpSTZJ)|(?P<kid>cmFXUWlP)|(?P<key_ops>clpYbGZiM0J6SWpwY)|(?P<kty>cmRIa2lPaUp)|(?P<nonce>dWIyNWpaU0k2)|(?P<p2c>d01tTWlP)|(?P<p2s>d01uTWlPaU)|(?P<ppt>d2NIUWlPaU)|(?P<sub>emRXSWlPaU)|(?P<svt>emRuUWlP)|(?P<tag>MFlXY2lPaU)|(?P<typ>MGVYQWlPaUp)|(?P<url>MWNtd2l)|(?P<use>MWMyVWlPaUp)|(?P<ver>MlpYSWlPaU)|(?P<version>MlpYSnphVzl1SWpv)|(?P<x>NElqb2)|(?P<x5c>NE5XTWlP)|(?P<x5t>NE5YUWlPaU)|(?P<x5ts256>NE5YUWpVekkxTmlJNkl)|(?P<x5u>NE5YVWlPaU)|(?P<zip>NmFYQWlPaU))[a-zA-Z0-9\\/\\\\_+\\-\\r\\n]{40,}={0,2}", "keywords": ["zxlk"], "entropy": 2}, {"id": "kraken-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:kraken)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9\\/=_\\+\\-]{80,90})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["kraken"]}, {"id": "kubernetes-secret-yaml", "regex": "(?i)(?:\\bkind:[ \\t]*[\"']?\\bsecret\\b[\"']?(?s:.){0,200}?\\bdata:(?s:.){0,100}?\\s+([\\w.-]+:(?:[ \\t]*(?:\\||>[-+]?)\\s+)?[ \\t]*(?:[\"']?[a-z0-9+/]{10,}={0,3}[\"']?|\\{\\{[ \\t\\w\"|$:=,.-]+}}|\"\"|''))|\\bdata:(?s:.){0,100}?\\s+([\\w.-]+:(?:[ \\t]*(?:\\||>[-+]?)\\s+)?[ \\t]*(?:[\"']?[a-z0-9+/]{10,}={0,3}[\"']?|\\{\\{[ \\t\\w\"|$:=,.-]+}}|\"\"|''))(?s:.){0,200}?\\bkind:[ \\t]*[\"']?\\bsecret\\b[\"']?)", "keywords": ["secret"]}, {"id": "kucoin-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:kucoin)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-f0-9]{24})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["kucoin"]}, {"id": "kucoin-secret-key", "regex": "(?i)[\\w.-]{0,50}?(?:kucoin)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["kucoin"]}, {"id": "launchdarkly-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:launchdarkly)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9=_\\-]{40})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["launchdarkly"]}, {"id": "linear-api-key", "regex": "lin_api_(?i)[a-z0-9]{40}", "keywords": ["lin_api_"], "entropy": 2}, {"id": "linear-client-secret", "regex": "(?i)[\\w.-]{0,50}?(?:linear)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-f0-9]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["linear"], "entropy": 2}, {"id": "linkedin-client-id", "regex": "(?i)[\\w.-]{0,50}?(?:linked[_-]?in)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{14})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["linkedin", "linked_in", "linked-in"], "entropy": 2}, {"id": "linkedin-client-secret", "regex": "(?i)[\\w.-]{0,50}?(?:linked[_-]?in)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{16})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["linkedin", "linked_in", "linked-in"], "entropy": 2}, {"id": "lob-api-key", "regex": "(?i)[\\w.-]{0,50}?(?:lob)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}((live|test)_[a-f0-9]{35})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["test_", "live_"]}, {"id": "lob-pub-api-key", "regex": "(?i)[\\w.-]{0,50}?(?:lob)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}((test|live)_pub_[a-f0-9]{31})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["test_pub", "live_pub", "_pub"]}, {"id": "looker-client-id", "regex": "(?i)[\\w.-]{0,50}?(?:looker)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{20})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["looker"]}, {"id": "looker-client-secret", "regex": "(?i)[\\w.-]{0,50}?(?:looker)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{24})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["looker"]}, {"id": "mailchimp-api-key", "regex": "(?i)[\\w.-]{0,50}?(?:MailchimpSDK.initialize|mailchimp)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-f0-9]{32}-us\\d\\d)(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["mailchimp"]}, {"id": "mailgun-private-api-token", "regex": "(?i)[\\w.-]{0,50}?(?:mailgun)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}(key-[a-f0-9]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["mailgun"]}, {"id": "mailgun-pub-key", "regex": "(?i)[\\w.-]{0,50}?(?:mailgun)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}(pubkey-[a-f0-9]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["mailgun"]}, {"id": "mailgun-signing-key", "regex": "(?i)[\\w.-]{0,50}?(?:mailgun)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-h0-9]{32}-[a-h0-9]{8}-[a-h0-9]{8})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["mailgun"]}, {"id": "mapbox-api-token", "regex": "(?i)[\\w.-]{0,50}?(?:mapbox)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}(pk\\.[a-z0-9]{60}\\.[a-z0-9]{22})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["mapbox"]}, {"id": "mattermost-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:mattermost)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{26})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["mattermost"]}, {"id": "maxmind-license-key", "regex": "\\b([A-Za-z0-9]{6}_[A-Za-z0-9]{29}_mmk)(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["_mmk"], "entropy": 4}, {"id": "messagebird-api-token", "regex": "(?i)[\\w.-]{0,50}?(?:message[_-]?bird)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{25})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["messagebird", "message-bird", "message_bird"]}, {"id": "messagebird-client-id", "regex": "(?i)[\\w.-]{0,50}?(?:message[_-]?bird)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["messagebird", "message-bird", "message_bird"]}, {"id": "microsoft-teams-webhook", "regex": "https://[a-z0-9]+\\.webhook\\.office\\.com/webhookb2/[a-z0-9]{8}-([a-z0-9]{4}-){3}[a-z0-9]{12}@[a-z0-9]{8}-([a-z0-9]{4}-){3}[a-z0-9]{12}/IncomingWebhook/[a-z0-9]{32}/[a-z0-9]{8}-([a-z0-9]{4}-){3}[a-z0-9]{12}", "keywords": ["webhook.office.com", "webhookb2", "incomingwebhook"]}, {"id": "netlify-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:netlify)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9=_\\-]{40,46})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["netlify"]}, {"id": "new-relic-browser-api-token", "regex": "(?i)[\\w.-]{0,50}?(?:new-relic|newrelic|new_relic)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}(NRJS-[a-f0-9]{19})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["nrjs-"]}, {"id": "new-relic-insert-key", "regex": "(?i)[\\w.-]{0,50}?(?:new-relic|newrelic|new_relic)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}(NRII-[a-z0-9-]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["nrii-"]}, {"id": "new-relic-user-api-id", "regex": "(?i)[\\w.-]{0,50}?(?:new-relic|newrelic|new_relic)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{64})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["new-relic", "newrelic", "new_relic"]}, {"id": "new-relic-user-api-key", "regex": "(?i)[\\w.-]{0,50}?(?:new-relic|newrelic|new_relic)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}(NRAK-[a-z0-9]{27})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["nrak"]}, {"id": "notion-api-token", "regex": "\\b(ntn_[0-9]{11}[A-Za-z0-9]{32}[A-Za-z0-9]{3})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["ntn_"], "entropy": 4}, {"id": "npm-access-token", "regex": "(?i)\\b(npm_[a-z0-9]{36})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["npm_"], "entropy": 2}, {"id": "nuget-config-password", "regex": "(?i)<add key=\\\"(?:(?:ClearText)?Password)\\\"\\s*value=\\\"(.{8,})\\\"\\s*/>", "keywords": ["<add key="], "entropy": 1}, {"id": "nytimes-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:nytimes|new-york-times,|newyorktimes)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9=_\\-]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["nytimes", "new-york-times", "newyorktimes"]}, {"id": "octopus-deploy-api-key", "regex": "\\b(API-[A-Z0-9]{26})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["api-"], "entropy": 3}, {"id": "okta-access-token", "regex": "[\\w.-]{0,50}?(?i:[\\w.-]{0,50}?(?:(?-i:[Oo]kta|OKTA))(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3})(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}(00[\\w=\\-]{40})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["okta"], "entropy": 4}, {"id": "openai-api-key", "regex": "\\b(sk-(?:proj|svcacct|admin)-(?:[A-Za-z0-9_-]{74}|[A-Za-z0-9_-]{58})T3BlbkFJ(?:[A-Za-z0-9_-]{74}|[A-Za-z0-9_-]{58})\\b|sk-[a-zA-Z0-9]{20}T3BlbkFJ[a-zA-Z0-9]{20})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["t3blbkfj"], "entropy": 3}, {"id": "openshift-user-token", "regex": "\\b(sha256~[\\w-]{43})(?:[^\\w-]|\\z)", "keywords": ["sha256~"], "entropy": 3.5}, {"id": "perplexity-api-key", "regex": "\\b(pplx-[a-zA-Z0-9]{48})(?:[\\x60'\"\\s;]|\\\\[nr]|$|\\b)", "keywords": ["pplx-"], "entropy": 4}, {"id": "plaid-api-token", "regex": "(?i)[\\w.-]{0,50}?(?:plaid)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}(access-(?:sandbox|development|production)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["plaid"]}, {"id": "plaid-client-id", "regex": "(?i)[\\w.-]{0,50}?(?:plaid)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{24})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["plaid"], "entropy": 3.5}, {"id": "plaid-secret-key", "regex": "(?i)[\\w.-]{0,50}?(?:plaid)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{30})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["plaid"], "entropy": 3.5}, {"id": "planetscale-api-token", "regex": "\\b(pscale_tkn_(?i)[\\w=\\.-]{32,64})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["pscale_tkn_"], "entropy": 3}, {"id": "planetscale-oauth-token", "regex": "\\b(pscale_oauth_[\\w=\\.-]{32,64})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["pscale_oauth_"], "entropy": 3}, {"id": "planetscale-password", "regex": "(?i)\\b(pscale_pw_(?i)[\\w=\\.-]{32,64})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["pscale_pw_"], "entropy": 3}, {"id": "postman-api-token", "regex": "\\b(PMAK-(?i)[a-f0-9]{24}\\-[a-f0-9]{34})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["pmak-"], "entropy": 3}, {"id": "prefect-api-token", "regex": "\\b(pnu_[a-zA-Z0-9]{36})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["pnu_"], "entropy": 2}, {"id": "private-key", "regex": "(?i)-----BEGIN[ A-Z0-9_-]{0,100}PRIVATE KEY(?: BLOCK)?-----[\\s\\S-]{64,}?KEY(?: BLOCK)?-----", "keywords": ["-----begin"]}, {"id": "privateai-api-token", "regex": "[\\w.-]{0,50}?(?i:[\\w.-]{0,50}?(?:private[_-]?ai)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3})(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{32})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["privateai", "private_ai", "private-ai"], "entropy": 3}, {"id": "pulumi-api-token", "regex": "\\b(pul-[a-f0-9]{40})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["pul-"], "entropy": 2}, {"id": "pypi-upload-token", "regex": "pypi-AgEIcHlwaS5vcmc[\\w-]{50,1000}", "keywords": ["pypi-ageichlwas5vcmc"], "entropy": 3}, {"id": "rapidapi-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:rapidapi)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9_-]{50})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["rapidapi"]}, {"id": "readme-api-token", "regex": "\\b(rdme_[a-z0-9]{70})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["rdme_"], "entropy": 2}, {"id": "rubygems-api-token", "regex": "\\b(rubygems_[a-f0-9]{48})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["rubygems_"], "entropy": 2}, {"id": "scalingo-api-token", "regex": "\\b(tk-us-[\\w-]{48})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["tk-us-"], "entropy": 2}, {"id": "sendbird-access-id", "regex": "(?i)[\\w.-]{0,50}?(?:sendbird)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["sendbird"]}, {"id": "sendbird-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:sendbird)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-f0-9]{40})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["sendbird"]}, {"id": "sendgrid-api-token", "regex": "\\b(SG\\.(?i)[a-z0-9=_\\-\\.]{66})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["sg."], "entropy": 2}, {"id": "sendinblue-api-token", "regex": "\\b(xkeysib-[a-f0-9]{64}\\-(?i)[a-z0-9]{16})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["xkeysib-"], "entropy": 2}, {"id": "sentry-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:sentry)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-f0-9]{64})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["sentry"], "entropy": 3}, {"id": "sentry-org-token", "regex": "\\bsntrys_eyJpYXQiO[a-zA-Z0-9+/]{10,200}(?:LCJyZWdpb25fdXJs|InJlZ2lvbl91cmwi|cmVnaW9uX3VybCI6)[a-zA-Z0-9+/]{10,200}={0,2}_[a-zA-Z0-9+/]{43}(?:[^a-zA-Z0-9+/]|\\z)", "keywords": ["sntrys_eyjpyxqio"], "entropy": 4.5}, {"id": "sentry-user-token", "regex": "\\b(sntryu_[a-f0-9]{64})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["sntryu_"], "entropy": 3.5}, {"id": "settlemint-application-access-token", "regex": "\\b(sm_aat_[a-zA-Z0-9]{16})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["sm_aat"], "entropy": 3}, {"id": "settlemint-personal-access-token", "regex": "\\b(sm_pat_[a-zA-Z0-9]{16})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["sm_pat"], "entropy": 3}, {"id": "settlemint-service-access-token", "regex": "\\b(sm_sat_[a-zA-Z0-9]{16})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["sm_sat"], "entropy": 3}, {"id": "shippo-api-token", "regex": "\\b(shippo_(?:live|test)_[a-fA-F0-9]{40})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["shippo_"], "entropy": 2}, {"id": "shopify-access-token", "regex": "shpat_[a-fA-F0-9]{32}", "keywords": ["shpat_"], "entropy": 2}, {"id": "shopify-custom-access-token", "regex": "shpca_[a-fA-F0-9]{32}", "keywords": ["shpca_"], "entropy": 2}, {"id": "shopify-private-app-access-token", "regex": "shppa_[a-fA-F0-9]{32}", "keywords": ["shppa_"], "entropy": 2}, {"id": "shopify-shared-secret", "regex": "shpss_[a-fA-F0-9]{32}", "keywords": ["shpss_"], "entropy": 2}, {"id": "sidekiq-secret", "regex": "(?i)[\\w.-]{0,50}?(?:BUNDLE_ENTERPRISE__CONTRIBSYS__COM|BUNDLE_GEMS__CONTRIBSYS__COM)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-f0-9]{8}:[a-f0-9]{8})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["bundle_enterprise__contribsys__com", "bundle_gems__contribsys__com"]}, {"id": "sidekiq-sensitive-url", "regex": "(?i)\\bhttps?://([a-f0-9]{8}:[a-f0-9]{8})@(?:gems.contribsys.com|enterprise.contribsys.com)(?:[\\/|\\#|\\?|:]|$)", "keywords": ["gems.contribsys.com", "enterprise.contribsys.com"]}, {"id": "slack-app-token", "regex": "(?i)xapp-\\d-[A-Z0-9]+-\\d+-[a-z0-9]+", "keywords": ["xapp"], "entropy": 2}, {"id": "slack-bot-token", "regex": "xoxb-[0-9]{10,13}-[0-9]{10,13}[a-zA-Z0-9-]*", "keywords": ["xoxb"], "entropy": 3}, {"id": "slack-config-access-token", "regex": "(?i)xoxe.xox[bp]-\\d-[A-Z0-9]{163,166}", "keywords": ["xoxe.xoxb-", "xoxe.xoxp-"], "entropy": 2}, {"id": "slack-config-refresh-token", "regex": "(?i)xoxe-\\d-[A-Z0-9]{146}", "keywords": ["xoxe-"], "entropy": 2}, {"id": "slack-legacy-bot-token", "regex": "xoxb-[0-9]{8,14}-[a-zA-Z0-9]{18,26}", "keywords": ["xoxb"], "entropy": 2}, {"id": "slack-legacy-token", "regex": "xox[os]-\\d+-\\d+-\\d+-[a-fA-F\\d]+", "keywords": ["xoxo", "xoxs"], "entropy": 2}, {"id": "slack-legacy-workspace-token", "regex": "xox[ar]-(?:\\d-)?[0-9a-zA-Z]{8,48}", "keywords": ["xoxa", "xoxr"], "entropy": 2}, {"id": "slack-user-token", "regex": "xox[pe](?:-[0-9]{10,13}){3}-[a-zA-Z0-9-]{28,34}", "keywords": ["xoxp-", "xoxe-"], "entropy": 2}, {"id": "slack-webhook-url", "regex": "(?:https?://)?hooks.slack.com/(?:services|workflows|triggers)/[A-Za-z0-9+/]{43,56}", "keywords": ["hooks.slack.com"]}, {"id": "snyk-api-token", "regex": "(?i)[\\w.-]{0,50}?(?:snyk[_.-]?(?:(?:api|oauth)[_.-]?)?(?:key|token))(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["snyk"]}, {"id": "sonar-api-token", "regex": "(?i)[\\w.-]{0,50}?(?:sonar[_.-]?(login|token))(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}((?:squ_|sqp_|sqa_)?[a-z0-9=_\\-]{40})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "secretGroup": 2, "keywords": ["sonar"]}, {"id": "sourcegraph-access-token", "regex": "(?i)\\b(\\b(sgp_(?:[a-fA-F0-9]{16}|local)_[a-fA-F0-9]{40}|sgp_[a-fA-F0-9]{40}|[a-fA-F0-9]{40})\\b)(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["sgp_", "sourcegraph"], "entropy": 3}, {"id": "square-access-token", "regex": "\\b((?:EAAA|sq0atp-)[\\w-]{22,60})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["sq0atp-", "eaaa"], "entropy": 2}, {"id": "squarespace-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:squarespace)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["squarespace"]}, {"id": "stripe-access-token", "regex": "\\b((?:sk|rk)_(?:test|live|prod)_[a-zA-Z0-9]{10,99})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["sk_test", "sk_live", "sk_prod", "rk_test", "rk_live", "rk_prod"], "entropy": 2}, {"id": "sumologic-access-id", "regex": "[\\w.-]{0,50}?(?i:[\\w.-]{0,50}?(?:(?-i:[Ss]umo|SUMO))(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3})(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}(su[a-zA-Z0-9]{12})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["sumo"], "entropy": 3}, {"id": "sumologic-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:(?-i:[Ss]umo|SUMO))(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{64})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["sumo"], "entropy": 3}, {"id": "telegram-bot-api-token", "regex": "(?i)[\\w.-]{0,50}?(?:telegr)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([0-9]{5,16}:(?-i:A)[a-z0-9_\\-]{34})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["telegr"]}, {"id": "travisci-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:travis)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{22})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["travis"]}, {"id": "twilio-api-key", "regex": "SK[0-9a-fA-F]{32}", "keywords": ["sk"], "entropy": 3}, {"id": "twitch-api-token", "regex": "(?i)[\\w.-]{0,50}?(?:twitch)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{30})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["twitch"]}, {"id": "twitter-access-secret", "regex": "(?i)[\\w.-]{0,50}?(?:twitter)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{45})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["twitter"]}, {"id": "twitter-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:twitter)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([0-9]{15,25}-[a-zA-Z0-9]{20,40})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["twitter"]}, {"id": "twitter-api-key", "regex": "(?i)[\\w.-]{0,50}?(?:twitter)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{25})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["twitter"]}, {"id": "twitter-api-secret", "regex": "(?i)[\\w.-]{0,50}?(?:twitter)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{50})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["twitter"]}, {"id": "twitter-bearer-token", "regex": "(?i)[\\w.-]{0,50}?(?:twitter)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}(A{22}[a-zA-Z0-9%]{80,100})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["twitter"]}, {"id": "typeform-api-token", "regex": "(?i)[\\w.-]{0,50}?(?:typeform)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}(tfp_[a-z0-9\\-_\\.=]{59})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["tfp_"]}, {"id": "vault-batch-token", "regex": "\\b(hvb\\.[\\w-]{138,300})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["hvb."], "entropy": 4}, {"id": "vault-service-token", "regex": "\\b((?:hvs\\.[\\w-]{90,120}|s\\.(?i:[a-z0-9]{24})))(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["hvs.", "s."], "entropy": 3.5}, {"id": "yandex-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:yandex)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}(t1\\.[A-Z0-9a-z_-]+[=]{0,2}\\.[A-Z0-9a-z_-]{86}[=]{0,2})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["yandex"]}, {"id": "yandex-api-key", "regex": "(?i)[\\w.-]{0,50}?(?:yandex)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}(AQVN[A-Za-z0-9_\\-]{35,38})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["yandex"]}, {"id": "yandex-aws-access-token", "regex": "(?i)[\\w.-]{0,50}?(?:yandex)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}(YC[a-zA-Z0-9_\\-]{38})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["yandex"]}, {"id": "zendesk-secret-key", "regex": "(?i)[\\w.-]{0,50}?(?:zendesk)(?:[ \\t\\w.-]{0,20})[\\s'\"]{0,3}(?:=|>|:{1,3}=|\\|\\||:|=>|\\?=|,)[\\x60'\"\\s=]{0,5}([a-z0-9]{40})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", "keywords": ["zendesk"]}]}
package/redact.mjs ADDED
@@ -0,0 +1,219 @@
1
+ // Text leaving this machine for a Teams server is redacted first: a secret pasted into a card, a session or a mail is
2
+ // replaced by a marker naming what it was (`[redacted:<variable>]`, `[redacted:<rule>]`), never its value. Two sources:
3
+ // - the values this machine keeps in its custody files (KEY=value lines), which no detector knows; the files are named
4
+ // in `<supercode home>/custody.json` ({ "files": [...] }) and in SUPERCODE_CUSTODY_FILES (path-separated);
5
+ // - gitleaks' default rule set, vendored unchanged as redact/gitleaks-rules.json (its source and sha256 inside): each
6
+ // rule's keywords gate it, its secret group is what is replaced, and its entropy floor is kept.
7
+ // Local files are never touched; only what is sent is redacted.
8
+ import { readFileSync, existsSync, statSync } from 'node:fs';
9
+ import { createHash } from 'node:crypto';
10
+ import { delimiter, join } from 'node:path';
11
+ import { homedir } from 'node:os';
12
+
13
+ const vendored = JSON.parse(readFileSync(new URL('./redact/gitleaks-rules.json', import.meta.url), 'utf8'));
14
+
15
+ // A pattern's leading runs that may be empty (`[\w.-]{0,50}?`, 101 of gitleaks' rules, two in a row on the
16
+ // keyword-context rules) only widen the match to the left; with them gone the same secret groups are found, the match
17
+ // starting later. Go's RE2 runs them in linear time, but JavaScript backtracks every split of them at every start: two
18
+ // of them on a 5 MB tool result held the connector's one thread for minutes, its link with it.
19
+ const LEADING_RUN = /^(?:\[(?:\\.|[^\]\\])*\]|\\[wWsSdD]|\.)(?:\{0,\d+\}|\*)\??/;
20
+ // Only into groups that capture nothing: a capturing group may be the secret, which must be replaced whole.
21
+ const OPENER = /^(?:\(\?i\)|\(\?i:|\(\?:)/;
22
+ function dropLeadingRuns(source) {
23
+ let src = source, head = 0;
24
+ for (;;) {
25
+ const opener = OPENER.exec(src.slice(head));
26
+ if (opener) { head += opener[0].length; continue; }
27
+ const run = LEADING_RUN.exec(src.slice(head));
28
+ if (!run) return src;
29
+ src = src.slice(0, head) + src.slice(head + run[0].length);
30
+ }
31
+ }
32
+
33
+ // Go's `(?i)` in the middle of a pattern applies to the rest of its group; JavaScript writes that as `(?i:…)`.
34
+ function jsPattern(source) {
35
+ let flags = 'g', src = dropLeadingRuns(source);
36
+ if (src.startsWith('(?i)')) { src = src.slice(4); flags += 'i'; }
37
+ let out = '', depth = 0; const close = [];
38
+ for (let i = 0; i < src.length; i += 1) {
39
+ const ch = src[i];
40
+ if (ch === '\\') { out += ch + (src[i + 1] ?? ''); i += 1; continue; }
41
+ if (ch === '[') { const end = src.indexOf(']', i + 2); out += src.slice(i, end + 1); i = end; continue; }
42
+ if (src.startsWith('(?i)', i)) { out += '(?i:'; close.push(depth); i += 3; continue; }
43
+ if (ch === '(') depth += 1;
44
+ if (ch === ')') { while (close.length && close.at(-1) === depth) { out += ')'; close.pop(); } depth -= 1; }
45
+ out += ch;
46
+ }
47
+ while (close.length) { out += ')'; close.pop(); }
48
+ try { return new RegExp(out, flags); } catch { /* a Node without modifier groups: case-insensitive throughout */ }
49
+ try { return new RegExp(src.replaceAll('(?i)', ''), flags.includes('i') ? flags : `${flags}i`); } catch { return null; }
50
+ }
51
+
52
+ const entropy = (text) => { const counts = {}; for (const ch of text) counts[ch] = (counts[ch] ?? 0) + 1; let h = 0; for (const n of Object.values(counts)) { const p = n / text.length; h -= p * Math.log2(p); } return h; };
53
+ // The longest text a pattern can match (Infinity when a run is unbounded): a rule whose match is bounded and must
54
+ // contain one of its keywords is run only on the text around each place a keyword occurs, not on the whole string.
55
+ function longestMatch(source) {
56
+ let i = 0;
57
+ const atom = () => {
58
+ const ch = source[i];
59
+ if (ch === '\\') {
60
+ const next = source[i + 1]; i += 2;
61
+ if (next === 'x') { i += 2; return 1; }
62
+ if (next === 'u') { if (source[i] === '{') i = source.indexOf('}', i) + 1; else i += 4; return 1; }
63
+ if (next === 'b' || next === 'B') return 0;
64
+ if (/[1-9]/.test(next)) return Infinity;
65
+ return 1;
66
+ }
67
+ if (ch === '[') { i += 1; if (source[i] === '^') i += 1; if (source[i] === ']') i += 1; while (i < source.length && source[i] !== ']') i += source[i] === '\\' ? 2 : 1; i += 1; return 1; }
68
+ if (ch === '(') {
69
+ i += 1; let look = false;
70
+ if (source[i] === '?') {
71
+ if (source.startsWith('?=', i) || source.startsWith('?!', i)) { look = true; i += 2; }
72
+ else if (source.startsWith('?<=', i) || source.startsWith('?<!', i)) { look = true; i += 3; }
73
+ else if (source[i + 1] === '<') i = source.indexOf('>', i) + 1;
74
+ else { const colon = source.indexOf(':', i); i = colon + 1; }
75
+ }
76
+ const inner = alternatives(); i += 1; // ')'
77
+ return look ? 0 : inner;
78
+ }
79
+ if (ch === '^' || ch === '$') { i += 1; return 0; }
80
+ i += 1; return 1;
81
+ };
82
+ const sequence = () => {
83
+ let total = 0;
84
+ while (i < source.length && source[i] !== '|' && source[i] !== ')') {
85
+ let one = atom();
86
+ const q = source[i];
87
+ if (q === '*' || q === '+') { one = one ? Infinity : 0; i += 1; }
88
+ else if (q === '?') { i += 1; }
89
+ else if (q === '{' && /^\{\d+(,\d*)?\}/.test(source.slice(i))) {
90
+ const [, n, m] = /^\{(\d+)(?:,(\d*))?\}/.exec(source.slice(i));
91
+ const most = m === undefined ? Number(n) : m === '' ? Infinity : Number(m);
92
+ one = one === 0 ? 0 : one * most; i = source.indexOf('}', i) + 1;
93
+ }
94
+ if (source[i] === '?') i += 1; // lazy
95
+ total += one;
96
+ }
97
+ return total;
98
+ };
99
+ const alternatives = () => { let most = sequence(); while (source[i] === '|') { i += 1; most = Math.max(most, sequence()); } return most; };
100
+ try { const most = alternatives(); return i >= source.length ? most : Infinity; } catch { return Infinity; }
101
+ }
102
+
103
+ const RULES = vendored.rules.map((rule) => { const regex = jsPattern(rule.regex); return { id: rule.id, regex, longest: regex ? longestMatch(regex.source) : Infinity, group: rule.secretGroup ?? null, keywords: (rule.keywords ?? []).map((k) => k.toLowerCase()), entropy: rule.entropy ?? null }; }).filter((rule) => rule.regex);
104
+ export const RULESET = { source: vendored.source, sha256: vendored.sha256, rules: RULES.length };
105
+
106
+ // Where each of many literals occurs, in one pass over the text (gitleaks finds its keywords the same way, with one
107
+ // automaton): a lookahead finds every position some literal starts at, and only those are compared literal by literal.
108
+ // Hundreds of separate includes() read a 5 MB tool result hundreds of times.
109
+ const escapeLiteral = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
110
+ function literalScanner(literals) {
111
+ const unique = [...new Set(literals)].filter(Boolean);
112
+ if (!unique.length) return () => new Map();
113
+ const starts = new RegExp(`(?=(?:${[...unique].sort((a, b) => b.length - a.length).map(escapeLiteral).join('|')}))`, 'g');
114
+ return (text) => {
115
+ const hits = new Map();
116
+ for (const { index } of text.matchAll(starts)) for (const literal of unique) if (text.startsWith(literal, index)) { if (!hits.has(literal)) hits.set(literal, []); hits.get(literal).push(index); }
117
+ return hits;
118
+ };
119
+ }
120
+ const keywordHits = literalScanner(RULES.flatMap((rule) => rule.keywords));
121
+ const EVERY_RULE_KEYED = RULES.every((rule) => rule.keywords.length);
122
+ const anyKeyword = new RegExp([...new Set(RULES.flatMap((rule) => rule.keywords))].sort((a, b) => b.length - a.length).map(escapeLiteral).join('|') || '(?!)');
123
+
124
+ // The custody values are read again when custody.json or a file it names changes (looked at once every
125
+ // CUSTODY_LOOK_MS): a value added while a connector runs is redacted from then on, not from its next restart.
126
+ const CUSTODY_LOOK_MS = 30_000;
127
+ let custodyCache = null, custodyStamp = null, custodyLookedAt = 0;
128
+ const stampOf = (file) => { try { const s = statSync(file); return `${s.ino}:${s.size}:${s.mtimeMs}`; } catch { return '-'; } };
129
+ function custodyFiles(env) {
130
+ const home = env.SUPERCODE_HOME || join(env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'supercode');
131
+ let files = String(env.SUPERCODE_CUSTODY_FILES ?? '').split(delimiter).filter(Boolean);
132
+ try { files = files.concat(JSON.parse(readFileSync(join(home, 'custody.json'), 'utf8')).files ?? []); } catch { /* none named */ }
133
+ return { files, stamp: [join(home, 'custody.json'), ...files].map(stampOf).join('|') };
134
+ }
135
+ /** Each custody value (at least 8 characters) with the variable that holds it, longest first. */
136
+ export function custodyValues(env = process.env) {
137
+ const now = Date.now();
138
+ if (custodyCache && now - custodyLookedAt < CUSTODY_LOOK_MS) return custodyCache;
139
+ custodyLookedAt = now;
140
+ const { files, stamp } = custodyFiles(env);
141
+ if (custodyCache && stamp === custodyStamp) return custodyCache;
142
+ custodyStamp = stamp;
143
+ const values = new Map();
144
+ for (const file of files) {
145
+ let text = ''; try { if (existsSync(file)) text = readFileSync(file, 'utf8'); } catch { continue; }
146
+ for (const line of text.split('\n')) {
147
+ const m = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(line);
148
+ if (!m) continue;
149
+ const value = m[2].trim().replace(/^(['"])(.*)\1$/, '$2');
150
+ // A custody file also holds plain settings (a URL, a host, a number): only a secret is a value to redact, by its
151
+ // name (…KEY, …TOKEN, …SECRET, …PASSWORD, …COOKIE, …CREDENTIAL…) or by being long and random.
152
+ const named = /(?:KEY|TOKEN|SECRET|PASSW|COOKIE|CREDENTIAL|PRIVATE|AUTH|SESSION|DSN|WEBHOOK)/i.test(m[1]);
153
+ const plain = /^(?:https?:\/\/|\/|~|[0-9.:,_-]+$|true$|false$)/i.test(value);
154
+ if (value.length >= 8 && !plain && (named || (value.length >= 20 && entropy(value) >= 3.5)) && !values.has(value)) values.set(value, m[1]);
155
+ }
156
+ }
157
+ custodyCache = [...values].map(([value, name]) => ({ value, name })).sort((a, b) => b.value.length - a.value.length);
158
+ custodyBasis = createHash('sha256').update(JSON.stringify([RULESET.sha256, custodyCache.map(({ value }) => value)])).digest('hex');
159
+ return custodyCache;
160
+ }
161
+ let custodyBasis = null;
162
+ /** What a redaction was made under (the rule set and the custody values), as a digest: text found clean under one
163
+ * basis is clean under it again, and needs another look only when it changes. */
164
+ export function redactionBasis(env = process.env) { custodyValues(env); return custodyBasis; }
165
+
166
+ // Text this far either side of a keyword is searched by a bounded rule, beyond its longest match.
167
+ const WINDOW_MARGIN = 256;
168
+ /** The spans of a text within `reach` of an occurrence (`hits`) of one of `keywords`, merged. */
169
+ function keywordSpans(hits, length, keywords, reach) {
170
+ const spans = [];
171
+ for (const keyword of keywords) for (const at of hits.get(keyword) ?? []) spans.push([Math.max(0, at - reach), Math.min(length, at + keyword.length + reach)]);
172
+ spans.sort((a, b) => a[0] - b[0]);
173
+ const merged = [];
174
+ for (const span of spans) { const last = merged.at(-1); if (last && span[0] <= last[1]) last[1] = Math.max(last[1], span[1]); else merged.push(span); }
175
+ return merged;
176
+ }
177
+
178
+ /** `text` with every secret replaced by its marker. */
179
+ export function redactText(text, env = process.env) {
180
+ if (typeof text !== 'string' || text.length < 8) return text;
181
+ let out = text;
182
+ // Custody values are random, so one alternation of them gives the regex engine nothing to skip on: each is looked
183
+ // for on its own, which reads a 5 MB text in a few dozen milliseconds.
184
+ for (const { value, name } of custodyValues(env)) if (out.includes(value)) out = out.split(value).join(`[redacted:${name}]`);
185
+ // Every rule has keywords, so a text holding none of them is done (the one test is cheap; finding where each keyword
186
+ // occurs is not, and most texts are short and hold none).
187
+ let lower = out.toLowerCase();
188
+ if (EVERY_RULE_KEYED && !anyKeyword.test(lower)) return out;
189
+ let hits = keywordHits(lower);
190
+ for (const rule of RULES) {
191
+ if (rule.keywords.length && !rule.keywords.some((k) => hits.has(k))) continue;
192
+ const replace = (text) => {
193
+ rule.regex.lastIndex = 0;
194
+ return text.replace(rule.regex, (...match) => {
195
+ // as gitleaks reads a rule: its secret group, else its first group, else the whole match
196
+ const whole = match[0], secret = rule.group ? match[rule.group] : typeof match[1] === 'string' ? match[1] : whole;
197
+ if (!secret || secret.startsWith('[redacted:')) return whole;
198
+ if (rule.entropy && entropy(secret) < rule.entropy) return whole;
199
+ return whole.replace(secret, `[redacted:${rule.id}]`);
200
+ });
201
+ };
202
+ // gitleaks runs a rule whose keyword occurs over the whole text; a bounded rule's match holds a keyword, so here
203
+ // it runs only around each one (lowercasing that changes a length, outside ASCII, falls back to the whole text).
204
+ const spans = rule.keywords.length && Number.isFinite(rule.longest) && lower.length === out.length ? keywordSpans(hits, out.length, rule.keywords, rule.longest + WINDOW_MARGIN) : null;
205
+ let next;
206
+ if (!spans) next = replace(out);
207
+ else { next = ''; let from = 0; for (const [start, end] of spans) { next += out.slice(from, start) + replace(out.slice(start, end)); from = end; } next += out.slice(from); }
208
+ if (next !== out) { out = next; lower = out.toLowerCase(); hits = keywordHits(lower); }
209
+ }
210
+ return out;
211
+ }
212
+
213
+ /** A JSON value with every string in it redacted (keys kept). */
214
+ export function redactValue(value, env = process.env) {
215
+ if (typeof value === 'string') return redactText(value, env);
216
+ if (Array.isArray(value)) return value.map((item) => redactValue(item, env));
217
+ if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, redactValue(item, env)]));
218
+ return value;
219
+ }
@@ -8,6 +8,9 @@ import { APP_EVENTS, PUBLISHABLE_EVENTS, eventReadAction } from './event-catalog
8
8
  import { EVENT_DATA_LIMIT } from '../event-limits.mjs';
9
9
 
10
10
  const stmt = (sql, ...params) => ({sql, params});
11
+ // A Durable Object binds at most 100 parameters to a statement, so id lists go in batches.
12
+ const batches = (list, size = 90) => Array.from({length: Math.ceil(list.length / size)}, (_, at) => list.slice(at * size, (at + 1) * size));
13
+ const holes = list => list.map(() => '?').join(',');
11
14
  const fail = (status,code,message) => {throw Object.assign(new Error(message),{status,code});};
12
15
  const text = (value,label) => {if(typeof value!=='string'||!value||value.length>256||/[\x00-\x1f]/.test(value))fail(400,'invalid_request',`${label} must be a nonempty string of at most 256 characters`);return value;};
13
16
  const eventOf = row => ({id:row.id,org:row.team_id,at:row.created_at,actor:JSON.parse(row.actor_json),kind:row.kind,resource:{kind:row.resource_kind,id:row.resource_id},data:JSON.parse(row.data_json)});
@@ -84,6 +87,68 @@ export async function publishedEventRoute(ctx) {
84
87
  await guardedMutation(api,res,auth,guards,[stmt('INSERT INTO org_events(id,team_id,source_id,source_revision,kind,resource_kind,resource_id,actor_json,data_json,created_at) VALUES (?,?,?,?,?,?,?,?,?,?)',body.id,auth.team_id,publication[1],body.revision,body.kind,body.resource.kind,body.resource.id,JSON.stringify(actor),JSON.stringify(body.data),now)],{status:201,body:response,targetId:body.resource.id});
85
88
  send(res,201,response,requestId);return true;
86
89
  }
90
+ // An admin purges published events by id: each one's data becomes a tombstone (and so does the resource snapshot it
91
+ // left), its row and sequence kept so followers' cursors hold. The request names ids only, never the text purged, and
92
+ // the log line names the ids. A secret pasted into a card reaches here with the card; this is how it leaves.
93
+ if(sub==='/events/purge'&&req.method==='POST'){
94
+ if(!isAdmin(auth))fail(403,'forbidden','an admin purges events');
95
+ const body=await parseJson(req);exact(body,['events']);
96
+ if(!Array.isArray(body.events)||!body.events.length||body.events.length>500||body.events.some(id=>typeof id!=='string'||!/^event_[0-9a-f]{64}$/.test(id)))fail(400,'invalid_request','events must name 1 to 500 event ids');
97
+ const ids=[...new Set(body.events)],found=[];
98
+ for(const part of batches(ids))found.push(...(await api.store.query(`SELECT id FROM org_events WHERE team_id=? AND id IN (${holes(part)})`,[auth.team_id,...part])).map(row=>row.id));
99
+ const tombstone='{"purged":true}',response=result(api,auth,{purged:found,unknown:ids.filter(id=>!found.includes(id))});
100
+ await guardedMutation(api,res,auth,authorityGuards(auth,api.clock.nowMs()),batches(found).flatMap(part=>[
101
+ stmt(`UPDATE org_events SET data_json=? WHERE team_id=? AND id IN (${holes(part)})`,tombstone,auth.team_id,...part),
102
+ stmt(`UPDATE event_resources SET event_json=json_set(event_json,'$.data',json(?)) WHERE team_id=? AND json_extract(event_json,'$.id') IN (${holes(part)})`,tombstone,auth.team_id,...part),
103
+ // Each event was also copied whole into the webhook body of every subscribed installation (migration 015): pending
104
+ // rows would still deliver it, and finished ones keep it for a week.
105
+ stmt(`UPDATE app_webhook_deliveries SET body=json_set(body,'$.data',json(?)) WHERE installation_id IN (SELECT id FROM app_installations WHERE team_id=?) AND event_id IN (${holes(part)})`,tombstone,auth.team_id,...part),
106
+ ]),{status:200,body:response,targetId:null});
107
+ console.log(`events purged by ${auth.principal_id}: ${found.join(',') || 'none'}`);
108
+ send(res,200,response,requestId);return true;
109
+ }
110
+ // An admin redacts text from named sessions' recorded messages and the captures they came from. The text is in the
111
+ // request body (never a URL, never logged); the answer and the log line are counts per session. Ids alone cannot find
112
+ // which messages hold a pasted secret, and a search would carry it in a URL.
113
+ if(sub==='/sessions/purge'&&req.method==='POST'){
114
+ if(!isAdmin(auth))fail(403,'forbidden','an admin purges session text');
115
+ const body=await parseJson(req);exact(body,['sessions','values']);
116
+ if(!Array.isArray(body.sessions)||!body.sessions.length||body.sessions.length>20||body.sessions.some(id=>typeof id!=='string'||!/^ses_[0-9a-f]{32}$/.test(id)))fail(400,'invalid_request','sessions must name 1 to 20 session ids');
117
+ if(!Array.isArray(body.values)||!body.values.length||body.values.length>20||body.values.some(v=>typeof v!=='string'||v.length<16||v.length>512||/["\\\u0000-\u001f]/.test(v)))fail(400,'invalid_request','values must be 1 to 20 strings of 16 to 512 characters with no quotes, backslashes or control characters');
118
+ const marker='[redacted]',counts={};
119
+ for(const session of new Set(body.sessions)){
120
+ const natives=(await api.store.query("SELECT DISTINCT json_extract(descriptor_json,'$.native_session_id') AS native FROM bindings WHERE team_id=? AND session_id=?",[auth.team_id,session])).map(row=>row.native).filter(Boolean);
121
+ const statements=[];
122
+ for(const value of body.values){
123
+ statements.push(stmt('UPDATE messages SET value_json=replace(value_json,?,?),search_text=replace(search_text,?,?) WHERE team_id=? AND session_id=? AND (instr(value_json,?)>0 OR instr(search_text,?)>0)',value,marker,value,marker,auth.team_id,session,value,value));
124
+ if(natives.length)statements.push(stmt(`UPDATE capture_parts SET payload_json=replace(payload_json,?,?) WHERE team_id=? AND instr(payload_json,?)>0 AND capture_id IN (SELECT id FROM captures WHERE team_id=? AND native_id IN (${natives.map(()=>'?').join(',')}))`,value,marker,auth.team_id,value,auth.team_id,...natives));
125
+ }
126
+ // The native transcript each capture uploaded (and any export made from it) holds the text byte for byte, behind
127
+ // a manifest's sha256: those files go whole, so a later native export or continuation answers fidelity_unavailable
128
+ // rather than the secret. An upload or content-addressed chunk another session still names is kept, and the answer
129
+ // counts how many kept chunks still hold a value.
130
+ const mine="SELECT upload_id FROM session_artifacts WHERE team_id=? AND session_id=?",others="SELECT 1 FROM session_artifacts o WHERE o.team_id=? AND o.session_id<>? AND o.upload_id=",both=[auth.team_id,session,auth.team_id,session];
131
+ const uploads=(await api.store.query(`SELECT DISTINCT upload_id FROM session_artifacts a WHERE team_id=? AND session_id=? AND NOT EXISTS (${others}a.upload_id)`,both)).map(row=>row.upload_id);
132
+ const blobs=(await api.store.query(`SELECT DISTINCT sha256 FROM artifact_chunk_refs r WHERE team_id=? AND upload_id IN (${mine}) AND NOT EXISTS (${others}r.upload_id)`,[auth.team_id,...both])).map(row=>row.sha256);
133
+ statements.push(
134
+ stmt(`DELETE FROM artifact_chunks WHERE team_id=? AND upload_id IN (${mine}) AND NOT EXISTS (${others}artifact_chunks.upload_id)`,auth.team_id,...both),
135
+ stmt(`DELETE FROM artifact_chunk_refs WHERE team_id=? AND upload_id IN (${mine}) AND NOT EXISTS (${others}artifact_chunk_refs.upload_id)`,auth.team_id,...both),
136
+ stmt('DELETE FROM session_artifacts WHERE team_id=? AND session_id=?',auth.team_id,session),
137
+ ...batches(uploads).map(part=>stmt(`DELETE FROM uploads WHERE team_id=? AND id IN (${holes(part)}) AND NOT EXISTS (SELECT 1 FROM session_artifacts a WHERE a.team_id=uploads.team_id AND a.upload_id=uploads.id)`,auth.team_id,...part)),
138
+ ...batches(blobs).map(part=>stmt(`DELETE FROM artifact_blobs WHERE team_id=? AND sha256 IN (${holes(part)}) AND NOT EXISTS (SELECT 1 FROM artifact_chunk_refs r WHERE r.team_id=artifact_blobs.team_id AND r.sha256=artifact_blobs.sha256)`,auth.team_id,...part)));
139
+ const before=await api.store.one(`SELECT (SELECT count(*) FROM messages WHERE team_id=? AND session_id=? AND (${body.values.map(()=>'instr(value_json,?)>0').join(' OR ')})) messages`,[auth.team_id,session,...body.values]);
140
+ await api.store.guardedTransaction(authorityGuards(auth,api.clock.nowMs()),statements);
141
+ let kept=0,holding=0;
142
+ for(const part of batches(blobs,70)){
143
+ const row=await api.store.one(`SELECT count(*) kept,coalesce(sum(${body.values.map(()=>'instr(CAST(bytes AS TEXT),?)>0').join(' OR ')}),0) holding FROM artifact_blobs WHERE team_id=? AND sha256 IN (${holes(part)})`,[...body.values,auth.team_id,...part]);
144
+ kept+=row?.kept??0;holding+=row?.holding??0;
145
+ }
146
+ const left=await api.store.one('SELECT count(*) n FROM session_artifacts WHERE team_id=? AND session_id=?',[auth.team_id,session]);
147
+ counts[session]={messages:before?.messages??0,natives:natives.length,artifacts_removed:uploads.length,artifacts_left:left?.n??0,chunks_removed:blobs.length-kept,shared_chunks_kept:kept,shared_chunks_holding_value:holding};
148
+ }
149
+ console.log(`session text purged by ${auth.principal_id}: ${JSON.stringify(counts)}`);
150
+ send(res,200,result(api,auth,{sessions:counts}),requestId);return true;
151
+ }
87
152
  if(sub==='/events/catalogue'&&req.method==='GET'){send(res,200,result(api,auth,{kinds:APP_EVENTS}),requestId);return true;}
88
153
  if(sub==='/events'&&req.method==='GET'){await eventStream(ctx);return true;}
89
154
  return false;