@node9/policy-engine 1.61.0 → 1.62.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/dist/index.d.mts +46 -1
- package/dist/index.d.ts +46 -1
- package/dist/index.js +107 -2
- package/dist/index.mjs +106 -2
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -712,6 +712,13 @@ declare function computeSecurityScore(opts: {
|
|
|
712
712
|
*/
|
|
713
713
|
declare function classifyScanSignal(key: keyof ScanSignals): Severity;
|
|
714
714
|
/**
|
|
715
|
+
* @deprecated Score-1 redesign (2026-07): the Report score is now LIVE +
|
|
716
|
+
* POSTURE via {@link computeAgentDeviceScore}; session-scan findings moved to
|
|
717
|
+
* the onboarding forecast and no longer feed a "current risk" number (they
|
|
718
|
+
* are lifetime data — blending them inflated the score with pre-install
|
|
719
|
+
* history). Kept exported for npm back-compat (removal = breaking major);
|
|
720
|
+
* do not add new callers.
|
|
721
|
+
*
|
|
715
722
|
* Compute a 0-100 risk-posture score that blends audit-log severity counts
|
|
716
723
|
* with forward-only scan signal counts.
|
|
717
724
|
*
|
|
@@ -752,6 +759,44 @@ declare function computeBlendedSecurityScore(opts: {
|
|
|
752
759
|
score: number;
|
|
753
760
|
tier: ScoreTier;
|
|
754
761
|
};
|
|
762
|
+
/**
|
|
763
|
+
* The Report's "how at-risk am I RIGHT NOW?" score: a 0.5/0.5 blend of
|
|
764
|
+
*
|
|
765
|
+
* runtime = computeSecurityScore(live audit counts) — what node9 CAUGHT
|
|
766
|
+
* in the window (rate-based, unchanged math);
|
|
767
|
+
* posture = the fleet-average machine posture score — how exposed the
|
|
768
|
+
* machines are NOW (already includes disk/secret exposure via
|
|
769
|
+
* the posture scorecard's secrets check).
|
|
770
|
+
*
|
|
771
|
+
* Session-scan (lifetime) findings deliberately do NOT feed this score —
|
|
772
|
+
* they are pre-install history, not current risk (they surface in the
|
|
773
|
+
* onboarding forecast instead).
|
|
774
|
+
*
|
|
775
|
+
* Missing-data semantics (the day-1 false-100 fix):
|
|
776
|
+
* - no activity AND no posture → { score: null, tier: null } — the caller
|
|
777
|
+
* renders "not enough data", NEVER an invented 100/healthy;
|
|
778
|
+
* - no activity, posture known → posture IS the risk (a fresh machine that
|
|
779
|
+
* can read ~/.aws scores badly regardless of activity);
|
|
780
|
+
* - activity, no posture yet → the pure runtime score;
|
|
781
|
+
* - both → round(0.5·runtime + 0.5·posture).
|
|
782
|
+
*
|
|
783
|
+
* An EARNED 100 (real activity, nothing caught) blends normally — only the
|
|
784
|
+
* no-data 100 is suppressed. Pure; posture defensively clamped to [0,100].
|
|
785
|
+
*/
|
|
786
|
+
declare function computeAgentDeviceScore(opts: {
|
|
787
|
+
/** LIVE audit-window severity counts — scan never feeds this score. */
|
|
788
|
+
audit: {
|
|
789
|
+
critical: number;
|
|
790
|
+
high: number;
|
|
791
|
+
medium: number;
|
|
792
|
+
total: number;
|
|
793
|
+
};
|
|
794
|
+
/** Fleet-average machine posture score, or null when no machine reports. */
|
|
795
|
+
posture: number | null;
|
|
796
|
+
}): {
|
|
797
|
+
score: number | null;
|
|
798
|
+
tier: ScoreTier | null;
|
|
799
|
+
};
|
|
755
800
|
|
|
756
801
|
/**
|
|
757
802
|
* One sensitive path that the blast walker found readable on disk.
|
|
@@ -1055,4 +1100,4 @@ declare function previewArgs(input: Record<string, unknown>, max: number): strin
|
|
|
1055
1100
|
/** Engine version stamped on audit entries for future drift detection. */
|
|
1056
1101
|
declare const ENGINE_VERSION = "1.4.0";
|
|
1057
1102
|
|
|
1058
|
-
export { AST_FS_REGEX_RULES, type AuditEntryForClassify, BASH_TOOL_NAMES, BUILTIN_SHIELDS, type BlastEnvFinding, type BlastFinding, type BlastResult, type BlastSummary, CANONICAL_EXTRACTOR_HASH, CANONICAL_EXTRACTOR_VERSION, COST_PER_LOOP_ITER_USD, type CanonicalAgent, type CanonicalFinding, type CanonicalFindingType, type CanonicalSourceType, DEFAULT_EGRESS_ALLOWLIST, DESTRUCTIVE_OP_RE, DLP_PATTERNS, type DlpMatch, ENGINE_VERSION, type EgressPolicy, type EgressVerdict, type ExtractContext, FILE_TOOLS, FLAGS_WITH_VALUES, type FsOpVerdict, type InjectionConfidence, type InjectionContext, type InjectionMatch, LONG_OUTPUT_THRESHOLD_BYTES, LOOP_MAX_RECORDS, LOOP_THRESHOLD_FOR_WASTE, type LoopWindowEvaluation, PRIVILEGE_ESCALATION_RE, type PiiPattern, type PipeChainAnalysis, type PolicyConfig, type PolicyContext, type PolicyHostHooks, type PolicyVerdict, type ProvenanceLookup, type ProvenanceTrust, REALTIME_PII_PATTERNS, type RiskMetadata, SCAN_SIGNAL_WEIGHTS, SENSITIVE_PATH_RE, SENSITIVE_PATH_REGEXES, type ScanFinding, type ScanSignals, type ScanSummary, type ScoreTier, type SessionExtractContext, type SessionToolCall, type Severity, type ShellCommandAnalysis, type ShellDestination, type ShieldDefinition, type ShieldOverrides, type ShieldVerdict, type SmartCondition, type SmartRule, type ToolCallEntry, type ToolCallRecord, analyzeFsOperation, analyzePipeChain, analyzeShellCommand, checkDangerousSql, classifyAuditEntry, classifyRuleSeverity, classifyScanSignal, computeArgsHash, computeBlendedSecurityScore, computeScanScore, computeSecurityScore, dedupeCanonicalFindings, detectArgsPii, detectDangerousEval, detectDangerousShellExec, detectPii, evaluateEgress, evaluateLoopWindow, evaluatePolicy, evaluateSmartConditions, extractAllSshHosts, extractCanonicalFindings, extractNetworkTargets, extractPositionalArgs, extractSessionLevelFindings, extractShellDestinations, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShieldVerdict, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, parseAllSshHostsFromCommand, parseDestHost, previewArgs, redactText, resolvePinned, scanArgs, scanInjection, scanText, sensitivePathMatch, summarizeBlast, summarizeScan, toScanFinding, truncateBlastPath, validateOverrides, validateRegex, validateShieldDefinition };
|
|
1103
|
+
export { AST_FS_REGEX_RULES, type AuditEntryForClassify, BASH_TOOL_NAMES, BUILTIN_SHIELDS, type BlastEnvFinding, type BlastFinding, type BlastResult, type BlastSummary, CANONICAL_EXTRACTOR_HASH, CANONICAL_EXTRACTOR_VERSION, COST_PER_LOOP_ITER_USD, type CanonicalAgent, type CanonicalFinding, type CanonicalFindingType, type CanonicalSourceType, DEFAULT_EGRESS_ALLOWLIST, DESTRUCTIVE_OP_RE, DLP_PATTERNS, type DlpMatch, ENGINE_VERSION, type EgressPolicy, type EgressVerdict, type ExtractContext, FILE_TOOLS, FLAGS_WITH_VALUES, type FsOpVerdict, type InjectionConfidence, type InjectionContext, type InjectionMatch, LONG_OUTPUT_THRESHOLD_BYTES, LOOP_MAX_RECORDS, LOOP_THRESHOLD_FOR_WASTE, type LoopWindowEvaluation, PRIVILEGE_ESCALATION_RE, type PiiPattern, type PipeChainAnalysis, type PolicyConfig, type PolicyContext, type PolicyHostHooks, type PolicyVerdict, type ProvenanceLookup, type ProvenanceTrust, REALTIME_PII_PATTERNS, type RiskMetadata, SCAN_SIGNAL_WEIGHTS, SENSITIVE_PATH_RE, SENSITIVE_PATH_REGEXES, type ScanFinding, type ScanSignals, type ScanSummary, type ScoreTier, type SessionExtractContext, type SessionToolCall, type Severity, type ShellCommandAnalysis, type ShellDestination, type ShieldDefinition, type ShieldOverrides, type ShieldVerdict, type SmartCondition, type SmartRule, type ToolCallEntry, type ToolCallRecord, analyzeFsOperation, analyzePipeChain, analyzeShellCommand, checkDangerousSql, classifyAuditEntry, classifyRuleSeverity, classifyScanSignal, computeAgentDeviceScore, computeArgsHash, computeBlendedSecurityScore, computeScanScore, computeSecurityScore, dedupeCanonicalFindings, detectArgsPii, detectDangerousEval, detectDangerousShellExec, detectPii, evaluateEgress, evaluateLoopWindow, evaluatePolicy, evaluateSmartConditions, extractAllSshHosts, extractCanonicalFindings, extractNetworkTargets, extractPositionalArgs, extractSessionLevelFindings, extractShellDestinations, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShieldVerdict, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, parseAllSshHostsFromCommand, parseDestHost, previewArgs, redactText, resolvePinned, scanArgs, scanInjection, scanText, sensitivePathMatch, summarizeBlast, summarizeScan, toScanFinding, truncateBlastPath, validateOverrides, validateRegex, validateShieldDefinition };
|
package/dist/index.d.ts
CHANGED
|
@@ -712,6 +712,13 @@ declare function computeSecurityScore(opts: {
|
|
|
712
712
|
*/
|
|
713
713
|
declare function classifyScanSignal(key: keyof ScanSignals): Severity;
|
|
714
714
|
/**
|
|
715
|
+
* @deprecated Score-1 redesign (2026-07): the Report score is now LIVE +
|
|
716
|
+
* POSTURE via {@link computeAgentDeviceScore}; session-scan findings moved to
|
|
717
|
+
* the onboarding forecast and no longer feed a "current risk" number (they
|
|
718
|
+
* are lifetime data — blending them inflated the score with pre-install
|
|
719
|
+
* history). Kept exported for npm back-compat (removal = breaking major);
|
|
720
|
+
* do not add new callers.
|
|
721
|
+
*
|
|
715
722
|
* Compute a 0-100 risk-posture score that blends audit-log severity counts
|
|
716
723
|
* with forward-only scan signal counts.
|
|
717
724
|
*
|
|
@@ -752,6 +759,44 @@ declare function computeBlendedSecurityScore(opts: {
|
|
|
752
759
|
score: number;
|
|
753
760
|
tier: ScoreTier;
|
|
754
761
|
};
|
|
762
|
+
/**
|
|
763
|
+
* The Report's "how at-risk am I RIGHT NOW?" score: a 0.5/0.5 blend of
|
|
764
|
+
*
|
|
765
|
+
* runtime = computeSecurityScore(live audit counts) — what node9 CAUGHT
|
|
766
|
+
* in the window (rate-based, unchanged math);
|
|
767
|
+
* posture = the fleet-average machine posture score — how exposed the
|
|
768
|
+
* machines are NOW (already includes disk/secret exposure via
|
|
769
|
+
* the posture scorecard's secrets check).
|
|
770
|
+
*
|
|
771
|
+
* Session-scan (lifetime) findings deliberately do NOT feed this score —
|
|
772
|
+
* they are pre-install history, not current risk (they surface in the
|
|
773
|
+
* onboarding forecast instead).
|
|
774
|
+
*
|
|
775
|
+
* Missing-data semantics (the day-1 false-100 fix):
|
|
776
|
+
* - no activity AND no posture → { score: null, tier: null } — the caller
|
|
777
|
+
* renders "not enough data", NEVER an invented 100/healthy;
|
|
778
|
+
* - no activity, posture known → posture IS the risk (a fresh machine that
|
|
779
|
+
* can read ~/.aws scores badly regardless of activity);
|
|
780
|
+
* - activity, no posture yet → the pure runtime score;
|
|
781
|
+
* - both → round(0.5·runtime + 0.5·posture).
|
|
782
|
+
*
|
|
783
|
+
* An EARNED 100 (real activity, nothing caught) blends normally — only the
|
|
784
|
+
* no-data 100 is suppressed. Pure; posture defensively clamped to [0,100].
|
|
785
|
+
*/
|
|
786
|
+
declare function computeAgentDeviceScore(opts: {
|
|
787
|
+
/** LIVE audit-window severity counts — scan never feeds this score. */
|
|
788
|
+
audit: {
|
|
789
|
+
critical: number;
|
|
790
|
+
high: number;
|
|
791
|
+
medium: number;
|
|
792
|
+
total: number;
|
|
793
|
+
};
|
|
794
|
+
/** Fleet-average machine posture score, or null when no machine reports. */
|
|
795
|
+
posture: number | null;
|
|
796
|
+
}): {
|
|
797
|
+
score: number | null;
|
|
798
|
+
tier: ScoreTier | null;
|
|
799
|
+
};
|
|
755
800
|
|
|
756
801
|
/**
|
|
757
802
|
* One sensitive path that the blast walker found readable on disk.
|
|
@@ -1055,4 +1100,4 @@ declare function previewArgs(input: Record<string, unknown>, max: number): strin
|
|
|
1055
1100
|
/** Engine version stamped on audit entries for future drift detection. */
|
|
1056
1101
|
declare const ENGINE_VERSION = "1.4.0";
|
|
1057
1102
|
|
|
1058
|
-
export { AST_FS_REGEX_RULES, type AuditEntryForClassify, BASH_TOOL_NAMES, BUILTIN_SHIELDS, type BlastEnvFinding, type BlastFinding, type BlastResult, type BlastSummary, CANONICAL_EXTRACTOR_HASH, CANONICAL_EXTRACTOR_VERSION, COST_PER_LOOP_ITER_USD, type CanonicalAgent, type CanonicalFinding, type CanonicalFindingType, type CanonicalSourceType, DEFAULT_EGRESS_ALLOWLIST, DESTRUCTIVE_OP_RE, DLP_PATTERNS, type DlpMatch, ENGINE_VERSION, type EgressPolicy, type EgressVerdict, type ExtractContext, FILE_TOOLS, FLAGS_WITH_VALUES, type FsOpVerdict, type InjectionConfidence, type InjectionContext, type InjectionMatch, LONG_OUTPUT_THRESHOLD_BYTES, LOOP_MAX_RECORDS, LOOP_THRESHOLD_FOR_WASTE, type LoopWindowEvaluation, PRIVILEGE_ESCALATION_RE, type PiiPattern, type PipeChainAnalysis, type PolicyConfig, type PolicyContext, type PolicyHostHooks, type PolicyVerdict, type ProvenanceLookup, type ProvenanceTrust, REALTIME_PII_PATTERNS, type RiskMetadata, SCAN_SIGNAL_WEIGHTS, SENSITIVE_PATH_RE, SENSITIVE_PATH_REGEXES, type ScanFinding, type ScanSignals, type ScanSummary, type ScoreTier, type SessionExtractContext, type SessionToolCall, type Severity, type ShellCommandAnalysis, type ShellDestination, type ShieldDefinition, type ShieldOverrides, type ShieldVerdict, type SmartCondition, type SmartRule, type ToolCallEntry, type ToolCallRecord, analyzeFsOperation, analyzePipeChain, analyzeShellCommand, checkDangerousSql, classifyAuditEntry, classifyRuleSeverity, classifyScanSignal, computeArgsHash, computeBlendedSecurityScore, computeScanScore, computeSecurityScore, dedupeCanonicalFindings, detectArgsPii, detectDangerousEval, detectDangerousShellExec, detectPii, evaluateEgress, evaluateLoopWindow, evaluatePolicy, evaluateSmartConditions, extractAllSshHosts, extractCanonicalFindings, extractNetworkTargets, extractPositionalArgs, extractSessionLevelFindings, extractShellDestinations, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShieldVerdict, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, parseAllSshHostsFromCommand, parseDestHost, previewArgs, redactText, resolvePinned, scanArgs, scanInjection, scanText, sensitivePathMatch, summarizeBlast, summarizeScan, toScanFinding, truncateBlastPath, validateOverrides, validateRegex, validateShieldDefinition };
|
|
1103
|
+
export { AST_FS_REGEX_RULES, type AuditEntryForClassify, BASH_TOOL_NAMES, BUILTIN_SHIELDS, type BlastEnvFinding, type BlastFinding, type BlastResult, type BlastSummary, CANONICAL_EXTRACTOR_HASH, CANONICAL_EXTRACTOR_VERSION, COST_PER_LOOP_ITER_USD, type CanonicalAgent, type CanonicalFinding, type CanonicalFindingType, type CanonicalSourceType, DEFAULT_EGRESS_ALLOWLIST, DESTRUCTIVE_OP_RE, DLP_PATTERNS, type DlpMatch, ENGINE_VERSION, type EgressPolicy, type EgressVerdict, type ExtractContext, FILE_TOOLS, FLAGS_WITH_VALUES, type FsOpVerdict, type InjectionConfidence, type InjectionContext, type InjectionMatch, LONG_OUTPUT_THRESHOLD_BYTES, LOOP_MAX_RECORDS, LOOP_THRESHOLD_FOR_WASTE, type LoopWindowEvaluation, PRIVILEGE_ESCALATION_RE, type PiiPattern, type PipeChainAnalysis, type PolicyConfig, type PolicyContext, type PolicyHostHooks, type PolicyVerdict, type ProvenanceLookup, type ProvenanceTrust, REALTIME_PII_PATTERNS, type RiskMetadata, SCAN_SIGNAL_WEIGHTS, SENSITIVE_PATH_RE, SENSITIVE_PATH_REGEXES, type ScanFinding, type ScanSignals, type ScanSummary, type ScoreTier, type SessionExtractContext, type SessionToolCall, type Severity, type ShellCommandAnalysis, type ShellDestination, type ShieldDefinition, type ShieldOverrides, type ShieldVerdict, type SmartCondition, type SmartRule, type ToolCallEntry, type ToolCallRecord, analyzeFsOperation, analyzePipeChain, analyzeShellCommand, checkDangerousSql, classifyAuditEntry, classifyRuleSeverity, classifyScanSignal, computeAgentDeviceScore, computeArgsHash, computeBlendedSecurityScore, computeScanScore, computeSecurityScore, dedupeCanonicalFindings, detectArgsPii, detectDangerousEval, detectDangerousShellExec, detectPii, evaluateEgress, evaluateLoopWindow, evaluatePolicy, evaluateSmartConditions, extractAllSshHosts, extractCanonicalFindings, extractNetworkTargets, extractPositionalArgs, extractSessionLevelFindings, extractShellDestinations, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShieldVerdict, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, parseAllSshHostsFromCommand, parseDestHost, previewArgs, redactText, resolvePinned, scanArgs, scanInjection, scanText, sensitivePathMatch, summarizeBlast, summarizeScan, toScanFinding, truncateBlastPath, validateOverrides, validateRegex, validateShieldDefinition };
|
package/dist/index.js
CHANGED
|
@@ -57,6 +57,7 @@ __export(src_exports, {
|
|
|
57
57
|
classifyAuditEntry: () => classifyAuditEntry,
|
|
58
58
|
classifyRuleSeverity: () => classifyRuleSeverity,
|
|
59
59
|
classifyScanSignal: () => classifyScanSignal,
|
|
60
|
+
computeAgentDeviceScore: () => computeAgentDeviceScore,
|
|
60
61
|
computeArgsHash: () => computeArgsHash,
|
|
61
62
|
computeBlendedSecurityScore: () => computeBlendedSecurityScore,
|
|
62
63
|
computeScanScore: () => computeScanScore,
|
|
@@ -1140,7 +1141,7 @@ function analyzeSqlDestructive(command) {
|
|
|
1140
1141
|
description: "The AI wants to drop or truncate a database table via the shell. This permanently deletes the table structure or all its data."
|
|
1141
1142
|
};
|
|
1142
1143
|
}
|
|
1143
|
-
var CHMOD_OPEN_PERM_TOKENS = /* @__PURE__ */ new Set(["777", "0777", "a+rwx"
|
|
1144
|
+
var CHMOD_OPEN_PERM_TOKENS = /* @__PURE__ */ new Set(["777", "0777", "a+rwx"]);
|
|
1144
1145
|
var COMMAND_WRAPPERS = /* @__PURE__ */ new Set([
|
|
1145
1146
|
"sudo",
|
|
1146
1147
|
"doas",
|
|
@@ -1460,6 +1461,97 @@ function analyzeFsOperation(command) {
|
|
|
1460
1461
|
fsOpCache.set(normalized, computed);
|
|
1461
1462
|
return computed;
|
|
1462
1463
|
}
|
|
1464
|
+
var stripDotSlash = (p) => p.replace(/^\.\//, "");
|
|
1465
|
+
function isSensitiveCleanupName(p) {
|
|
1466
|
+
const base = p.replace(/^.*[\\/]/, "");
|
|
1467
|
+
return /^\.env(\.|$)/i.test(base) || /(?:^|[\\/])\.(?:ssh|aws|gnupg|git)(?:[\\/]|$)/i.test(p) || /\.(?:pem|key|p12|pfx|crt)$/i.test(base) || /^\.?(?:netrc|npmrc|pgpass|htpasswd)$/i.test(base) || /^id_(?:rsa|dsa|ecdsa|ed25519)/i.test(base) || /credential/i.test(p) || /secret/i.test(base);
|
|
1468
|
+
}
|
|
1469
|
+
function isWaivableCleanupTarget(p) {
|
|
1470
|
+
if (/^[/~]/.test(p) || /^\$/.test(p)) return false;
|
|
1471
|
+
if (/(?:^|[\\/])\.\.(?:[\\/]|$)/.test(p)) return false;
|
|
1472
|
+
if (/[*?[{]/.test(p)) return false;
|
|
1473
|
+
if (isSensitiveCleanupName(p)) return false;
|
|
1474
|
+
return true;
|
|
1475
|
+
}
|
|
1476
|
+
function deriveRedirOp(sample) {
|
|
1477
|
+
try {
|
|
1478
|
+
const f = sharedParser.Parse(sample, "cmd");
|
|
1479
|
+
let op = -1;
|
|
1480
|
+
syntax.Walk(f, (node) => {
|
|
1481
|
+
const n = node;
|
|
1482
|
+
if (n && syntax.NodeType(n) === "Redirect" && op < 0) op = n.Op;
|
|
1483
|
+
return true;
|
|
1484
|
+
});
|
|
1485
|
+
return op;
|
|
1486
|
+
} catch {
|
|
1487
|
+
return -1;
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
var REDIR_TRUNCATE_OPS = /* @__PURE__ */ new Set([deriveRedirOp(">_f")]);
|
|
1491
|
+
var REDIR_HEREDOC_OPS = /* @__PURE__ */ new Set([
|
|
1492
|
+
deriveRedirOp("cat <<X\nX"),
|
|
1493
|
+
deriveRedirOp("cat <<-X\nX")
|
|
1494
|
+
]);
|
|
1495
|
+
function collectSameCommandCreations(f) {
|
|
1496
|
+
const created = /* @__PURE__ */ new Set();
|
|
1497
|
+
try {
|
|
1498
|
+
const stmts = Array.isArray(f?.Stmts) ? f.Stmts : [];
|
|
1499
|
+
for (const stmt of stmts) {
|
|
1500
|
+
if (!stmt || !stmt.Cmd || syntax.NodeType(stmt.Cmd) !== "CallExpr") continue;
|
|
1501
|
+
const redirs = stmt.Redirs || [];
|
|
1502
|
+
if (!redirs.some((r) => r && REDIR_HEREDOC_OPS.has(r.Op))) continue;
|
|
1503
|
+
for (const r of redirs) {
|
|
1504
|
+
if (r && REDIR_TRUNCATE_OPS.has(r.Op) && r.N == null) {
|
|
1505
|
+
const w = resolveWordLiteral(r.Word);
|
|
1506
|
+
if (w) created.add(stripDotSlash(w));
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
} catch {
|
|
1511
|
+
return created;
|
|
1512
|
+
}
|
|
1513
|
+
return created;
|
|
1514
|
+
}
|
|
1515
|
+
function isRmCreatedInCommandCleanup(command) {
|
|
1516
|
+
if (!/\brm\b/.test(command)) return false;
|
|
1517
|
+
const f = parseShared(command);
|
|
1518
|
+
if (f === PARSE_FAIL) return false;
|
|
1519
|
+
const created = collectSameCommandCreations(f);
|
|
1520
|
+
if (created.size === 0) return false;
|
|
1521
|
+
let sawRm = false;
|
|
1522
|
+
let ok = true;
|
|
1523
|
+
try {
|
|
1524
|
+
syntax.Walk(f, (node) => {
|
|
1525
|
+
if (!node || !ok) return false;
|
|
1526
|
+
const n = node;
|
|
1527
|
+
if (syntax.NodeType(n) !== "CallExpr") return true;
|
|
1528
|
+
const args = n.Args || [];
|
|
1529
|
+
const name = (resolveWordLiteral(args[0]) ?? "").toLowerCase();
|
|
1530
|
+
if (name !== "rm") return true;
|
|
1531
|
+
sawRm = true;
|
|
1532
|
+
const { flags, paths } = extractLiteralArgs(n);
|
|
1533
|
+
if (args.length - 1 > flags.length + paths.length) {
|
|
1534
|
+
ok = false;
|
|
1535
|
+
return false;
|
|
1536
|
+
}
|
|
1537
|
+
if (paths.length === 0) {
|
|
1538
|
+
ok = false;
|
|
1539
|
+
return false;
|
|
1540
|
+
}
|
|
1541
|
+
for (const p of paths) {
|
|
1542
|
+
const np = stripDotSlash(p);
|
|
1543
|
+
if (!created.has(np) || !isWaivableCleanupTarget(np)) {
|
|
1544
|
+
ok = false;
|
|
1545
|
+
return false;
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
return true;
|
|
1549
|
+
});
|
|
1550
|
+
} catch {
|
|
1551
|
+
return false;
|
|
1552
|
+
}
|
|
1553
|
+
return sawRm && ok;
|
|
1554
|
+
}
|
|
1463
1555
|
function analyzeFsOperationImpl(command) {
|
|
1464
1556
|
const f = parseShared(command);
|
|
1465
1557
|
if (f === PARSE_FAIL) return null;
|
|
@@ -2218,8 +2310,9 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2218
2310
|
}
|
|
2219
2311
|
}
|
|
2220
2312
|
if (config.policy.smartRules.length > 0) {
|
|
2313
|
+
const rmCleanupWaiver = bashCommand !== null && isRmCreatedInCommandCleanup(bashCommand);
|
|
2221
2314
|
const matches = config.policy.smartRules.filter(
|
|
2222
|
-
(rule) => matchesPattern(toolName, rule.tool) && !(bashCommand !== null && rule.name && AST_FS_REGEX_RULES.has(rule.name)) && evaluateSmartConditions(args, rule)
|
|
2315
|
+
(rule) => matchesPattern(toolName, rule.tool) && !(bashCommand !== null && rule.name && AST_FS_REGEX_RULES.has(rule.name)) && !(rmCleanupWaiver && rule.name === "review-rm" && rule.verdict === "review") && evaluateSmartConditions(args, rule)
|
|
2223
2316
|
);
|
|
2224
2317
|
const matchedRule = resolvePinned(matches);
|
|
2225
2318
|
if (matchedRule) {
|
|
@@ -3456,6 +3549,17 @@ function computeBlendedSecurityScore(opts) {
|
|
|
3456
3549
|
}
|
|
3457
3550
|
return computeSecurityScore({ critical, high, medium, total });
|
|
3458
3551
|
}
|
|
3552
|
+
function computeAgentDeviceScore(opts) {
|
|
3553
|
+
const { audit } = opts;
|
|
3554
|
+
const posture = Number.isFinite(opts.posture) ? Math.max(0, Math.min(100, opts.posture)) : null;
|
|
3555
|
+
const tierOf = (s) => s >= 80 ? "good" : s >= 50 ? "at-risk" : "critical";
|
|
3556
|
+
if (audit.total === 0 && posture === null) return { score: null, tier: null };
|
|
3557
|
+
if (audit.total === 0) return { score: posture, tier: tierOf(posture) };
|
|
3558
|
+
const runtime = computeSecurityScore(audit);
|
|
3559
|
+
if (posture === null) return runtime;
|
|
3560
|
+
const score = Math.round(0.5 * runtime.score + 0.5 * posture);
|
|
3561
|
+
return { score, tier: tierOf(score) };
|
|
3562
|
+
}
|
|
3459
3563
|
|
|
3460
3564
|
// src/blast/index.ts
|
|
3461
3565
|
function truncateBlastPath(full) {
|
|
@@ -3909,6 +4013,7 @@ var ENGINE_VERSION = "1.4.0";
|
|
|
3909
4013
|
classifyAuditEntry,
|
|
3910
4014
|
classifyRuleSeverity,
|
|
3911
4015
|
classifyScanSignal,
|
|
4016
|
+
computeAgentDeviceScore,
|
|
3912
4017
|
computeArgsHash,
|
|
3913
4018
|
computeBlendedSecurityScore,
|
|
3914
4019
|
computeScanScore,
|
package/dist/index.mjs
CHANGED
|
@@ -1031,7 +1031,7 @@ function analyzeSqlDestructive(command) {
|
|
|
1031
1031
|
description: "The AI wants to drop or truncate a database table via the shell. This permanently deletes the table structure or all its data."
|
|
1032
1032
|
};
|
|
1033
1033
|
}
|
|
1034
|
-
var CHMOD_OPEN_PERM_TOKENS = /* @__PURE__ */ new Set(["777", "0777", "a+rwx"
|
|
1034
|
+
var CHMOD_OPEN_PERM_TOKENS = /* @__PURE__ */ new Set(["777", "0777", "a+rwx"]);
|
|
1035
1035
|
var COMMAND_WRAPPERS = /* @__PURE__ */ new Set([
|
|
1036
1036
|
"sudo",
|
|
1037
1037
|
"doas",
|
|
@@ -1351,6 +1351,97 @@ function analyzeFsOperation(command) {
|
|
|
1351
1351
|
fsOpCache.set(normalized, computed);
|
|
1352
1352
|
return computed;
|
|
1353
1353
|
}
|
|
1354
|
+
var stripDotSlash = (p) => p.replace(/^\.\//, "");
|
|
1355
|
+
function isSensitiveCleanupName(p) {
|
|
1356
|
+
const base = p.replace(/^.*[\\/]/, "");
|
|
1357
|
+
return /^\.env(\.|$)/i.test(base) || /(?:^|[\\/])\.(?:ssh|aws|gnupg|git)(?:[\\/]|$)/i.test(p) || /\.(?:pem|key|p12|pfx|crt)$/i.test(base) || /^\.?(?:netrc|npmrc|pgpass|htpasswd)$/i.test(base) || /^id_(?:rsa|dsa|ecdsa|ed25519)/i.test(base) || /credential/i.test(p) || /secret/i.test(base);
|
|
1358
|
+
}
|
|
1359
|
+
function isWaivableCleanupTarget(p) {
|
|
1360
|
+
if (/^[/~]/.test(p) || /^\$/.test(p)) return false;
|
|
1361
|
+
if (/(?:^|[\\/])\.\.(?:[\\/]|$)/.test(p)) return false;
|
|
1362
|
+
if (/[*?[{]/.test(p)) return false;
|
|
1363
|
+
if (isSensitiveCleanupName(p)) return false;
|
|
1364
|
+
return true;
|
|
1365
|
+
}
|
|
1366
|
+
function deriveRedirOp(sample) {
|
|
1367
|
+
try {
|
|
1368
|
+
const f = sharedParser.Parse(sample, "cmd");
|
|
1369
|
+
let op = -1;
|
|
1370
|
+
syntax.Walk(f, (node) => {
|
|
1371
|
+
const n = node;
|
|
1372
|
+
if (n && syntax.NodeType(n) === "Redirect" && op < 0) op = n.Op;
|
|
1373
|
+
return true;
|
|
1374
|
+
});
|
|
1375
|
+
return op;
|
|
1376
|
+
} catch {
|
|
1377
|
+
return -1;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
var REDIR_TRUNCATE_OPS = /* @__PURE__ */ new Set([deriveRedirOp(">_f")]);
|
|
1381
|
+
var REDIR_HEREDOC_OPS = /* @__PURE__ */ new Set([
|
|
1382
|
+
deriveRedirOp("cat <<X\nX"),
|
|
1383
|
+
deriveRedirOp("cat <<-X\nX")
|
|
1384
|
+
]);
|
|
1385
|
+
function collectSameCommandCreations(f) {
|
|
1386
|
+
const created = /* @__PURE__ */ new Set();
|
|
1387
|
+
try {
|
|
1388
|
+
const stmts = Array.isArray(f?.Stmts) ? f.Stmts : [];
|
|
1389
|
+
for (const stmt of stmts) {
|
|
1390
|
+
if (!stmt || !stmt.Cmd || syntax.NodeType(stmt.Cmd) !== "CallExpr") continue;
|
|
1391
|
+
const redirs = stmt.Redirs || [];
|
|
1392
|
+
if (!redirs.some((r) => r && REDIR_HEREDOC_OPS.has(r.Op))) continue;
|
|
1393
|
+
for (const r of redirs) {
|
|
1394
|
+
if (r && REDIR_TRUNCATE_OPS.has(r.Op) && r.N == null) {
|
|
1395
|
+
const w = resolveWordLiteral(r.Word);
|
|
1396
|
+
if (w) created.add(stripDotSlash(w));
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
} catch {
|
|
1401
|
+
return created;
|
|
1402
|
+
}
|
|
1403
|
+
return created;
|
|
1404
|
+
}
|
|
1405
|
+
function isRmCreatedInCommandCleanup(command) {
|
|
1406
|
+
if (!/\brm\b/.test(command)) return false;
|
|
1407
|
+
const f = parseShared(command);
|
|
1408
|
+
if (f === PARSE_FAIL) return false;
|
|
1409
|
+
const created = collectSameCommandCreations(f);
|
|
1410
|
+
if (created.size === 0) return false;
|
|
1411
|
+
let sawRm = false;
|
|
1412
|
+
let ok = true;
|
|
1413
|
+
try {
|
|
1414
|
+
syntax.Walk(f, (node) => {
|
|
1415
|
+
if (!node || !ok) return false;
|
|
1416
|
+
const n = node;
|
|
1417
|
+
if (syntax.NodeType(n) !== "CallExpr") return true;
|
|
1418
|
+
const args = n.Args || [];
|
|
1419
|
+
const name = (resolveWordLiteral(args[0]) ?? "").toLowerCase();
|
|
1420
|
+
if (name !== "rm") return true;
|
|
1421
|
+
sawRm = true;
|
|
1422
|
+
const { flags, paths } = extractLiteralArgs(n);
|
|
1423
|
+
if (args.length - 1 > flags.length + paths.length) {
|
|
1424
|
+
ok = false;
|
|
1425
|
+
return false;
|
|
1426
|
+
}
|
|
1427
|
+
if (paths.length === 0) {
|
|
1428
|
+
ok = false;
|
|
1429
|
+
return false;
|
|
1430
|
+
}
|
|
1431
|
+
for (const p of paths) {
|
|
1432
|
+
const np = stripDotSlash(p);
|
|
1433
|
+
if (!created.has(np) || !isWaivableCleanupTarget(np)) {
|
|
1434
|
+
ok = false;
|
|
1435
|
+
return false;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
return true;
|
|
1439
|
+
});
|
|
1440
|
+
} catch {
|
|
1441
|
+
return false;
|
|
1442
|
+
}
|
|
1443
|
+
return sawRm && ok;
|
|
1444
|
+
}
|
|
1354
1445
|
function analyzeFsOperationImpl(command) {
|
|
1355
1446
|
const f = parseShared(command);
|
|
1356
1447
|
if (f === PARSE_FAIL) return null;
|
|
@@ -2109,8 +2200,9 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2109
2200
|
}
|
|
2110
2201
|
}
|
|
2111
2202
|
if (config.policy.smartRules.length > 0) {
|
|
2203
|
+
const rmCleanupWaiver = bashCommand !== null && isRmCreatedInCommandCleanup(bashCommand);
|
|
2112
2204
|
const matches = config.policy.smartRules.filter(
|
|
2113
|
-
(rule) => matchesPattern(toolName, rule.tool) && !(bashCommand !== null && rule.name && AST_FS_REGEX_RULES.has(rule.name)) && evaluateSmartConditions(args, rule)
|
|
2205
|
+
(rule) => matchesPattern(toolName, rule.tool) && !(bashCommand !== null && rule.name && AST_FS_REGEX_RULES.has(rule.name)) && !(rmCleanupWaiver && rule.name === "review-rm" && rule.verdict === "review") && evaluateSmartConditions(args, rule)
|
|
2114
2206
|
);
|
|
2115
2207
|
const matchedRule = resolvePinned(matches);
|
|
2116
2208
|
if (matchedRule) {
|
|
@@ -3347,6 +3439,17 @@ function computeBlendedSecurityScore(opts) {
|
|
|
3347
3439
|
}
|
|
3348
3440
|
return computeSecurityScore({ critical, high, medium, total });
|
|
3349
3441
|
}
|
|
3442
|
+
function computeAgentDeviceScore(opts) {
|
|
3443
|
+
const { audit } = opts;
|
|
3444
|
+
const posture = Number.isFinite(opts.posture) ? Math.max(0, Math.min(100, opts.posture)) : null;
|
|
3445
|
+
const tierOf = (s) => s >= 80 ? "good" : s >= 50 ? "at-risk" : "critical";
|
|
3446
|
+
if (audit.total === 0 && posture === null) return { score: null, tier: null };
|
|
3447
|
+
if (audit.total === 0) return { score: posture, tier: tierOf(posture) };
|
|
3448
|
+
const runtime = computeSecurityScore(audit);
|
|
3449
|
+
if (posture === null) return runtime;
|
|
3450
|
+
const score = Math.round(0.5 * runtime.score + 0.5 * posture);
|
|
3451
|
+
return { score, tier: tierOf(score) };
|
|
3452
|
+
}
|
|
3350
3453
|
|
|
3351
3454
|
// src/blast/index.ts
|
|
3352
3455
|
function truncateBlastPath(full) {
|
|
@@ -3799,6 +3902,7 @@ export {
|
|
|
3799
3902
|
classifyAuditEntry,
|
|
3800
3903
|
classifyRuleSeverity,
|
|
3801
3904
|
classifyScanSignal,
|
|
3905
|
+
computeAgentDeviceScore,
|
|
3802
3906
|
computeArgsHash,
|
|
3803
3907
|
computeBlendedSecurityScore,
|
|
3804
3908
|
computeScanScore,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node9/policy-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.62.0",
|
|
4
4
|
"description": "Shared policy evaluation engine for node9 — DLP, smart rules, AST shell parsing, shields, loop detection. Pure functions, no I/O. Used by both node9-proxy and the node9 SaaS firewall.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://node9.ai",
|