"""SecretDetector: find secrets and credentials in code, configs, logs, chats and documents with a compact Ettin token tagger. from cbx_secrets import SecretDetector det = SecretDetector() # this folder: model.safetensors, config.json, tokenizer, secrets_config.json det.extract('export STRIPE_KEY="sk_live_..."') det.redact('export STRIPE_KEY="sk_live_..."') (Not named secrets.py: that would hide Python's standard `secrets` module.) The model tags every token with a BIO tag (B-/I- per label, O = not a secret); tags are joined into character spans. A text is read in one pass, up to the model's full context length (7,999 tokens, from config.json). Only a text longer than that is read in overlapping pieces; a token in an overlap takes its tag from the piece where it sits further from the edge, so a value split by a piece boundary is still read in one piece. """ import json, os, time import torch import torch.nn as nn from safetensors.torch import load_file from transformers import AutoConfig, AutoModel, AutoTokenizer _EDGE_LEAD, _EDGE_TRAIL = "=.\"'*<:;,`", ".,;:\"'*>`" # punctuation a token can glue onto a value class _Tagger(nn.Module): def __init__(self, config, n_tags): super().__init__() self.backbone = AutoModel.from_config(config, attn_implementation="sdpa") self.head = nn.Linear(config.hidden_size, n_tags) def forward(self, input_ids): h = self.backbone(input_ids=input_ids, attention_mask=torch.ones_like(input_ids)).last_hidden_state return self.head(h.float()) class SecretDetector: def __init__(self, path=None, device=None, overlap=64, join="none"): path = path or os.path.dirname(os.path.abspath(__file__)) self.cfg = json.load(open(os.path.join(path, "secrets_config.json"))) self.tags, self.labels = self.cfg["tags"], self.cfg["labels"] self.overlap, self.join = overlap, join # join="word" also merges pieces of one word self.min_len = self.cfg.get("min_len") or {} # {"*": n, "