@cybedefend/vibedefend 1.4.6 → 1.5.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/README.md +10 -0
- package/dist/hook-runner.js +611 -162
- package/dist/hooks/runtime/api.js +29 -2
- package/dist/hooks/runtime/api.js.map +1 -1
- package/dist/hooks/runtime/emit.js +40 -0
- package/dist/hooks/runtime/emit.js.map +1 -1
- package/dist/hooks/runtime/fetch-rules.js +33 -2
- package/dist/hooks/runtime/fetch-rules.js.map +1 -1
- package/dist/hooks/runtime/guard-rules-cache.js +25 -3
- package/dist/hooks/runtime/guard-rules-cache.js.map +1 -1
- package/dist/hooks/runtime/judge.js +10 -2
- package/dist/hooks/runtime/judge.js.map +1 -1
- package/dist/hooks/runtime/pre-compact.js +9 -0
- package/dist/hooks/runtime/pre-compact.js.map +1 -1
- package/dist/hooks/runtime/resolve.js +18 -9
- package/dist/hooks/runtime/resolve.js.map +1 -1
- package/dist/hooks/runtime/session-activation.js +222 -0
- package/dist/hooks/runtime/session-activation.js.map +1 -0
- package/dist/hooks/runtime/session-review.js +13 -3
- package/dist/hooks/runtime/session-review.js.map +1 -1
- package/dist/hooks/runtime/session-start.js +33 -3
- package/dist/hooks/runtime/session-start.js.map +1 -1
- package/dist/hooks/runtime/usage-block.js +199 -0
- package/dist/hooks/runtime/usage-block.js.map +1 -0
- package/dist/hooks/runtime/user-prompt-submit.js +6 -1
- package/dist/hooks/runtime/user-prompt-submit.js.map +1 -1
- package/dist/login.js +22 -3
- package/dist/login.js.map +1 -1
- package/package.json +1 -1
package/dist/hook-runner.js
CHANGED
|
@@ -5764,26 +5764,34 @@ async function resolveProjectIdWithApi(cwd = process.cwd(), deps = {}) {
|
|
|
5764
5764
|
const resolveSync = deps.resolveSync ?? resolveProjectId;
|
|
5765
5765
|
const base = resolveSync(cwd);
|
|
5766
5766
|
if (base.projectId) return base;
|
|
5767
|
+
let repository;
|
|
5768
|
+
const none = (inactiveReason) => ({
|
|
5769
|
+
...base,
|
|
5770
|
+
inactiveReason,
|
|
5771
|
+
...repository ? { repository } : {}
|
|
5772
|
+
});
|
|
5767
5773
|
try {
|
|
5768
5774
|
const getOrigin = deps.getOrigin ?? getOriginRemote;
|
|
5769
5775
|
const originUrl = getOrigin(cwd);
|
|
5770
|
-
if (!originUrl) return
|
|
5776
|
+
if (!originUrl) return none("no-git-origin");
|
|
5771
5777
|
const remote = parseRemoteUrl(originUrl);
|
|
5772
|
-
if (!remote
|
|
5778
|
+
if (!remote) return none("unsupported-remote");
|
|
5779
|
+
repository = `${remote.host}/${remote.slug}`;
|
|
5780
|
+
if (remote.provider === null) return none("unsupported-remote");
|
|
5773
5781
|
const key = `${remote.provider}:${remote.host}:${remote.slug}`;
|
|
5774
5782
|
const cached = getCachedRepoLink(key, deps.cacheDeps);
|
|
5775
5783
|
if (cached.kind === "positive") {
|
|
5776
5784
|
return { projectId: cached.projectId, apiBase: base.apiBase };
|
|
5777
5785
|
}
|
|
5778
5786
|
if (cached.kind === "negative") {
|
|
5779
|
-
return
|
|
5787
|
+
return none("no-linked-project");
|
|
5780
5788
|
}
|
|
5781
5789
|
const config = (deps.loadConfig ?? loadRuntimeConfig)();
|
|
5782
|
-
if (!config) return
|
|
5790
|
+
if (!config) return none("lookup-failed");
|
|
5783
5791
|
const apiBase = base.apiBase ?? config.region.apiBase;
|
|
5784
5792
|
const resolveTokenFn = deps.resolveTokenFn ?? resolveTokenWithRefresh;
|
|
5785
5793
|
const token = await resolveTokenFn(config.region.mcpName);
|
|
5786
|
-
if (!token) return
|
|
5794
|
+
if (!token) return none("not-signed-in");
|
|
5787
5795
|
const resolveByRepo = deps.resolveByRepo ?? resolveProjectByRepository;
|
|
5788
5796
|
const matches = await resolveByRepo(
|
|
5789
5797
|
apiBase,
|
|
@@ -5791,7 +5799,7 @@ async function resolveProjectIdWithApi(cwd = process.cwd(), deps = {}) {
|
|
|
5791
5799
|
{ provider: remote.provider, slug: remote.slug, host: remote.host },
|
|
5792
5800
|
{ timeoutMs: deps.timeoutMs ?? RESOLVE_API_TIMEOUT_MS }
|
|
5793
5801
|
);
|
|
5794
|
-
if (matches === null) return
|
|
5802
|
+
if (matches === null) return none("lookup-failed");
|
|
5795
5803
|
if (matches.length === 1 && matches[0].vibedefendEnabled !== false) {
|
|
5796
5804
|
const m = matches[0];
|
|
5797
5805
|
writeCachedRepoLink(
|
|
@@ -5802,13 +5810,171 @@ async function resolveProjectIdWithApi(cwd = process.cwd(), deps = {}) {
|
|
|
5802
5810
|
return { projectId: m.projectId, apiBase: base.apiBase };
|
|
5803
5811
|
}
|
|
5804
5812
|
writeCachedRepoLink(key, { negative: true }, deps.cacheDeps);
|
|
5805
|
-
return
|
|
5813
|
+
return none("no-linked-project");
|
|
5806
5814
|
} catch {
|
|
5807
|
-
return
|
|
5815
|
+
return none("lookup-failed");
|
|
5808
5816
|
}
|
|
5809
5817
|
}
|
|
5810
5818
|
|
|
5819
|
+
// src/hooks/runtime/usage-block.ts
|
|
5820
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "node:fs";
|
|
5821
|
+
import { homedir as homedir6 } from "node:os";
|
|
5822
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
5823
|
+
var BLOCKED_CODE = "VIBEDEFEND_BLOCKED";
|
|
5824
|
+
var USAGE_WARNING_HEADER = "x-vibedefend-usage-warning";
|
|
5825
|
+
var UNITS = ["injection", "judgment", "diff_scan"];
|
|
5826
|
+
var REASONS = [
|
|
5827
|
+
"free_tier_exhausted",
|
|
5828
|
+
"cap_reached",
|
|
5829
|
+
"unpaid",
|
|
5830
|
+
"product_inactive"
|
|
5831
|
+
];
|
|
5832
|
+
var WARNINGS = [
|
|
5833
|
+
"approaching_limit",
|
|
5834
|
+
"limit_reached"
|
|
5835
|
+
];
|
|
5836
|
+
function pick(allowed, value) {
|
|
5837
|
+
return allowed.find((v) => v === value);
|
|
5838
|
+
}
|
|
5839
|
+
async function readVibedefendBlock(res) {
|
|
5840
|
+
if (res.status !== 402) return null;
|
|
5841
|
+
let body;
|
|
5842
|
+
try {
|
|
5843
|
+
body = await res.json();
|
|
5844
|
+
} catch {
|
|
5845
|
+
return null;
|
|
5846
|
+
}
|
|
5847
|
+
if (!body || typeof body !== "object") return null;
|
|
5848
|
+
const root = body;
|
|
5849
|
+
const data = root.data && typeof root.data === "object" ? root.data : root;
|
|
5850
|
+
const source = data.code === BLOCKED_CODE ? data : root;
|
|
5851
|
+
if (source.code !== BLOCKED_CODE) return null;
|
|
5852
|
+
return {
|
|
5853
|
+
reason: pick(REASONS, source.reason) ?? "unknown",
|
|
5854
|
+
unit: pick(UNITS, source.unit) ?? "unknown"
|
|
5855
|
+
};
|
|
5856
|
+
}
|
|
5857
|
+
function readUsageWarning(res) {
|
|
5858
|
+
let raw;
|
|
5859
|
+
try {
|
|
5860
|
+
raw = res.headers?.get?.(USAGE_WARNING_HEADER);
|
|
5861
|
+
} catch {
|
|
5862
|
+
return null;
|
|
5863
|
+
}
|
|
5864
|
+
if (typeof raw !== "string") return null;
|
|
5865
|
+
const parts = raw.trim().split(":");
|
|
5866
|
+
if (parts.length !== 2) return null;
|
|
5867
|
+
const unit = pick(UNITS, parts[0]);
|
|
5868
|
+
const warning = pick(WARNINGS, parts[1]);
|
|
5869
|
+
if (!unit || !warning) return null;
|
|
5870
|
+
return { unit, warning };
|
|
5871
|
+
}
|
|
5872
|
+
var UNIT_LABEL = {
|
|
5873
|
+
injection: "Security rules injection",
|
|
5874
|
+
judgment: "Action Guards judgments",
|
|
5875
|
+
diff_scan: "Diff scans",
|
|
5876
|
+
unknown: "Part of VibeDefend"
|
|
5877
|
+
};
|
|
5878
|
+
var UNIT_EFFECT = {
|
|
5879
|
+
injection: "no security or business rules are added to your edits",
|
|
5880
|
+
judgment: "Action Guards are off for this session",
|
|
5881
|
+
diff_scan: "no diff scan can be started",
|
|
5882
|
+
unknown: "part of VibeDefend is paused"
|
|
5883
|
+
};
|
|
5884
|
+
function blockSentence(block) {
|
|
5885
|
+
const label = UNIT_LABEL[block.unit];
|
|
5886
|
+
const effect = UNIT_EFFECT[block.unit];
|
|
5887
|
+
switch (block.reason) {
|
|
5888
|
+
case "free_tier_exhausted":
|
|
5889
|
+
return `VibeDefend paused: ${label} used up this month's free tier, so ${effect}. An administrator of your organisation can add a card in the VibeDefend usage page to continue.`;
|
|
5890
|
+
case "cap_reached":
|
|
5891
|
+
return `VibeDefend paused: ${label} reached the monthly spending cap of your organisation, so ${effect}. An administrator can raise the cap in the VibeDefend usage page.`;
|
|
5892
|
+
case "unpaid":
|
|
5893
|
+
return "VibeDefend paused: your organisation's last VibeDefend invoice is unpaid, so rules, Action Guards and diff scans are off. An administrator can update the card in the VibeDefend usage page.";
|
|
5894
|
+
case "product_inactive":
|
|
5895
|
+
return "VibeDefend is not active for your organisation, so rules, Action Guards and diff scans are off. An administrator can activate it in the CybeDefend dashboard.";
|
|
5896
|
+
default:
|
|
5897
|
+
return `VibeDefend paused: ${label} is blocked for your organisation, so ${effect}. An administrator can check the VibeDefend usage page.`;
|
|
5898
|
+
}
|
|
5899
|
+
}
|
|
5900
|
+
function warningSentence(w) {
|
|
5901
|
+
const label = UNIT_LABEL[w.unit];
|
|
5902
|
+
if (w.warning === "approaching_limit") {
|
|
5903
|
+
return `VibeDefend: ${label} has used 80 % of this month's allowance of your organisation. An administrator can follow it in the VibeDefend usage page.`;
|
|
5904
|
+
}
|
|
5905
|
+
return `VibeDefend: ${label} has reached this month's limit of your organisation; the next requests will be refused. An administrator can add a card or raise the cap in the VibeDefend usage page.`;
|
|
5906
|
+
}
|
|
5907
|
+
var ORG_WIDE_REASONS = /* @__PURE__ */ new Set([
|
|
5908
|
+
"unpaid",
|
|
5909
|
+
"product_inactive"
|
|
5910
|
+
]);
|
|
5911
|
+
function blockKey(block) {
|
|
5912
|
+
return ORG_WIDE_REASONS.has(block.reason) ? `block:${block.reason}` : `block:${block.reason}:${block.unit}`;
|
|
5913
|
+
}
|
|
5914
|
+
var NOTICES_CAP = 300;
|
|
5915
|
+
function defaultUsageNoticesFile() {
|
|
5916
|
+
const dir = process.env.CYBEDEFEND_BASELINE_DIR ?? join5(homedir6(), ".cybedefend");
|
|
5917
|
+
return join5(dir, "usage-notices-sessions.json");
|
|
5918
|
+
}
|
|
5919
|
+
function makeUsageNotifier(opts = {}) {
|
|
5920
|
+
const file = opts.file ?? defaultUsageNoticesFile();
|
|
5921
|
+
const write = opts.write ?? ((line) => {
|
|
5922
|
+
try {
|
|
5923
|
+
process.stderr.write(line);
|
|
5924
|
+
} catch {
|
|
5925
|
+
}
|
|
5926
|
+
});
|
|
5927
|
+
function load() {
|
|
5928
|
+
try {
|
|
5929
|
+
if (!existsSync7(file)) return [];
|
|
5930
|
+
const data = JSON.parse(readFileSync6(file, "utf8"));
|
|
5931
|
+
return Array.isArray(data.seen) ? data.seen.filter((s) => typeof s === "string") : [];
|
|
5932
|
+
} catch {
|
|
5933
|
+
return [];
|
|
5934
|
+
}
|
|
5935
|
+
}
|
|
5936
|
+
function once(sessionId, key, sentence) {
|
|
5937
|
+
if (sessionId) {
|
|
5938
|
+
const marker = `${sessionId}|${key}`;
|
|
5939
|
+
const seen = load();
|
|
5940
|
+
if (seen.includes(marker)) return null;
|
|
5941
|
+
try {
|
|
5942
|
+
mkdirSync4(dirname3(file), { recursive: true });
|
|
5943
|
+
writeFileSync3(
|
|
5944
|
+
file,
|
|
5945
|
+
JSON.stringify({ seen: [...seen, marker].slice(-NOTICES_CAP) }),
|
|
5946
|
+
"utf8"
|
|
5947
|
+
);
|
|
5948
|
+
} catch {
|
|
5949
|
+
}
|
|
5950
|
+
}
|
|
5951
|
+
write(`[VibeDefend] ${sentence}
|
|
5952
|
+
`);
|
|
5953
|
+
return sentence;
|
|
5954
|
+
}
|
|
5955
|
+
return {
|
|
5956
|
+
notifyBlock: (sessionId, block) => once(sessionId, blockKey(block), blockSentence(block)),
|
|
5957
|
+
notifyWarning: (sessionId, w) => once(sessionId, `warning:${w.unit}:${w.warning}`, warningSentence(w))
|
|
5958
|
+
};
|
|
5959
|
+
}
|
|
5960
|
+
|
|
5811
5961
|
// src/hooks/runtime/api.ts
|
|
5962
|
+
async function reportUsageSignal(res, listener) {
|
|
5963
|
+
const emit2 = (signal) => {
|
|
5964
|
+
try {
|
|
5965
|
+
listener?.(signal);
|
|
5966
|
+
} catch {
|
|
5967
|
+
}
|
|
5968
|
+
};
|
|
5969
|
+
if (!res.ok) {
|
|
5970
|
+
const block = await readVibedefendBlock(res);
|
|
5971
|
+
if (block) emit2({ kind: "blocked", block });
|
|
5972
|
+
return false;
|
|
5973
|
+
}
|
|
5974
|
+
const warning = readUsageWarning(res);
|
|
5975
|
+
if (warning) emit2({ kind: "warning", warning });
|
|
5976
|
+
return true;
|
|
5977
|
+
}
|
|
5812
5978
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
5813
5979
|
async function fetchBusinessRules(opts) {
|
|
5814
5980
|
const f = opts.fetchImpl ?? globalThis.fetch;
|
|
@@ -5832,7 +5998,7 @@ async function fetchBusinessRules(opts) {
|
|
|
5832
5998
|
} catch {
|
|
5833
5999
|
return null;
|
|
5834
6000
|
}
|
|
5835
|
-
if (!res.
|
|
6001
|
+
if (!await reportUsageSignal(res, opts.onUsageSignal)) return null;
|
|
5836
6002
|
let body;
|
|
5837
6003
|
try {
|
|
5838
6004
|
body = await res.json();
|
|
@@ -5867,7 +6033,7 @@ async function fetchSecurityRules(opts) {
|
|
|
5867
6033
|
} catch {
|
|
5868
6034
|
return null;
|
|
5869
6035
|
}
|
|
5870
|
-
if (!res.
|
|
6036
|
+
if (!await reportUsageSignal(res, opts.onUsageSignal)) return null;
|
|
5871
6037
|
let body;
|
|
5872
6038
|
try {
|
|
5873
6039
|
body = await res.json();
|
|
@@ -5906,6 +6072,12 @@ async function fetchProposalsCount(opts) {
|
|
|
5906
6072
|
return { total, raw: body };
|
|
5907
6073
|
}
|
|
5908
6074
|
|
|
6075
|
+
// src/hooks/runtime/session-activation.ts
|
|
6076
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
6077
|
+
import { homedir as homedir7 } from "node:os";
|
|
6078
|
+
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join6, resolve as resolvePath2 } from "node:path";
|
|
6079
|
+
import { fileURLToPath } from "node:url";
|
|
6080
|
+
|
|
5909
6081
|
// src/hooks/runtime/emit.ts
|
|
5910
6082
|
var ENVELOPE_CLIENTS = {
|
|
5911
6083
|
SessionStart: /* @__PURE__ */ new Set(["codex"]),
|
|
@@ -5926,6 +6098,10 @@ function emit(body, client) {
|
|
|
5926
6098
|
}
|
|
5927
6099
|
function emitContext(body, client, hookEventName) {
|
|
5928
6100
|
if (!body) return;
|
|
6101
|
+
if (client === "cursor" && hookEventName === "SessionStart") {
|
|
6102
|
+
process.stdout.write(JSON.stringify({ additional_context: body }) + "\n");
|
|
6103
|
+
return;
|
|
6104
|
+
}
|
|
5929
6105
|
if (ENVELOPE_CLIENTS[hookEventName].has(client)) {
|
|
5930
6106
|
const payload = JSON.stringify({
|
|
5931
6107
|
hookSpecificOutput: {
|
|
@@ -5938,6 +6114,25 @@ function emitContext(body, client, hookEventName) {
|
|
|
5938
6114
|
}
|
|
5939
6115
|
emit(body, client);
|
|
5940
6116
|
}
|
|
6117
|
+
var SYSTEM_MESSAGE_CLIENTS = /* @__PURE__ */ new Set([
|
|
6118
|
+
"claude-code",
|
|
6119
|
+
"codex"
|
|
6120
|
+
]);
|
|
6121
|
+
function emitSessionNotice(notice, client) {
|
|
6122
|
+
if (SYSTEM_MESSAGE_CLIENTS.has(client)) {
|
|
6123
|
+
process.stdout.write(
|
|
6124
|
+
JSON.stringify({
|
|
6125
|
+
systemMessage: notice.userMessage,
|
|
6126
|
+
hookSpecificOutput: {
|
|
6127
|
+
hookEventName: "SessionStart",
|
|
6128
|
+
additionalContext: notice.modelContext
|
|
6129
|
+
}
|
|
6130
|
+
}) + "\n"
|
|
6131
|
+
);
|
|
6132
|
+
return;
|
|
6133
|
+
}
|
|
6134
|
+
emitContext(notice.modelContext, client, "SessionStart");
|
|
6135
|
+
}
|
|
5941
6136
|
function emitStop(body, client, reason) {
|
|
5942
6137
|
if (!body) return;
|
|
5943
6138
|
if (client === "claude-code") {
|
|
@@ -5952,6 +6147,143 @@ function emitStop(body, client, reason) {
|
|
|
5952
6147
|
emit(body, client);
|
|
5953
6148
|
}
|
|
5954
6149
|
|
|
6150
|
+
// src/hooks/runtime/session-activation.ts
|
|
6151
|
+
var SETTLED_REASONS = /* @__PURE__ */ new Set([
|
|
6152
|
+
"no-git-origin",
|
|
6153
|
+
"unsupported-remote",
|
|
6154
|
+
"no-linked-project"
|
|
6155
|
+
]);
|
|
6156
|
+
function isSettledReason(reason) {
|
|
6157
|
+
return SETTLED_REASONS.has(reason);
|
|
6158
|
+
}
|
|
6159
|
+
var SESSIONS_CAP = 200;
|
|
6160
|
+
function defaultStoreFile() {
|
|
6161
|
+
const dir = process.env.CYBEDEFEND_BASELINE_DIR ?? join6(homedir7(), ".cybedefend");
|
|
6162
|
+
return join6(dir, "inactive-sessions.json");
|
|
6163
|
+
}
|
|
6164
|
+
function defaultInactiveSessionStore(file = defaultStoreFile(), now = Date.now) {
|
|
6165
|
+
function load() {
|
|
6166
|
+
if (!existsSync8(file)) return {};
|
|
6167
|
+
try {
|
|
6168
|
+
const parsed = JSON.parse(readFileSync7(file, "utf8"));
|
|
6169
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
6170
|
+
} catch {
|
|
6171
|
+
return {};
|
|
6172
|
+
}
|
|
6173
|
+
}
|
|
6174
|
+
function save(map) {
|
|
6175
|
+
try {
|
|
6176
|
+
mkdirSync5(dirname4(file), { recursive: true });
|
|
6177
|
+
writeFileSync4(file, JSON.stringify(map) + "\n", "utf8");
|
|
6178
|
+
} catch {
|
|
6179
|
+
}
|
|
6180
|
+
}
|
|
6181
|
+
return {
|
|
6182
|
+
get(sessionId) {
|
|
6183
|
+
const rec = load()[sessionId];
|
|
6184
|
+
if (!rec || typeof rec !== "object" || typeof rec.cwd !== "string" || typeof rec.reason !== "string") {
|
|
6185
|
+
return null;
|
|
6186
|
+
}
|
|
6187
|
+
return rec;
|
|
6188
|
+
},
|
|
6189
|
+
mark(sessionId, record) {
|
|
6190
|
+
const map = load();
|
|
6191
|
+
map[sessionId] = { ...record, at: now() };
|
|
6192
|
+
const entries = Object.entries(map);
|
|
6193
|
+
if (entries.length > SESSIONS_CAP) {
|
|
6194
|
+
entries.sort(([, a], [, b]) => (a.at ?? 0) - (b.at ?? 0));
|
|
6195
|
+
save(Object.fromEntries(entries.slice(-SESSIONS_CAP)));
|
|
6196
|
+
return;
|
|
6197
|
+
}
|
|
6198
|
+
save(map);
|
|
6199
|
+
},
|
|
6200
|
+
clear(sessionId) {
|
|
6201
|
+
const map = load();
|
|
6202
|
+
if (!(sessionId in map)) return;
|
|
6203
|
+
delete map[sessionId];
|
|
6204
|
+
save(map);
|
|
6205
|
+
}
|
|
6206
|
+
};
|
|
6207
|
+
}
|
|
6208
|
+
function sessionIdOf(stdin) {
|
|
6209
|
+
for (const key of ["session_id", "sessionId", "conversation_id", "trajectory_id"]) {
|
|
6210
|
+
const v = stdin[key];
|
|
6211
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
6212
|
+
}
|
|
6213
|
+
return null;
|
|
6214
|
+
}
|
|
6215
|
+
function sessionFolderOf(stdin, client, env = process.env) {
|
|
6216
|
+
const candidates = [];
|
|
6217
|
+
if (client === "cursor") {
|
|
6218
|
+
const roots = stdin.workspace_roots;
|
|
6219
|
+
if (Array.isArray(roots)) candidates.push(roots[0]);
|
|
6220
|
+
candidates.push(env.CURSOR_PROJECT_DIR);
|
|
6221
|
+
}
|
|
6222
|
+
candidates.push(stdin.cwd);
|
|
6223
|
+
for (const c of candidates) {
|
|
6224
|
+
const folder = absoluteFolder(c);
|
|
6225
|
+
if (folder) return folder;
|
|
6226
|
+
}
|
|
6227
|
+
return process.cwd();
|
|
6228
|
+
}
|
|
6229
|
+
function absoluteFolder(value) {
|
|
6230
|
+
if (typeof value !== "string" || value.length === 0) return null;
|
|
6231
|
+
let p = value;
|
|
6232
|
+
if (p.startsWith("file://")) {
|
|
6233
|
+
try {
|
|
6234
|
+
p = fileURLToPath(p);
|
|
6235
|
+
} catch {
|
|
6236
|
+
return null;
|
|
6237
|
+
}
|
|
6238
|
+
}
|
|
6239
|
+
return isAbsolute2(p) ? resolvePath2(p) : null;
|
|
6240
|
+
}
|
|
6241
|
+
function isSessionInactive(stdin, deps = {}) {
|
|
6242
|
+
try {
|
|
6243
|
+
const sessionId = sessionIdOf(stdin);
|
|
6244
|
+
if (!sessionId) return false;
|
|
6245
|
+
const record = (deps.store ?? defaultInactiveSessionStore()).get(sessionId);
|
|
6246
|
+
if (!record || !isSettledReason(record.reason)) return false;
|
|
6247
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
6248
|
+
if (record.cwd !== cwd) return false;
|
|
6249
|
+
const resolveSync = deps.resolveSync ?? resolveProjectId;
|
|
6250
|
+
return !resolveSync(cwd).projectId;
|
|
6251
|
+
} catch {
|
|
6252
|
+
return false;
|
|
6253
|
+
}
|
|
6254
|
+
}
|
|
6255
|
+
function whyInactive(reason, repository) {
|
|
6256
|
+
const repo = repository ? `\`${repository}\`` : "this folder";
|
|
6257
|
+
switch (reason) {
|
|
6258
|
+
case "no-git-origin":
|
|
6259
|
+
return "this folder has no `.cybedefend/config.json` and no GitHub or GitLab `origin` remote";
|
|
6260
|
+
case "unsupported-remote":
|
|
6261
|
+
return `this folder has no \`.cybedefend/config.json\` and its git \`origin\`${repository ? ` (${repo})` : ""} is not on GitHub or GitLab`;
|
|
6262
|
+
case "no-linked-project":
|
|
6263
|
+
return `this folder has no \`.cybedefend/config.json\` and no CybeDefend project with VibeDefend enabled matches ${repo}`;
|
|
6264
|
+
case "not-signed-in":
|
|
6265
|
+
return `you are not signed in, so the CybeDefend project for ${repo} could not be looked up`;
|
|
6266
|
+
case "lookup-failed":
|
|
6267
|
+
return `the CybeDefend API did not answer, so the project for ${repo} could not be looked up`;
|
|
6268
|
+
}
|
|
6269
|
+
}
|
|
6270
|
+
function composeInactiveNotice(reason, repository, client) {
|
|
6271
|
+
const why = whyInactive(reason, repository);
|
|
6272
|
+
const settled = isSettledReason(reason);
|
|
6273
|
+
const userMessage = settled ? `\u{1F6E1}\uFE0F CybeDefend is off for this session: ${why}. Run \`vibedefend link\` in this folder to turn it on.` : reason === "not-signed-in" ? `\u{1F6E1}\uFE0F CybeDefend is off for now: ${why}. Run \`vibedefend login\` to turn it on.` : `\u{1F6E1}\uFE0F CybeDefend is off for now: ${why}. It will try again on the next prompt or edit.`;
|
|
6274
|
+
const scope = settled ? "- Its hooks stay off until this session ends: no rules fetch, no proposal inbox, no Action Guards, no end-of-session review." : "- Its hooks stay quiet until the project can be found.";
|
|
6275
|
+
const relay = SYSTEM_MESSAGE_CLIENTS.has(client) ? "- The user has already been shown this notice; do not repeat it." : "- Tell the user once, in one short line, that CybeDefend is off for this session and why.";
|
|
6276
|
+
const modelContext = [
|
|
6277
|
+
"## CybeDefend is not active in this session",
|
|
6278
|
+
`CybeDefend found no project for this folder: ${why}.`,
|
|
6279
|
+
scope,
|
|
6280
|
+
"- Do not call CybeDefend MCP tools, run the CybeDefend workflow or ask for a CybeDefend project id unless the user asks about CybeDefend.",
|
|
6281
|
+
relay,
|
|
6282
|
+
""
|
|
6283
|
+
].join("\n");
|
|
6284
|
+
return { userMessage, modelContext };
|
|
6285
|
+
}
|
|
6286
|
+
|
|
5955
6287
|
// src/hooks/runtime/fetch-rules.ts
|
|
5956
6288
|
function emitPreToolUseContext(body, client) {
|
|
5957
6289
|
emitContext(body, client, "PreToolUse");
|
|
@@ -5971,13 +6303,16 @@ async function fetchRulesHook(deps = {}) {
|
|
|
5971
6303
|
const fetchRules = deps.fetchRulesFn ?? fetchBusinessRules;
|
|
5972
6304
|
const fetchSecRules = deps.fetchSecurityRulesFn ?? fetchSecurityRules;
|
|
5973
6305
|
const emitFn = deps.emitFn ?? emitPreToolUseContext;
|
|
6306
|
+
const usageNotifier = deps.usageNotifier ?? makeUsageNotifier();
|
|
5974
6307
|
const stdin = await readStdin();
|
|
5975
6308
|
const event = sniff(stdin);
|
|
5976
6309
|
if (!INTERESTED_TOOLS.has(event.toolName)) return;
|
|
5977
6310
|
if (!event.filePath) return;
|
|
5978
6311
|
const config = loadConfig();
|
|
5979
6312
|
if (!config) return;
|
|
5980
|
-
const
|
|
6313
|
+
const folder = sessionFolderOf(stdin, event.client);
|
|
6314
|
+
if (isSessionInactive(stdin, { store: deps.sessionStore, cwd: folder })) return;
|
|
6315
|
+
const { projectId, apiBase: configApiBase } = await resolveProject(folder);
|
|
5981
6316
|
if (!projectId) return;
|
|
5982
6317
|
const apiBase = process.env.CYBEDEFEND_API_BASE ?? configApiBase ?? config.region.apiBase;
|
|
5983
6318
|
const token = resolveTok(config.region.mcpName, {
|
|
@@ -5987,13 +6322,20 @@ async function fetchRulesHook(deps = {}) {
|
|
|
5987
6322
|
const intent = buildIntent(event);
|
|
5988
6323
|
const files = event.filePaths.length > 0 ? event.filePaths : [event.filePath];
|
|
5989
6324
|
const fileLabel = files.join(", ");
|
|
6325
|
+
const sessionId = sessionIdOf2(stdin);
|
|
6326
|
+
const usageNotices = [];
|
|
6327
|
+
const onUsageSignal = (signal) => {
|
|
6328
|
+
const sentence = signal.kind === "blocked" ? usageNotifier.notifyBlock(sessionId, signal.block) : usageNotifier.notifyWarning(sessionId, signal.warning);
|
|
6329
|
+
if (sentence) usageNotices.push(sentence);
|
|
6330
|
+
};
|
|
5990
6331
|
const fetchOpts = {
|
|
5991
6332
|
apiBase,
|
|
5992
6333
|
projectId,
|
|
5993
6334
|
token,
|
|
5994
6335
|
files,
|
|
5995
6336
|
intent,
|
|
5996
|
-
topK: 5
|
|
6337
|
+
topK: 5,
|
|
6338
|
+
onUsageSignal
|
|
5997
6339
|
};
|
|
5998
6340
|
const [businessResult, securityResult] = await Promise.all([
|
|
5999
6341
|
fetchRules(fetchOpts),
|
|
@@ -6017,8 +6359,18 @@ async function fetchRulesHook(deps = {}) {
|
|
|
6017
6359
|
)
|
|
6018
6360
|
);
|
|
6019
6361
|
}
|
|
6362
|
+
if (usageNotices.length > 0) {
|
|
6363
|
+
blocks.push(
|
|
6364
|
+
["## CybeDefend VibeDefend usage", ...usageNotices.map((n) => `- ${n}`), ""].join("\n")
|
|
6365
|
+
);
|
|
6366
|
+
}
|
|
6020
6367
|
if (blocks.length > 0) emitFn(blocks.join("\n"), event.client);
|
|
6021
6368
|
}
|
|
6369
|
+
function sessionIdOf2(stdin) {
|
|
6370
|
+
if (typeof stdin.session_id === "string" && stdin.session_id) return stdin.session_id;
|
|
6371
|
+
if (typeof stdin.sessionId === "string" && stdin.sessionId) return stdin.sessionId;
|
|
6372
|
+
return void 0;
|
|
6373
|
+
}
|
|
6022
6374
|
function buildIntent(event) {
|
|
6023
6375
|
const limit = parseInt(process.env.CYBEDEFEND_INTENT_BYTES ?? "800", 10);
|
|
6024
6376
|
const snippet = event.newContent.slice(0, Number.isFinite(limit) ? limit : 800).replace(/\s+/g, " ").trim();
|
|
@@ -6099,9 +6451,9 @@ var DIRECT_PROPOSE_DIRECTIVES = {
|
|
|
6099
6451
|
|
|
6100
6452
|
// src/hooks/runtime/self-update-check.ts
|
|
6101
6453
|
import { spawn } from "node:child_process";
|
|
6102
|
-
import { existsSync as
|
|
6103
|
-
import { homedir as
|
|
6104
|
-
import { join as
|
|
6454
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8, rmSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
6455
|
+
import { homedir as homedir8 } from "node:os";
|
|
6456
|
+
import { join as join7 } from "node:path";
|
|
6105
6457
|
|
|
6106
6458
|
// src/hooks/runtime/semver.ts
|
|
6107
6459
|
function parseVersion(input) {
|
|
@@ -6128,8 +6480,8 @@ function isNewer(latest, current) {
|
|
|
6128
6480
|
}
|
|
6129
6481
|
|
|
6130
6482
|
// src/self-update.ts
|
|
6131
|
-
import { fileURLToPath } from "node:url";
|
|
6132
|
-
import { existsSync as
|
|
6483
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
6484
|
+
import { existsSync as existsSync9, realpathSync } from "node:fs";
|
|
6133
6485
|
var GLOBAL_MODES = /* @__PURE__ */ new Set([
|
|
6134
6486
|
"npm-global",
|
|
6135
6487
|
"pnpm-global",
|
|
@@ -6157,14 +6509,14 @@ function detectInstallMode(scriptPath) {
|
|
|
6157
6509
|
}
|
|
6158
6510
|
function resolveScriptPath() {
|
|
6159
6511
|
const fromArgv = process.argv[1];
|
|
6160
|
-
if (fromArgv &&
|
|
6512
|
+
if (fromArgv && existsSync9(fromArgv)) {
|
|
6161
6513
|
try {
|
|
6162
6514
|
return realpathSync(fromArgv);
|
|
6163
6515
|
} catch {
|
|
6164
6516
|
return fromArgv;
|
|
6165
6517
|
}
|
|
6166
6518
|
}
|
|
6167
|
-
return
|
|
6519
|
+
return fileURLToPath2(import.meta.url);
|
|
6168
6520
|
}
|
|
6169
6521
|
|
|
6170
6522
|
// src/hooks/runtime/self-update-check.ts
|
|
@@ -6179,9 +6531,9 @@ function isValidRegistryVersion(value) {
|
|
|
6179
6531
|
}
|
|
6180
6532
|
async function selfUpdateCheck(deps) {
|
|
6181
6533
|
const now = deps.now ?? (() => Date.now());
|
|
6182
|
-
const dir = deps.cybeDir ??
|
|
6183
|
-
const throttlePath =
|
|
6184
|
-
const logPath =
|
|
6534
|
+
const dir = deps.cybeDir ?? join7(process.env.CYBEDEFEND_HOME ?? homedir8(), ".cybedefend");
|
|
6535
|
+
const throttlePath = join7(dir, "update-check.json");
|
|
6536
|
+
const logPath = join7(dir, "last-update.log");
|
|
6185
6537
|
const installMode = deps.installMode ?? (deps.runnerSource ? detectInstallMode(deps.runnerSource) : detectInstallMode(resolveScriptPath()));
|
|
6186
6538
|
const priorFailure = takeUpdateLogNotice(logPath);
|
|
6187
6539
|
const throttle = readThrottle(throttlePath);
|
|
@@ -6214,8 +6566,8 @@ function applyOrNotify(deps, installMode, latest, logPath) {
|
|
|
6214
6566
|
}
|
|
6215
6567
|
function readThrottle(path) {
|
|
6216
6568
|
try {
|
|
6217
|
-
if (!
|
|
6218
|
-
const data = JSON.parse(
|
|
6569
|
+
if (!existsSync10(path)) return null;
|
|
6570
|
+
const data = JSON.parse(readFileSync8(path, "utf8"));
|
|
6219
6571
|
if (typeof data.lastCheckedAt !== "number") return null;
|
|
6220
6572
|
return { lastCheckedAt: data.lastCheckedAt };
|
|
6221
6573
|
} catch {
|
|
@@ -6225,16 +6577,16 @@ function readThrottle(path) {
|
|
|
6225
6577
|
function writeThrottle(path, throttle) {
|
|
6226
6578
|
try {
|
|
6227
6579
|
ensureDirFor(path);
|
|
6228
|
-
|
|
6580
|
+
writeFileSync5(path, JSON.stringify(throttle) + "\n");
|
|
6229
6581
|
} catch {
|
|
6230
6582
|
}
|
|
6231
6583
|
}
|
|
6232
6584
|
function takeUpdateLogNotice(logPath) {
|
|
6233
6585
|
try {
|
|
6234
|
-
if (!
|
|
6586
|
+
if (!existsSync10(logPath)) return null;
|
|
6235
6587
|
let parsed = {};
|
|
6236
6588
|
try {
|
|
6237
|
-
parsed = JSON.parse(
|
|
6589
|
+
parsed = JSON.parse(readFileSync8(logPath, "utf8"));
|
|
6238
6590
|
} catch {
|
|
6239
6591
|
parsed = {};
|
|
6240
6592
|
}
|
|
@@ -6309,18 +6661,18 @@ function pmCommand(mode, spec) {
|
|
|
6309
6661
|
|
|
6310
6662
|
// src/hooks/runtime/hook-refresh-trigger.ts
|
|
6311
6663
|
import { spawn as spawn2 } from "node:child_process";
|
|
6312
|
-
import { existsSync as
|
|
6313
|
-
import { homedir as
|
|
6314
|
-
import { dirname as
|
|
6664
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "node:fs";
|
|
6665
|
+
import { homedir as homedir9 } from "node:os";
|
|
6666
|
+
import { dirname as dirname5, join as join8 } from "node:path";
|
|
6315
6667
|
function maybeRefreshStaleWiring(deps) {
|
|
6316
6668
|
try {
|
|
6317
6669
|
const { config, runnerVersion } = deps;
|
|
6318
6670
|
if (!config.runnerSource || config.installedVersion === runnerVersion) {
|
|
6319
6671
|
return false;
|
|
6320
6672
|
}
|
|
6321
|
-
const cliEntry =
|
|
6322
|
-
if (!(deps.exists ??
|
|
6323
|
-
const markerPath = deps.markerPath ??
|
|
6673
|
+
const cliEntry = join8(dirname5(dirname5(config.runnerSource)), "bin", "vibedefend.js");
|
|
6674
|
+
if (!(deps.exists ?? existsSync11)(cliEntry)) return false;
|
|
6675
|
+
const markerPath = deps.markerPath ?? join8(process.env.CYBEDEFEND_HOME ?? homedir9(), ".cybedefend", "hook-refresh.json");
|
|
6324
6676
|
if (readMarkerVersion(markerPath) === runnerVersion) return false;
|
|
6325
6677
|
(deps.spawnRefresh ?? defaultSpawnRefresh)(cliEntry);
|
|
6326
6678
|
writeMarker(markerPath, runnerVersion);
|
|
@@ -6331,8 +6683,8 @@ function maybeRefreshStaleWiring(deps) {
|
|
|
6331
6683
|
}
|
|
6332
6684
|
function readMarkerVersion(path) {
|
|
6333
6685
|
try {
|
|
6334
|
-
if (!
|
|
6335
|
-
const data = JSON.parse(
|
|
6686
|
+
if (!existsSync11(path)) return null;
|
|
6687
|
+
const data = JSON.parse(readFileSync9(path, "utf8"));
|
|
6336
6688
|
return typeof data.version === "string" ? data.version : null;
|
|
6337
6689
|
} catch {
|
|
6338
6690
|
return null;
|
|
@@ -6341,7 +6693,7 @@ function readMarkerVersion(path) {
|
|
|
6341
6693
|
function writeMarker(path, version) {
|
|
6342
6694
|
try {
|
|
6343
6695
|
ensureDirFor(path);
|
|
6344
|
-
|
|
6696
|
+
writeFileSync6(path, JSON.stringify({ version, spawnedAt: Date.now() }) + "\n");
|
|
6345
6697
|
} catch {
|
|
6346
6698
|
}
|
|
6347
6699
|
}
|
|
@@ -6355,18 +6707,18 @@ function defaultSpawnRefresh(cliEntry) {
|
|
|
6355
6707
|
}
|
|
6356
6708
|
|
|
6357
6709
|
// src/version.ts
|
|
6358
|
-
import { readFileSync as
|
|
6359
|
-
import { fileURLToPath as
|
|
6360
|
-
import { dirname as
|
|
6710
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
6711
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
6712
|
+
import { dirname as dirname6, join as join9 } from "node:path";
|
|
6361
6713
|
var PKG_NAME = "@cybedefend/vibedefend";
|
|
6362
6714
|
function defaultPkgPath() {
|
|
6363
|
-
return
|
|
6715
|
+
return join9(dirname6(fileURLToPath3(import.meta.url)), "..", "package.json");
|
|
6364
6716
|
}
|
|
6365
6717
|
function readPackageMeta(opts = {}) {
|
|
6366
6718
|
if (typeof opts.bakedVersion === "string" && opts.bakedVersion.length > 0) {
|
|
6367
6719
|
return { name: PKG_NAME, version: opts.bakedVersion };
|
|
6368
6720
|
}
|
|
6369
|
-
const readFile = opts.readFile ?? ((p) =>
|
|
6721
|
+
const readFile = opts.readFile ?? ((p) => readFileSync10(p, "utf8"));
|
|
6370
6722
|
const pkgPath = opts.pkgPath ?? defaultPkgPath();
|
|
6371
6723
|
try {
|
|
6372
6724
|
return JSON.parse(readFile(pkgPath));
|
|
@@ -6375,13 +6727,22 @@ function readPackageMeta(opts = {}) {
|
|
|
6375
6727
|
}
|
|
6376
6728
|
}
|
|
6377
6729
|
var pkg = readPackageMeta({
|
|
6378
|
-
bakedVersion: true ? "1.
|
|
6730
|
+
bakedVersion: true ? "1.5.0" : void 0
|
|
6379
6731
|
});
|
|
6380
6732
|
|
|
6381
6733
|
// src/hooks/runtime/session-start.ts
|
|
6382
6734
|
function emitSessionStartContext(body, client) {
|
|
6383
6735
|
emitContext(body, client, "SessionStart");
|
|
6384
6736
|
}
|
|
6737
|
+
function announceInactive(resolution, sessionId, cwd, store, client, emitNoticeFn) {
|
|
6738
|
+
const reason = resolution.inactiveReason ?? "no-linked-project";
|
|
6739
|
+
if (sessionId) {
|
|
6740
|
+
const prior = store.get(sessionId);
|
|
6741
|
+
if (prior && prior.cwd === cwd && prior.reason === reason) return;
|
|
6742
|
+
store.mark(sessionId, { cwd, reason });
|
|
6743
|
+
}
|
|
6744
|
+
emitNoticeFn(composeInactiveNotice(reason, resolution.repository, client), client);
|
|
6745
|
+
}
|
|
6385
6746
|
async function sessionStartHook(deps = {}) {
|
|
6386
6747
|
const readStdin = deps.readStdin ?? readStdinJson;
|
|
6387
6748
|
const loadConfig = deps.loadConfig ?? loadRuntimeConfig;
|
|
@@ -6390,6 +6751,7 @@ async function sessionStartHook(deps = {}) {
|
|
|
6390
6751
|
const fetchProposals = deps.fetchProposalsFn ?? fetchProposalsCount;
|
|
6391
6752
|
const selfUpdateCheckFn = deps.selfUpdateCheckFn ?? selfUpdateCheck;
|
|
6392
6753
|
const emitFn = deps.emitFn ?? emitSessionStartContext;
|
|
6754
|
+
const emitNoticeFn = deps.emitNoticeFn ?? emitSessionNotice;
|
|
6393
6755
|
const stdin = await readStdin();
|
|
6394
6756
|
const event = sniff(stdin);
|
|
6395
6757
|
const config = loadConfig();
|
|
@@ -6398,8 +6760,16 @@ async function sessionStartHook(deps = {}) {
|
|
|
6398
6760
|
config,
|
|
6399
6761
|
runnerVersion: pkg.version
|
|
6400
6762
|
});
|
|
6401
|
-
const
|
|
6402
|
-
|
|
6763
|
+
const folder = sessionFolderOf(stdin, event.client);
|
|
6764
|
+
const resolution = await resolveProject(folder);
|
|
6765
|
+
const { projectId, apiBase: configApiBase } = resolution;
|
|
6766
|
+
const sessionStore = deps.sessionStore ?? defaultInactiveSessionStore();
|
|
6767
|
+
const sessionId = sessionIdOf(stdin);
|
|
6768
|
+
if (!projectId) {
|
|
6769
|
+
announceInactive(resolution, sessionId, folder, sessionStore, event.client, emitNoticeFn);
|
|
6770
|
+
return;
|
|
6771
|
+
}
|
|
6772
|
+
if (sessionId) sessionStore.clear(sessionId);
|
|
6403
6773
|
const apiBase = process.env.CYBEDEFEND_API_BASE ?? configApiBase ?? config.region.apiBase;
|
|
6404
6774
|
if (process.env.CYBEDEFEND_DRY_RUN === "1") {
|
|
6405
6775
|
emitFn(
|
|
@@ -6491,7 +6861,7 @@ async function sessionStartHook(deps = {}) {
|
|
|
6491
6861
|
}
|
|
6492
6862
|
|
|
6493
6863
|
// src/hooks/runtime/session-review.ts
|
|
6494
|
-
import { existsSync as
|
|
6864
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "node:fs";
|
|
6495
6865
|
var COUNTED_TOOLS = /* @__PURE__ */ new Set([
|
|
6496
6866
|
"Edit",
|
|
6497
6867
|
"Write",
|
|
@@ -6533,6 +6903,10 @@ async function sessionReviewHook(deps = {}) {
|
|
|
6533
6903
|
}
|
|
6534
6904
|
if (editCount === 0) return;
|
|
6535
6905
|
if (editCount < resolveReviewThreshold(config)) return;
|
|
6906
|
+
const folder = sessionFolderOf(stdin, event.client);
|
|
6907
|
+
if (isSessionInactive(stdin, { store: deps.sessionStore, cwd: folder })) return;
|
|
6908
|
+
const { projectId } = await (deps.resolveProject ?? resolveProjectIdWithApi)(folder);
|
|
6909
|
+
if (!projectId) return;
|
|
6536
6910
|
const userPrompt = extractFirstUserPrompt(transcript);
|
|
6537
6911
|
const proposalMode = resolveProposalMode(config);
|
|
6538
6912
|
let body = composeReviewBody(
|
|
@@ -6543,8 +6917,7 @@ async function sessionReviewHook(deps = {}) {
|
|
|
6543
6917
|
);
|
|
6544
6918
|
const withDiffScan = config.hooks.enableDiffScan !== false;
|
|
6545
6919
|
if (withDiffScan) {
|
|
6546
|
-
|
|
6547
|
-
body += "\n" + composeDiffScanBody(extractChangedFiles(transcript, void 0, cwd));
|
|
6920
|
+
body += "\n" + composeDiffScanBody(extractChangedFiles(transcript, void 0, folder));
|
|
6548
6921
|
}
|
|
6549
6922
|
emitFn(body, event.client, composeStopReason(withDiffScan, proposalMode));
|
|
6550
6923
|
}
|
|
@@ -6575,9 +6948,9 @@ function loadTranscript(stdin, readFile) {
|
|
|
6575
6948
|
return out;
|
|
6576
6949
|
}
|
|
6577
6950
|
function defaultReadTranscript(path) {
|
|
6578
|
-
if (!
|
|
6951
|
+
if (!existsSync12(path)) return null;
|
|
6579
6952
|
try {
|
|
6580
|
-
return
|
|
6953
|
+
return readFileSync11(path, "utf8");
|
|
6581
6954
|
} catch {
|
|
6582
6955
|
return null;
|
|
6583
6956
|
}
|
|
@@ -6810,6 +7183,10 @@ async function preCompactHook(deps = {}) {
|
|
|
6810
7183
|
const config = loadConfig();
|
|
6811
7184
|
if (!config) return;
|
|
6812
7185
|
if (!config.hooks.enableSessionReview) return;
|
|
7186
|
+
const folder = sessionFolderOf(stdin, event.client);
|
|
7187
|
+
if (isSessionInactive(stdin, { store: deps.sessionStore, cwd: folder })) return;
|
|
7188
|
+
const { projectId } = await (deps.resolveProject ?? resolveProjectIdWithApi)(folder);
|
|
7189
|
+
if (!projectId) return;
|
|
6813
7190
|
if (process.env.CYBEDEFEND_DRY_RUN === "1") {
|
|
6814
7191
|
emitFn(
|
|
6815
7192
|
`## \u{1F6E1}\uFE0F CybeDefend PreCompact (dry-run, client=${event.client})
|
|
@@ -6837,19 +7214,19 @@ async function preCompactHook(deps = {}) {
|
|
|
6837
7214
|
}
|
|
6838
7215
|
|
|
6839
7216
|
// src/hooks/runtime/user-prompt-submit.ts
|
|
6840
|
-
import { existsSync as
|
|
6841
|
-
import { homedir as
|
|
6842
|
-
import { join as
|
|
6843
|
-
var
|
|
7217
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "node:fs";
|
|
7218
|
+
import { homedir as homedir10 } from "node:os";
|
|
7219
|
+
import { join as join10 } from "node:path";
|
|
7220
|
+
var SESSIONS_CAP2 = 100;
|
|
6844
7221
|
function defaultSessionsFile() {
|
|
6845
|
-
const dir = process.env.CYBEDEFEND_BASELINE_DIR ??
|
|
6846
|
-
return
|
|
7222
|
+
const dir = process.env.CYBEDEFEND_BASELINE_DIR ?? join10(homedir10(), ".cybedefend");
|
|
7223
|
+
return join10(dir, "user-prompt-seen-sessions.json");
|
|
6847
7224
|
}
|
|
6848
7225
|
function defaultSessionTracker(file = defaultSessionsFile()) {
|
|
6849
7226
|
function load() {
|
|
6850
|
-
if (!
|
|
7227
|
+
if (!existsSync13(file)) return [];
|
|
6851
7228
|
try {
|
|
6852
|
-
const raw =
|
|
7229
|
+
const raw = readFileSync12(file, "utf8");
|
|
6853
7230
|
const data = JSON.parse(raw);
|
|
6854
7231
|
if (!Array.isArray(data.sessions)) return [];
|
|
6855
7232
|
return data.sessions.filter((s) => typeof s === "string");
|
|
@@ -6864,10 +7241,10 @@ function defaultSessionTracker(file = defaultSessionsFile()) {
|
|
|
6864
7241
|
async markSeen(sessionId) {
|
|
6865
7242
|
const existing = load();
|
|
6866
7243
|
if (existing.includes(sessionId)) return;
|
|
6867
|
-
const next = [...existing, sessionId].slice(-
|
|
7244
|
+
const next = [...existing, sessionId].slice(-SESSIONS_CAP2);
|
|
6868
7245
|
try {
|
|
6869
|
-
|
|
6870
|
-
|
|
7246
|
+
mkdirSync6(join10(file, ".."), { recursive: true });
|
|
7247
|
+
writeFileSync7(file, JSON.stringify({ sessions: next }), "utf8");
|
|
6871
7248
|
} catch {
|
|
6872
7249
|
}
|
|
6873
7250
|
}
|
|
@@ -6889,7 +7266,9 @@ async function userPromptSubmitHook(deps = {}) {
|
|
|
6889
7266
|
}
|
|
6890
7267
|
const config = loadConfig();
|
|
6891
7268
|
if (!config) return;
|
|
6892
|
-
const
|
|
7269
|
+
const folder = sessionFolderOf(stdin, event.client);
|
|
7270
|
+
if (isSessionInactive(stdin, { store: deps.sessionStore, cwd: folder })) return;
|
|
7271
|
+
const { projectId, apiBase: configApiBase } = await resolveProject(folder);
|
|
6893
7272
|
if (!projectId) return;
|
|
6894
7273
|
const apiBase = process.env.CYBEDEFEND_API_BASE ?? configApiBase ?? config.region.apiBase;
|
|
6895
7274
|
const token = resolveTok(config.region.mcpName, {
|
|
@@ -7938,10 +8317,10 @@ function nearMiss(req, rule) {
|
|
|
7938
8317
|
}
|
|
7939
8318
|
|
|
7940
8319
|
// src/hooks/runtime/guard-rules-cache.ts
|
|
7941
|
-
import { readFileSync as
|
|
7942
|
-
import { dirname as
|
|
7943
|
-
import { homedir as
|
|
7944
|
-
import { join as
|
|
8320
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync14, mkdirSync as mkdirSync7 } from "node:fs";
|
|
8321
|
+
import { dirname as dirname7 } from "node:path";
|
|
8322
|
+
import { homedir as homedir11 } from "node:os";
|
|
8323
|
+
import { join as join11 } from "node:path";
|
|
7945
8324
|
|
|
7946
8325
|
// node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
|
|
7947
8326
|
var external_exports = {};
|
|
@@ -12053,8 +12432,8 @@ function parseRuleFields(raw) {
|
|
|
12053
12432
|
}
|
|
12054
12433
|
|
|
12055
12434
|
// src/hooks/runtime/guard-rules-cache.ts
|
|
12056
|
-
var DEFAULT_CACHE_FILE =
|
|
12057
|
-
|
|
12435
|
+
var DEFAULT_CACHE_FILE = join11(
|
|
12436
|
+
homedir11(),
|
|
12058
12437
|
".cybedefend",
|
|
12059
12438
|
"guards-cache.json"
|
|
12060
12439
|
);
|
|
@@ -12134,14 +12513,26 @@ function makeGuardRulesCache(opts) {
|
|
|
12134
12513
|
const entry = readCache(ctx.projectId);
|
|
12135
12514
|
const ageSec = entry ? (Date.now() - entry.fetchedAt) / 1e3 : Infinity;
|
|
12136
12515
|
if (entry && ageSec < staleSec) {
|
|
12516
|
+
if (entry.blocked) return { kind: "blocked", block: entry.blocked };
|
|
12137
12517
|
return { kind: "rules", rules: entry.rules, llmJudge: entry.llmJudge };
|
|
12138
12518
|
}
|
|
12139
12519
|
try {
|
|
12140
12520
|
const result = await fetchFn({
|
|
12141
12521
|
...ctx,
|
|
12142
|
-
etag: entry?.etag ?? null
|
|
12522
|
+
etag: entry?.blocked ? null : entry?.etag ?? null
|
|
12143
12523
|
});
|
|
12144
|
-
if (result.
|
|
12524
|
+
if (result.blocked) {
|
|
12525
|
+
writeCache({
|
|
12526
|
+
projectId: ctx.projectId,
|
|
12527
|
+
rules: [],
|
|
12528
|
+
etag: null,
|
|
12529
|
+
fetchedAt: Date.now(),
|
|
12530
|
+
llmJudge: null,
|
|
12531
|
+
blocked: result.blocked
|
|
12532
|
+
});
|
|
12533
|
+
return { kind: "blocked", block: result.blocked };
|
|
12534
|
+
}
|
|
12535
|
+
if (result.notModified && entry && !entry.blocked) {
|
|
12145
12536
|
writeCache({
|
|
12146
12537
|
...entry,
|
|
12147
12538
|
fetchedAt: Date.now()
|
|
@@ -12163,7 +12554,7 @@ function makeGuardRulesCache(opts) {
|
|
|
12163
12554
|
return { kind: "rules", rules: fresh.rules, llmJudge: fresh.llmJudge };
|
|
12164
12555
|
} catch (e) {
|
|
12165
12556
|
const msg = e instanceof Error ? e.message : String(e);
|
|
12166
|
-
if (entry?.rules?.length) {
|
|
12557
|
+
if (entry?.rules?.length && !entry.blocked) {
|
|
12167
12558
|
const ageSec2 = Math.round((Date.now() - entry.fetchedAt) / 1e3);
|
|
12168
12559
|
process.stderr.write(
|
|
12169
12560
|
`[VibeDefend] Action Guards: rule refresh failed (${msg}). Using cached rules (age=${ageSec2}s).
|
|
@@ -12178,9 +12569,9 @@ function makeGuardRulesCache(opts) {
|
|
|
12178
12569
|
}
|
|
12179
12570
|
}
|
|
12180
12571
|
function readCache(projectId) {
|
|
12181
|
-
if (!
|
|
12572
|
+
if (!existsSync14(cacheFile)) return null;
|
|
12182
12573
|
try {
|
|
12183
|
-
const raw =
|
|
12574
|
+
const raw = readFileSync13(cacheFile, "utf8");
|
|
12184
12575
|
const data = JSON.parse(raw);
|
|
12185
12576
|
if (data.projectId !== projectId) return null;
|
|
12186
12577
|
return data;
|
|
@@ -12191,16 +12582,16 @@ function makeGuardRulesCache(opts) {
|
|
|
12191
12582
|
function writeCache(entry) {
|
|
12192
12583
|
try {
|
|
12193
12584
|
ensureDirFor2(cacheFile);
|
|
12194
|
-
|
|
12585
|
+
writeFileSync8(cacheFile, JSON.stringify(entry), "utf8");
|
|
12195
12586
|
} catch {
|
|
12196
12587
|
}
|
|
12197
12588
|
}
|
|
12198
12589
|
return { refreshIfStale };
|
|
12199
12590
|
}
|
|
12200
12591
|
function ensureDirFor2(filePath) {
|
|
12201
|
-
const dir =
|
|
12202
|
-
if (!
|
|
12203
|
-
|
|
12592
|
+
const dir = dirname7(filePath);
|
|
12593
|
+
if (!existsSync14(dir)) {
|
|
12594
|
+
mkdirSync7(dir, { recursive: true });
|
|
12204
12595
|
}
|
|
12205
12596
|
}
|
|
12206
12597
|
var DEFAULT_TIMEOUT_MS2 = 8e3;
|
|
@@ -12223,6 +12614,10 @@ async function fetchEffectiveRulesFromGateway(ctx, opts = {}) {
|
|
|
12223
12614
|
return { rules: [], etag: ctx.etag ?? null, notModified: true };
|
|
12224
12615
|
}
|
|
12225
12616
|
if (!res.ok) {
|
|
12617
|
+
const blocked = await readVibedefendBlock(res);
|
|
12618
|
+
if (blocked) {
|
|
12619
|
+
return { rules: [], etag: null, notModified: false, blocked };
|
|
12620
|
+
}
|
|
12226
12621
|
throw new Error(`HTTP ${res.status} from effective-rules endpoint`);
|
|
12227
12622
|
}
|
|
12228
12623
|
const body = await res.json();
|
|
@@ -12243,6 +12638,9 @@ function parseLlmJudgeHint(raw) {
|
|
|
12243
12638
|
}
|
|
12244
12639
|
|
|
12245
12640
|
// src/hooks/runtime/judge.ts
|
|
12641
|
+
function isJudgeBlocked(res) {
|
|
12642
|
+
return res !== null && "blocked" in res;
|
|
12643
|
+
}
|
|
12246
12644
|
var DEFAULT_TIMEOUT_MS3 = 1500;
|
|
12247
12645
|
async function callJudge(input, opts = {}) {
|
|
12248
12646
|
const f = opts.fetchImpl ?? globalThis.fetch;
|
|
@@ -12266,7 +12664,11 @@ async function callJudge(input, opts = {}) {
|
|
|
12266
12664
|
}),
|
|
12267
12665
|
signal: AbortSignal.timeout(timeoutMs)
|
|
12268
12666
|
});
|
|
12269
|
-
if (!res.ok)
|
|
12667
|
+
if (!res.ok) {
|
|
12668
|
+
const blocked = await readVibedefendBlock(res);
|
|
12669
|
+
return blocked ? { blocked } : null;
|
|
12670
|
+
}
|
|
12671
|
+
const usageWarning = readUsageWarning(res);
|
|
12270
12672
|
const body = await res.json();
|
|
12271
12673
|
const data = body.data ?? body;
|
|
12272
12674
|
const decision = data["decision"];
|
|
@@ -12276,7 +12678,8 @@ async function callJudge(input, opts = {}) {
|
|
|
12276
12678
|
reason: typeof data["reason"] === "string" ? data["reason"] : "",
|
|
12277
12679
|
model: typeof data["model"] === "string" ? data["model"] : "",
|
|
12278
12680
|
quotaRemaining: typeof data["quota_remaining"] === "number" ? data["quota_remaining"] : -1,
|
|
12279
|
-
quotaExceeded: Boolean(data["quota_exceeded"])
|
|
12681
|
+
quotaExceeded: Boolean(data["quota_exceeded"]),
|
|
12682
|
+
...usageWarning ? { usageWarning } : {}
|
|
12280
12683
|
};
|
|
12281
12684
|
} catch {
|
|
12282
12685
|
return null;
|
|
@@ -12321,11 +12724,11 @@ function detectPromptInjection(text) {
|
|
|
12321
12724
|
}
|
|
12322
12725
|
|
|
12323
12726
|
// src/hooks/runtime/judge-cache.ts
|
|
12324
|
-
import { readFileSync as
|
|
12325
|
-
import { dirname as
|
|
12326
|
-
import { homedir as
|
|
12327
|
-
var DEFAULT_JUDGE_CACHE_FILE =
|
|
12328
|
-
|
|
12727
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync15, mkdirSync as mkdirSync8 } from "node:fs";
|
|
12728
|
+
import { dirname as dirname8, join as join12 } from "node:path";
|
|
12729
|
+
import { homedir as homedir12 } from "node:os";
|
|
12730
|
+
var DEFAULT_JUDGE_CACHE_FILE = join12(
|
|
12731
|
+
homedir12(),
|
|
12329
12732
|
".cybedefend",
|
|
12330
12733
|
"guards-judge-cache.json"
|
|
12331
12734
|
);
|
|
@@ -12335,9 +12738,9 @@ function makeJudgeCache(opts = {}) {
|
|
|
12335
12738
|
const ttlMs = opts.ttlMs ?? DEFAULT_JUDGE_TTL_MS;
|
|
12336
12739
|
const now = opts.now ?? (() => Date.now());
|
|
12337
12740
|
function readAll() {
|
|
12338
|
-
if (!
|
|
12741
|
+
if (!existsSync15(cacheFile)) return {};
|
|
12339
12742
|
try {
|
|
12340
|
-
return JSON.parse(
|
|
12743
|
+
return JSON.parse(readFileSync14(cacheFile, "utf8"));
|
|
12341
12744
|
} catch {
|
|
12342
12745
|
return {};
|
|
12343
12746
|
}
|
|
@@ -12357,36 +12760,36 @@ function makeJudgeCache(opts = {}) {
|
|
|
12357
12760
|
}
|
|
12358
12761
|
all[key] = { res, at: t };
|
|
12359
12762
|
ensureDirFor3(cacheFile);
|
|
12360
|
-
|
|
12763
|
+
writeFileSync9(cacheFile, JSON.stringify(all), "utf8");
|
|
12361
12764
|
} catch {
|
|
12362
12765
|
}
|
|
12363
12766
|
}
|
|
12364
12767
|
return { get, set };
|
|
12365
12768
|
}
|
|
12366
12769
|
function ensureDirFor3(filePath) {
|
|
12367
|
-
const dir =
|
|
12368
|
-
if (!
|
|
12369
|
-
|
|
12770
|
+
const dir = dirname8(filePath);
|
|
12771
|
+
if (!existsSync15(dir)) {
|
|
12772
|
+
mkdirSync8(dir, { recursive: true });
|
|
12370
12773
|
}
|
|
12371
12774
|
}
|
|
12372
12775
|
|
|
12373
12776
|
// src/hooks/runtime/guard-check.ts
|
|
12374
|
-
import { homedir as
|
|
12375
|
-
import { join as
|
|
12777
|
+
import { homedir as homedir14 } from "node:os";
|
|
12778
|
+
import { join as join14 } from "node:path";
|
|
12376
12779
|
|
|
12377
12780
|
// src/hooks/runtime/guard-violations-buffer.ts
|
|
12378
12781
|
import {
|
|
12379
12782
|
appendFileSync,
|
|
12380
|
-
existsSync as
|
|
12381
|
-
readFileSync as
|
|
12382
|
-
writeFileSync as
|
|
12383
|
-
mkdirSync as
|
|
12783
|
+
existsSync as existsSync16,
|
|
12784
|
+
readFileSync as readFileSync15,
|
|
12785
|
+
writeFileSync as writeFileSync10,
|
|
12786
|
+
mkdirSync as mkdirSync9
|
|
12384
12787
|
} from "node:fs";
|
|
12385
|
-
import { dirname as
|
|
12386
|
-
import { homedir as
|
|
12387
|
-
import { join as
|
|
12388
|
-
var DEFAULT_BUFFER_FILE =
|
|
12389
|
-
|
|
12788
|
+
import { dirname as dirname9 } from "node:path";
|
|
12789
|
+
import { homedir as homedir13 } from "node:os";
|
|
12790
|
+
import { join as join13 } from "node:path";
|
|
12791
|
+
var DEFAULT_BUFFER_FILE = join13(
|
|
12792
|
+
homedir13(),
|
|
12390
12793
|
".cybedefend",
|
|
12391
12794
|
"guards-violations-buffer.jsonl"
|
|
12392
12795
|
);
|
|
@@ -12405,10 +12808,10 @@ function makeViolationsBuffer(opts = {}) {
|
|
|
12405
12808
|
}
|
|
12406
12809
|
}
|
|
12407
12810
|
async function flushViolations2(ctx, postFn) {
|
|
12408
|
-
if (!
|
|
12811
|
+
if (!existsSync16(bufferFile)) return;
|
|
12409
12812
|
let content;
|
|
12410
12813
|
try {
|
|
12411
|
-
content =
|
|
12814
|
+
content = readFileSync15(bufferFile, "utf8").trim();
|
|
12412
12815
|
} catch {
|
|
12413
12816
|
return;
|
|
12414
12817
|
}
|
|
@@ -12427,7 +12830,7 @@ function makeViolationsBuffer(opts = {}) {
|
|
|
12427
12830
|
}
|
|
12428
12831
|
if (records.length === 0) {
|
|
12429
12832
|
try {
|
|
12430
|
-
|
|
12833
|
+
writeFileSync10(bufferFile, "", "utf8");
|
|
12431
12834
|
} catch {
|
|
12432
12835
|
}
|
|
12433
12836
|
return;
|
|
@@ -12457,10 +12860,10 @@ function makeViolationsBuffer(opts = {}) {
|
|
|
12457
12860
|
}
|
|
12458
12861
|
try {
|
|
12459
12862
|
if (failed.length === 0) {
|
|
12460
|
-
|
|
12863
|
+
writeFileSync10(bufferFile, "", "utf8");
|
|
12461
12864
|
} else {
|
|
12462
12865
|
const serialised = failed.map((r) => JSON.stringify(r)).join("\n") + "\n";
|
|
12463
|
-
|
|
12866
|
+
writeFileSync10(bufferFile, serialised, "utf8");
|
|
12464
12867
|
}
|
|
12465
12868
|
} catch {
|
|
12466
12869
|
}
|
|
@@ -12468,9 +12871,9 @@ function makeViolationsBuffer(opts = {}) {
|
|
|
12468
12871
|
return { bufferViolation: bufferViolation2, flushViolations: flushViolations2 };
|
|
12469
12872
|
}
|
|
12470
12873
|
function ensureDirFor4(filePath) {
|
|
12471
|
-
const dir =
|
|
12472
|
-
if (!
|
|
12473
|
-
|
|
12874
|
+
const dir = dirname9(filePath);
|
|
12875
|
+
if (!existsSync16(dir)) {
|
|
12876
|
+
mkdirSync9(dir, { recursive: true });
|
|
12474
12877
|
}
|
|
12475
12878
|
}
|
|
12476
12879
|
var _defaultBuffer = makeViolationsBuffer();
|
|
@@ -12496,11 +12899,11 @@ function appendDebugLog(entry) {
|
|
|
12496
12899
|
try {
|
|
12497
12900
|
const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
12498
12901
|
if (!homeDir) return;
|
|
12499
|
-
const { mkdirSync:
|
|
12500
|
-
const { join:
|
|
12501
|
-
const dir =
|
|
12502
|
-
|
|
12503
|
-
appendFileSync2(
|
|
12902
|
+
const { mkdirSync: mkdirSync10, appendFileSync: appendFileSync2 } = __require("node:fs");
|
|
12903
|
+
const { join: join15 } = __require("node:path");
|
|
12904
|
+
const dir = join15(homeDir, ".cybedefend");
|
|
12905
|
+
mkdirSync10(dir, { recursive: true });
|
|
12906
|
+
appendFileSync2(join15(dir, "hook-debug.log"), entry + "\n", "utf8");
|
|
12504
12907
|
} catch {
|
|
12505
12908
|
}
|
|
12506
12909
|
}
|
|
@@ -12671,6 +13074,28 @@ function stringifyTarget(t) {
|
|
|
12671
13074
|
return `git ${t.subcommand}`;
|
|
12672
13075
|
}
|
|
12673
13076
|
}
|
|
13077
|
+
async function consultJudge(judge, sessionId, cacheKey, input) {
|
|
13078
|
+
const cached = judge.cacheGet(cacheKey);
|
|
13079
|
+
if (cached) return { kind: "result", res: cached };
|
|
13080
|
+
if (judge.usage?.isBlocked(sessionId)) return { kind: "blocked" };
|
|
13081
|
+
const answer = await judge.callJudge(input);
|
|
13082
|
+
if (isJudgeBlocked(answer)) {
|
|
13083
|
+
if (judge.usage) {
|
|
13084
|
+
judge.usage.markBlocked(sessionId);
|
|
13085
|
+
judge.usage.notifier.notifyBlock(sessionId, answer.blocked);
|
|
13086
|
+
} else {
|
|
13087
|
+
judge.warnOnce(sessionId, blockSentence(answer.blocked));
|
|
13088
|
+
}
|
|
13089
|
+
return { kind: "blocked" };
|
|
13090
|
+
}
|
|
13091
|
+
if (!answer) return { kind: "result", res: null };
|
|
13092
|
+
const { usageWarning, ...res } = answer;
|
|
13093
|
+
if (usageWarning) {
|
|
13094
|
+
judge.usage?.notifier.notifyWarning(sessionId, usageWarning);
|
|
13095
|
+
}
|
|
13096
|
+
judge.cacheSet(cacheKey, res);
|
|
13097
|
+
return { kind: "result", res };
|
|
13098
|
+
}
|
|
12674
13099
|
async function runJudgeStep(args) {
|
|
12675
13100
|
const {
|
|
12676
13101
|
judge,
|
|
@@ -12699,30 +13124,30 @@ async function runJudgeStep(args) {
|
|
|
12699
13124
|
const actionHash = hashTarget(targetStr);
|
|
12700
13125
|
const ruleIds = llmRules.map((r) => r.id).sort().join(",");
|
|
12701
13126
|
const cacheKey = `${actionHash}|${ruleIds}|confirm`;
|
|
12702
|
-
|
|
12703
|
-
|
|
12704
|
-
|
|
12705
|
-
|
|
12706
|
-
|
|
12707
|
-
|
|
12708
|
-
|
|
12709
|
-
|
|
12710
|
-
|
|
12711
|
-
|
|
12712
|
-
|
|
12713
|
-
|
|
12714
|
-
|
|
12715
|
-
|
|
12716
|
-
|
|
12717
|
-
|
|
12718
|
-
|
|
12719
|
-
|
|
12720
|
-
|
|
12721
|
-
|
|
12722
|
-
|
|
12723
|
-
}
|
|
12724
|
-
if (res) judge.cacheSet(cacheKey, res);
|
|
13127
|
+
const consultation = await consultJudge(judge, sessionId, cacheKey, {
|
|
13128
|
+
apiBaseResolved: projectContext.apiBaseResolved,
|
|
13129
|
+
token: projectContext.token,
|
|
13130
|
+
projectId: request.projectId,
|
|
13131
|
+
tool: request.tool,
|
|
13132
|
+
agent: request.agent,
|
|
13133
|
+
sessionId,
|
|
13134
|
+
phase: "confirm",
|
|
13135
|
+
// Judge over the FULL (schema-bounded 2048) redacted action — the default
|
|
13136
|
+
// redactTarget cap of 256 (right for the DB-stored violation summary) would
|
|
13137
|
+
// truncate a long dangerous action and risk an overturn on a partial view.
|
|
13138
|
+
actionSummary: redactTarget(targetStr, 2048),
|
|
13139
|
+
actionHash,
|
|
13140
|
+
rules: llmRules.map((r) => ({
|
|
13141
|
+
id: r.id,
|
|
13142
|
+
name: r.name ?? r.id,
|
|
13143
|
+
description: r.description ?? "",
|
|
13144
|
+
severity: r.severity
|
|
13145
|
+
}))
|
|
13146
|
+
});
|
|
13147
|
+
if (consultation.kind === "blocked") {
|
|
13148
|
+
return { engine: "llm", outcome: "quota_exceeded" };
|
|
12725
13149
|
}
|
|
13150
|
+
const res = consultation.res;
|
|
12726
13151
|
if (!res) {
|
|
12727
13152
|
return {
|
|
12728
13153
|
engine: "llm",
|
|
@@ -12785,27 +13210,25 @@ async function runCatchJudgeStep(args) {
|
|
|
12785
13210
|
const actionHash = hashTarget(targetStr);
|
|
12786
13211
|
const ruleIds = near.map((r) => r.id).sort();
|
|
12787
13212
|
const cacheKey = `${actionHash}|${ruleIds.join(",")}|catch`;
|
|
12788
|
-
|
|
12789
|
-
|
|
12790
|
-
|
|
12791
|
-
|
|
12792
|
-
|
|
12793
|
-
|
|
12794
|
-
|
|
12795
|
-
|
|
12796
|
-
|
|
12797
|
-
|
|
12798
|
-
|
|
12799
|
-
|
|
12800
|
-
|
|
12801
|
-
|
|
12802
|
-
|
|
12803
|
-
|
|
12804
|
-
|
|
12805
|
-
|
|
12806
|
-
|
|
12807
|
-
if (res) judge.cacheSet(cacheKey, res);
|
|
12808
|
-
}
|
|
13213
|
+
const consultation = await consultJudge(judge, sessionId, cacheKey, {
|
|
13214
|
+
apiBaseResolved: projectContext.apiBaseResolved,
|
|
13215
|
+
token: projectContext.token,
|
|
13216
|
+
projectId: request.projectId,
|
|
13217
|
+
tool: request.tool,
|
|
13218
|
+
agent: request.agent,
|
|
13219
|
+
sessionId,
|
|
13220
|
+
phase: "catch",
|
|
13221
|
+
actionSummary: redactTarget(targetStr, 2048),
|
|
13222
|
+
actionHash,
|
|
13223
|
+
rules: near.map((r) => ({
|
|
13224
|
+
id: r.id,
|
|
13225
|
+
name: r.name ?? r.id,
|
|
13226
|
+
description: r.description ?? "",
|
|
13227
|
+
severity: r.severity
|
|
13228
|
+
}))
|
|
13229
|
+
});
|
|
13230
|
+
if (consultation.kind === "blocked") return void 0;
|
|
13231
|
+
const res = consultation.res;
|
|
12809
13232
|
if (!res) return void 0;
|
|
12810
13233
|
if (res.quotaExceeded) {
|
|
12811
13234
|
judge.warnOnce(
|
|
@@ -12851,6 +13274,9 @@ function emitUnverifiedBanner(reason) {
|
|
|
12851
13274
|
}
|
|
12852
13275
|
}
|
|
12853
13276
|
}
|
|
13277
|
+
function sessionIdOf3(envelope) {
|
|
13278
|
+
return typeof envelope.session_id === "string" ? envelope.session_id : typeof envelope.sessionId === "string" ? envelope.sessionId : void 0;
|
|
13279
|
+
}
|
|
12854
13280
|
async function runPreToolUseGuardWith(opts) {
|
|
12855
13281
|
const {
|
|
12856
13282
|
stdinEnvelope,
|
|
@@ -12866,11 +13292,19 @@ async function runPreToolUseGuardWith(opts) {
|
|
|
12866
13292
|
);
|
|
12867
13293
|
return 0;
|
|
12868
13294
|
}
|
|
13295
|
+
if (rulesOutcome.kind === "blocked") {
|
|
13296
|
+
const sentence = opts.usageNotifier?.notifyBlock(
|
|
13297
|
+
sessionIdOf3(stdinEnvelope),
|
|
13298
|
+
rulesOutcome.block
|
|
13299
|
+
);
|
|
13300
|
+
if (sentence) emitAgentMessage("warn", sentence);
|
|
13301
|
+
return 0;
|
|
13302
|
+
}
|
|
12869
13303
|
const rules = rulesOutcome.rules;
|
|
12870
13304
|
const caller = sniff(stdinEnvelope);
|
|
12871
13305
|
const partial = mapEnvelopeToGuardRequest(stdinEnvelope, caller.client);
|
|
12872
13306
|
if (partial === null) return 0;
|
|
12873
|
-
const sessionId =
|
|
13307
|
+
const sessionId = sessionIdOf3(stdinEnvelope);
|
|
12874
13308
|
const guardRequest = {
|
|
12875
13309
|
...partial,
|
|
12876
13310
|
projectId: projectContext.projectId,
|
|
@@ -13014,11 +13448,21 @@ function emitAgentMessage(verdict, message) {
|
|
|
13014
13448
|
}
|
|
13015
13449
|
function makeProductionJudgeDeps() {
|
|
13016
13450
|
const cache = makeJudgeCache();
|
|
13017
|
-
const warnDir = process.env.CYBEDEFEND_BASELINE_DIR ??
|
|
13451
|
+
const warnDir = process.env.CYBEDEFEND_BASELINE_DIR ?? join14(homedir14(), ".cybedefend");
|
|
13018
13452
|
const warnTracker = defaultSessionTracker(
|
|
13019
|
-
|
|
13453
|
+
join14(warnDir, "guards-judge-warned-sessions.json")
|
|
13454
|
+
);
|
|
13455
|
+
const blockedTracker = defaultSessionTracker(
|
|
13456
|
+
join14(warnDir, "guards-judge-blocked-sessions.json")
|
|
13020
13457
|
);
|
|
13021
13458
|
return {
|
|
13459
|
+
usage: {
|
|
13460
|
+
notifier: makeUsageNotifier(),
|
|
13461
|
+
isBlocked: (sessionId) => sessionId ? blockedTracker.hasSeen(sessionId) : false,
|
|
13462
|
+
markBlocked: (sessionId) => {
|
|
13463
|
+
if (sessionId) void blockedTracker.markSeen(sessionId);
|
|
13464
|
+
}
|
|
13465
|
+
},
|
|
13022
13466
|
callJudge,
|
|
13023
13467
|
cacheGet: (k) => cache.get(k),
|
|
13024
13468
|
cacheSet: (k, r) => cache.set(k, r),
|
|
@@ -13038,7 +13482,9 @@ async function runPreToolUseGuard() {
|
|
|
13038
13482
|
const envelope = await readStdinJson();
|
|
13039
13483
|
const config = loadRuntimeConfig();
|
|
13040
13484
|
if (!config) return 0;
|
|
13041
|
-
const
|
|
13485
|
+
const folder = sessionFolderOf(envelope, sniff(envelope).client);
|
|
13486
|
+
if (isSessionInactive(envelope, { cwd: folder })) return 0;
|
|
13487
|
+
const { projectId, apiBase: configApiBase } = await resolveProjectIdWithApi(folder);
|
|
13042
13488
|
if (!projectId) return 0;
|
|
13043
13489
|
const apiBaseResolved = process.env.CYBEDEFEND_API_BASE ?? configApiBase ?? config.region.apiBase;
|
|
13044
13490
|
const token = await resolveTokenWithRefresh(
|
|
@@ -13076,7 +13522,10 @@ async function runPreToolUseGuard() {
|
|
|
13076
13522
|
judge: makeProductionJudgeDeps(),
|
|
13077
13523
|
// v1.1 catch — OFF by default; opt in with CYBEDEFEND_JUDGE_CATCH=1 once
|
|
13078
13524
|
// the call-rate gate is validated in the field.
|
|
13079
|
-
judgeCatch: process.env.CYBEDEFEND_JUDGE_CATCH === "1"
|
|
13525
|
+
judgeCatch: process.env.CYBEDEFEND_JUDGE_CATCH === "1",
|
|
13526
|
+
// The refusal sentence goes out through the agent-message channels
|
|
13527
|
+
// (stderr included), so this notifier itself writes nothing.
|
|
13528
|
+
usageNotifier: makeUsageNotifier({ write: () => void 0 })
|
|
13080
13529
|
});
|
|
13081
13530
|
try {
|
|
13082
13531
|
await buffer.flushViolations(
|