codiedev 0.9.88 → 0.9.90
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/dist/captureLog.d.ts +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +11 -8
- package/dist/codie.js +56 -4
- package/dist/codieLauncher.d.ts +3 -0
- package/dist/codieLauncher.js +16 -1
- package/dist/commands/ping.d.ts +10 -1
- package/dist/commands/ping.js +39 -10
- package/dist/commands/send.d.ts +1 -0
- package/dist/commands/send.js +15 -55
- package/dist/commands/share.js +2 -2
- package/dist/hook.js +47 -0
- package/dist/mcp.js +14 -29
- package/dist/promptGuard.d.ts +63 -0
- package/dist/promptGuard.js +261 -0
- package/dist/scrub/promptRules.d.ts +119 -0
- package/dist/scrub/promptRules.js +941 -0
- package/dist/utils.d.ts +5 -1
- package/dist/utils.js +52 -13
- package/package.json +1 -1
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Stop a prompt that has a key in it before it reaches the model. Off unless
|
|
3
|
+
// a company admin turns it on (Team Security → Settings → Stop prompts with a
|
|
4
|
+
// key). Everything happens on the laptop: the prompt and the key are never
|
|
5
|
+
// sent anywhere; when a prompt is stopped the server only hears which kind of
|
|
6
|
+
// key it was and which harness, for the team's activity feed.
|
|
7
|
+
//
|
|
8
|
+
// Precision over recall: only a provider's own key format (sk-ant-…, ghp_…,
|
|
9
|
+
// AKIA…), a private key block, a password in a URL, or one of the company's
|
|
10
|
+
// own token formats stops a prompt, and only when the value looks real (not a
|
|
11
|
+
// placeholder, sample or test value: scrub/rules notWorthRotating). Generic
|
|
12
|
+
// shapes (KEY=value, Bearer …, JWTs) never stop one; the scrub still removes
|
|
13
|
+
// them from stored sessions.
|
|
14
|
+
//
|
|
15
|
+
// The hook must answer fast and offline, so it reads a cached copy of the
|
|
16
|
+
// company's setting. The cache is refreshed in the background (at session
|
|
17
|
+
// start, and from the prompt hook when it is over REFRESH_MS old).
|
|
18
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
21
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
22
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
23
|
+
}
|
|
24
|
+
Object.defineProperty(o, k2, desc);
|
|
25
|
+
}) : (function(o, m, k, k2) {
|
|
26
|
+
if (k2 === undefined) k2 = k;
|
|
27
|
+
o[k2] = m[k];
|
|
28
|
+
}));
|
|
29
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
30
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
31
|
+
}) : function(o, v) {
|
|
32
|
+
o["default"] = v;
|
|
33
|
+
});
|
|
34
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
35
|
+
var ownKeys = function(o) {
|
|
36
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
37
|
+
var ar = [];
|
|
38
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
39
|
+
return ar;
|
|
40
|
+
};
|
|
41
|
+
return ownKeys(o);
|
|
42
|
+
};
|
|
43
|
+
return function (mod) {
|
|
44
|
+
if (mod && mod.__esModule) return mod;
|
|
45
|
+
var result = {};
|
|
46
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
47
|
+
__setModuleDefault(result, mod);
|
|
48
|
+
return result;
|
|
49
|
+
};
|
|
50
|
+
})();
|
|
51
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
+
exports.REFRESH_MS = void 0;
|
|
53
|
+
exports.keyLabel = keyLabel;
|
|
54
|
+
exports.formatRules = formatRules;
|
|
55
|
+
exports.findPromptKey = findPromptKey;
|
|
56
|
+
exports.blockMessage = blockMessage;
|
|
57
|
+
exports.blockResponse = blockResponse;
|
|
58
|
+
exports.readPromptGuardPolicy = readPromptGuardPolicy;
|
|
59
|
+
exports.writePromptGuardPolicy = writePromptGuardPolicy;
|
|
60
|
+
exports.refreshPromptGuardPolicy = refreshPromptGuardPolicy;
|
|
61
|
+
exports.maybeSpawnPromptGuardRefresh = maybeSpawnPromptGuardRefresh;
|
|
62
|
+
exports.checkPrompt = checkPrompt;
|
|
63
|
+
exports.reportPromptBlocked = reportPromptBlocked;
|
|
64
|
+
const fs = __importStar(require("fs"));
|
|
65
|
+
const path = __importStar(require("path"));
|
|
66
|
+
const child_process_1 = require("child_process");
|
|
67
|
+
const configDir_1 = require("./configDir");
|
|
68
|
+
const promptRules_1 = require("./scrub/promptRules");
|
|
69
|
+
const scrubProviderRules_1 = require("./scrub/scrubProviderRules");
|
|
70
|
+
exports.REFRESH_MS = 10 * 60_000;
|
|
71
|
+
const CACHE_FILE = "prompt-guard.json";
|
|
72
|
+
const FETCH_TIMEOUT_MS = 8_000;
|
|
73
|
+
const LABELS = {
|
|
74
|
+
...scrubProviderRules_1.PROVIDER_RULE_LABELS,
|
|
75
|
+
"supabase-token": "Supabase token", "groq-key": "Groq key", "xai-key": "xAI key", "replicate-token": "Replicate token",
|
|
76
|
+
"aws-access-key": "AWS key", "openai-key": "OpenAI key", "anthropic-key": "Anthropic key",
|
|
77
|
+
"github-token": "GitHub token", "gitlab-token": "GitLab token", "stripe-key": "Stripe key",
|
|
78
|
+
"slack-token": "Slack token", "slack-webhook": "Slack webhook", "google-api-key": "Google API key",
|
|
79
|
+
"pem-private-key": "private key", "convex-deploy-key": "Convex deploy key", "linear-key": "Linear key",
|
|
80
|
+
"codiedev-key": "Codiedev key", "fireworks-key": "Fireworks key", "npm-token": "npm token",
|
|
81
|
+
"sendgrid-key": "SendGrid key", "twilio-key": "Twilio key", "azure-storage-key": "Azure storage key",
|
|
82
|
+
"azure-sas": "Azure SAS token", "url-password": "password in a URL",
|
|
83
|
+
"curl-auth-header": "token in a curl command", "curl-auth-user": "password in a curl command",
|
|
84
|
+
};
|
|
85
|
+
/** Keys that are real but not worth stopping a prompt for: Stripe test-mode
|
|
86
|
+
* keys (sandbox data only), and a Firebase web config's apiKey (public by
|
|
87
|
+
* design: it ships in every Firebase web app). A Google key anywhere else
|
|
88
|
+
* (Gemini, Maps server keys) still stops the prompt. */
|
|
89
|
+
function harmlessHere(rule, value, prompt) {
|
|
90
|
+
if (rule === "stripe-key" && /^[sr]k_test_/.test(value))
|
|
91
|
+
return true;
|
|
92
|
+
if (rule === "google-api-key" && /firebaseapp\.com|authDomain|firebaseConfig/i.test(prompt))
|
|
93
|
+
return true;
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
function keyLabel(rule) {
|
|
97
|
+
if (rule.startsWith("custom:"))
|
|
98
|
+
return rule.slice("custom:".length);
|
|
99
|
+
return LABELS[rule] ?? rule.replace(/-/g, " ");
|
|
100
|
+
}
|
|
101
|
+
/** The company's own token formats as scrub rules; a bad one is skipped. */
|
|
102
|
+
function formatRules(formats) {
|
|
103
|
+
const out = [];
|
|
104
|
+
for (const f of formats) {
|
|
105
|
+
try {
|
|
106
|
+
out.push({ name: `custom:${f.name}`, re: new RegExp(`(?<![A-Za-z0-9])${f.source}(?![A-Za-z0-9_-])`, "g"), ...(f.keyword ? { keywords: [f.keyword] } : {}) });
|
|
107
|
+
}
|
|
108
|
+
catch { /* skip */ }
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
/** The first real key in a prompt, or null. */
|
|
113
|
+
function findPromptKey(prompt, formats = [], now = Date.now()) {
|
|
114
|
+
if (!prompt || prompt.length > 2_000_000)
|
|
115
|
+
return null;
|
|
116
|
+
const { redactions } = (0, promptRules_1.scrubSecrets)(prompt, { extraRules: formatRules(formats) });
|
|
117
|
+
for (const r of redactions) {
|
|
118
|
+
if (promptRules_1.GENERIC_RULES.has(r.rule))
|
|
119
|
+
continue;
|
|
120
|
+
const value = prompt.slice(r.start, r.end);
|
|
121
|
+
if ((0, promptRules_1.notWorthRotating)({ rule: r.rule, value }, now))
|
|
122
|
+
continue;
|
|
123
|
+
if (harmlessHere(r.rule, value, prompt))
|
|
124
|
+
continue;
|
|
125
|
+
return { rule: r.rule, label: keyLabel(r.rule), hint: (0, promptRules_1.hintOf)(r.rule, value) };
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
/** What the person sees. Names the kind of key and its last four, says
|
|
130
|
+
* nothing was sent, and how to do it instead. */
|
|
131
|
+
function blockMessage(key) {
|
|
132
|
+
const article = /^[aeiou]/i.test(key.label) ? "an" : "a";
|
|
133
|
+
return `Codiedev stopped this prompt: it has ${article} ${key.label} in it (…${key.hint}). ` +
|
|
134
|
+
"Nothing was sent to the model. Put the key in an environment variable or .env file and refer to it by name " +
|
|
135
|
+
"(for example $API_KEY), then send the prompt again. Your team turned on this check.";
|
|
136
|
+
}
|
|
137
|
+
/** The hook's stdout for each harness when it stops a prompt. */
|
|
138
|
+
function blockResponse(harness, message) {
|
|
139
|
+
// Cursor: beforeSubmitPrompt. Everyone else that reads stdout (Claude
|
|
140
|
+
// Code, Codex, Copilot's userPromptSubmitted, and our Codie / pi adapters)
|
|
141
|
+
// takes the Claude-style decision.
|
|
142
|
+
if (harness === "cursor")
|
|
143
|
+
return { continue: false, user_message: message };
|
|
144
|
+
return { decision: "block", reason: message };
|
|
145
|
+
}
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// The company's setting, cached on the laptop
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
function cachePath(stateDir) {
|
|
150
|
+
return path.join(stateDir, CACHE_FILE);
|
|
151
|
+
}
|
|
152
|
+
function readPromptGuardPolicy(stateDir = (0, configDir_1.codiedevStateDir)()) {
|
|
153
|
+
try {
|
|
154
|
+
const v = JSON.parse(fs.readFileSync(cachePath(stateDir), "utf8"));
|
|
155
|
+
if (typeof v.fetchedAt !== "number")
|
|
156
|
+
return null;
|
|
157
|
+
return {
|
|
158
|
+
blockPrompts: v.blockPrompts === true,
|
|
159
|
+
formats: Array.isArray(v.formats) ? v.formats.filter((f) => f && typeof f.name === "string" && typeof f.source === "string").slice(0, 50) : [],
|
|
160
|
+
fetchedAt: v.fetchedAt,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function writePromptGuardPolicy(policy, stateDir = (0, configDir_1.codiedevStateDir)()) {
|
|
168
|
+
fs.mkdirSync(stateDir, { recursive: true, mode: 0o700 });
|
|
169
|
+
const file = cachePath(stateDir);
|
|
170
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
171
|
+
fs.writeFileSync(tmp, JSON.stringify(policy), { mode: 0o600 });
|
|
172
|
+
fs.renameSync(tmp, file);
|
|
173
|
+
}
|
|
174
|
+
/** Ask the server for the company's setting and cache it. On any failure the
|
|
175
|
+
* last cached answer stays (and its age makes the next hook try again). */
|
|
176
|
+
async function refreshPromptGuardPolicy(config, fetchImpl = fetch, stateDir = (0, configDir_1.codiedevStateDir)(), now = Date.now()) {
|
|
177
|
+
if (!config?.token || !config.backendUrl)
|
|
178
|
+
return null;
|
|
179
|
+
try {
|
|
180
|
+
const res = await fetchImpl(`${config.backendUrl}/api/cli/promptGuard`, {
|
|
181
|
+
headers: { Authorization: `Bearer ${config.token}` },
|
|
182
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
183
|
+
});
|
|
184
|
+
if (!res.ok)
|
|
185
|
+
return null;
|
|
186
|
+
const body = (await res.json());
|
|
187
|
+
const formats = Array.isArray(body.formats)
|
|
188
|
+
? body.formats.filter((f) => f && typeof f.name === "string" && typeof f.source === "string" && f.source.length <= 300).slice(0, 50)
|
|
189
|
+
: [];
|
|
190
|
+
const policy = { blockPrompts: body.blockPrompts === true, formats, fetchedAt: now };
|
|
191
|
+
writePromptGuardPolicy(policy, stateDir);
|
|
192
|
+
return policy;
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/** Start a background refresh when the cached setting is missing or stale.
|
|
199
|
+
* Stamps the cache's age first so a burst of hooks starts only one. */
|
|
200
|
+
function maybeSpawnPromptGuardRefresh(opts) {
|
|
201
|
+
try {
|
|
202
|
+
if (!opts.connected || process.env.CODIEDEV_PROMPT_GUARD_CHILD)
|
|
203
|
+
return "disabled";
|
|
204
|
+
const stateDir = opts.stateDir ?? (0, configDir_1.codiedevStateDir)();
|
|
205
|
+
const now = opts.now ?? Date.now();
|
|
206
|
+
const stamp = path.join(stateDir, "prompt-guard.refresh");
|
|
207
|
+
if (!opts.force) {
|
|
208
|
+
const cached = readPromptGuardPolicy(stateDir);
|
|
209
|
+
if (cached && now - cached.fetchedAt < exports.REFRESH_MS && cached.fetchedAt <= now)
|
|
210
|
+
return "fresh";
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
const last = fs.statSync(stamp).mtimeMs;
|
|
214
|
+
if (now - last < 60_000 && last <= now)
|
|
215
|
+
return "fresh"; // one in flight
|
|
216
|
+
}
|
|
217
|
+
catch { /* never */ }
|
|
218
|
+
fs.mkdirSync(stateDir, { recursive: true, mode: 0o700 });
|
|
219
|
+
fs.writeFileSync(stamp, String(now));
|
|
220
|
+
const t = new Date(now);
|
|
221
|
+
fs.utimesSync(stamp, t, t);
|
|
222
|
+
const child = (opts.spawn ?? child_process_1.spawn)(process.execPath, [opts.hookEntry ?? path.join(__dirname, "hook.js"), "prompt-guard-refresh"], {
|
|
223
|
+
detached: true,
|
|
224
|
+
stdio: "ignore",
|
|
225
|
+
env: { ...process.env, CODIEDEV_PROMPT_GUARD_CHILD: "1" },
|
|
226
|
+
});
|
|
227
|
+
child.on?.("error", () => { });
|
|
228
|
+
child.unref?.();
|
|
229
|
+
return "spawned";
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
return "error";
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/** Check one prompt against the cached setting. Never throws. */
|
|
236
|
+
function checkPrompt(prompt, harness, policy, now = Date.now()) {
|
|
237
|
+
try {
|
|
238
|
+
if (!policy?.blockPrompts || !prompt)
|
|
239
|
+
return { blocked: false };
|
|
240
|
+
const key = findPromptKey(prompt, policy.formats, now);
|
|
241
|
+
if (!key)
|
|
242
|
+
return { blocked: false };
|
|
243
|
+
const message = blockMessage(key);
|
|
244
|
+
return { blocked: true, key, message, response: blockResponse(harness, message) };
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return { blocked: false };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/** Tell the server a prompt was stopped: the rule and harness only. Detached
|
|
251
|
+
* and best-effort; a slow network never holds the prompt hook. */
|
|
252
|
+
function reportPromptBlocked(config, rule, harness, fetchImpl = fetch) {
|
|
253
|
+
if (!config?.token || !config.backendUrl)
|
|
254
|
+
return Promise.resolve();
|
|
255
|
+
return fetchImpl(`${config.backendUrl}/api/cli/promptBlocked`, {
|
|
256
|
+
method: "POST",
|
|
257
|
+
headers: { Authorization: `Bearer ${config.token}`, "Content-Type": "application/json" },
|
|
258
|
+
body: JSON.stringify({ rule: rule.slice(0, 80), harness }),
|
|
259
|
+
signal: AbortSignal.timeout(3_000),
|
|
260
|
+
}).then(() => undefined, () => undefined);
|
|
261
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
export interface Redaction {
|
|
2
|
+
rule: string;
|
|
3
|
+
start: number;
|
|
4
|
+
end: number;
|
|
5
|
+
}
|
|
6
|
+
export interface ScrubResult {
|
|
7
|
+
text: string;
|
|
8
|
+
redactions: Redaction[];
|
|
9
|
+
}
|
|
10
|
+
/** Rules a caller adds for one scrub (a company's own token formats). */
|
|
11
|
+
export interface ExtraRule {
|
|
12
|
+
name: string;
|
|
13
|
+
re: RegExp;
|
|
14
|
+
keywords?: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
/** Shannon entropy in bits per character, as Gitleaks measures it. */
|
|
17
|
+
export declare function shannonEntropy(s: string): number;
|
|
18
|
+
export declare function scrubSecrets(input: string, opts?: {
|
|
19
|
+
extraRules?: readonly ExtraRule[];
|
|
20
|
+
}): ScrubResult;
|
|
21
|
+
/** Rewrite `input` with each span replaced by `[REDACTED:<rule>]`. Spans must
|
|
22
|
+
* be sorted by start and not overlap. One forward pass: repeated
|
|
23
|
+
* slice+concat would copy the whole string per redaction (quadratic). */
|
|
24
|
+
export declare function redactSpans(input: string, spans: readonly Redaction[]): string;
|
|
25
|
+
/** Where in a session a secret appeared. "typed" is a short message the
|
|
26
|
+
* person wrote; "pasted" is a long one (a log, config or JSON dump pasted in);
|
|
27
|
+
* "attached" is text the harness added to the person's turn (an @-mentioned
|
|
28
|
+
* file, a resumed session's summary). Older rows say "prompt" for all three. */
|
|
29
|
+
export type SecretWhere = "typed" | "pasted" | "attached" | "prompt" | "tool_call" | "commit" | "tool_output" | "assistant" | "unknown";
|
|
30
|
+
export interface SecretLocation {
|
|
31
|
+
where: SecretWhere;
|
|
32
|
+
/** Tool or MCP tool name for tool calls/outputs when the transcript has it. */
|
|
33
|
+
tool?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface SecretFinding {
|
|
36
|
+
rule: string;
|
|
37
|
+
/** Last four alphanumeric characters of the redacted value — enough for a
|
|
38
|
+
* person to recognise which key to rotate, never enough to use it. */
|
|
39
|
+
hint: string;
|
|
40
|
+
/** Distinct places in the session the key appeared. */
|
|
41
|
+
locations: SecretLocation[];
|
|
42
|
+
/** The variable or field it was assigned to (`X_BEARER_TOKEN`), when the
|
|
43
|
+
* text shows one. A name, never the value. */
|
|
44
|
+
name?: string;
|
|
45
|
+
/** The file it was written to or read from (`altcap-bot/.env`), when the
|
|
46
|
+
* text shows one: last two path parts only. */
|
|
47
|
+
file?: string;
|
|
48
|
+
}
|
|
49
|
+
/** What a caller may check before a key becomes a finding (exceptions).
|
|
50
|
+
* `value` is the key itself: use it to compare, never store it. */
|
|
51
|
+
export interface KeyContext {
|
|
52
|
+
rule: string;
|
|
53
|
+
value: string;
|
|
54
|
+
name?: string;
|
|
55
|
+
file?: string;
|
|
56
|
+
}
|
|
57
|
+
export declare const GENERIC_RULES: ReadonlySet<string>;
|
|
58
|
+
/** Values that share a key's shape but are test or sample values: words
|
|
59
|
+
* joined by _ or - with only digits or one repeated character between
|
|
60
|
+
* (`cdv_local_pilot_token_2026`, `…_token_000000`), a long counting run
|
|
61
|
+
* (`sk-1234567890…`), or a long repeated tail. Still removed from sessions;
|
|
62
|
+
* just not listed as a key to rotate. */
|
|
63
|
+
export declare function looksLikeTestValue(value: string): boolean;
|
|
64
|
+
export declare const isTestFile: (file: string) => boolean;
|
|
65
|
+
/** Where in one transcript line (a JSONL record) some text appeared, and the
|
|
66
|
+
* file that line reads or writes, if it names one. For any detector that
|
|
67
|
+
* works line by line (PHI/PII). `text` is the matched text as it reads
|
|
68
|
+
* after JSON unescaping. */
|
|
69
|
+
export declare function locateInLine(line: string, text: string): SecretLocation & {
|
|
70
|
+
file?: string;
|
|
71
|
+
};
|
|
72
|
+
/** Where each `[REDACTED:<rule>]` marker sits in an already-scrubbed
|
|
73
|
+
* transcript. Re-labels older findings that predate typed/pasted/attached. */
|
|
74
|
+
export declare function locateMarkers(scrubbed: string, rule: string): SecretLocation[];
|
|
75
|
+
/** Rules that match a shape (a secret-named variable, a header, a URL, a PEM
|
|
76
|
+
* block) rather than a provider's own key format. */
|
|
77
|
+
export declare const SHAPE_RULES: ReadonlySet<string>;
|
|
78
|
+
/** The name or location of a secret, or a value built from words, rather
|
|
79
|
+
* than a random key: an ARN, a secret-store path, or a value whose letters
|
|
80
|
+
* are mostly whole words (`mobile-data/client2`, `bracecomb-hc-service`,
|
|
81
|
+
* `mobileDataClientV2`). A random key does not break into words, whatever
|
|
82
|
+
* separators it has: under 1% of random 16-character keys pass this, and
|
|
83
|
+
* next to none at 24 or more. */
|
|
84
|
+
export declare function namesASecret(v: string): boolean;
|
|
85
|
+
/** True when a redacted value should not be listed as a key to rotate: a
|
|
86
|
+
* placeholder, a sample or test value, a reference to a variable, or a
|
|
87
|
+
* credential that expires by itself. `now` is injectable for tests. */
|
|
88
|
+
export declare function notWorthRotating(k: KeyContext, now?: number): boolean;
|
|
89
|
+
/** Last four letters or digits of a value. A PEM block ends in a fixed
|
|
90
|
+
* footer, and in a transcript its line breaks are a literal `\n`: the hint
|
|
91
|
+
* comes from the key body, without either. */
|
|
92
|
+
export declare function hintOf(rule: string, value: string): string;
|
|
93
|
+
/** One finding per distinct (rule, last-4) in a scrub result, with every
|
|
94
|
+
* distinct place it appeared. */
|
|
95
|
+
export declare function secretFindings(input: string, redactions: Redaction[], opts?: {
|
|
96
|
+
skip?: (k: KeyContext) => boolean;
|
|
97
|
+
now?: number;
|
|
98
|
+
}): SecretFinding[];
|
|
99
|
+
export interface PossibleSecret {
|
|
100
|
+
label: string;
|
|
101
|
+
prefix?: string;
|
|
102
|
+
hint: string;
|
|
103
|
+
length: number;
|
|
104
|
+
shape: "hex" | "base64" | "alnum";
|
|
105
|
+
snippet: string;
|
|
106
|
+
locations: SecretLocation[];
|
|
107
|
+
/** Spans in the text this was read from, for redaction once confirmed. */
|
|
108
|
+
spans: Array<{
|
|
109
|
+
start: number;
|
|
110
|
+
end: number;
|
|
111
|
+
}>;
|
|
112
|
+
}
|
|
113
|
+
export declare function possibleSecrets(text: string, limit?: number): PossibleSecret[];
|
|
114
|
+
/** Redact every occurrence of an already-reviewed possible secret. */
|
|
115
|
+
export declare function redactPossible(text: string, match: {
|
|
116
|
+
label: string;
|
|
117
|
+
hint: string;
|
|
118
|
+
length: number;
|
|
119
|
+
}, rule?: string): ScrubResult;
|