@lapismd/agent-redaction 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,4 @@
1
+ Apache-2.0
2
+
3
+ This project is licensed under the Apache License 2.0.
4
+ See <https://www.apache.org/licenses/LICENSE-2.0>.
package/README.md CHANGED
@@ -1,3 +1,10 @@
1
- # Temporary Holding Version
1
+ # Agent redaction
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Portable complete-block sanitization for observable agent transcripts. The core
4
+ has no runtime effects. Explicit Node and Deno adapters add reviewed Secretlint
5
+ detectors and granted secret discovery. This package does not sanitize external
6
+ agent software, native VCS archives or data written before capture.
7
+
8
+ See the canonical [specification](spec/src/sanitization.md) and
9
+ [verification record](spec/src/verification.md). Publication is not part of local
10
+ development acceptance.
package/dist/deno.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { createNodeDetector as createDenoDetector, createNodeRedaction as createDenoRedaction, refreshNodeSecrets as refreshDenoSecrets, } from "./node.js";
2
+ export type { SecretGrants } from "./node.js";
3
+ export { SynchronousRedaction } from "./sync.js";
package/dist/deno.js ADDED
@@ -0,0 +1,4 @@
1
+ // Deno's node compatibility is an explicit, separately tested enforcement adapter.
2
+ // Permissions remain enforced by Deno; nothing is discovered on module import.
3
+ export { createNodeDetector as createDenoDetector, createNodeRedaction as createDenoRedaction, refreshNodeSecrets as refreshDenoSecrets, } from "./node.js";
4
+ export { SynchronousRedaction } from "./sync.js";
@@ -0,0 +1,4 @@
1
+ export { RedactionScope } from "./sanitizer.js";
2
+ export { CompleteBlock } from "./stream.js";
3
+ export { LIMITS, omit } from "./types.js";
4
+ export type { Json, LogicalPath, TrustedRoot, Detector, Span, Result, Sanitized, SanitizationSummary, Omission, OmissionReason, Policy, } from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { RedactionScope } from "./sanitizer.js";
2
+ export { CompleteBlock } from "./stream.js";
3
+ export { LIMITS, omit } from "./types.js";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ import { parentPort } from "node:worker_threads";
2
+ import { secretlintDetector } from "./secretlint.js";
3
+ parentPort.on("message", async ({ id, text }) => {
4
+ try {
5
+ parentPort.postMessage({
6
+ id,
7
+ spans: await secretlintDetector.scan(text),
8
+ });
9
+ }
10
+ catch {
11
+ parentPort.postMessage({ id, failure: true });
12
+ }
13
+ });
package/dist/node.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { RedactionScope } from "./sanitizer.js";
2
+ import { type Detector, type Policy } from "./types.js";
3
+ export { SynchronousRedaction } from "./sync.js";
4
+ export interface NodeDetector extends Detector {
5
+ dispose(): void;
6
+ }
7
+ /** Explicit construction starts an isolated scanner. No worker starts on import. */
8
+ export declare function createNodeDetector(timeoutMs?: number): NodeDetector;
9
+ export interface SecretGrants {
10
+ readonly environment?: readonly string[];
11
+ readonly dotenvFiles?: readonly string[];
12
+ }
13
+ /** Only explicit names and canonical paths are read. No directory discovery. */
14
+ export declare function refreshNodeSecrets(scope: Pick<RedactionScope, "registerSecrets" | "dispose">, grants: SecretGrants, environment?: Readonly<Record<string, string | undefined>>): Promise<boolean>;
15
+ export declare function createNodeRedaction(policy?: Omit<Policy, "detector">): {
16
+ scope: RedactionScope;
17
+ dispose(): void;
18
+ };
package/dist/node.js ADDED
@@ -0,0 +1,131 @@
1
+ import { Worker } from "node:worker_threads";
2
+ import { open, realpath } from "node:fs/promises";
3
+ import { parse } from "dotenv";
4
+ import { RedactionScope } from "./sanitizer.js";
5
+ import { LIMITS } from "./types.js";
6
+ export { SynchronousRedaction } from "./sync.js";
7
+ /** Explicit construction starts an isolated scanner. No worker starts on import. */
8
+ export function createNodeDetector(timeoutMs = 10_000) {
9
+ if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 60_000)
10
+ throw new Error("policy_invalid");
11
+ let worker, next = 0, closed = false;
12
+ const pending = new Map();
13
+ const fail = () => {
14
+ for (const entry of pending.values()) {
15
+ clearTimeout(entry.timer);
16
+ entry.reject(new Error("detector_failure"));
17
+ }
18
+ pending.clear();
19
+ const previous = worker;
20
+ worker = undefined;
21
+ void previous?.terminate();
22
+ };
23
+ const start = () => {
24
+ if (worker)
25
+ return worker;
26
+ // Empty worker env prevents DEBUG and profiler settings from exposing source.
27
+ const active = new Worker(new URL("./node-worker.js", import.meta.url), {
28
+ env: {},
29
+ stdout: true,
30
+ stderr: true,
31
+ resourceLimits: { maxOldGenerationSizeMb: 128 },
32
+ });
33
+ active.stdout?.resume();
34
+ active.stderr?.resume();
35
+ active.on("message", (message) => {
36
+ const entry = pending.get(message.id);
37
+ if (!entry)
38
+ return;
39
+ clearTimeout(entry.timer);
40
+ pending.delete(message.id);
41
+ if (message.failure || !Array.isArray(message.spans))
42
+ entry.reject(new Error("detector_failure"));
43
+ else
44
+ entry.resolve(message.spans);
45
+ });
46
+ active.on("error", () => {
47
+ if (worker === active)
48
+ fail();
49
+ });
50
+ active.on("exit", () => {
51
+ if (worker === active)
52
+ fail();
53
+ });
54
+ worker = active;
55
+ return active;
56
+ };
57
+ return Object.freeze({
58
+ id: "secretlint-13.0.5-explicit-27-v1",
59
+ scan(text) {
60
+ if (closed || pending.size >= 64)
61
+ return Promise.reject(new Error("detector_failure"));
62
+ return new Promise((resolve, reject) => {
63
+ const id = ++next;
64
+ const timer = setTimeout(fail, timeoutMs);
65
+ pending.set(id, { resolve, reject, timer });
66
+ try {
67
+ start().postMessage({ id, text });
68
+ }
69
+ catch {
70
+ fail();
71
+ }
72
+ });
73
+ },
74
+ dispose() {
75
+ closed = true;
76
+ fail();
77
+ },
78
+ });
79
+ }
80
+ /** Only explicit names and canonical paths are read. No directory discovery. */
81
+ export async function refreshNodeSecrets(scope, grants, environment = process.env) {
82
+ const values = [];
83
+ try {
84
+ if ((grants.environment?.length ?? 0) + (grants.dotenvFiles?.length ?? 0) >
85
+ LIMITS.secrets)
86
+ throw 0;
87
+ for (const name of grants.environment ?? []) {
88
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))
89
+ throw 0;
90
+ const value = environment[name];
91
+ if (value)
92
+ values.push(value);
93
+ }
94
+ for (const path of grants.dotenvFiles ?? []) {
95
+ const canonical = await realpath(path);
96
+ if (canonical !== path)
97
+ throw 0;
98
+ const handle = await open(canonical, "r");
99
+ try {
100
+ const stat = await handle.stat();
101
+ if (!stat.isFile() || stat.size > LIMITS.secretBytes)
102
+ throw 0;
103
+ const data = new Uint8Array(LIMITS.secretBytes + 1);
104
+ const { bytesRead } = await handle.read(data, 0, data.length, 0);
105
+ if (bytesRead > LIMITS.secretBytes)
106
+ throw 0;
107
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(data.subarray(0, bytesRead));
108
+ values.push(...Object.values(parse(text)).filter(Boolean));
109
+ }
110
+ finally {
111
+ await handle.close();
112
+ }
113
+ }
114
+ return scope.registerSecrets(values);
115
+ }
116
+ catch {
117
+ scope.dispose();
118
+ return false;
119
+ }
120
+ }
121
+ export function createNodeRedaction(policy = {}) {
122
+ const detector = createNodeDetector();
123
+ const scope = new RedactionScope({ ...policy, detector });
124
+ return {
125
+ scope,
126
+ dispose() {
127
+ scope.dispose();
128
+ detector.dispose();
129
+ },
130
+ };
131
+ }
@@ -0,0 +1,7 @@
1
+ import { type LogicalPath, type TrustedRoot } from "./types.js";
2
+ export declare class PathRegistry {
3
+ #private;
4
+ constructor(roots: readonly TrustedRoot[], destination: "public" | "private-portable");
5
+ classify(value: string): LogicalPath;
6
+ text(value: string): string;
7
+ }
package/dist/paths.js ADDED
@@ -0,0 +1,122 @@
1
+ import { EXTERNAL } from "./types.js";
2
+ const safeId = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
3
+ function decodePath(value) {
4
+ if (/^file:\/\//i.test(value)) {
5
+ const url = new URL(value);
6
+ if (url.username || url.password || url.search || url.hash)
7
+ throw 0;
8
+ const decoded = decodeURIComponent(url.pathname);
9
+ return url.hostname && url.hostname !== "localhost"
10
+ ? `//${url.hostname}${decoded}`
11
+ : decoded.replace(/^\/([A-Za-z]:\/)/, "$1");
12
+ }
13
+ return value;
14
+ }
15
+ function windows(value) {
16
+ return /^[A-Za-z]:[\\/]|^\\\\|^\/\/[^/]/.test(value);
17
+ }
18
+ function normal(value) {
19
+ const decoded = decodePath(value);
20
+ return windows(decoded) ? decoded.replace(/\\/g, "/") : decoded;
21
+ }
22
+ function absolute(value) {
23
+ return /^(?:\/|[A-Za-z]:\/)/.test(value);
24
+ }
25
+ function traversal(value) {
26
+ return (value.split("/").some((part) => part === ".." || part === ".") ||
27
+ /%2e|%2f|%5c/i.test(value));
28
+ }
29
+ function escape(value) {
30
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
31
+ }
32
+ export class PathRegistry {
33
+ #roots = [];
34
+ #public;
35
+ constructor(roots, destination) {
36
+ this.#public = destination === "public";
37
+ if (roots.length > 128)
38
+ throw 0;
39
+ for (const root of roots) {
40
+ if (root.kind === "workspace" && (!root.id || !safeId.test(root.id)))
41
+ throw 0;
42
+ if (root.repositoryId && !safeId.test(root.repositoryId))
43
+ throw 0;
44
+ for (const path of [root.path, ...(root.aliases ?? [])]) {
45
+ const clean = normal(path).replace(/\/+$/, "");
46
+ if (!absolute(clean) ||
47
+ traversal(clean) ||
48
+ !clean ||
49
+ /[\r\n\x00]/.test(clean))
50
+ throw 0;
51
+ const item = {
52
+ path: clean,
53
+ kind: root.kind,
54
+ id: root.id,
55
+ repositoryId: root.repositoryId,
56
+ sensitive: root.caseSensitive ?? !windows(path),
57
+ };
58
+ if (this.#roots.some((existing) => existing.path.toLowerCase() === clean.toLowerCase() &&
59
+ (existing.kind !== item.kind || existing.id !== item.id)))
60
+ throw 0;
61
+ this.#roots.push(item);
62
+ }
63
+ }
64
+ this.#roots.sort((a, b) => b.path.length - a.path.length);
65
+ }
66
+ classify(value) {
67
+ const external = (display = EXTERNAL) => Object.freeze({ kind: "external", display, resolvable: false });
68
+ let path;
69
+ try {
70
+ path = normal(value);
71
+ }
72
+ catch {
73
+ return external();
74
+ }
75
+ if (!absolute(path) ||
76
+ traversal(path) ||
77
+ /\[REDACTED\]|[\x00-\x1f]/.test(path))
78
+ return external();
79
+ for (const root of this.#roots) {
80
+ const candidate = root.sensitive ? path : path.toLowerCase();
81
+ const prefix = root.sensitive ? root.path : root.path.toLowerCase();
82
+ if (candidate !== prefix && !candidate.startsWith(prefix + "/"))
83
+ continue;
84
+ const relativePath = path.slice(root.path.length).replace(/^\//, "");
85
+ if (root.kind === "workspace")
86
+ return Object.freeze({
87
+ kind: "workspace",
88
+ workspaceId: root.id,
89
+ ...(root.repositoryId ? { repositoryId: root.repositoryId } : {}),
90
+ relativePath,
91
+ display: `$WORKSPACE[${root.id}]${relativePath ? "/" + relativePath : ""}`,
92
+ resolvable: true,
93
+ });
94
+ return external(this.#public
95
+ ? EXTERNAL
96
+ : `${root.kind === "home" ? "$HOME" : "$TMP"}${relativePath ? "/" + relativePath : ""}`);
97
+ }
98
+ return external();
99
+ }
100
+ text(value) {
101
+ if (this.#public)
102
+ value = value.replace(/\$(?:HOME|TMP)(?:[\\/][^\s<>"'`,;)]*)?/g, EXTERNAL);
103
+ // Skip web URLs as complete tokens; they are not local path capabilities.
104
+ const trusted = this.#roots
105
+ .map((root) => escape(root.path).replace(/\//g, "[\\\\/]"))
106
+ .join("|");
107
+ const candidate = `(?:https?|wss?):\\/\\/[^\\s<>\"'\x60]+|file:\\/\\/[^\\s<>\"'\x60]+|(?:${trusted || "(?!)"})(?:[\\\\/][^\\s<>\"'\x60,;)]*)?|[A-Za-z]:[\\\\/][^\\s<>\"'\x60,;)]*|\\\\\\\\[^\\s<>\"'\x60,;)]*|\\/(?:[^\\s/<>\"'\x60,;)]*\\/)+[^\\s<>\"'\x60,;)]*`;
108
+ const regex = new RegExp(candidate, "gi");
109
+ return value.replace(regex, (match, offset) => {
110
+ if (/^(?:https?|wss?):\/\//i.test(match))
111
+ return match;
112
+ // Do not match inside identifiers, URLs or already-portable placeholders.
113
+ const before = value.slice(0, offset);
114
+ if (offset > 0 && /[\w\/$\]]$/.test(before))
115
+ return match;
116
+ const after = value[offset + match.length];
117
+ if (after && /[\w.-]/.test(after))
118
+ return EXTERNAL;
119
+ return this.classify(match).display;
120
+ });
121
+ }
122
+ }
@@ -0,0 +1,11 @@
1
+ import { type Json, type LogicalPath, type Policy, type Result } from "./types.js";
2
+ export declare class RedactionScope {
3
+ #private;
4
+ constructor(policy?: Policy);
5
+ get policyId(): string;
6
+ registerSecrets(values: readonly string[]): boolean;
7
+ dispose(): void;
8
+ text(raw: string): Promise<Result<string>>;
9
+ path(raw: string): Promise<Result<LogicalPath>>;
10
+ value(raw: unknown): Promise<Result<Json>>;
11
+ }
@@ -0,0 +1,186 @@
1
+ import { PathRegistry } from "./paths.js";
2
+ import { formatSpans, maskSpans, SecretRegistry, sensitiveKey, stripTerminalControls, } from "./secrets.js";
3
+ import { LIMITS, REDACTED, omit, utf8Size, validUnicode, } from "./types.js";
4
+ class Failure {
5
+ reason;
6
+ constructor(reason) {
7
+ this.reason = reason;
8
+ }
9
+ }
10
+ export class RedactionScope {
11
+ #registry = new SecretRegistry();
12
+ #paths;
13
+ #detector;
14
+ #destination;
15
+ #valid = true;
16
+ constructor(policy = {}) {
17
+ this.#destination = policy.destination ?? "public";
18
+ this.#detector = policy.detector;
19
+ if (this.#detector && !/^[a-zA-Z0-9._:/-]{1,160}$/.test(this.#detector.id))
20
+ this.#valid = false;
21
+ this.#registry.add(policy.secrets ?? []);
22
+ try {
23
+ this.#paths = new PathRegistry(policy.roots ?? [], this.#destination);
24
+ }
25
+ catch {
26
+ this.#valid = false;
27
+ }
28
+ }
29
+ get policyId() {
30
+ return `agent-redaction/1/${this.#destination}/${this.#detector?.id ?? "portable"}`;
31
+ }
32
+ registerSecrets(values) {
33
+ return this.#registry.add(values);
34
+ }
35
+ dispose() {
36
+ this.#registry.dispose();
37
+ this.#paths = undefined;
38
+ this.#valid = false;
39
+ }
40
+ async #scan(raw, metadata) {
41
+ if (!this.#valid || !this.#registry.valid || !this.#paths)
42
+ throw new Failure("policy_invalid");
43
+ if (typeof raw !== "string" || !validUnicode(raw))
44
+ throw new Failure("invalid_input");
45
+ if (utf8Size(raw) > LIMITS.text)
46
+ throw new Failure("limit_exceeded");
47
+ const text = stripTerminalControls(raw);
48
+ const spans = [...this.#registry.spans(text), ...formatSpans(text)];
49
+ if (this.#detector) {
50
+ try {
51
+ spans.push(...(await this.#detector.scan(text)));
52
+ }
53
+ catch {
54
+ throw new Failure("detector_failure");
55
+ }
56
+ }
57
+ let redacted;
58
+ try {
59
+ redacted = maskSpans(text, spans);
60
+ }
61
+ catch {
62
+ throw new Failure("detector_failure");
63
+ }
64
+ const portable = this.#paths.text(redacted);
65
+ if (utf8Size(portable) > LIMITS.text)
66
+ throw new Failure("limit_exceeded");
67
+ metadata.redacted ||= text !== redacted;
68
+ metadata.pathsNormalized ||= portable !== redacted;
69
+ return portable;
70
+ }
71
+ #result(value, metadata) {
72
+ const summary = Object.freeze({
73
+ version: 1,
74
+ policy: this.policyId,
75
+ coverage: this.#detector ? "credential-detectors" : "portable",
76
+ ...metadata,
77
+ });
78
+ return Object.freeze({ status: "sanitized", value, summary });
79
+ }
80
+ async text(raw) {
81
+ const metadata = { redacted: false, pathsNormalized: false };
82
+ try {
83
+ return this.#result(await this.#scan(raw, metadata), metadata);
84
+ }
85
+ catch (error) {
86
+ return omit(error instanceof Failure ? error.reason : "invalid_input");
87
+ }
88
+ }
89
+ async path(raw) {
90
+ const result = await this.text(raw);
91
+ if (result.status === "omitted")
92
+ return result;
93
+ // A secret-bearing path may be displayed, but cannot be resolved.
94
+ const path = result.summary.redacted
95
+ ? Object.freeze({
96
+ kind: "external",
97
+ display: "[EXTERNAL_PATH]",
98
+ resolvable: false,
99
+ })
100
+ : this.#paths.classify(raw);
101
+ return Object.freeze({ ...result, value: path });
102
+ }
103
+ async value(raw) {
104
+ const metadata = { redacted: false, pathsNormalized: false };
105
+ let size = 0, nodes = 0;
106
+ const seen = new Set();
107
+ const charge = (value) => {
108
+ size += utf8Size(JSON.stringify(value));
109
+ if (size > LIMITS.record)
110
+ throw new Failure("limit_exceeded");
111
+ };
112
+ const visit = async (value, depth) => {
113
+ if (++nodes > 100_000 || depth > LIMITS.depth)
114
+ throw new Failure("limit_exceeded");
115
+ if (typeof value === "string") {
116
+ charge(value);
117
+ return this.#scan(value, metadata);
118
+ }
119
+ if (value === null ||
120
+ typeof value === "boolean" ||
121
+ (typeof value === "number" && Number.isFinite(value))) {
122
+ size += 32;
123
+ if (size > LIMITS.record)
124
+ throw new Failure("limit_exceeded");
125
+ return value;
126
+ }
127
+ if (typeof value !== "object" || seen.has(value))
128
+ throw new Failure("invalid_input");
129
+ const array = Array.isArray(value);
130
+ const prototype = Object.getPrototypeOf(value);
131
+ if ((!array && prototype !== Object.prototype && prototype !== null) ||
132
+ (array && prototype !== Array.prototype) ||
133
+ Object.getOwnPropertySymbols(value).length)
134
+ throw new Failure("invalid_input");
135
+ seen.add(value);
136
+ const descriptors = Object.getOwnPropertyDescriptors(value);
137
+ const keys = Object.keys(descriptors).filter((key) => !(array && key === "length"));
138
+ if (keys.length > 100_000)
139
+ throw new Failure("limit_exceeded");
140
+ const target = Object.create(null);
141
+ const entries = [];
142
+ for (const key of keys) {
143
+ const descriptor = descriptors[key];
144
+ if (!descriptor.enumerable ||
145
+ !Object.hasOwn(descriptor, "value") ||
146
+ key === "toJSON" ||
147
+ key === "__proto__" ||
148
+ key === "constructor" ||
149
+ key === "prototype")
150
+ throw new Failure("invalid_input");
151
+ charge(key);
152
+ if (array && key !== String(entries.length))
153
+ throw new Failure("invalid_input");
154
+ const cleanKey = await this.#scan(key, metadata);
155
+ if (cleanKey !== key)
156
+ throw new Failure("invalid_input");
157
+ let next;
158
+ if (sensitiveKey(key)) {
159
+ next = REDACTED;
160
+ metadata.redacted = true;
161
+ }
162
+ else
163
+ next = await visit(descriptor.value, depth + 1);
164
+ if (array)
165
+ entries.push(next);
166
+ else
167
+ target[key] = next;
168
+ }
169
+ if (array && entries.length !== value.length)
170
+ throw new Failure("invalid_input");
171
+ seen.delete(value);
172
+ return Object.freeze(array ? entries : target);
173
+ };
174
+ try {
175
+ if (!this.#valid || !this.#registry.valid)
176
+ throw new Failure("policy_invalid");
177
+ const value = await visit(raw, 0);
178
+ if (utf8Size(JSON.stringify(value)) > LIMITS.record)
179
+ throw new Failure("limit_exceeded");
180
+ return this.#result(value, metadata);
181
+ }
182
+ catch (error) {
183
+ return omit(error instanceof Failure ? error.reason : "invalid_input");
184
+ }
185
+ }
186
+ }
@@ -0,0 +1,3 @@
1
+ import type { Detector } from "./types.js";
2
+ export declare const RULE_INVENTORY: readonly string[];
3
+ export declare const secretlintDetector: Detector;
@@ -0,0 +1,128 @@
1
+ import { lintSource } from "@secretlint/core";
2
+ import { secretLintProfiler } from "@secretlint/profiler";
3
+ import { creator as rule0 } from "@secretlint/secretlint-rule-aws";
4
+ import { creator as rule1 } from "@secretlint/secretlint-rule-gcp";
5
+ import { creator as rule2 } from "@secretlint/secretlint-rule-privatekey";
6
+ import { creator as rule3 } from "@secretlint/secretlint-rule-npm";
7
+ import { creator as rule4 } from "@secretlint/secretlint-rule-basicauth";
8
+ import { creator as rule5 } from "@secretlint/secretlint-rule-slack";
9
+ import { creator as rule6 } from "@secretlint/secretlint-rule-sendgrid";
10
+ import { creator as rule7 } from "@secretlint/secretlint-rule-shopify";
11
+ import { creator as rule8 } from "@secretlint/secretlint-rule-stripe";
12
+ import { creator as rule9 } from "@secretlint/secretlint-rule-github";
13
+ import { creator as rule10 } from "@secretlint/secretlint-rule-gitlab";
14
+ import { creator as rule11 } from "@secretlint/secretlint-rule-grafana";
15
+ import { creator as rule12 } from "@secretlint/secretlint-rule-openai";
16
+ import { creator as rule13 } from "@secretlint/secretlint-rule-anthropic";
17
+ import { creator as rule14 } from "@secretlint/secretlint-rule-groq";
18
+ import { creator as rule15 } from "@secretlint/secretlint-rule-huggingface";
19
+ import { creator as rule16 } from "@secretlint/secretlint-rule-linear";
20
+ import { creator as rule17 } from "@secretlint/secretlint-rule-notion";
21
+ import { creator as rule18 } from "@secretlint/secretlint-rule-1password";
22
+ import { creator as rule19 } from "@secretlint/secretlint-rule-database-connection-string";
23
+ import { creator as rule20 } from "@secretlint/secretlint-rule-hashicorp-vault";
24
+ import { creator as rule21 } from "@secretlint/secretlint-rule-vercel";
25
+ import { creator as rule22 } from "@secretlint/secretlint-rule-databricks";
26
+ import { creator as rule23 } from "@secretlint/secretlint-rule-docker";
27
+ import { creator as rule24 } from "@secretlint/secretlint-rule-figma";
28
+ import { creator as rule25 } from "@secretlint/secretlint-rule-cloudflare";
29
+ import { creator as rule26 } from "@secretlint/secretlint-rule-tailscale";
30
+ export const RULE_INVENTORY = Object.freeze([
31
+ "aws",
32
+ "gcp",
33
+ "privatekey",
34
+ "npm",
35
+ "basicauth",
36
+ "slack",
37
+ "sendgrid",
38
+ "shopify",
39
+ "stripe",
40
+ "github",
41
+ "gitlab",
42
+ "grafana",
43
+ "openai",
44
+ "anthropic",
45
+ "groq",
46
+ "huggingface",
47
+ "linear",
48
+ "notion",
49
+ "1password",
50
+ "database-connection-string",
51
+ "hashicorp-vault",
52
+ "vercel",
53
+ "databricks",
54
+ "docker",
55
+ "figma",
56
+ "cloudflare",
57
+ "tailscale",
58
+ ]);
59
+ // No config loader, preset, filter-comments, allows, or producer options.
60
+ const rules = [
61
+ rule0,
62
+ rule1,
63
+ rule2,
64
+ rule3,
65
+ rule4,
66
+ rule5,
67
+ rule6,
68
+ rule7,
69
+ rule8,
70
+ rule9,
71
+ rule10,
72
+ rule11,
73
+ rule12,
74
+ rule13,
75
+ rule14,
76
+ rule15,
77
+ rule16,
78
+ rule17,
79
+ rule18,
80
+ rule19,
81
+ rule20,
82
+ rule21,
83
+ rule22,
84
+ rule23,
85
+ rule24,
86
+ rule25,
87
+ rule26,
88
+ ];
89
+ secretLintProfiler.setEnabled(false);
90
+ export const secretlintDetector = Object.freeze({
91
+ id: "secretlint-13.0.5-explicit-27-v1",
92
+ async scan(text) {
93
+ try {
94
+ const result = await lintSource({
95
+ source: {
96
+ content: text,
97
+ filePath: "transcript.txt",
98
+ ext: ".txt",
99
+ contentType: "text",
100
+ },
101
+ options: {
102
+ noPhysicFilePath: true,
103
+ maskSecrets: true,
104
+ config: { rules: rules.map((rule) => ({ id: rule.meta.id, rule })) },
105
+ },
106
+ });
107
+ // Some rules report an assignment prefix with a credential-length range.
108
+ // Mask the complete affected lines so a misaligned range cannot retain a suffix.
109
+ return result.messages.map((message) => {
110
+ const [start, end] = message.range;
111
+ if (!Number.isInteger(start) ||
112
+ !Number.isInteger(end) ||
113
+ start < 0 ||
114
+ end <= start ||
115
+ end > text.length)
116
+ throw 0;
117
+ const lineEnd = text.indexOf("\n", end);
118
+ return Object.freeze({
119
+ start: text.lastIndexOf("\n", Math.max(0, start - 1)) + 1,
120
+ end: lineEnd === -1 ? text.length : lineEnd,
121
+ });
122
+ });
123
+ }
124
+ catch {
125
+ throw new Error("detector_failure");
126
+ }
127
+ },
128
+ });