🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

replen

Package Overview
Dependencies
Maintainers
1
Versions
68
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

replen - npm Package Compare versions

Comparing version
1.5.5
to
1.5.6
+4
-4
dist/discover-projects.js

@@ -148,4 +148,4 @@ // Local-filesystem project discovery for day-1 onboarding.

* upserts by it); slug is the URL-safe display label derived from the
* repo name. So a local `~/projects/drone/` whose remote is
* `acme/palisade-website` registers as slug `palisade-website` — matching
* repo name. So a local `~/projects/widget/` whose remote is
* `acme/web-app` registers as slug `web-app` — matching
* the repo, not the local folder.

@@ -176,4 +176,4 @@ */

// an org rename) minted a fresh slug and the server
// inserted a duplicate row. `~/code/drone` with remote
// `nsokin/palisade-website` now registers as slug `palisade-website`.
// inserted a duplicate row. `~/code/widget` with remote
// `acme/web-app` now registers as slug `web-app`.
// The repo name is also the fallback display name, so a generic

@@ -180,0 +180,0 @@ // package.json name (the Next.js starter's "nextn", etc.) doesn't stick.

@@ -22,2 +22,60 @@ // `npx replen immerse` — hosted Immersion sender (M2).

const MAX_FILE_BYTES = 1_000_000; // mirror the server per-file cap
// Never read or transmit these, no matter what the manifest names. The
// traversal guard in send() keeps reads inside the repo; this denylist keeps
// in-repo secrets (.env files, private keys, credential stores) out of the
// payload even if a compromised/hostile manifest asks for them. Matching is
// on lowercased path segments with trailing dots stripped, so ".ENV" or
// ".env." can't slip through.
const SENSITIVE_DIRS = new Set([".git", ".aws", ".ssh", ".gnupg", ".docker"]);
const SENSITIVE_NAMES = new Set([
".npmrc", ".netrc", ".envrc", ".pgpass", ".dockercfg",
"credentials", "serviceaccount.json", "service-account.json",
"terraform.tfstate", "terraform.tfstate.backup",
]);
const SENSITIVE_EXTS = [
".pem", ".key", ".p12", ".pfx", ".pgp", ".gpg", ".asc",
".jks", ".keystore", ".ppk", ".tfstate",
];
// Programming-source extensions. A file such as token.ts or secret.ts is SOURCE
// (grounding it is the whole point of immersion), so the secret-word heuristic
// below must not treat a code file as a credential store just because its name
// contains "token". Secrets live in config / data / dotfiles, not .ts modules.
const SOURCE_EXTS = new Set([
".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".java",
".rb", ".php", ".cs", ".c", ".cc", ".cpp", ".h", ".hpp", ".swift", ".kt",
".scala", ".sh", ".css", ".scss", ".sass", ".html", ".vue", ".svelte", ".sql",
]);
const SECRET_WORDS = new Set([
"secret", "secrets", "credential", "credentials", "token", "tokens",
"password", "passwords", "passwd",
]);
function isSensitivePath(rel) {
const segs = rel.split(/[\\/]/).map((s) => s.toLowerCase().replace(/\.+$/, ""));
if (segs.some((s) => SENSITIVE_DIRS.has(s)))
return true;
const base = segs[segs.length - 1] ?? "";
if (SENSITIVE_NAMES.has(base))
return true;
if (base === ".env" || base.startsWith(".env."))
return true;
if (SENSITIVE_EXTS.some((ext) => base.endsWith(ext)))
return true;
// SSH / signing private keys: id_rsa, id_ed25519, id_ecdsa, id_dsa, and
// extensionless *_key / deploy_key files.
if (/^id_(rsa|dsa|ecdsa|ed25519)/.test(base))
return true;
const dot = base.lastIndexOf(".");
const ext = dot > 0 ? base.slice(dot) : "";
if (ext === "" && (base === "deploy_key" || base.endsWith("_key")))
return true;
// Secret-word heuristic. Whole-word match (so "tokenizer.ts" is not caught),
// and never exclude a real source file: github-token.json is a secret store,
// token.ts is source that immersion is meant to send.
if (!SOURCE_EXTS.has(ext)) {
const words = base.split(/[^a-z0-9]+/).filter(Boolean);
if (words.some((w) => SECRET_WORDS.has(w)))
return true;
}
return false;
}
export async function runImmerse(argv) {

@@ -90,2 +148,4 @@ const sub = (argv[0] ?? "").toLowerCase();

continue;
if (isSensitivePath(rel))
continue; // in-repo secret: never read, never send
const abs = resolve(root, rel);

@@ -92,0 +152,0 @@ if (abs !== root && !abs.startsWith(root + sep))

@@ -450,3 +450,12 @@ // `npx replen uninstall` — reverse every local change `npx replen` made,

const ts = new Date().toISOString().replace(/[:.]/g, "-");
writeFileSync(`${path}.bak.${ts}`, readFileSync(path));
const backup = `${path}.bak.${ts}`;
// The backed-up config can carry tokens; keep it 0600 like the atomic
// write above, not the umask default (typically world-readable 0644).
writeFileSync(backup, readFileSync(path), { mode: 0o600 });
try {
chmodSync(backup, 0o600);
}
catch {
/* best-effort */
}
}

@@ -453,0 +462,0 @@ // ============================================================================

@@ -36,5 +36,11 @@ ---

curl -sS -H "x-digest-token: $TOKEN" \
"$BASE/api/inventory/today?repo=<owner/name>&days=2&limit=10"
"$BASE/api/inventory/today?repo=<owner/name>&limit=10"
```
Do NOT pass `days`. Omitting it lets the server pick the window adaptively:
a wide first-run window for a repo that's never surfaced a match, then a
since-last-open window (everything found since you last opened this repo,
floored at ~a month) on later opens. Pass `days` only to force a fixed
lookback for debugging.
Parse the JSON response. Note:

@@ -41,0 +47,0 @@

{
"name": "replen",
"version": "1.5.5",
"version": "1.5.6",
"description": "Make your AI coding tools smarter. One command, no API keys, free. Replen watches what your projects actually do and surfaces a few things worth bringing in each month. Use one as is, port a piece of another, cherry pick an idea, or build it clean room. The match happens inside your AI tool's session. A few actionable matches a month, by design.",

@@ -5,0 +5,0 @@ "type": "module",