@tuent/sentinel 0.1.2 → 0.1.4
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/SECURITY_MODEL.md +9 -0
- package/dist/Sentinel-4QKPFHTI.js +10 -0
- package/dist/{Sentinel-xFCyXH45.d.ts → Sentinel-DT0IyGQi.d.ts} +127 -9
- package/dist/{chunk-PDWWRZXF.js → chunk-2IPSTUNH.js} +18 -7
- package/dist/chunk-B6S2PBS4.js +47 -0
- package/dist/{chunk-QIYQWOLO.js → chunk-FIEIGBYL.js} +387 -242
- package/dist/{chunk-L4R3LPJS.js → chunk-HRI2Y326.js} +119 -35
- package/dist/{chunk-GRN5P3H2.js → chunk-I2FVDDSG.js} +242 -94
- package/dist/{chunk-WLIDSTS4.js → chunk-KWZ7JKKO.js} +221 -27
- package/dist/{chunk-FWIISAZZ.js → chunk-LTBVWF5H.js} +201 -80
- package/dist/chunk-TKAKHSZ3.js +1 -0
- package/dist/cli.js +33 -35
- package/dist/gateway/index.d.ts +20 -1
- package/dist/gateway/index.js +4 -4
- package/dist/gatewayDaemon.js +38 -16
- package/dist/index.d.ts +31 -19
- package/dist/index.js +9 -8
- package/dist/logAdapter-WM43W3S7.js +7 -0
- package/dist/{mcpAdapter-R47GX2P3.js → mcpAdapter-WYAXUE7T.js} +2 -2
- package/dist/{policyLoader-KZL2U4M2.js → policyLoader-XX6BQXNB.js} +8 -4
- package/package.json +1 -1
- package/dist/Sentinel-XMSJE4DZ.js +0 -10
- package/dist/chunk-FMZWHT4M.js +0 -20
- package/dist/logAdapter-IB6ZDEV2.js +0 -7
package/dist/cli.js
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
`];for(const
|
|
4
|
-
`).map(
|
|
2
|
+
import"./chunk-TKAKHSZ3.js";import{AgentProfileManager as B,AlertManager as me,AuditTrail as b,BaselineBuilder as x,CorrelationDetector as he,DeviationDetector as _,FileStorageBackend as ye,ProfileStore as we,ReportGenerator as ve,Sentinel as E,SentinelRunner as $e,generateFleetReport as Ae}from"./chunk-I2FVDDSG.js";import{runInitClaudeCode as be}from"./chunk-LTBVWF5H.js";import{readReleaseToken as Ie}from"./chunk-LATQNIRW.js";import{deriveAgentId as Se}from"./chunk-B5QKJHSV.js";import"./chunk-B6S2PBS4.js";import"./chunk-FIEIGBYL.js";import{checkSaneNumber as P,loadPolicy as De,suggestKey as Te}from"./chunk-KWZ7JKKO.js";import{getOrCreateKeyPair as R}from"./chunk-NUXSUSYY.js";import{join as g}from"path";import{homedir as h}from"os";import{readFile as N,writeFile as C,access as G,mkdir as U}from"fs/promises";function ke(t){const o=new Date(t);if(isNaN(o.getTime()))return"unknown";const e=Date.now()-o.getTime();if(e<0)return"just now";const s=Math.floor(e/6e4);if(s<1)return"just now";if(s<60)return`${s} minute${s===1?"":"s"} ago`;const i=Math.floor(s/60);if(i<24)return`${i} hour${i===1?"":"s"} ago`;const a=Math.floor(i/24);if(a<14)return`${a} day${a===1?"":"s"} ago`;const d=Math.floor(a/7);if(a<60)return`${d} week${d===1?"":"s"} ago`;if(a>=365)return"over a year ago";const c=Math.floor(a/30);return`${c} month${c===1?"":"s"} ago`}function Ee(t,o){const n=new Date(o),e=isNaN(n.getTime())?1/0:Math.floor((Date.now()-n.getTime())/864e5);return t<.3||e>30?"declining":t>.7&&e<7?"rising":"stable"}function Ce(t){return t<.3?"inner":t<.65?"middle":"outer"}function J(t){if(t.length===0)return"No petals selected.";const o=new Map;for(const e of t)o.set(e.id,e.label);const n=[`Selected petals (${t.length}):
|
|
3
|
+
`];for(const e of t){const s=Ce(e.layer),i=e.isRichData?"":" [filler]";n.push(`- ${e.label}${i}`),n.push(` Category: ${e.category}`),n.push(` Layer zone: ${s} (${(e.layer*100).toFixed(0)}%)`),n.push(` Openness: ${(e.openness*100).toFixed(0)}%`),n.push(` Description: ${e.description}`),n.push(` Last active: ${e.lastActive}`);const a=ke(e.lastActive),d=e.weight!=null?Ee(e.weight,e.lastActive):"stable";if(n.push(` Temporal: Last active ${a} | Weight trend: ${d}`),e.source){const c={seed:"seed data",agent:"observed from activity",manual:"filesystem scan",diary:"personal diary entry",conversation:"created from conversation","agent-monitor":"monitored agent activity"};n.push(` Source: ${c[e.source]??e.source}`)}if(e.weight!=null&&n.push(` Weight: ${e.weight.toFixed(2)}`),e.connections.length>0){const c=e.connections.map(r=>o.get(r)??je(r));n.push(` Connections: ${c.join(", ")}`)}if(e.files&&e.files.length>0){const c=e.files.slice(0,10).map(r=>r.split("/").pop()??r);n.push(` Key files: ${c.join(", ")}`)}if(e.fileContents&&e.fileContents.length>0){n.push(" File contents:");for(const c of e.fileContents)n.push(` --- ${c.name} ---`),n.push(c.content.split(`
|
|
4
|
+
`).map(r=>` ${r}`).join(`
|
|
5
5
|
`))}n.push("")}return n.join(`
|
|
6
|
-
`)}function
|
|
6
|
+
`)}function je(t){return t.split("-").map(o=>o.charAt(0).toUpperCase()+o.slice(1)).join(" ")}function Me(t){const o=[Re(t.agentName,t.agentId,t.role),Ne(t.baseline),Fe(t.e),xe(t.findings),Pe()],n=Le(t.t);return n&&o.push(n),o.join(`
|
|
7
7
|
|
|
8
|
-
`)}function
|
|
9
|
-
`)}function
|
|
10
|
-
Not yet established. Insufficient data for baseline computation.`;const o=Object.entries(
|
|
11
|
-
`)}function
|
|
8
|
+
`)}function Re(t,o,n){const e=["=== Agent Identity ===",`Name: ${t}`,`ID: ${o}`];if(n){if(e.push(`Role: ${n.description}`),e.push(`Allowed actions: ${n.allowedActions.length>0?n.allowedActions.join(", "):"Not defined"}`),n.forbiddenTargetPatterns.length>0&&e.push(`Forbidden targets: ${n.forbiddenTargetPatterns.join(", ")}`),n.expectedSchedule){const s=[];n.expectedSchedule.activeHours&&s.push(`hours ${n.expectedSchedule.activeHours[0]}-${n.expectedSchedule.activeHours[1]}`),n.expectedSchedule.activeDays&&s.push(`days ${n.expectedSchedule.activeDays.join(", ")}`),s.length>0&&e.push(`Expected schedule: ${s.join("; ")}`)}}else e.push("No role definition \u2014 monitoring behavioral baseline only");return e.join(`
|
|
9
|
+
`)}function Ne(t){if(!t)return`=== Behavioral Baseline ===
|
|
10
|
+
Not yet established. Insufficient data for baseline computation.`;const o=Object.entries(t.actionDistribution).map(([e,s])=>`${e}: ${s}%`).join(", ");return["=== Behavioral Baseline ===",`Period: ${t.periodDays} days`,`Total sessions: ${t.totalSessions}`,`Total events: ${t.totalEvents}`,`Average events per session: ${t.averageEventsPerSession}`,`Average session duration: ${t.averageSessionDurationMinutes} minutes`,`Typical active hours: ${t.typicalActiveHours[0]}-${t.typicalActiveHours[1]}`,`Typical active days: ${t.typicalActiveDays.length>0?t.typicalActiveDays.join(", "):"N/A"}`,`Action distribution: ${o||"N/A"}`,`Normal weight range: ${t.normalWeightRange[0].toFixed(2)}-${t.normalWeightRange[1].toFixed(2)}`,`Top targets: ${t.topTargets.length>0?t.topTargets.slice(0,10).join(", "):"N/A"}`].join(`
|
|
11
|
+
`)}function Fe(t){const o=`=== Recent Activity (${t.length} sessions) ===`;return t.length===0?`${o}
|
|
12
12
|
No recent activity recorded.`:`${o}
|
|
13
|
-
${
|
|
14
|
-
No security findings detected. Agent behavior is within expected parameters.`;const n=[o];for(const
|
|
15
|
-
`)}function
|
|
16
|
-
`)}function
|
|
17
|
-
${
|
|
18
|
-
`)}function
|
|
19
|
-
`)}function
|
|
20
|
-
`)}function
|
|
21
|
-
`)}function
|
|
22
|
-
`)}async function
|
|
23
|
-
`),console.log(
|
|
24
|
-
`),console.log(
|
|
13
|
+
${J(t)}`}function xe(t){const o="=== Security Findings ===";if(t.length===0)return`${o}
|
|
14
|
+
No security findings detected. Agent behavior is within expected parameters.`;const n=[o];for(const e of t)n.push(`[${e.severity}] ${e.type}`),n.push(` Description: ${e.description}`),n.push(` Evidence: ${e.evidence.action} \u2192 ${e.evidence.target} at ${e.evidence.timestamp}`),e.evidence.baselineComparison&&n.push(` Baseline comparison: ${e.evidence.baselineComparison}`),n.push(` Recommendation: ${e.recommendation}`),n.push("");return n.join(`
|
|
15
|
+
`)}function Pe(){return["=== Analysis Request ===","Based on the data above, provide:","1. OVERALL HEALTH ASSESSMENT: Is this agent operating normally?","2. RISK SUMMARY: What is the overall risk level?","3. FINDING REVIEW: For each finding, confirm or adjust its severity with reasoning.","4. RECOMMENDATIONS: Specific actions the owner should take.","5. MONITORING GUIDANCE: What patterns to watch for going forward."].join(`
|
|
16
|
+
`)}function Le(t){return!t||t.length===0?null:`=== Owner Context ===
|
|
17
|
+
${J(t)}`}import{request as V}from"http";var u=process.argv.slice(2),p=v(u,"--agent"),Q=v(u,"--name"),qe=v(u,"--role"),Oe=u.includes("--create"),He=u.includes("--compute-baseline"),Ke=u.includes("--monitor"),We=u.includes("--init-config"),Be=u.includes("--init-policy"),_e=u.includes("init")&&u.includes("claude-code"),Ge=u.includes("--force"),z=v(u,"--from-policy"),Ue=u.includes("--report"),Je=u.includes("--report-all"),Ve=u.includes("--correlations"),Qe=u.includes("--verify-audit"),ze=u.includes("--enroll-manifest"),Ye=u.includes("--recompute-stats"),Xe=u.includes("--health"),Ze=u.includes("--restrict"),et=u.includes("--quarantine"),tt=u.includes("--release")||u[0]==="release",nt=u.includes("--status"),ot=u.includes("--start-task"),st=u.includes("--end-task"),at=u.includes("--intent-status"),it=u.includes("--intent-report"),Y=v(u,"--task-id"),X=v(u,"--description"),Z=v(u,"--relaxed-actions"),ee=v(u,"--phases"),te=v(u,"--reason"),L=v(u,"--quarantine"),q=v(u,"--quarantine-reason"),T=v(u,"--period"),ne=v(u,"--format"),S=new B;function v(t,o){const n=t.find(s=>s.startsWith(o+"="));if(!n)return;const e=n.indexOf("=");return n.slice(e+1)}async function k(t,o){try{const n=await N(g(t,o,"mode.json"),"utf-8"),e=JSON.parse(n);return e.mode==="restricted"||e.mode==="quarantined"||e.mode==="normal"?e:{mode:"normal"}}catch{return{mode:"normal"}}}function oe(t){const o=["Agent","Score","Status","Mode","C/H/M/L","Last Event","Baseline"],n=t.map(r=>[r.agentId,String(r.score),r.status,r.mode,r.findings,r.lastEvent,r.baseline?"\u2713":"\u2717"]),e=[o,...n],s=o.map((r,l)=>Math.max(...e.map(f=>f[l].length))),i=(r,l)=>r+" ".repeat(l-r.length),a=o.map((r,l)=>i(r,s[l])).join(" "),d=s.map(r=>"-".repeat(r)).join(" "),c=n.map(r=>r.map((l,f)=>i(l,s[f])).join(" "));return[a,d,...c].join(`
|
|
18
|
+
`)}function se(t){const o=["Agent ID","Mode","Reason","Changed"],n=t.map(r=>[r.agentId,r.mode.toUpperCase(),r.reason,r.changed]),e=[o,...n],s=o.map((r,l)=>Math.max(...e.map(f=>f[l].length))),i=(r,l)=>r+" ".repeat(l-r.length),a=o.map((r,l)=>i(r,s[l])).join(" "),d=s.map(r=>"-".repeat(r)).join(" "),c=n.map(r=>r.map((l,f)=>i(l,s[f])).join(" "));return[a,d,...c].join(`
|
|
19
|
+
`)}function ae(t){const o=["Agent ID","Sessions","Last Active","Role","Baseline","Mode","Status"],n=t.map(r=>[r.agentId,String(r.sessions),r.lastActive,r.roleDefined?"\u2713":"\u2717",r.baselineDefined?"\u2713":"\u2717",r.mode??"normal",r.status]),e=[o,...n],s=o.map((r,l)=>Math.max(...e.map(f=>f[l].length))),i=(r,l)=>r+" ".repeat(l-r.length),a=o.map((r,l)=>i(r,s[l])).join(" "),d=s.map(r=>"-".repeat(r)).join(" "),c=n.map(r=>r.map((l,f)=>i(l,s[f])).join(" "));return[a,d,...c].join(`
|
|
20
|
+
`)}function O(t,o){return t<10?"New":o.some(n=>n.severity==="HIGH"||n.severity==="CRITICAL")?"At Risk":o.some(n=>n.severity==="MEDIUM")?"Caution":"Healthy"}function H(t,o){const n=Date.now()-o*24*60*60*1e3;return t.filter(e=>new Date(e.lastActive).getTime()>=n)}function ie(t){return{id:t.id,label:t.label,category:t.category,description:t.description,layer:t.layer,lastActive:t.lastActive,openness:t.openness,connections:t.connections,isRichData:!0,source:t.source,core:t.core,sharable:t.sharable,weight:t.weight,files:t.files}}function K(t,o){const n=["Sentinel \u2014 Live Monitoring","\u2500".repeat(26)];for(const[e,s]of t){const i=(o.get(e)??"manual").toUpperCase(),a=s.getFindings(),d=a.filter(w=>w.severity==="HIGH"||w.severity==="CRITICAL").length,c=a.filter(w=>w.severity==="MEDIUM").length;let r=`${a.length} findings`;d>0?r=`${d} HIGH`:c>0&&(r=`${c} MEDIUM`);const l=O(s.sessionCount,a),f=e.padEnd(18),y=i.padEnd(5);n.push(`[${f}] ${y}| ${s.eventCount} events | ${s.sessionCount} sessions | ${r} | ${l}`)}return n.push(""),n.push("Press Ctrl+C to stop."),n.join(`
|
|
21
|
+
`)}function re(t){const o=["Monitoring stopped. Summary:"];for(const[n,e]of t){const s=e.getFindings();let i=`${s.length} findings`;if(s.length>0){const a={};for(const c of s)a[c.severity]=(a[c.severity]??0)+1;const d=Object.entries(a).map(([c,r])=>`${r} ${c}`);i=`${s.length} finding${s.length>1?"s":""} (${d.join(", ")})`}o.push(`- ${n}: ${e.eventCount} events, ${e.sessionCount} sessions, ${i}`)}return o.join(`
|
|
22
|
+
`)}async function rt(t,o){const n=g(h(),".dahlia","agents"),e=await k(n,t);if(e.mode==="quarantined"){console.log(`Agent ${t} is quarantined \u2014 cannot downgrade to restricted.`);return}const s=e.mode;await U(g(n,t),{recursive:!0}),await C(g(n,t,"mode.json"),JSON.stringify({mode:"restricted",reason:o,timestamp:new Date().toISOString(),previousMode:s}),"utf-8");const i=await R(g(n,t)),a=new b(t,{logDir:g(n,t)});await a.open(),a.setSigningKey(i.privateKey,i.publicKey),await a.logModeChange("restricted",o,s),await a.close(),console.log(`Agent ${t} RESTRICTED: ${o}`)}async function lt(t,o){const n=g(h(),".dahlia","agents"),s=(await k(n,t)).mode;await U(g(n,t),{recursive:!0}),await C(g(n,t,"mode.json"),JSON.stringify({mode:"quarantined",reason:o,timestamp:new Date().toISOString(),previousMode:s}),"utf-8");const i=await R(g(n,t)),a=new b(t,{logDir:g(n,t)});await a.open(),a.setSigningKey(i.privateKey,i.publicKey),await a.logModeChange("quarantined",o,s),await a.close(),console.log(`Agent ${t} QUARANTINED: ${o}`)}function ct(t,o=1500){return new Promise(n=>{const e=V({host:"127.0.0.1",port:t,path:"/api/sentinel/health",method:"GET"},s=>{s.resume(),n(s.statusCode===200)});e.on("error",()=>n(!1)),e.setTimeout(o,()=>{e.destroy(),n(!1)}),e.end()})}function dt(t,o,n){return new Promise((e,s)=>{const i=JSON.stringify({agentId:n,reason:"operator release (sentinel release)"}),a=V({host:"127.0.0.1",port:t,path:"/api/sentinel/release",method:"POST",headers:{"content-type":"application/json","content-length":Buffer.byteLength(i),"x-sentinel-token":o}},d=>{let c="";d.on("data",r=>c+=r),d.on("end",()=>{try{e({status:d.statusCode??0,body:JSON.parse(c)})}catch{e({status:d.statusCode??0,body:c})}})});a.on("error",s),a.setTimeout(3e3,()=>a.destroy(new Error("timeout"))),a.write(i),a.end()})}function le(t){const{id:o,selfDerived:n,previousMode:e,mode:s}=t;return n&&e==="normal"?`Agent ${o} was already normal \u2014 no change. If you are still locked out, your workspace id may differ (e.g. a symlinked root). Run: sentinel release --agent=<id>`:`[live] Agent ${o} RELEASED \u2014 was ${e}, now ${s}. Applied to the running daemon (no restart).`}async function gt(t){const o=g(h(),".dahlia","agents"),n=t===void 0,e=t??Se(process.cwd());n&&console.log(`Resolved agent-id from cwd: ${e}`);const s=Ie(h());if(s&&await ct(s.port))try{const r=await dt(s.port,s.token,e);if(r.status===200&&typeof r.body=="object"&&r.body.ok){console.log(le({id:e,selfDerived:n,previousMode:String(r.body.previousMode),mode:String(r.body.mode)}));return}const l=typeof r.body=="object"?JSON.stringify(r.body):r.body;console.error(`[live] daemon refused release (HTTP ${r.status}): ${l}`);return}catch(r){console.error(`[live] daemon release call failed: ${r.message}. Falling back to mode.json.`)}const i=await k(o,e);if(i.mode==="normal"){console.log(`Agent ${e} is already in normal mode.`);return}const a=i.mode;await C(g(o,e,"mode.json"),JSON.stringify({mode:"normal",reason:"manual release",timestamp:new Date().toISOString(),previousMode:a}),"utf-8");const d=await R(g(o,e)),c=new b(e,{logDir:g(o,e)});await c.open(),c.setSigningKey(d.privateKey,d.publicKey),await c.logModeChange("normal","manual release",a),await c.close(),console.log(`[fallback] Agent ${e} RELEASED (was ${a}) \u2014 no running daemon; mode.json written, applies on the daemon's next start.`)}async function ut(){const t=await S.listAgents();if(t.length===0){console.log("No monitored agents found.");return}const o=g(h(),".dahlia","agents"),n=[];for(const s of t){const i=await k(o,s);n.push({agentId:s,mode:i.mode,reason:i.reason??"-",changed:i.timestamp?i.timestamp.split("T")[0]:"-"})}console.log(`Agent Mode Status
|
|
23
|
+
`),console.log(se(n));const e=n.filter(s=>s.mode!=="normal");if(e.length>0){console.log("");for(const s of e){const i=s.mode==="quarantined"?"QUARANTINED":"RESTRICTED";console.log(`\u26A0 ${s.agentId}: ${i}`)}}}async function ft(){const t=await S.listAgents();if(t.length===0){console.log("No monitored agents found. Create one with --create --agent=ID --name=NAME");return}const o=[];for(const e of t)try{const a=(await S.loadAgentProfile(e)).build().filter(I=>I.source==="agent-monitor"),d=a.length,c=a.length>0?a.map(I=>I.lastActive).sort().reverse()[0].split("T")[0]:"never",l=await new x(e).loadBaseline(e),f=await S.loadRole(e),y=[];if(l){const I=H(a,7),F=new _(l,f);for(const $ of I){const D={label:$.label,category:$.category,lastActive:$.lastActive,description:$.description,weight:$.weight,source:$.source,files:$.files,connections:$.connections,eventCount:$.eventCount};y.push(...F.analyzeSession(D))}}const w=g(h(),".dahlia","agents"),M=await k(w,e);o.push({agentId:e,sessions:d,lastActive:c,roleDefined:f!==null,baselineDefined:l!==null,status:O(d,y),mode:M.mode})}catch(s){console.warn(`Error loading agent ${e}:`,s),o.push({agentId:e,sessions:0,lastActive:"error",roleDefined:!1,baselineDefined:!1,status:"Error",mode:"normal"})}const n=o.filter(e=>e.mode&&e.mode!=="normal");if(n.length>0){for(const e of n){const s=e.mode==="quarantined"?"QUARANTINED":"RESTRICTED";console.log(`\u26A0 ${e.agentId}: ${s}`)}console.log("")}console.log(`Sentinel Agent Overview
|
|
24
|
+
`),console.log(ae(o))}async function pt(t){const o=await S.loadAgentProfile(t),n=await S.loadRole(t),s=await new x(t).loadBaseline(t),a=o.build().filter(D=>D.source==="agent-monitor"),d=H(a,7),c=d.map(ie),r=[];if(s){const D=new _(s,n);for(const A of d){const pe={label:A.label,category:A.category,lastActive:A.lastActive,description:A.description,weight:A.weight,source:A.source,files:A.files,connections:A.connections};r.push(...D.analyzeSession(pe))}}const l=g(h(),".dahlia","profile.json"),f=new ye(l),y=new we({backend:f});let w=[];await y.load()&&(w=y.build().filter(A=>A.core===!0).map(ie));const I=Me({agentId:t,agentName:t,role:n,baseline:s,e:c,findings:r,t:w}),F=new Date().toISOString().split("T")[0],$=`Sentinel Agent Report \u2014 ${t} \u2014 ${d.length} recent sessions \u2014 generated ${F}
|
|
25
25
|
|
|
26
|
-
`;process.stdout.write($+I)}async function
|
|
27
|
-
`),console.log("Template config created at ~/.dahlia/sentinel.json \u2014 edit it with your agent details then run: npm run sentinel -- --monitor")}
|
|
28
|
-
\u26A0 [${
|
|
29
|
-
`+
|
|
30
|
-
`);for(const
|
|
31
|
-
`),console.log(
|
|
26
|
+
`;process.stdout.write($+I)}async function mt(t){const o=g(h(),".dahlia","agents"),n=new b(t,{logDir:g(o,t)});await n.open();const e=new x(t),s=await e.computeBaseline(n);await n.close(),await e.saveBaseline(s),console.log(`Baseline computed for agent: ${t}`),console.log(` Sessions: ${s.totalSessions}`),console.log(` Period: ${s.periodDays} days`),console.log(" Action distribution:");for(const[i,a]of Object.entries(s.actionDistribution))console.log(` ${i}: ${a}%`);console.log(` Saved to: ~/.dahlia/agents/${t}/baseline.json`)}async function ht(t,o,n){let e;if(n){let s;try{s=await N(n,"utf-8")}catch(i){console.error(`Failed to read role file "${n}":`,i);return}try{e=JSON.parse(s)}catch(i){console.error(`Invalid JSON in role file "${n}":`,i.message);return}}await S.createAgent(t,o,e),console.log(`Agent created: ${t} (${o})`),e&&console.log(` Role loaded from: ${n}`)}async function yt(){const t=g(h(),".dahlia","sentinel.json");try{await G(t),console.log(`Config already exists at ${t}`);return}catch{}await C(t,JSON.stringify({agents:[{agentId:"example-agent",name:"Example Agent",adapterType:"log",logPath:"/path/to/agent/activity.log",logFormat:"json-lines"}]},null,2)+`
|
|
27
|
+
`),console.log("Template config created at ~/.dahlia/sentinel.json \u2014 edit it with your agent details then run: npm run sentinel -- --monitor")}var wt=new Set(["log","webhook","mcp","manual"]),vt=new Set(["json-lines","csv"]),$t=new Set(["LOW","MEDIUM","HIGH","CRITICAL"]),ce=new Set(["informational","actionable"]),At=new Set(["console","webhook","file"]);function m(t){throw new Error(`sentinel.json validation error: ${t}`)}function j(t,o,n){for(const e of Object.keys(o))if(!n.includes(e)){const s=Te(e,n);m(`unknown key "${e}" in ${t}.`+(s?` Did you mean "${s}"?`:"")+` Valid keys: ${n.join(", ")}`)}}function de(t){(typeof t!="object"||t===null||Array.isArray(t))&&m("top level must be a JSON object");const o=t;o.baselineWindowDays!==void 0&&m("baselineWindowDays is not supported \u2014 it was never consumed; the baseline window is fixed at 30 days. Remove this field."),o.checkIntervalMs!==void 0&&m("checkIntervalMs is not supported \u2014 it was never consumed. Remove this field."),o.alertWebhook!==void 0&&m('alertWebhook is not supported \u2014 it was never consumed. Use alerts.channels with a webhook channel ({ "type": "webhook", "name": ..., "config": { "url": ... } }) instead.'),j("sentinel.json",o,["agents","alerts","baselineWindowDays","checkIntervalMs","alertWebhook"]),Array.isArray(o.agents)||m("agents must be an array");for(let n=0;n<o.agents.length;n++){const e=`agents[${n}]`,s=o.agents[n];if((typeof s!="object"||s===null)&&m(`${e} must be an object`),s.roleDefinitionPath!==void 0&&m(`${e}.roleDefinitionPath is not supported \u2014 it was never consumed; roles are loaded from the agent profile directory (~/.dahlia/agents/<id>/role.json). Remove this field.`),j(e,s,["agentId","name","adapterType","logPath","logFormat","fieldMapping","mcpLogDir","webhookPort","webhookApiKey","roleDefinitionPath","readExisting"]),(typeof s.agentId!="string"||s.agentId.length===0)&&m(`${e}.agentId is required and must be a non-empty string`),(typeof s.name!="string"||s.name.length===0)&&m(`${e}.name is required and must be a non-empty string`),(typeof s.adapterType!="string"||!wt.has(s.adapterType))&&m(`${e}.adapterType must be one of: log, webhook, mcp, manual`),s.logFormat!==void 0&&(typeof s.logFormat!="string"||!vt.has(s.logFormat))&&m(`${e}.logFormat must be one of: json-lines, csv`),s.webhookPort!==void 0){const i=P(s.webhookPort,{integer:!0,min:1,max:65535});i&&m(`${e}.webhookPort ${i}`)}s.readExisting!==void 0&&typeof s.readExisting!="boolean"&&m(`${e}.readExisting must be a boolean`)}if(o.alerts!==void 0){(typeof o.alerts!="object"||o.alerts===null)&&m("alerts must be an object");const n=o.alerts;if(j("alerts",n,["minSeverity","minKind","dedupeWindowMs","quietHoursEnabled","quietHours","channels"]),n.minSeverity!==void 0&&(typeof n.minSeverity!="string"||!$t.has(n.minSeverity))&&m("alerts.minSeverity must be one of: LOW, MEDIUM, HIGH, CRITICAL"),n.minKind!==void 0&&(typeof n.minKind!="string"||!ce.has(n.minKind))&&m("alerts.minKind must be one of: informational, actionable"),n.dedupeWindowMs!==void 0){const e=P(n.dedupeWindowMs,{min:0});e&&m(`alerts.dedupeWindowMs ${e}`)}if(n.quietHoursEnabled!==void 0&&typeof n.quietHoursEnabled!="boolean"&&m("alerts.quietHoursEnabled must be a boolean"),n.quietHours!==void 0){const e=n.quietHours,s=i=>P(i,{integer:!0,min:0,max:23});(!Array.isArray(e)||e.length!==2||s(e[0])||s(e[1]))&&m("alerts.quietHours must be [start, end] with integer values 0-23")}if(n.channels!==void 0){Array.isArray(n.channels)||m("alerts.channels must be an array");for(let e=0;e<n.channels.length;e++){const s=`alerts.channels[${e}]`,i=n.channels[e];(typeof i!="object"||i===null)&&m(`${s} must be an object`),j(s,i,["type","name","config","minKind"]),(typeof i.type!="string"||!At.has(i.type))&&m(`${s}.type must be one of: console, webhook, file`),i.minKind!==void 0&&(typeof i.minKind!="string"||!ce.has(i.minKind))&&m(`${s}.minKind must be one of: informational, actionable`),i.config!==void 0&&((typeof i.config!="object"||i.config===null)&&m(`${s}.config must be an object`),j(`${s}.config`,i.config,["url","headers","filePath"]))}}}return t}async function W(){const t=g(h(),".dahlia","sentinel.json");let o;try{o=await N(t,"utf-8")}catch(e){if(e.code==="ENOENT")return null;throw e}let n;try{n=JSON.parse(o)}catch(e){if(e instanceof SyntaxError)return console.error(`Invalid JSON in sentinel config: ${e.message}`),null;throw e}return de(n)}async function bt(t){const o=await W();if(!o){console.log("No sentinel config found. Create ~/.dahlia/sentinel.json or use --init-config to add agents. See docs for config format.");return}let n=o.agents;if(t&&(n=n.filter(l=>l.agentId===t),n.length===0)){console.log(`Agent "${t}" not found in sentinel.json`);return}const e=new Map,s=new Map,i=l=>l&&l.startsWith("~/")?h()+l.slice(1):l;let a;if(o.alerts){const l={...o.alerts.minSeverity&&{minSeverity:o.alerts.minSeverity},...o.alerts.dedupeWindowMs!==void 0&&{dedupeWindowMs:o.alerts.dedupeWindowMs},...o.alerts.quietHoursEnabled!==void 0&&{quietHoursEnabled:o.alerts.quietHoursEnabled},...o.alerts.quietHours&&{quietHours:o.alerts.quietHours},...o.alerts.minKind&&{minKind:o.alerts.minKind},...o.alerts.channels&&{channels:o.alerts.channels}};a=new me(l)}for(const l of n)try{const f=new $e(l.agentId,void 0,{type:l.adapterType,logPath:i(l.logPath),logFormat:l.logFormat,fieldMapping:l.fieldMapping,mcpLogDir:i(l.mcpLogDir),webhookPort:l.webhookPort,webhookApiKey:l.webhookApiKey,readExisting:l.readExisting});f.setAuditLogDir(g(h(),".dahlia","agents",l.agentId)),f.setFindingCallback(y=>{(y.severity==="HIGH"||y.severity==="CRITICAL")&&console.log(`
|
|
28
|
+
\u26A0 [${y.agentId}] ${y.severity}: ${y.description}`)}),a&&f.setAlertManager(a),await f.start(),e.set(l.agentId,f),s.set(l.agentId,l.adapterType)}catch(f){console.error(`Failed to start monitoring for agent ${l.agentId}:`,f)}console.log(K(e,s));const d=setInterval(()=>{console.clear(),console.log(K(e,s))},5e3),c=async()=>{clearInterval(d);for(const[l,f]of e)try{await f.stop()}catch(y){console.warn(`Error stopping runner ${l}:`,y)}console.log(`
|
|
29
|
+
`+re(e)),process.exit(0)},r=()=>{c().catch(l=>{console.error("Error during shutdown:",l),process.exit(1)})};process.on("SIGINT",r),process.on("SIGTERM",r)}async function It(t,o,n){const s=await new ve(t).generateReport({periodDays:o,format:n});process.stdout.write(s)}async function St(t,o){const n=await Ae({periodDays:t,format:o});process.stdout.write(n)}async function Dt(t=30){const o=await W();if(!o){console.log("No sentinel config found. Create ~/.dahlia/sentinel.json or use --init-config.");return}const n=g(h(),".dahlia","agents"),e=new Map;try{for(const a of o.agents){const d=new b(a.agentId,{logDir:g(n,a.agentId)});await d.open(),e.set(a.agentId,d)}const i=await new he().detect(e,t*24*60*60*1e3);if(i.length===0){console.log("No cross-agent correlations detected.");return}console.log(`Found ${i.length} cross-agent correlation(s):
|
|
30
|
+
`);for(const a of i)console.log(`[${a.severity}] ${a.rule}`),console.log(` Agent A: ${a.agentA.agentId} \u2014 ${a.agentA.action} on ${a.agentA.target}`),console.log(` Agent B: ${a.agentB.agentId} \u2014 ${a.agentB.action} on ${a.agentB.target}`),console.log(` Time delta: ${Math.round(a.timeDeltaMs/1e3)}s`),console.log(` Recommendation: ${a.recommendation}`),console.log("")}finally{for(const s of e.values())await s.close()}}async function Tt(t){const o=g(h(),".dahlia","agents"),n=g(o,t,"cumulative-stats.json");let e="(none)";try{e=JSON.parse(await N(n,"utf-8")).totalEntries}catch{}const i=await new b(t,{logDir:g(o,t)}).recomputeCumulativeStats();console.log(`Recomputed cumulative-stats for ${t}:`),console.log(` totalEntries: ${e} \u2192 ${i.totalEntries}`),console.log(` scope: ${i.countScope}`),console.log(" (Read-only over the signed trail; only cumulative-stats.json rewritten.)")}async function kt(t){const o=g(h(),".dahlia","agents"),n=g(o,t),e=await R(n),s=new b(t,{logDir:n});s.setSigningKey(e.privateKey,e.publicKey);const i=L&&q?[{file:L,reason:q}]:void 0,a=await s.enrollManifest(i?{quarantine:i}:void 0);console.log(`Manifest enrolled for ${t}: ${a.records} file record(s) signed + chained`+(a.quarantined?`, ${a.quarantined} quarantine record(s)`:"")+"."),i&&console.log(` Quarantined: ${L} \u2014 ${q}`),console.log(" (Read-only over audit entries; protects from enrollment forward.)")}async function Et(t){const o=g(h(),".dahlia","agents"),n=new b(t,{logDir:g(o,t)});await n.open();const e=await n.verify();if(e.valid?console.log(`Audit trail for ${t}: VALID (${e.totalEntries} entries verified)`):e.brokenAt!==void 0?console.log(`Audit trail for ${t}: BROKEN at entry ${e.brokenAt} (${e.totalEntries} entries checked)`):console.log(`Audit trail for ${t}: INVALID \u2014 file-level manifest integrity failure (${e.totalEntries} entries chain-verified)`),e.manifest){const i=e.manifest;console.log(` Manifest: ${i.recordCount} file record(s) \u2014 ${i.ok?"OK":`${i.issues.length} issue(s)`}`);for(const a of i.issues)console.log(` [${a.type}] ${a.detail}`);for(const a of i.quarantined)console.log(` [QUARANTINED] ${a.file} \u2014 ${a.reason} (retained on disk, excluded from verdict)`)}const s=await n.query({type:"finding",limit:1e4});if(s.length>0){let i=0,a=0,d=0;for(const r of s){const l=r.decision;l==="deny"?a++:l==="modify"?d++:i++}console.log(` Findings: ${s.length} total \u2014 ${i} allowed, ${a} denied, ${d} modified`);const c=s.slice(0,5);for(const r of c){const l=r,f=l.decision,y=l.modification,w=l.description,M=l._decisionDefaulted===!0;f==="modify"&&y?.type==="append_args"?console.log(` [modified] ${w} \u2014 appended args: [${y.args.join(", ")}]`):console.log(f==="deny"?` [denied] ${w}`:M?` [allowed (legacy)] ${w}`:` [allowed] ${w}`)}}await n.close()}async function Ct(){const t=g(h(),".dahlia","agents"),o=new E({agentsDir:t}),e=await new B(t).listAgents();if(e.length===0){console.log("No monitored agents found."),await o.stop();return}const s=[];for(const i of e){const a=await o.getHealthScore(i);s.push({agentId:i,score:a.score,status:a.status,mode:a.mode,findings:`${a.findings.critical}/${a.findings.high}/${a.findings.medium}/${a.findings.low}`,lastEvent:a.lastEvent?a.lastEvent.split("T")[0]:"-",baseline:a.baselineEstablished})}await o.stop(),console.log(`Agent Health Dashboard
|
|
31
|
+
`),console.log(oe(s))}var ge=`# Sentinel \u2014 Agent Security Policy
|
|
32
32
|
version: "1.0"
|
|
33
33
|
|
|
34
34
|
agent:
|
|
@@ -56,8 +56,6 @@ policy:
|
|
|
56
56
|
# schedule:
|
|
57
57
|
# hours: [9, 18]
|
|
58
58
|
# days: [Monday, Tuesday, Wednesday, Thursday, Friday]
|
|
59
|
-
# limits:
|
|
60
|
-
# maxEventsPerHour: 500
|
|
61
59
|
|
|
62
60
|
# enforcement:
|
|
63
61
|
# restrictAfter: 2
|
|
@@ -69,12 +67,12 @@ policy:
|
|
|
69
67
|
# minSeverity: MEDIUM
|
|
70
68
|
|
|
71
69
|
# repo:
|
|
72
|
-
#
|
|
70
|
+
# root: . # scan this directory for sensitive files
|
|
73
71
|
# mapPath: ~/.dahlia/repo-sensitivity.json
|
|
74
72
|
# overlayPath: ~/.dahlia/repo-sensitivity.review.json
|
|
75
|
-
`;async function
|
|
76
|
-
`),console.log(` Task ID: ${
|
|
77
|
-
`),console.log(` Intent starts: ${
|
|
78
|
-
Last ${d.length} misaligned action(s):`);for(const c of d){const
|
|
79
|
-
Recent drift findings: ${
|
|
80
|
-
Sentinel + Claude Code integration ready.`)}catch(n){console.error(`Init failed: ${n.message}`),process.exit(1)}}if(
|
|
73
|
+
`;async function ue(){const t=g(process.cwd(),".sentinel.yaml");try{await G(t),console.log(`.sentinel.yaml already exists in ${process.cwd()}`);return}catch{}await C(t,ge,"utf-8"),console.log("Created .sentinel.yaml \u2014 edit then run: npm run sentinel -- --from-policy .sentinel.yaml")}async function fe(t){const o=await De(t),n=await E.fromPolicy(t);console.log(`Loaded policy for agent ${o.agent.id}. Monitoring active.`);const e=async()=>{await n.stop(),console.log("Monitoring stopped."),process.exit(0)},s=()=>{e().catch(i=>{console.error("Error during shutdown:",i),process.exit(1)})};process.on("SIGINT",s),process.on("SIGTERM",s)}async function jt(t,o,n){const e=g(h(),".dahlia","agents"),s=new E({agentsDir:e});await s.addAgent(t,t);const i=Z?Z.split(","):void 0,a=ee?ee.split(",").map(c=>c.trim()):void 0,d=s.startTask(t,o,n,{relaxedActions:i,phases:a});if(!d){console.log(`Failed to start task for agent ${t}.`),await s.stop();return}console.log(`Task started for agent ${t}:`),console.log(` Task ID: ${d.taskId}`),console.log(` Description: ${d.description}`),console.log(` Keywords: ${d.keywords.join(", ")}`),d.relaxedActions?.length&&console.log(` Relaxed: ${d.relaxedActions.join(", ")}`),d.phases?.length&&console.log(` Phases: ${d.phases.join(", ")}`),console.log(` TTL: ${(d.ttlMs/6e4).toFixed(0)} minutes`),await s.stop()}async function Mt(t){const o=g(h(),".dahlia","agents"),n=new E({agentsDir:o});await n.addAgent(t,t);const e=n.endTask(t);e?(console.log(`Task ended for agent ${t}:`),console.log(` Task ID: ${e.taskId}`),console.log(` Description: ${e.description}`),console.log(` Status: ${e.status}`)):console.log(`No active task found for agent ${t}.`),await n.stop()}async function Rt(t){const o=g(h(),".dahlia","agents"),n=new E({agentsDir:o});await n.addAgent(t,t);const e=n.getActiveTask(t);if(!e){console.log(`No active task for agent ${t}.`),await n.stop();return}const s=Date.now()-new Date(e.startedAt).getTime(),i=e.ttlMs-s;console.log(`Active task for agent ${t}:
|
|
74
|
+
`),console.log(` Task ID: ${e.taskId}`),console.log(` Description: ${e.description}`),console.log(` Keywords: ${e.keywords.join(", ")}`),console.log(` Status: ${e.status}`),console.log(` Active for: ${(s/6e4).toFixed(1)} minutes`),console.log(` TTL remain: ${i>0?(i/6e4).toFixed(1)+" minutes":"EXPIRED"}`),e.relaxedActions?.length&&console.log(` Relaxed: ${e.relaxedActions.join(", ")}`),e.phases?.length&&console.log(` Phases: ${e.phases.join(", ")}`),e.acceptableActions.length>0&&console.log(` Acceptable: ${e.acceptableActions.map(a=>`${a.action}:${a.targetPattern}`).join(", ")}`),await n.stop()}async function Nt(t){const o=g(h(),".dahlia","agents"),n=new b(t,{logDir:g(o,t)});await n.open();const e=await n.getStats(),s=await n.query({type:"intent_check"}),a=(await n.query({type:"finding"})).filter(c=>c.findingType==="intent_drift");console.log(`Intent Alignment Report \u2014 ${t}
|
|
75
|
+
`),console.log(` Intent starts: ${e.intentStartCount}`),console.log(` Intent ends: ${e.intentEndCount}`),console.log(` Intent checks: ${e.intentCheckCount}`),console.log(` Drift findings: ${e.intentDriftCount}`),e.averageAlignmentScore!==null&&console.log(` Avg alignment: ${e.averageAlignmentScore.toFixed(2)}`);const d=s.filter(c=>c.aligned===!1).slice(0,5);if(d.length>0){console.log(`
|
|
76
|
+
Last ${d.length} misaligned action(s):`);for(const c of d){const r=typeof c.score=="number"?c.score.toFixed(2):"?",l=c.action??"?",f=c.primaryTarget??c.targets?.[0]??"?";console.log(` score: ${r} ${l} \u2192 ${f} (${c.timestamp.split("T")[0]})`)}}if(a.length>0){console.log(`
|
|
77
|
+
Recent drift findings: ${a.length}`);for(const c of a.slice(0,3)){const r=c.severity??"?",l=c.description??"";console.log(` [${r}] ${typeof l=="string"?l.slice(0,80):l}`)}}await n.close()}async function Ft(t,o){try{const n=await be({force:t,port:o});if(n.created.length>0){console.log("Created:");for(const e of n.created)console.log(` ${e}`)}if(n.skipped.length>0){console.log("Skipped (already exists \u2014 use --force to overwrite):");for(const e of n.skipped)console.log(` ${e}`)}if(n.merged.length>0){console.log("Merged:");for(const e of n.merged)console.log(` ${e}`)}if(n.errors.length>0){console.log("Errors:");for(const e of n.errors)console.log(` ${e}`)}n.errors.length===0&&console.log(`
|
|
78
|
+
Sentinel + Claude Code integration ready.`)}catch(n){console.error(`Init failed: ${n.message}`),process.exit(1)}}if(_e){const t=v(u,"--port");await Ft(Ge,t?parseInt(t,10):void 0)}else if(ot&&p&&Y&&X)await jt(p,Y,X);else if(st&&p)await Mt(p);else if(at&&p)await Rt(p);else if(it&&p)await Nt(p);else if(Be)await ue();else if(z)await fe(z);else if(ze&&p)await kt(p);else if(Ye&&p)await Tt(p);else if(Qe&&p)await Et(p);else if(Xe)await Ct();else if(Ze&&p)await rt(p,te??"No reason provided");else if(et&&p)await lt(p,te??"No reason provided");else if(tt)await gt(p);else if(nt)await ut();else if(We)await yt();else if(Ke)await bt(p);else if(Oe&&p&&Q)await ht(p,Q,qe);else if(He&&p)await mt(p);else if(Je){const t=T?parseInt(T,10):30;await St(t,ne==="json"?"json":"markdown")}else if(Ue&&p){const t=T?parseInt(T,10):30;await It(p,t,ne==="json"?"json":"markdown")}else if(Ve){const t=T?parseInt(T,10):30;await Dt(t)}else p?await pt(p):await ft();
|
package/dist/gateway/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { v as Sentinel, e as AgentRole, S as SecurityFinding } from '../Sentinel-
|
|
1
|
+
import { v as Sentinel, e as AgentRole, S as SecurityFinding } from '../Sentinel-DT0IyGQi.js';
|
|
2
2
|
import 'node:crypto';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -75,6 +75,13 @@ interface SentinelGatewayOptions {
|
|
|
75
75
|
* Operator-only, same channel as unknownTools.
|
|
76
76
|
*/
|
|
77
77
|
allowUnknownTools?: string[];
|
|
78
|
+
/**
|
|
79
|
+
* Build identity reported via /health (daemon-staleness fix). Content hash of
|
|
80
|
+
* the daemon entry file this process was launched from; session-start compares
|
|
81
|
+
* it to the hash of the current on-disk entry and relaunches on mismatch.
|
|
82
|
+
* Absent → /health reports "unknown" → next session-start relaunches (safe side).
|
|
83
|
+
*/
|
|
84
|
+
buildId?: string;
|
|
78
85
|
}
|
|
79
86
|
declare class SentinelGateway {
|
|
80
87
|
private readonly configuredPort;
|
|
@@ -90,6 +97,9 @@ declare class SentinelGateway {
|
|
|
90
97
|
private readonly releaseToken;
|
|
91
98
|
/** Item D (F-8): disposition for unknown (non-MCP, unrecognized) tool names. */
|
|
92
99
|
private readonly unknownTools;
|
|
100
|
+
/** Daemon-staleness build identity (content hash of the launched-from entry),
|
|
101
|
+
* reported via /health. "unknown" when not supplied by the launcher. */
|
|
102
|
+
private readonly buildId;
|
|
93
103
|
private server;
|
|
94
104
|
private running;
|
|
95
105
|
private signalHandlersInstalled;
|
|
@@ -164,6 +174,15 @@ declare class SentinelGateway {
|
|
|
164
174
|
* when the isolation gate is ON.
|
|
165
175
|
*/
|
|
166
176
|
private resolveBPathRouting;
|
|
177
|
+
/**
|
|
178
|
+
* Forbidden-pattern list for ONE request: the gateway's construction-time
|
|
179
|
+
* list unioned with the request's merged-role patterns (operator-ceiling ∩
|
|
180
|
+
* workspace yaml). Without this, a workspace's custom forbid targets were
|
|
181
|
+
* enforced by the role validator inside wrap() but never reached the
|
|
182
|
+
* gateway's own bash-L1 / Grep layers, which checked only the static list.
|
|
183
|
+
* Normalization is idempotent (the chokepoint globstar-prepend).
|
|
184
|
+
*/
|
|
185
|
+
private patternsForRequest;
|
|
167
186
|
private handlePreToolUse;
|
|
168
187
|
private handlePostToolUse;
|
|
169
188
|
private handleSessionEnd;
|
package/dist/gateway/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SentinelGateway
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-HRI2Y326.js";
|
|
4
4
|
import "../chunk-B5QKJHSV.js";
|
|
5
|
-
import "../chunk-
|
|
6
|
-
import "../chunk-
|
|
7
|
-
import "../chunk-
|
|
5
|
+
import "../chunk-B6S2PBS4.js";
|
|
6
|
+
import "../chunk-FIEIGBYL.js";
|
|
7
|
+
import "../chunk-KWZ7JKKO.js";
|
|
8
8
|
export {
|
|
9
9
|
SentinelGateway
|
|
10
10
|
};
|
package/dist/gatewayDaemon.js
CHANGED
|
@@ -1,26 +1,48 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runGatewayDaemon
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-HRI2Y326.js";
|
|
5
|
+
import {
|
|
6
|
+
computeBuildId,
|
|
7
|
+
runSessionStart
|
|
8
|
+
} from "./chunk-LTBVWF5H.js";
|
|
9
|
+
import "./chunk-LATQNIRW.js";
|
|
5
10
|
import "./chunk-B5QKJHSV.js";
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-B6S2PBS4.js";
|
|
12
|
+
import "./chunk-FIEIGBYL.js";
|
|
13
|
+
import "./chunk-KWZ7JKKO.js";
|
|
9
14
|
|
|
10
15
|
// src/gatewayDaemon.ts
|
|
16
|
+
import { fileURLToPath } from "url";
|
|
11
17
|
var args = process.argv.slice(2);
|
|
12
|
-
var
|
|
13
|
-
|
|
14
|
-
for (let i = 0; i < args.length; i++) {
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
var selfPath = fileURLToPath(import.meta.url);
|
|
19
|
+
function getFlag(name) {
|
|
20
|
+
for (let i = 0; i < args.length; i++) {
|
|
21
|
+
if (args[i] === name && args[i + 1]) return args[i + 1];
|
|
22
|
+
}
|
|
23
|
+
return void 0;
|
|
17
24
|
}
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
if (args.includes("--session-start")) {
|
|
26
|
+
const portArg = getFlag("--port");
|
|
27
|
+
runSessionStart({
|
|
28
|
+
cwd: getFlag("--cwd"),
|
|
29
|
+
port: portArg ? parseInt(portArg, 10) : void 0,
|
|
30
|
+
gatewayEntry: selfPath
|
|
31
|
+
}).catch((err) => {
|
|
32
|
+
console.error("[SENTINEL GATEWAY] session-start error:", err);
|
|
33
|
+
process.exit(0);
|
|
34
|
+
});
|
|
35
|
+
} else {
|
|
36
|
+
const policyPath = getFlag("--policy");
|
|
37
|
+
const portArg = getFlag("--port");
|
|
38
|
+
const port = portArg ? parseInt(portArg, 10) : void 0;
|
|
39
|
+
if (!policyPath) {
|
|
40
|
+
console.error("[SENTINEL GATEWAY] --policy <path> is required");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
runGatewayDaemon({ policyPath, port, buildId: computeBuildId(selfPath) }).catch((err) => {
|
|
44
|
+
console.error("[SENTINEL GATEWAY] Fatal:", err);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
});
|
|
21
47
|
}
|
|
22
|
-
runGatewayDaemon({ policyPath, port }).catch((err) => {
|
|
23
|
-
console.error("[SENTINEL GATEWAY] Fatal:", err);
|
|
24
|
-
process.exit(1);
|
|
25
|
-
});
|
|
26
48
|
//# sourceMappingURL=gatewayDaemon.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as AgentActivityEvent, S as SecurityFinding } from './Sentinel-
|
|
2
|
-
export { a as AcceptableAction, b as AdapterConfig, c as AgentBaseline, d as AgentMode, e as AgentRole, f as AlertChannel, g as AlertConfig, h as AllowResponse, i as AuditEntry, j as AuditQueryOptions, B as BlockResponse, C as CorrelationFinding, E as ExceptionApprovalContext, k as ExceptionApprovalFn, G as GuideResponse, H as HookCheckpoint, l as HookContext, m as HookHandler, n as HookRegistration, o as HookResponse, I as IntentAlignmentConfig, p as IntentAlignmentResult, M as ModifiableEventFields, q as MonitorOptions, O as OverlayDecisionType, R as RepoSensitivityMap, r as ReportOptions, s as RoleException, t as SecuritySeverity, u as SensitivityOverlay, v as Sentinel, w as SentinelConfig, T as TaskIntent } from './Sentinel-
|
|
1
|
+
import { A as AgentActivityEvent, S as SecurityFinding } from './Sentinel-DT0IyGQi.js';
|
|
2
|
+
export { a as AcceptableAction, b as AdapterConfig, c as AgentBaseline, d as AgentMode, e as AgentRole, f as AlertChannel, g as AlertConfig, h as AllowResponse, i as AuditEntry, j as AuditQueryOptions, B as BlockResponse, C as CorrelationFinding, E as ExceptionApprovalContext, k as ExceptionApprovalFn, G as GuideResponse, H as HookCheckpoint, l as HookContext, m as HookHandler, n as HookRegistration, o as HookResponse, I as IntentAlignmentConfig, p as IntentAlignmentResult, M as ModifiableEventFields, q as MonitorOptions, O as OverlayDecisionType, R as RepoSensitivityMap, r as ReportOptions, s as RoleException, t as SecuritySeverity, u as SensitivityOverlay, v as Sentinel, w as SentinelConfig, T as TaskIntent } from './Sentinel-DT0IyGQi.js';
|
|
3
3
|
import 'node:crypto';
|
|
4
4
|
|
|
5
5
|
interface SentinelPolicy {
|
|
@@ -35,17 +35,11 @@ interface SentinelPolicy {
|
|
|
35
35
|
hours: [number, number];
|
|
36
36
|
days?: string[];
|
|
37
37
|
};
|
|
38
|
-
limits?: {
|
|
39
|
-
maxEventsPerHour?: number;
|
|
40
|
-
maxSessionDuration?: number;
|
|
41
|
-
};
|
|
42
38
|
};
|
|
43
39
|
enforcement?: {
|
|
44
40
|
restrictAfter?: number;
|
|
45
41
|
quarantineAfter?: number;
|
|
46
42
|
approvalRequired?: boolean;
|
|
47
|
-
/** Minimum finding kind that triggers enforcement actions. Defaults to "actionable". */
|
|
48
|
-
minKind?: "informational" | "actionable";
|
|
49
43
|
/**
|
|
50
44
|
* List of finding types to promote from "informational" to "actionable".
|
|
51
45
|
* Cannot include LOCKED_ACTIONABLE_TYPES (they are already actionable).
|
|
@@ -121,30 +115,48 @@ declare function runInitClaudeCode(options: {
|
|
|
121
115
|
home?: string;
|
|
122
116
|
}): Promise<InitReport>;
|
|
123
117
|
|
|
124
|
-
/**
|
|
125
|
-
* Walks up from a given directory looking for .sentinel.yaml.
|
|
126
|
-
* Stops at $HOME (inclusive) or filesystem root.
|
|
127
|
-
*/
|
|
128
118
|
declare function discoverPolicy(startDir: string, home: string): string | null;
|
|
129
119
|
|
|
130
120
|
/**
|
|
131
|
-
* `session-start` entry point — called by the hook script on SessionStart
|
|
121
|
+
* `session-start` entry point — called by the hook script on SessionStart, and
|
|
122
|
+
* the single source of truth for the gateway lifecycle decision (anti-drift: the
|
|
123
|
+
* cc hook delegates here via the dual-mode daemon entry rather than carrying its
|
|
124
|
+
* own copy of this logic).
|
|
132
125
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
126
|
+
* Flow:
|
|
127
|
+
* 1. Discover .sentinel.yaml (walk up from cwd to $HOME); none → no gateway.
|
|
128
|
+
* 2. Resolve the daemon entry AT LAUNCH (not a baked path) and hash it — the
|
|
129
|
+
* current build identity.
|
|
130
|
+
* 3. Liveness: is a daemon already running (PID file)?
|
|
131
|
+
* - none → cold spawn (today's path).
|
|
132
|
+
* - running → GET /health, compare its build id to the current entry hash:
|
|
133
|
+
* match → reuse (unchanged fast path).
|
|
134
|
+
* mismatch → RELAUNCH: SIGTERM (→ SIGKILL fallback) the stale daemon,
|
|
135
|
+
* spawn the current build, wait ready. This is pure process
|
|
136
|
+
* replacement: it writes NO mode_change / release / trail
|
|
137
|
+
* state — the new daemon rebuilds mode (mode.json) and count
|
|
138
|
+
* (the append-only trail) lazily via _initWorkspaceAgent, so
|
|
139
|
+
* a restricted/quarantined agent stays restricted/quarantined
|
|
140
|
+
* with the same escalation count across the relaunch.
|
|
141
|
+
* Concurrency: a relaunch holds an atomic lock so two simultaneous session-
|
|
142
|
+
* starts converge on ONE daemon (the loser waits for ready and reuses) instead
|
|
143
|
+
* of racing two daemons onto the port.
|
|
138
144
|
*/
|
|
139
145
|
interface SessionStartResult {
|
|
140
|
-
action: "reused" | "spawned" | "no-policy";
|
|
146
|
+
action: "reused" | "spawned" | "relaunched" | "no-policy";
|
|
141
147
|
pid?: number;
|
|
142
148
|
policyPath?: string;
|
|
149
|
+
/** Why a relaunch happened (build mismatch) or null — for telemetry/tests. */
|
|
150
|
+
relaunchReason?: string | null;
|
|
143
151
|
}
|
|
144
152
|
declare function runSessionStart(options?: {
|
|
145
153
|
cwd?: string;
|
|
146
154
|
home?: string;
|
|
147
155
|
port?: number;
|
|
156
|
+
/** Daemon entry to launch. Defaults to resolveGatewayEntryPoint() (resolved at
|
|
157
|
+
* launch, NOT a baked path — closes the pnpm-relocation / dangling cases). The
|
|
158
|
+
* dual-mode entry passes its own path so the launcher spawns the current build. */
|
|
159
|
+
gatewayEntry?: string;
|
|
148
160
|
}): Promise<SessionStartResult>;
|
|
149
161
|
|
|
150
162
|
export { AgentActivityEvent, type CliApprovalOptions, type InitReport, SecurityFinding, type SentinelPolicy, type SessionStartResult, createCliApproval, discoverPolicy, loadPolicy, loadPolicyFromString, runInitClaudeCode, runSessionStart };
|
package/dist/index.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import
|
|
2
|
-
runInitClaudeCode,
|
|
3
|
-
runSessionStart
|
|
4
|
-
} from "./chunk-FWIISAZZ.js";
|
|
1
|
+
import "./chunk-TKAKHSZ3.js";
|
|
5
2
|
import {
|
|
6
3
|
Sentinel,
|
|
7
4
|
createCliApproval
|
|
8
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-I2FVDDSG.js";
|
|
6
|
+
import {
|
|
7
|
+
runInitClaudeCode,
|
|
8
|
+
runSessionStart
|
|
9
|
+
} from "./chunk-LTBVWF5H.js";
|
|
9
10
|
import "./chunk-LATQNIRW.js";
|
|
10
11
|
import {
|
|
11
12
|
discoverPolicy
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
13
|
+
} from "./chunk-B6S2PBS4.js";
|
|
14
|
+
import "./chunk-FIEIGBYL.js";
|
|
14
15
|
import {
|
|
15
16
|
loadPolicy,
|
|
16
17
|
loadPolicyFromString
|
|
17
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-KWZ7JKKO.js";
|
|
18
19
|
import "./chunk-NUXSUSYY.js";
|
|
19
20
|
export {
|
|
20
21
|
Sentinel,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
LogAdapter
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-2IPSTUNH.js";
|
|
4
4
|
|
|
5
5
|
// src/adapters/mcpAdapter.ts
|
|
6
6
|
var ACTION_MAP = {
|
|
@@ -175,4 +175,4 @@ export {
|
|
|
175
175
|
McpAdapter,
|
|
176
176
|
mapToolToAction
|
|
177
177
|
};
|
|
178
|
-
//# sourceMappingURL=mcpAdapter-
|
|
178
|
+
//# sourceMappingURL=mcpAdapter-WYAXUE7T.js.map
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
LOCKED_ACTIONABLE_TYPES,
|
|
3
|
+
checkSaneNumber,
|
|
3
4
|
loadPolicy,
|
|
4
5
|
loadPolicyFromString,
|
|
5
6
|
policyToConfig,
|
|
6
|
-
policyToRole
|
|
7
|
-
|
|
7
|
+
policyToRole,
|
|
8
|
+
suggestKey
|
|
9
|
+
} from "./chunk-KWZ7JKKO.js";
|
|
8
10
|
export {
|
|
9
11
|
LOCKED_ACTIONABLE_TYPES,
|
|
12
|
+
checkSaneNumber,
|
|
10
13
|
loadPolicy,
|
|
11
14
|
loadPolicyFromString,
|
|
12
15
|
policyToConfig,
|
|
13
|
-
policyToRole
|
|
16
|
+
policyToRole,
|
|
17
|
+
suggestKey
|
|
14
18
|
};
|
|
15
|
-
//# sourceMappingURL=policyLoader-
|
|
19
|
+
//# sourceMappingURL=policyLoader-XX6BQXNB.js.map
|
package/package.json
CHANGED