@akasecurity/cli 0.9.2 → 0.9.3
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 +4 -0
- package/dist/cli.js +258 -78
- package/package.json +4 -4
- package/web-ui/web-ui/.next/BUILD_ID +1 -1
- package/web-ui/web-ui/.next/app-build-manifest.json +39 -39
- package/web-ui/web-ui/.next/app-path-routes-manifest.json +5 -5
- package/web-ui/web-ui/.next/build-manifest.json +2 -2
- package/web-ui/web-ui/.next/prerender-manifest.json +18 -18
- package/web-ui/web-ui/.next/server/app/(app)/activity/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/activity/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/data-shares/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/data-shares/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/detections/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/detections/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/[id]/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/[id]/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/new/page.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/new/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/page.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/findings/page.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/findings/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/inventory/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/inventory/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/policies/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/policies/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/scan/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/scan/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/security/page.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/security/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/settings/page.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/settings/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/updates/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/updates/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/_not-found.html +1 -1
- package/web-ui/web-ui/.next/server/app/_not-found.rsc +2 -2
- package/web-ui/web-ui/.next/server/app/index.html +1 -1
- package/web-ui/web-ui/.next/server/app/index.rsc +2 -2
- package/web-ui/web-ui/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app-paths-manifest.json +5 -5
- package/web-ui/web-ui/.next/server/chunks/142.js +7 -7
- package/web-ui/web-ui/.next/server/chunks/472.js +2 -2
- package/web-ui/web-ui/.next/server/chunks/830.js +2 -2
- package/web-ui/web-ui/.next/server/chunks/853.js +5 -5
- package/web-ui/web-ui/.next/server/chunks/871.js +1 -1
- package/web-ui/web-ui/.next/server/functions-config-manifest.json +4 -4
- package/web-ui/web-ui/.next/server/middleware-manifest.json +5 -5
- package/web-ui/web-ui/.next/server/pages/404.html +1 -1
- package/web-ui/web-ui/.next/server/pages/500.html +1 -1
- package/web-ui/web-ui/.next/server/pages-manifest.json +1 -1
- package/web-ui/web-ui/.next/server/server-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/server-reference-manifest.json +1 -1
- package/web-ui/web-ui/.next/static/chunks/517-a4643cfd51efbca4.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/activity/page-72558bcb945a0abc.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/data-shares/page-540aa3dbe1c0b3ad.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/detections/page-8fac90d5b77b7f89.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/[id]/{page-3a5138f424e43b6e.js → page-06ee1868466199df.js} +1 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/new/page-8b2ea0bbbc4fc69c.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/page-2aa604a6c36786fc.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/findings/page-28f48db02051d88c.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/inventory/page-15bc43471843cf2e.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/layout-f034effcd10a332b.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/policies/page-de465eacbb359987.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/scan/page-fa10c50b1436102d.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/security/page-a61b90eb3a575f85.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/settings/page-be0284caa1e78227.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/updates/page-eec4e54274dcbb4d.js +1 -0
- package/web-ui/web-ui/.next/static/css/a30725ee12aca680.css +2 -0
- package/web-ui/web-ui/.next/static/chunks/517-977bc0a7a7be88cc.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/activity/page-96995843d6cbfb84.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/data-shares/page-28eeea5c4a816f53.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/detections/page-0c3888b9381a6e80.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/new/page-32d67b6c8165acda.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/page-1c376f3818bfba45.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/findings/page-9c9b1d29895a0592.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/inventory/page-4334a50ed9dcace4.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/layout-de6da4ea92784950.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/policies/page-4817c44647618832.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/scan/page-5d2aa8c946dd2cae.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/security/page-c0597d1c1acb1cc4.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/settings/page-2a7b0d9dab536ae1.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/updates/page-576a976afbe33369.js +0 -1
- package/web-ui/web-ui/.next/static/css/1ea302d29fc67ba4.css +0 -2
- /package/web-ui/web-ui/.next/static/{mAOCejrDqkMoDfzjWr6qu → X_Z1LZQpnGXTuZXKkv4Vb}/_buildManifest.js +0 -0
- /package/web-ui/web-ui/.next/static/{mAOCejrDqkMoDfzjWr6qu → X_Z1LZQpnGXTuZXKkv4Vb}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -48,6 +48,10 @@ aka dashboard # open the local web dashboard over your store
|
|
|
48
48
|
|
|
49
49
|
Run `aka --help` for the full command list.
|
|
50
50
|
|
|
51
|
+
### External subcommands
|
|
52
|
+
|
|
53
|
+
Any other command dispatches, git-style, to an executable named `aka-<command>` on your PATH (POSIX only; built-ins always win). For example, with [aka-claude-tools](https://github.com/akasecurity/claude-tools) installed, `aka claude` launches the hardened Claude Code profile via its `aka-claude` launcher.
|
|
54
|
+
|
|
51
55
|
## The Claude Code plugin
|
|
52
56
|
|
|
53
57
|
The CLI gives you the dashboard, store, and scanning. To actually **intercept** a Claude Code session you also need the AKA plugin, which installs from the Claude Code plugin marketplace (not npm). With the CLI installed:
|
package/dist/cli.js
CHANGED
|
@@ -18381,6 +18381,9 @@ var ExceptionBundleEntry = DetectionException.pick({
|
|
|
18381
18381
|
useCount: true,
|
|
18382
18382
|
conditions: true
|
|
18383
18383
|
});
|
|
18384
|
+
function isMatchableUnder(keyVersion, key) {
|
|
18385
|
+
return key.status === "present" && key.version === keyVersion;
|
|
18386
|
+
}
|
|
18384
18387
|
|
|
18385
18388
|
// ../packages/schema/src/zod/rule.ts
|
|
18386
18389
|
var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
|
|
@@ -25500,11 +25503,22 @@ function purgeSampleData(db) {
|
|
|
25500
25503
|
function linkHost(input, hostId) {
|
|
25501
25504
|
return hostId ? { ...input, hostId } : input;
|
|
25502
25505
|
}
|
|
25506
|
+
function closeQuietly(db) {
|
|
25507
|
+
try {
|
|
25508
|
+
db.close();
|
|
25509
|
+
} catch {
|
|
25510
|
+
}
|
|
25511
|
+
}
|
|
25503
25512
|
function openWithPragmas(file2) {
|
|
25504
25513
|
const db = new DatabaseSync(file2);
|
|
25505
|
-
|
|
25506
|
-
|
|
25507
|
-
|
|
25514
|
+
try {
|
|
25515
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
25516
|
+
db.exec("PRAGMA busy_timeout = 2000");
|
|
25517
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
25518
|
+
} catch (err) {
|
|
25519
|
+
closeQuietly(db);
|
|
25520
|
+
throw err;
|
|
25521
|
+
}
|
|
25508
25522
|
return db;
|
|
25509
25523
|
}
|
|
25510
25524
|
function backupLegacyStore(file2) {
|
|
@@ -25516,43 +25530,80 @@ function backupLegacyStore(file2) {
|
|
|
25516
25530
|
}
|
|
25517
25531
|
return backup;
|
|
25518
25532
|
}
|
|
25533
|
+
function openAndInitialize(file2) {
|
|
25534
|
+
let db = openWithPragmas(file2);
|
|
25535
|
+
try {
|
|
25536
|
+
if (isForeignSqliteLineage(db)) {
|
|
25537
|
+
db.close();
|
|
25538
|
+
const backup = backupLegacyStore(file2);
|
|
25539
|
+
db = openWithPragmas(file2);
|
|
25540
|
+
akaWarn(
|
|
25541
|
+
`Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
|
|
25542
|
+
);
|
|
25543
|
+
}
|
|
25544
|
+
applyMigrations(db, file2);
|
|
25545
|
+
tightenPerms(file2);
|
|
25546
|
+
const policies = new SqlitePoliciesRepository(db);
|
|
25547
|
+
const installedPacks = new SqliteInstalledPacksRepository(db);
|
|
25548
|
+
const repositories = {
|
|
25549
|
+
events: new SqliteEventsRepository(db),
|
|
25550
|
+
findings: new SqliteFindingsRepository(db),
|
|
25551
|
+
policies,
|
|
25552
|
+
installedPacks,
|
|
25553
|
+
scanLedger: new SqliteScanLedgerRepository(db),
|
|
25554
|
+
exceptions: new SqliteExceptionsRepository(db),
|
|
25555
|
+
resolutions: new SqliteResolutionsRepository(db),
|
|
25556
|
+
ruleProbeCache: new SqliteRuleProbeCacheRepository(db),
|
|
25557
|
+
security: new SqliteSecurityRepository(db),
|
|
25558
|
+
detections: new SqliteDetectionsRepository(db),
|
|
25559
|
+
shares: new SqliteSharesRepository(db),
|
|
25560
|
+
policyCatalog: new SqlitePolicyCatalogRepository(installedPacks),
|
|
25561
|
+
inventory: new SqliteInventoryRepository(db),
|
|
25562
|
+
inventoryAssets: new SqliteInventoryAssetsRepository(db),
|
|
25563
|
+
projectFiles: new SqliteProjectFilesRepository(db),
|
|
25564
|
+
activity: new SqliteActivityRepository(db),
|
|
25565
|
+
sourceProject: new SqliteSourceProjectRepository(db),
|
|
25566
|
+
auditEvents: new SqliteAuditEventsRepository(db),
|
|
25567
|
+
classifiedData: new SqliteClassifiedDataRepository(db),
|
|
25568
|
+
inspectionDefinitions: new SqliteInspectionDefinitionsRepository(db),
|
|
25569
|
+
inspectionFindings: new SqliteInspectionFindingsRepository(db),
|
|
25570
|
+
configInventory: new SqliteConfigInventoryRepository(db)
|
|
25571
|
+
};
|
|
25572
|
+
policies.seedDefaults();
|
|
25573
|
+
return { db, ...repositories };
|
|
25574
|
+
} catch (err) {
|
|
25575
|
+
closeQuietly(db);
|
|
25576
|
+
throw err;
|
|
25577
|
+
}
|
|
25578
|
+
}
|
|
25519
25579
|
function openLocalDatabase(dir) {
|
|
25520
25580
|
ensureDataDirSync(dir);
|
|
25521
25581
|
const file2 = join2(dir, DB_FILENAME);
|
|
25522
|
-
|
|
25523
|
-
|
|
25524
|
-
|
|
25525
|
-
|
|
25526
|
-
|
|
25527
|
-
|
|
25528
|
-
|
|
25529
|
-
|
|
25530
|
-
|
|
25531
|
-
|
|
25532
|
-
|
|
25533
|
-
|
|
25534
|
-
|
|
25535
|
-
|
|
25536
|
-
|
|
25537
|
-
|
|
25538
|
-
|
|
25539
|
-
|
|
25540
|
-
|
|
25541
|
-
|
|
25542
|
-
|
|
25543
|
-
|
|
25544
|
-
|
|
25545
|
-
|
|
25546
|
-
|
|
25547
|
-
const projectFiles = new SqliteProjectFilesRepository(db);
|
|
25548
|
-
const activity = new SqliteActivityRepository(db);
|
|
25549
|
-
const sourceProject = new SqliteSourceProjectRepository(db);
|
|
25550
|
-
const auditEvents = new SqliteAuditEventsRepository(db);
|
|
25551
|
-
const classifiedData = new SqliteClassifiedDataRepository(db);
|
|
25552
|
-
const inspectionDefinitions = new SqliteInspectionDefinitionsRepository(db);
|
|
25553
|
-
const inspectionFindings = new SqliteInspectionFindingsRepository(db);
|
|
25554
|
-
const configInventory = new SqliteConfigInventoryRepository(db);
|
|
25555
|
-
policies.seedDefaults();
|
|
25582
|
+
const {
|
|
25583
|
+
db,
|
|
25584
|
+
events,
|
|
25585
|
+
findings,
|
|
25586
|
+
policies,
|
|
25587
|
+
installedPacks,
|
|
25588
|
+
scanLedger,
|
|
25589
|
+
exceptions,
|
|
25590
|
+
resolutions,
|
|
25591
|
+
ruleProbeCache,
|
|
25592
|
+
security,
|
|
25593
|
+
detections,
|
|
25594
|
+
shares,
|
|
25595
|
+
policyCatalog,
|
|
25596
|
+
inventory,
|
|
25597
|
+
inventoryAssets,
|
|
25598
|
+
projectFiles,
|
|
25599
|
+
activity,
|
|
25600
|
+
sourceProject,
|
|
25601
|
+
auditEvents,
|
|
25602
|
+
classifiedData,
|
|
25603
|
+
inspectionDefinitions,
|
|
25604
|
+
inspectionFindings,
|
|
25605
|
+
configInventory
|
|
25606
|
+
} = openAndInitialize(file2);
|
|
25556
25607
|
function recordCapture(event, detected) {
|
|
25557
25608
|
failOpenTransaction(db, () => {
|
|
25558
25609
|
const sessionId = event.metadata?.sessionId;
|
|
@@ -25764,8 +25815,9 @@ function computeFindingKey(input) {
|
|
|
25764
25815
|
|
|
25765
25816
|
// ../packages/persistence/src/fingerprint.ts
|
|
25766
25817
|
import { createHmac, randomBytes } from "crypto";
|
|
25767
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
25818
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
25768
25819
|
import { join as join3 } from "path";
|
|
25820
|
+
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
25769
25821
|
var KEY_FILENAME = "exception.key";
|
|
25770
25822
|
var KEY_MATERIAL_BYTES = 32;
|
|
25771
25823
|
function keyFilePath(dataDir2) {
|
|
@@ -25789,6 +25841,46 @@ function parseKeyFile(raw) {
|
|
|
25789
25841
|
}
|
|
25790
25842
|
return { version: version2, material: bytes };
|
|
25791
25843
|
}
|
|
25844
|
+
var KEY_VERSION_TABLES = ["exceptions", "blocked_detections"];
|
|
25845
|
+
var SQLITE_ERROR = 1;
|
|
25846
|
+
var FLOOR_BUSY_TIMEOUT_MS = 250;
|
|
25847
|
+
var FloorUnreadableError = class extends Error {
|
|
25848
|
+
code = "floor-unreadable";
|
|
25849
|
+
constructor(cause) {
|
|
25850
|
+
super(
|
|
25851
|
+
`cannot read the stored fingerprint key versions: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
25852
|
+
{ cause }
|
|
25853
|
+
);
|
|
25854
|
+
this.name = "FloorUnreadableError";
|
|
25855
|
+
}
|
|
25856
|
+
};
|
|
25857
|
+
function storedKeyVersionFloor(dataDir2) {
|
|
25858
|
+
const file2 = join3(dataDir2, DB_FILENAME);
|
|
25859
|
+
if (!existsSync3(file2)) return 0;
|
|
25860
|
+
let db;
|
|
25861
|
+
try {
|
|
25862
|
+
db = new DatabaseSync2(file2, { readOnly: true });
|
|
25863
|
+
db.exec(`PRAGMA busy_timeout = ${String(FLOOR_BUSY_TIMEOUT_MS)}`);
|
|
25864
|
+
let floor = 0;
|
|
25865
|
+
for (const table of KEY_VERSION_TABLES) {
|
|
25866
|
+
try {
|
|
25867
|
+
const row = getRow(
|
|
25868
|
+
db.prepare(`SELECT MAX(key_version) AS v FROM ${table}`)
|
|
25869
|
+
);
|
|
25870
|
+
floor = Math.max(floor, row?.v ?? 0);
|
|
25871
|
+
} catch (err) {
|
|
25872
|
+
if (err.errcode !== SQLITE_ERROR) {
|
|
25873
|
+
throw new FloorUnreadableError(err);
|
|
25874
|
+
}
|
|
25875
|
+
}
|
|
25876
|
+
}
|
|
25877
|
+
return floor;
|
|
25878
|
+
} catch (err) {
|
|
25879
|
+
throw err instanceof FloorUnreadableError ? err : new FloorUnreadableError(err);
|
|
25880
|
+
} finally {
|
|
25881
|
+
db?.close();
|
|
25882
|
+
}
|
|
25883
|
+
}
|
|
25792
25884
|
function writeKeyFile(dataDir2, key) {
|
|
25793
25885
|
ensureDataDirSync(dataDir2);
|
|
25794
25886
|
const file2 = keyFilePath(dataDir2);
|
|
@@ -25813,18 +25905,27 @@ function loadOrCreateFingerprintKey(dataDir2) {
|
|
|
25813
25905
|
tightenFile(keyFilePath(dataDir2));
|
|
25814
25906
|
return existing;
|
|
25815
25907
|
}
|
|
25816
|
-
return writeKeyFile(dataDir2, {
|
|
25908
|
+
return writeKeyFile(dataDir2, {
|
|
25909
|
+
version: storedKeyVersionFloor(dataDir2) + 1,
|
|
25910
|
+
material: randomBytes(KEY_MATERIAL_BYTES)
|
|
25911
|
+
});
|
|
25817
25912
|
}
|
|
25818
25913
|
function rotateFingerprintKey(dataDir2) {
|
|
25819
25914
|
const existing = readFingerprintKey(dataDir2);
|
|
25820
25915
|
return writeKeyFile(dataDir2, {
|
|
25821
|
-
version: (existing?.version ?? 0) + 1,
|
|
25916
|
+
version: Math.max(existing?.version ?? 0, storedKeyVersionFloor(dataDir2)) + 1,
|
|
25822
25917
|
material: randomBytes(KEY_MATERIAL_BYTES)
|
|
25823
25918
|
});
|
|
25824
25919
|
}
|
|
25825
25920
|
function fingerprintValue(key, raw) {
|
|
25826
25921
|
return createHmac("sha256", key.material).update(raw, "utf8").digest("hex");
|
|
25827
25922
|
}
|
|
25923
|
+
function isCurrentKeyVersion(key, keyVersion) {
|
|
25924
|
+
return isMatchableUnder(keyVersion, keyStateOf(key));
|
|
25925
|
+
}
|
|
25926
|
+
function keyStateOf(key) {
|
|
25927
|
+
return key === null ? { status: "absent" } : { status: "present", version: key.version };
|
|
25928
|
+
}
|
|
25828
25929
|
|
|
25829
25930
|
// ../packages/persistence/src/local-layout.ts
|
|
25830
25931
|
import { renameSync as renameSync3 } from "fs";
|
|
@@ -25870,11 +25971,11 @@ function readJson(file2) {
|
|
|
25870
25971
|
}
|
|
25871
25972
|
|
|
25872
25973
|
// ../packages/persistence/src/warn-era-cap.ts
|
|
25873
|
-
import { existsSync as
|
|
25974
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
25874
25975
|
import { join as join6 } from "path";
|
|
25875
25976
|
|
|
25876
25977
|
// ../packages/plugin-sdk/src/config.ts
|
|
25877
|
-
import { existsSync as
|
|
25978
|
+
import { existsSync as existsSync5 } from "fs";
|
|
25878
25979
|
import { join as join7 } from "path";
|
|
25879
25980
|
|
|
25880
25981
|
// ../packages/plugin-sdk/src/provider-env.ts
|
|
@@ -27933,7 +28034,7 @@ function bundledDetections() {
|
|
|
27933
28034
|
}
|
|
27934
28035
|
|
|
27935
28036
|
// ../packages/plugin-sdk/src/repo.ts
|
|
27936
|
-
import { existsSync as
|
|
28037
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, statSync } from "fs";
|
|
27937
28038
|
import { basename, dirname as dirname2, isAbsolute, join as join8, sep as sep2 } from "path";
|
|
27938
28039
|
function resolveRepoIdentity(cwd) {
|
|
27939
28040
|
try {
|
|
@@ -27972,7 +28073,7 @@ function resolveHeadRoot(cwd) {
|
|
|
27972
28073
|
function findGitRoot(start) {
|
|
27973
28074
|
let dir = start;
|
|
27974
28075
|
for (; ; ) {
|
|
27975
|
-
if (
|
|
28076
|
+
if (existsSync6(join8(dir, ".git"))) return dir;
|
|
27976
28077
|
const parent = dirname2(dir);
|
|
27977
28078
|
if (parent === dir) return void 0;
|
|
27978
28079
|
dir = parent;
|
|
@@ -27990,7 +28091,7 @@ function resolveGitContext(root) {
|
|
|
27990
28091
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
27991
28092
|
if (!target) return void 0;
|
|
27992
28093
|
const gitdir = isAbsolute(target) ? target : join8(root, target);
|
|
27993
|
-
if (
|
|
28094
|
+
if (existsSync6(join8(gitdir, "config"))) {
|
|
27994
28095
|
return { configPath: join8(gitdir, "config"), headRoot: root };
|
|
27995
28096
|
}
|
|
27996
28097
|
const commonRaw = safeRead(join8(gitdir, "commondir"))?.trim();
|
|
@@ -28084,7 +28185,7 @@ function resolveNonGitProject(startDir, recognizeMarker) {
|
|
|
28084
28185
|
|
|
28085
28186
|
// ../packages/plugin-sdk/src/project-files.ts
|
|
28086
28187
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
28087
|
-
import { existsSync as
|
|
28188
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync7 } from "fs";
|
|
28088
28189
|
import { basename as basename4, join as join11, relative, sep as sep4 } from "path";
|
|
28089
28190
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
28090
28191
|
".git",
|
|
@@ -28186,7 +28287,7 @@ function resolveProjectFiles(cwd) {
|
|
|
28186
28287
|
const fullPath = join11(dir, entry.name);
|
|
28187
28288
|
if (entry.isDirectory()) {
|
|
28188
28289
|
if (SKIP_DIRS.has(entry.name) || isIgnored(dirLayers, fullPath, true)) continue;
|
|
28189
|
-
if (
|
|
28290
|
+
if (existsSync7(join11(fullPath, ".git"))) continue;
|
|
28190
28291
|
if (visit3(fullPath, dirLayers)) return true;
|
|
28191
28292
|
continue;
|
|
28192
28293
|
}
|
|
@@ -28489,7 +28590,7 @@ function reinvokeArgv(subcommand, extraArgs = []) {
|
|
|
28489
28590
|
|
|
28490
28591
|
// ../packages/local-ops/src/update-cache.ts
|
|
28491
28592
|
import { spawn } from "child_process";
|
|
28492
|
-
import { existsSync as
|
|
28593
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
28493
28594
|
import { join as join14 } from "path";
|
|
28494
28595
|
var CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
28495
28596
|
function cachePath(home) {
|
|
@@ -28497,7 +28598,7 @@ function cachePath(home) {
|
|
|
28497
28598
|
}
|
|
28498
28599
|
function readCache(home) {
|
|
28499
28600
|
const path = cachePath(home);
|
|
28500
|
-
if (!
|
|
28601
|
+
if (!existsSync8(path)) return null;
|
|
28501
28602
|
try {
|
|
28502
28603
|
const raw = JSON.parse(readFileSync9(path, "utf8"));
|
|
28503
28604
|
if (!isRecord(raw) || typeof raw.checkedAt !== "number" || !isRecord(raw.report)) return null;
|
|
@@ -28507,7 +28608,7 @@ function readCache(home) {
|
|
|
28507
28608
|
}
|
|
28508
28609
|
}
|
|
28509
28610
|
function writeCache(home, cache) {
|
|
28510
|
-
if (!
|
|
28611
|
+
if (!existsSync8(dataDir(home))) return;
|
|
28511
28612
|
try {
|
|
28512
28613
|
writeFileSync5(cachePath(home), `${JSON.stringify(cache, null, 2)}
|
|
28513
28614
|
`);
|
|
@@ -28672,7 +28773,7 @@ function commandsHelp() {
|
|
|
28672
28773
|
}
|
|
28673
28774
|
|
|
28674
28775
|
// src/commands/check-updates.ts
|
|
28675
|
-
import { existsSync as
|
|
28776
|
+
import { existsSync as existsSync9 } from "fs";
|
|
28676
28777
|
import { parseArgs } from "util";
|
|
28677
28778
|
|
|
28678
28779
|
// src/lib/args.ts
|
|
@@ -28703,7 +28804,7 @@ function runCheckUpdates(argv) {
|
|
|
28703
28804
|
out.write(`${String(ups.length)} update(s) available \u2014 run \`aka update\` to apply.
|
|
28704
28805
|
`);
|
|
28705
28806
|
}
|
|
28706
|
-
if (
|
|
28807
|
+
if (existsSync9(dataDir(home))) {
|
|
28707
28808
|
const notifiedPluginIds = readCache(home)?.notifiedPluginIds ?? [];
|
|
28708
28809
|
writeCache(home, { checkedAt: Date.now(), report, notifiedPluginIds });
|
|
28709
28810
|
}
|
|
@@ -28857,7 +28958,7 @@ function runCompletion(argv) {
|
|
|
28857
28958
|
|
|
28858
28959
|
// src/commands/dashboard.ts
|
|
28859
28960
|
import { spawn as spawn3 } from "child_process";
|
|
28860
|
-
import { existsSync as
|
|
28961
|
+
import { existsSync as existsSync10 } from "fs";
|
|
28861
28962
|
import { createRequire as createRequire2 } from "module";
|
|
28862
28963
|
import { createServer } from "net";
|
|
28863
28964
|
import { dirname as dirname5, join as join15 } from "path";
|
|
@@ -28937,7 +29038,7 @@ async function runDashboard(argv) {
|
|
|
28937
29038
|
const bundledServer = [
|
|
28938
29039
|
join15(base, "web-ui", "server.js"),
|
|
28939
29040
|
join15(base, "web-ui", "web-ui", "server.js")
|
|
28940
|
-
].find((p) =>
|
|
29041
|
+
].find((p) => existsSync10(p));
|
|
28941
29042
|
if (bundledServer) {
|
|
28942
29043
|
launchStandalone(bundledServer, port, shouldOpen, url2);
|
|
28943
29044
|
return;
|
|
@@ -28956,7 +29057,7 @@ async function runDashboard(argv) {
|
|
|
28956
29057
|
onReadyOpen(child, shouldOpen, url2, `Starting the AKA dashboard at ${url2}
|
|
28957
29058
|
`);
|
|
28958
29059
|
};
|
|
28959
|
-
if (
|
|
29060
|
+
if (existsSync10(join15(webUiDir, ".next", "BUILD_ID"))) {
|
|
28960
29061
|
startNext();
|
|
28961
29062
|
return;
|
|
28962
29063
|
}
|
|
@@ -29473,10 +29574,29 @@ function printGranted(io, ex) {
|
|
|
29473
29574
|
`
|
|
29474
29575
|
);
|
|
29475
29576
|
}
|
|
29476
|
-
function blockedLine(entry) {
|
|
29477
|
-
|
|
29577
|
+
function blockedLine(entry, key) {
|
|
29578
|
+
const line = `${entry.reference} ${entry.maskedValue} ${entry.ruleId} ${formatRelative(entry.blockedAt)}`;
|
|
29579
|
+
return isCurrentKeyVersion(key, entry.keyVersion) ? line : `${line} [not approvable: recorded under key v${String(entry.keyVersion)}]`;
|
|
29580
|
+
}
|
|
29581
|
+
function keyAccessHint(err, dir) {
|
|
29582
|
+
const code = err?.code;
|
|
29583
|
+
if (code === "floor-unreadable") {
|
|
29584
|
+
return `The local store could not be read, and a key minted without it could reuse a version already in use \u2014 fix the store first (${dir}/aka.db).`;
|
|
29585
|
+
}
|
|
29586
|
+
return typeof code === "string" ? `Check the permissions on ${dir}/exception.key \u2014 do not delete it, that invalidates every existing grant.` : `Delete ${dir}/exception.key to start fresh \u2014 grants written under it already cannot match.`;
|
|
29587
|
+
}
|
|
29588
|
+
function readKeyForApprove(dir) {
|
|
29589
|
+
try {
|
|
29590
|
+
return readFingerprintKey(dir);
|
|
29591
|
+
} catch (err) {
|
|
29592
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
29593
|
+
throw new Error(`cannot read the fingerprint key: ${message}
|
|
29594
|
+
${keyAccessHint(err, dir)}`, {
|
|
29595
|
+
cause: err
|
|
29596
|
+
});
|
|
29597
|
+
}
|
|
29478
29598
|
}
|
|
29479
|
-
async function pickBlocked(entries, selector, io,
|
|
29599
|
+
async function pickBlocked(entries, selector, io, key) {
|
|
29480
29600
|
if (selector !== void 0) {
|
|
29481
29601
|
const matches = entries.filter(
|
|
29482
29602
|
(e) => e.reference.startsWith(selector) || e.maskedValue === selector
|
|
@@ -29489,10 +29609,14 @@ async function pickBlocked(entries, selector, io, dir) {
|
|
|
29489
29609
|
`the selector matches ${String(matches.length)} recent blocks \u2014 run 'aka exception approve' bare to pick from the list`
|
|
29490
29610
|
);
|
|
29491
29611
|
}
|
|
29492
|
-
|
|
29612
|
+
if (key === null) {
|
|
29613
|
+
throw new Error(
|
|
29614
|
+
`no blocked detection matches that reference, and matching by value needs the fingerprint key \u2014 none exists on this machine`
|
|
29615
|
+
);
|
|
29616
|
+
}
|
|
29493
29617
|
const fingerprint = fingerprintValue(key, selector);
|
|
29494
29618
|
const byValue = entries.filter(
|
|
29495
|
-
(e) => e.keyVersion
|
|
29619
|
+
(e) => isCurrentKeyVersion(key, e.keyVersion) && e.valueFingerprint === fingerprint
|
|
29496
29620
|
);
|
|
29497
29621
|
const newest = byValue[0];
|
|
29498
29622
|
if (newest) {
|
|
@@ -29511,20 +29635,20 @@ async function pickBlocked(entries, selector, io, dir) {
|
|
|
29511
29635
|
const only = entries[0];
|
|
29512
29636
|
if (entries.length === 1 && only) {
|
|
29513
29637
|
io.out(`Approving the only recent block:
|
|
29514
|
-
${blockedLine(only)}
|
|
29638
|
+
${blockedLine(only, key)}
|
|
29515
29639
|
`);
|
|
29516
29640
|
return only;
|
|
29517
29641
|
}
|
|
29518
29642
|
if (!io.isInteractive) {
|
|
29519
29643
|
io.err(`Blocked in the last 30 minutes:
|
|
29520
29644
|
`);
|
|
29521
|
-
for (const entry of entries) io.err(` ${blockedLine(entry)}
|
|
29645
|
+
for (const entry of entries) io.err(` ${blockedLine(entry, key)}
|
|
29522
29646
|
`);
|
|
29523
29647
|
throw new Error("pass the reference to approve: aka exception approve <reference>");
|
|
29524
29648
|
}
|
|
29525
29649
|
io.out("Blocked in the last 30 minutes:\n");
|
|
29526
29650
|
entries.forEach((entry, i) => {
|
|
29527
|
-
io.out(` ${String(i + 1)}) ${blockedLine(entry)}
|
|
29651
|
+
io.out(` ${String(i + 1)}) ${blockedLine(entry, key)}
|
|
29528
29652
|
`);
|
|
29529
29653
|
});
|
|
29530
29654
|
const answer = await io.ask(`Which one? [1-${String(entries.length)}]: `);
|
|
@@ -29556,7 +29680,13 @@ To pre-authorize a value instead: aka exception add --rule <ruleId>
|
|
|
29556
29680
|
return;
|
|
29557
29681
|
}
|
|
29558
29682
|
const selector = positionals[0]?.trim();
|
|
29559
|
-
const
|
|
29683
|
+
const key = readKeyForApprove(dir);
|
|
29684
|
+
const entry = await pickBlocked(entries, selector === "" ? void 0 : selector, io, key);
|
|
29685
|
+
if (!isCurrentKeyVersion(key, entry.keyVersion)) {
|
|
29686
|
+
throw new Error(
|
|
29687
|
+
key === null ? `cannot approve ${entry.reference}: the fingerprint key file is missing, so the fingerprint recorded for it can no longer be matched \u2014 trigger the detection again` : `cannot approve ${entry.reference}: it was blocked under fingerprint key v${String(entry.keyVersion)} and the key is now v${String(key.version)}, so a grant from it could never match \u2014 trigger the detection again`
|
|
29688
|
+
);
|
|
29689
|
+
}
|
|
29560
29690
|
const { scope, reason } = await resolveScopeAndReason(values, io);
|
|
29561
29691
|
if (scope.scope === "permanent") {
|
|
29562
29692
|
await confirmPermanent(io, entry.maskedValue, values.yes === true);
|
|
@@ -29869,11 +29999,8 @@ written under the old key simply stop matching. They remain listed for audit.
|
|
|
29869
29999
|
version2 = rotateFingerprintKey(dir).version;
|
|
29870
30000
|
} catch (err) {
|
|
29871
30001
|
const message = err instanceof Error ? err.message : String(err);
|
|
29872
|
-
throw new Error(
|
|
29873
|
-
|
|
29874
|
-
Delete ${dir}/exception.key to start fresh \u2014 grants written under it already cannot match.`,
|
|
29875
|
-
{ cause: err }
|
|
29876
|
-
);
|
|
30002
|
+
throw new Error(`cannot rotate: ${message}
|
|
30003
|
+
${keyAccessHint(err, dir)}`, { cause: err });
|
|
29877
30004
|
}
|
|
29878
30005
|
io.out(
|
|
29879
30006
|
`Fingerprint key rotated \u2014 new key version v${String(version2)}.
|
|
@@ -29907,13 +30034,13 @@ async function runException(argv, io = terminalPrompter()) {
|
|
|
29907
30034
|
}
|
|
29908
30035
|
|
|
29909
30036
|
// src/commands/init.ts
|
|
29910
|
-
import { existsSync as
|
|
30037
|
+
import { existsSync as existsSync12, statSync as statSync7 } from "fs";
|
|
29911
30038
|
import { join as join16 } from "path";
|
|
29912
30039
|
import * as readline from "readline/promises";
|
|
29913
30040
|
import { parseArgs as parseArgs6 } from "util";
|
|
29914
30041
|
|
|
29915
30042
|
// src/commands/plugins.ts
|
|
29916
|
-
import { existsSync as
|
|
30043
|
+
import { existsSync as existsSync11 } from "fs";
|
|
29917
30044
|
import { parseArgs as parseArgs5 } from "util";
|
|
29918
30045
|
function runPlugins(argv) {
|
|
29919
30046
|
const [sub, ...rest] = argv;
|
|
@@ -29930,7 +30057,7 @@ async function listPlugins(argv) {
|
|
|
29930
30057
|
const { values } = parseArgs5({ args: argv, options: HOME_OPTION });
|
|
29931
30058
|
const home = homeBase(values.home);
|
|
29932
30059
|
const active = /* @__PURE__ */ new Set();
|
|
29933
|
-
if (
|
|
30060
|
+
if (existsSync11(dbPath(home))) {
|
|
29934
30061
|
const db = openLocalDatabase(dataDir(home));
|
|
29935
30062
|
try {
|
|
29936
30063
|
const recent = await db.findings.recentFindings({ limit: 1e3 });
|
|
@@ -30041,7 +30168,7 @@ async function runInit(argv) {
|
|
|
30041
30168
|
const settings = settingsDir(home);
|
|
30042
30169
|
ensureLayoutDirSync(settings);
|
|
30043
30170
|
const settingsFile = join16(settings, "settings.json");
|
|
30044
|
-
const settingsCreated = !
|
|
30171
|
+
const settingsCreated = !existsSync12(settingsFile);
|
|
30045
30172
|
if (settingsCreated) {
|
|
30046
30173
|
writeOwnerOnlyFileSync(
|
|
30047
30174
|
settingsFile,
|
|
@@ -31069,7 +31196,50 @@ function applyPluginUpdate2(status) {
|
|
|
31069
31196
|
return ok;
|
|
31070
31197
|
}
|
|
31071
31198
|
|
|
31072
|
-
// src/
|
|
31199
|
+
// src/lib/external-dispatch.ts
|
|
31200
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
31201
|
+
import { constants } from "os";
|
|
31202
|
+
function commandOnPath(command) {
|
|
31203
|
+
const probe = spawnSync2("/bin/sh", ["-c", 'command -v "$1" >/dev/null 2>&1', "sh", command], {
|
|
31204
|
+
stdio: "ignore"
|
|
31205
|
+
});
|
|
31206
|
+
return probe.status === 0;
|
|
31207
|
+
}
|
|
31208
|
+
function isExternalCommandName(name) {
|
|
31209
|
+
return /^[a-z][a-z0-9-]*$/.test(name);
|
|
31210
|
+
}
|
|
31211
|
+
function shouldDispatchExternal(command, isBuiltin) {
|
|
31212
|
+
return !isBuiltin && isExternalCommandName(command);
|
|
31213
|
+
}
|
|
31214
|
+
function externalDispatchSupported(platform2 = process.platform) {
|
|
31215
|
+
return platform2 !== "win32";
|
|
31216
|
+
}
|
|
31217
|
+
function signalExitCode(signal) {
|
|
31218
|
+
const number4 = signal ? constants.signals[signal] : void 0;
|
|
31219
|
+
return number4 === void 0 ? 1 : 128 + number4;
|
|
31220
|
+
}
|
|
31221
|
+
function dispatchExternal(command, argv, deps = {}) {
|
|
31222
|
+
const spawn4 = deps.spawn ?? spawnSync2;
|
|
31223
|
+
const platform2 = deps.platform ?? process.platform;
|
|
31224
|
+
const exists = deps.exists ?? commandOnPath;
|
|
31225
|
+
if (!externalDispatchSupported(platform2)) return { found: false, status: 1 };
|
|
31226
|
+
const target = `aka-${command}`;
|
|
31227
|
+
const result = spawn4(target, argv, { stdio: "inherit" });
|
|
31228
|
+
if (result.error) {
|
|
31229
|
+
if (result.error.code === "ENOENT" && !exists(target)) return { found: false, status: 1 };
|
|
31230
|
+
process.stderr.write(`aka: ${target}: ${result.error.message}
|
|
31231
|
+
`);
|
|
31232
|
+
return { found: true, status: 1 };
|
|
31233
|
+
}
|
|
31234
|
+
if (result.status === null) {
|
|
31235
|
+
if (result.signal) process.stderr.write(`aka: ${target}: terminated by ${result.signal}
|
|
31236
|
+
`);
|
|
31237
|
+
return { found: true, status: signalExitCode(result.signal) };
|
|
31238
|
+
}
|
|
31239
|
+
return { found: true, status: result.status };
|
|
31240
|
+
}
|
|
31241
|
+
|
|
31242
|
+
// src/main.ts
|
|
31073
31243
|
var COMMANDS = {
|
|
31074
31244
|
init: runInit,
|
|
31075
31245
|
scan: runScan,
|
|
@@ -31106,7 +31276,9 @@ Usage: aka <command> [options]
|
|
|
31106
31276
|
|
|
31107
31277
|
Commands:
|
|
31108
31278
|
${commandsHelp()}
|
|
31109
|
-
|
|
31279
|
+
${externalDispatchSupported() ? `
|
|
31280
|
+
Anything else runs an external 'aka-<command>' from your PATH (so 'aka claude' runs aka-claude).
|
|
31281
|
+
` : ""}
|
|
31110
31282
|
Options:
|
|
31111
31283
|
--home <dir> Use an alternate AKA home (default: ~/.aka)
|
|
31112
31284
|
--no-update-check Skip the post-command "updates available" notice
|
|
@@ -31122,8 +31294,7 @@ function homeFromArgv(argv) {
|
|
|
31122
31294
|
}
|
|
31123
31295
|
return homeBase(void 0);
|
|
31124
31296
|
}
|
|
31125
|
-
async function main() {
|
|
31126
|
-
const argv = process.argv.slice(2);
|
|
31297
|
+
async function main(argv = process.argv.slice(2), dispatchDeps = {}) {
|
|
31127
31298
|
if (argv[0] === "-v" || argv[0] === "--version" || argv[0] === "version") {
|
|
31128
31299
|
process.stdout.write(`${cliVersion() ?? "unknown"}
|
|
31129
31300
|
`);
|
|
@@ -31136,7 +31307,14 @@ async function main() {
|
|
|
31136
31307
|
}
|
|
31137
31308
|
const noUpdateCheck = rawRest.includes("--no-update-check");
|
|
31138
31309
|
const rest = rawRest.filter((a) => a !== "--no-update-check");
|
|
31139
|
-
const handler = COMMANDS[command];
|
|
31310
|
+
const handler = Object.hasOwn(COMMANDS, command) ? COMMANDS[command] : void 0;
|
|
31311
|
+
if (shouldDispatchExternal(command, handler !== void 0)) {
|
|
31312
|
+
const external = dispatchExternal(command, rawRest, dispatchDeps);
|
|
31313
|
+
if (external.found) {
|
|
31314
|
+
process.exitCode = external.status;
|
|
31315
|
+
return;
|
|
31316
|
+
}
|
|
31317
|
+
}
|
|
31140
31318
|
if (!handler) {
|
|
31141
31319
|
process.stderr.write(`aka: unknown command '${command}'
|
|
31142
31320
|
|
|
@@ -31152,6 +31330,8 @@ ${USAGE}`);
|
|
|
31152
31330
|
}
|
|
31153
31331
|
}
|
|
31154
31332
|
}
|
|
31333
|
+
|
|
31334
|
+
// src/cli.ts
|
|
31155
31335
|
main().catch((err) => {
|
|
31156
31336
|
process.stderr.write(`aka: ${err instanceof Error ? err.message : String(err)}
|
|
31157
31337
|
`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akasecurity/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"description": "AI Traffic Control — local-first CLI to inspect and govern AI agent traffic on your machine",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
"typescript": "^5.8.0",
|
|
42
42
|
"vitest": "^4.1.10",
|
|
43
43
|
"@akasecurity/dashboard-ui": "0.8.0",
|
|
44
|
-
"@akasecurity/
|
|
44
|
+
"@akasecurity/detections": "0.8.0",
|
|
45
45
|
"@akasecurity/local-ops": "0.8.0",
|
|
46
|
+
"@akasecurity/eslint-config": "0.8.0",
|
|
46
47
|
"@akasecurity/plugin-sdk": "0.8.0",
|
|
47
|
-
"@akasecurity/schema": "0.8.0",
|
|
48
|
-
"@akasecurity/detections": "0.8.0",
|
|
49
48
|
"@akasecurity/persistence": "0.8.0",
|
|
49
|
+
"@akasecurity/schema": "0.8.0",
|
|
50
50
|
"@akasecurity/web-ui": "0.8.0"
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
X_Z1LZQpnGXTuZXKkv4Vb
|