agentinel 0.0.10 → 1.0.2
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/README.md +63 -23
- package/dist/asen.js +154 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/agentinel)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
[](https://github.com/aman-janwani/agentinel/actions)
|
|
7
|
+
[](#)
|
|
7
8
|
</div>
|
|
8
9
|
|
|
9
10
|
<br />
|
|
@@ -34,6 +35,7 @@ Every other tool in this space guards your terminal. **Agentinel guards your age
|
|
|
34
35
|
- **Known Malware:** Bundles a local OSV database of 216,000+ confirmed malicious packages.
|
|
35
36
|
- **Zero False Positives on Popular Packages:** Tested against the top 100 npm packages.
|
|
36
37
|
- **Heuristic Scanning:** Flags npm takedowns, packages under 30 days old with < 1k downloads (slopsquatting), publisher drift, and non-existent hallucinated names.
|
|
38
|
+
- **Cross-Platform Compatibility:** Fully tested and natively supported across macOS, Linux, and Windows.
|
|
37
39
|
- **Fails Open:** Designed so that if it crashes or can't reach the registry, it fails open. It will never permanently wedge your terminal or block your work.
|
|
38
40
|
|
|
39
41
|
---
|
|
@@ -75,10 +77,10 @@ The AI reads this, realizes the package is fake or malicious, and intelligently
|
|
|
75
77
|
|
|
76
78
|
What about installs that never go through an agent? (e.g., You typing `npm install` manually).
|
|
77
79
|
|
|
78
|
-
Agentinel provides an opt-in **PATH shim**.
|
|
80
|
+
Agentinel provides an opt-in **PATH shim**. By default, running `npx asen init` installs this shim automatically.
|
|
79
81
|
|
|
80
82
|
```sh
|
|
81
|
-
npx asen init
|
|
83
|
+
npx asen init
|
|
82
84
|
```
|
|
83
85
|
|
|
84
86
|
This puts a tiny, fail-open wrapper script earlier in your `PATH`. When you type `npm install`, the shim checks the package first. If it's safe, the real `npm` command runs instantly.
|
|
@@ -119,17 +121,31 @@ How do we stack up against traditional commercial security scanners?
|
|
|
119
121
|
|
|
120
122
|
---
|
|
121
123
|
|
|
122
|
-
## ⚙️
|
|
124
|
+
## ⚙️ Modes: warn vs strict
|
|
125
|
+
|
|
126
|
+
Agentinel supports two operating modes, controlled by the `mode` field. You can switch between them using `npx asen mode <warn|strict>`.
|
|
127
|
+
|
|
128
|
+
### warn (default)
|
|
129
|
+
Agentinel surfaces a warning in the agent output but does not block the install. The agent decides whether to proceed.
|
|
130
|
+
|
|
131
|
+
`.agentinel.json`
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"mode": "warn"
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
*Best for teams migrating to Agentinel gradually or using agents in read-heavy workflows.*
|
|
123
138
|
|
|
124
|
-
|
|
125
|
-
|
|
139
|
+
### strict
|
|
140
|
+
Agentinel hard-blocks the install and returns an error payload to the agent. The install never reaches npm.
|
|
126
141
|
|
|
142
|
+
`.agentinel.json`
|
|
127
143
|
```json
|
|
128
144
|
{
|
|
129
|
-
"mode": "strict"
|
|
130
|
-
"allowlist": {}
|
|
145
|
+
"mode": "strict"
|
|
131
146
|
}
|
|
132
147
|
```
|
|
148
|
+
*Recommended for production repos, CI pipelines, and any project with autonomous agentic access.*
|
|
133
149
|
|
|
134
150
|
---
|
|
135
151
|
|
|
@@ -137,31 +153,40 @@ To strictly block bad packages, change your `.agentinel.json` file to strict mod
|
|
|
137
153
|
|
|
138
154
|
Here are all the commands you can run via `npx asen <command>`:
|
|
139
155
|
|
|
140
|
-
### `npx asen init`
|
|
141
|
-
Wires up agent hooks and git hooks in the current repo.
|
|
156
|
+
### `npx asen init [--no-shim]`
|
|
157
|
+
Wires up agent hooks and git hooks in the current repo, and installs the global PATH shim for human terminal protection.
|
|
142
158
|
```bash
|
|
143
159
|
$ npx asen init
|
|
144
|
-
|
|
145
|
-
|
|
160
|
+
wrote .agentinel.json
|
|
161
|
+
registered the Claude Code PreToolUse hook in .claude/settings.json
|
|
162
|
+
installed the git pre-commit hook in .git/hooks
|
|
163
|
+
wrote shims for npm, npx, pnpm, yarn, bun in /Users/user/.agentinel/bin
|
|
164
|
+
added the shims to PATH in /Users/user/.zshrc
|
|
165
|
+
Mode is warn, so a risky package typed at the terminal will be reported, not blocked.
|
|
166
|
+
Open a new terminal, or run \`asen unshim\` to undo this.
|
|
167
|
+
|
|
168
|
+
agentinel is set up. New npm packages will be checked before they land.
|
|
169
|
+
Default mode is warn. Set "mode": "strict" in .agentinel.json to block instead.
|
|
146
170
|
```
|
|
147
171
|
|
|
148
|
-
|
|
149
|
-
Wires up hooks AND installs the global PATH shim for human terminal protection.
|
|
172
|
+
When used with `--no-shim`, it wires up hooks but skips installing the global PATH shim.
|
|
150
173
|
```bash
|
|
151
|
-
$ npx asen init --shim
|
|
152
|
-
|
|
153
|
-
|
|
174
|
+
$ npx asen init --no-shim
|
|
175
|
+
wrote .agentinel.json
|
|
176
|
+
registered the Claude Code PreToolUse hook in .claude/settings.json
|
|
177
|
+
installed the git pre-commit hook in .git/hooks
|
|
178
|
+
|
|
179
|
+
agentinel is set up. New npm packages will be checked before they land.
|
|
180
|
+
Default mode is warn. Set "mode": "strict" in .agentinel.json to block instead.
|
|
154
181
|
```
|
|
155
182
|
|
|
156
|
-
### `npx asen check`
|
|
183
|
+
### `npx asen check [pkg...]`
|
|
157
184
|
Scans the currently staged dependencies in your lockfile. Exits non-zero if flagged (great for CI/CD pipelines).
|
|
158
185
|
```bash
|
|
159
186
|
$ npx asen check
|
|
160
|
-
|
|
161
|
-
✅ All 142 staged packages passed security checks.
|
|
187
|
+
checked 142 package(s), nothing suspicious
|
|
162
188
|
```
|
|
163
189
|
|
|
164
|
-
### `npx asen check <package-name>`
|
|
165
190
|
Scans a specific package instantly without installing it.
|
|
166
191
|
```bash
|
|
167
192
|
$ npx asen check react-router-v7-fake
|
|
@@ -169,18 +194,33 @@ $ npx asen check react-router-v7-fake
|
|
|
169
194
|
This matches the profile of a slopsquatting or malicious package.
|
|
170
195
|
```
|
|
171
196
|
|
|
172
|
-
### `npx asen allow <
|
|
197
|
+
### `npx asen allow <pkg> --reason "..."`
|
|
173
198
|
Adds a package to the allowlist in `.agentinel.json` with a required reason. This provides an audited trail for your team.
|
|
174
199
|
```bash
|
|
175
200
|
$ npx asen allow my-internal-pkg --reason "Internal company package not on public npm"
|
|
176
|
-
|
|
201
|
+
allowlisted my-internal-pkg in .agentinel.json
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### `npx asen mode <warn|strict>`
|
|
205
|
+
Switches Agentinel's operating mode in the `.agentinel.json` file.
|
|
206
|
+
```bash
|
|
207
|
+
$ npx asen mode strict
|
|
208
|
+
set mode to strict in .agentinel.json
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### `npx asen uninstall`
|
|
212
|
+
Completely removes all Agentinel hooks from your repository config files (`.claude`, `.gemini`, `.github`, etc.) and removes global shims.
|
|
213
|
+
```bash
|
|
214
|
+
$ npx asen uninstall
|
|
215
|
+
agentinel has been completely uninstalled from this repository.
|
|
177
216
|
```
|
|
178
217
|
|
|
179
218
|
### `npx asen unshim`
|
|
180
219
|
Removes the global PATH shim.
|
|
181
220
|
```bash
|
|
182
221
|
$ npx asen unshim
|
|
183
|
-
|
|
222
|
+
removed /Users/user/.agentinel/bin
|
|
223
|
+
removed the PATH line from /Users/user/.zshrc
|
|
184
224
|
```
|
|
185
225
|
|
|
186
226
|
---
|
package/dist/asen.js
CHANGED
|
@@ -102,7 +102,21 @@ import { join } from "path";
|
|
|
102
102
|
// src/config/schema.ts
|
|
103
103
|
var CONFIG_FILENAME = ".agentinel.json";
|
|
104
104
|
function defaultConfig() {
|
|
105
|
-
return {
|
|
105
|
+
return {
|
|
106
|
+
mode: "warn",
|
|
107
|
+
allow: [
|
|
108
|
+
{
|
|
109
|
+
name: "asen",
|
|
110
|
+
reason: "Agentinel CLI tool",
|
|
111
|
+
date: (/* @__PURE__ */ new Date()).toISOString().substring(0, 10)
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "agentinel",
|
|
115
|
+
reason: "Agentinel package",
|
|
116
|
+
date: (/* @__PURE__ */ new Date()).toISOString().substring(0, 10)
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
};
|
|
106
120
|
}
|
|
107
121
|
function parseConfig(raw, warn2 = () => {
|
|
108
122
|
}) {
|
|
@@ -1464,9 +1478,120 @@ function asRecord2(value) {
|
|
|
1464
1478
|
return typeof value === "object" && value !== null ? value : null;
|
|
1465
1479
|
}
|
|
1466
1480
|
|
|
1467
|
-
// src/
|
|
1468
|
-
|
|
1481
|
+
// src/commands/mode.ts
|
|
1482
|
+
function runMode(targetMode) {
|
|
1483
|
+
if (targetMode !== "warn" && targetMode !== "strict") {
|
|
1484
|
+
console.error("usage: asen mode <warn|strict>");
|
|
1485
|
+
return 1;
|
|
1486
|
+
}
|
|
1487
|
+
const repoRoot = repoRootOrCwd();
|
|
1488
|
+
const config = loadConfig(repoRoot);
|
|
1489
|
+
if (config.mode === targetMode) {
|
|
1490
|
+
console.log(`mode is already ${targetMode}`);
|
|
1491
|
+
return 0;
|
|
1492
|
+
}
|
|
1493
|
+
config.mode = targetMode;
|
|
1494
|
+
saveConfig(repoRoot, config);
|
|
1495
|
+
console.log(`set mode to ${targetMode} in .agentinel.json`);
|
|
1496
|
+
return 0;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// src/commands/uninstall.ts
|
|
1500
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
1469
1501
|
import { join as join6 } from "path";
|
|
1502
|
+
var HOOK_MARKER2 = "agentinel";
|
|
1503
|
+
var HOOK_SUBCOMMAND2 = "hook claude-code";
|
|
1504
|
+
function runUninstall() {
|
|
1505
|
+
const repoRoot = repoRootOrCwd();
|
|
1506
|
+
unwireClaudeCodeHook(repoRoot);
|
|
1507
|
+
unwireCodexHook(repoRoot);
|
|
1508
|
+
unwireCopilotHook(repoRoot);
|
|
1509
|
+
unwireGeminiHook(repoRoot);
|
|
1510
|
+
unwirePreCommitHook(repoRoot);
|
|
1511
|
+
removeShim();
|
|
1512
|
+
console.log("agentinel has been completely uninstalled from this repository.");
|
|
1513
|
+
return 0;
|
|
1514
|
+
}
|
|
1515
|
+
function unwireClaudeCodeHook(repoRoot) {
|
|
1516
|
+
const path = join6(repoRoot, ".claude", "settings.json");
|
|
1517
|
+
if (!existsSync6(path)) return;
|
|
1518
|
+
const file = readJson2(path);
|
|
1519
|
+
if (file === null) return;
|
|
1520
|
+
const hooks = asRecord3(file.hooks);
|
|
1521
|
+
if (!hooks || !Array.isArray(hooks.PreToolUse)) return;
|
|
1522
|
+
const preToolUse = hooks.PreToolUse;
|
|
1523
|
+
hooks.PreToolUse = preToolUse.filter((hook) => !JSON.stringify(hook).includes(HOOK_SUBCOMMAND2));
|
|
1524
|
+
if (hooks.PreToolUse.length === 0) {
|
|
1525
|
+
delete hooks.PreToolUse;
|
|
1526
|
+
}
|
|
1527
|
+
if (Object.keys(hooks).length === 0) {
|
|
1528
|
+
delete file.hooks;
|
|
1529
|
+
}
|
|
1530
|
+
writeJson2(path, file);
|
|
1531
|
+
console.log("removed Claude Code hook");
|
|
1532
|
+
}
|
|
1533
|
+
function unwireCodexHook(repoRoot) {
|
|
1534
|
+
const path = join6(repoRoot, ".codex", "hooks.json");
|
|
1535
|
+
if (!existsSync6(path)) return;
|
|
1536
|
+
const file = readJson2(path);
|
|
1537
|
+
if (file === null) return;
|
|
1538
|
+
const hooks = asRecord3(file.hooks);
|
|
1539
|
+
if (!hooks || !Array.isArray(hooks.PreToolUse)) return;
|
|
1540
|
+
const preToolUse = hooks.PreToolUse;
|
|
1541
|
+
hooks.PreToolUse = preToolUse.filter((hook) => !JSON.stringify(hook).includes("hook codex"));
|
|
1542
|
+
writeJson2(path, file);
|
|
1543
|
+
console.log("removed Codex hook");
|
|
1544
|
+
}
|
|
1545
|
+
function unwireCopilotHook(repoRoot) {
|
|
1546
|
+
const path = join6(repoRoot, ".github", "hooks", "agentinel.json");
|
|
1547
|
+
if (!existsSync6(path)) return;
|
|
1548
|
+
const file = readJson2(path);
|
|
1549
|
+
if (file === null) return;
|
|
1550
|
+
const hooks = asRecord3(file.hooks);
|
|
1551
|
+
if (!hooks || !Array.isArray(hooks.preToolUse)) return;
|
|
1552
|
+
const preToolUse = hooks.preToolUse;
|
|
1553
|
+
hooks.preToolUse = preToolUse.filter((hook) => !JSON.stringify(hook).includes("hook copilot"));
|
|
1554
|
+
writeJson2(path, file);
|
|
1555
|
+
console.log("removed Copilot hook");
|
|
1556
|
+
}
|
|
1557
|
+
function unwireGeminiHook(repoRoot) {
|
|
1558
|
+
const path = join6(repoRoot, ".gemini", "settings.json");
|
|
1559
|
+
if (!existsSync6(path)) return;
|
|
1560
|
+
const file = readJson2(path);
|
|
1561
|
+
if (file === null) return;
|
|
1562
|
+
const hooks = asRecord3(file.hooks);
|
|
1563
|
+
if (!hooks || !Array.isArray(hooks.BeforeTool)) return;
|
|
1564
|
+
const beforeTool = hooks.BeforeTool;
|
|
1565
|
+
hooks.BeforeTool = beforeTool.filter((hook) => !JSON.stringify(hook).includes("hook gemini"));
|
|
1566
|
+
writeJson2(path, file);
|
|
1567
|
+
console.log("removed Gemini hook");
|
|
1568
|
+
}
|
|
1569
|
+
function unwirePreCommitHook(repoRoot) {
|
|
1570
|
+
if (!existsSync6(join6(repoRoot, ".git"))) return;
|
|
1571
|
+
const path = join6(hooksDirectory(repoRoot), "pre-commit");
|
|
1572
|
+
if (!existsSync6(path)) return;
|
|
1573
|
+
const existing = readFileSync6(path, "utf8");
|
|
1574
|
+
if (!existing.includes(HOOK_MARKER2)) return;
|
|
1575
|
+
rmSync2(path);
|
|
1576
|
+
console.log("removed git pre-commit hook");
|
|
1577
|
+
}
|
|
1578
|
+
function readJson2(path) {
|
|
1579
|
+
try {
|
|
1580
|
+
return asRecord3(JSON.parse(readFileSync6(path, "utf8"))) ?? {};
|
|
1581
|
+
} catch {
|
|
1582
|
+
return null;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
function writeJson2(path, value) {
|
|
1586
|
+
writeFileSync4(path, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
1587
|
+
}
|
|
1588
|
+
function asRecord3(value) {
|
|
1589
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
// src/hooks/claude-code.ts
|
|
1593
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1594
|
+
import { join as join7 } from "path";
|
|
1470
1595
|
|
|
1471
1596
|
// src/checks/package-guard/resolve.ts
|
|
1472
1597
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
@@ -1550,7 +1675,7 @@ function candidatesFor(command, repoRoot) {
|
|
|
1550
1675
|
}
|
|
1551
1676
|
function isLocalTool(repoRoot, name) {
|
|
1552
1677
|
const binary = name.includes("/") ? name.split("/").pop() : name;
|
|
1553
|
-
return
|
|
1678
|
+
return existsSync7(join7(repoRoot, "node_modules", ".bin", binary));
|
|
1554
1679
|
}
|
|
1555
1680
|
function warn(verdicts) {
|
|
1556
1681
|
const summary = plainSummary(verdicts);
|
|
@@ -1764,22 +1889,41 @@ async function runPreCommitHook() {
|
|
|
1764
1889
|
}
|
|
1765
1890
|
|
|
1766
1891
|
// bin/asen.ts
|
|
1892
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
1893
|
+
import { dirname as dirname2, join as join8 } from "path";
|
|
1894
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1895
|
+
function getVersion() {
|
|
1896
|
+
try {
|
|
1897
|
+
const file = fileURLToPath2(import.meta.url);
|
|
1898
|
+
const pkgPath = join8(dirname2(file), "..", "package.json");
|
|
1899
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
1900
|
+
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
1901
|
+
} catch {
|
|
1902
|
+
return "unknown";
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1767
1905
|
var USAGE = `asen, a guard for AI coding agent workflows
|
|
1768
1906
|
|
|
1769
|
-
asen init [--shim]
|
|
1907
|
+
asen init [--no-shim] set up the hooks and config in this repo
|
|
1770
1908
|
asen check [pkg...] check staged dependencies, or specific packages
|
|
1771
1909
|
asen allow <pkg> --reason "..." allowlist a flagged package, with a logged reason
|
|
1910
|
+
asen mode <warn|strict> switch Agentinel's operating mode
|
|
1911
|
+
asen uninstall remove all agent hooks from this repository
|
|
1772
1912
|
asen unshim remove the PATH shims that --shim installed
|
|
1773
1913
|
`;
|
|
1774
1914
|
async function main(argv2) {
|
|
1775
1915
|
const [command, ...rest] = argv2;
|
|
1776
1916
|
switch (command) {
|
|
1777
1917
|
case "init":
|
|
1778
|
-
return runInit({ shim: rest.includes("--shim") });
|
|
1918
|
+
return runInit({ shim: !rest.includes("--no-shim") });
|
|
1779
1919
|
case "check":
|
|
1780
1920
|
return runCheck(positionals(rest));
|
|
1781
1921
|
case "allow":
|
|
1782
1922
|
return runAllow(positionals(rest)[0], readFlag(rest, "--reason"));
|
|
1923
|
+
case "mode":
|
|
1924
|
+
return runMode(positionals(rest)[0]);
|
|
1925
|
+
case "uninstall":
|
|
1926
|
+
return runUninstall();
|
|
1783
1927
|
case "unshim":
|
|
1784
1928
|
return removeShim();
|
|
1785
1929
|
// Not documented in the usage text on purpose. These are what the installed hooks call.
|
|
@@ -1788,6 +1932,10 @@ async function main(argv2) {
|
|
|
1788
1932
|
// What the PATH shims call, with the whole command line as one argument.
|
|
1789
1933
|
case "check-command":
|
|
1790
1934
|
return runCheckCommand(rest[0]);
|
|
1935
|
+
case "--version":
|
|
1936
|
+
case "-v":
|
|
1937
|
+
console.log(`agentinel v${getVersion()}`);
|
|
1938
|
+
return 0;
|
|
1791
1939
|
case "--help":
|
|
1792
1940
|
case "-h":
|
|
1793
1941
|
case void 0:
|