@clear-capabilities/agentic-security-scanner 0.162.0 → 0.163.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/CHANGELOG.md +26 -0
- package/bin/agentic-security.js +87 -0
- package/dist/1340.index.js +1871 -0
- package/dist/3477.index.js +727 -0
- package/dist/3499.index.js +23 -3
- package/dist/4113.index.js +1 -1
- package/dist/5014.index.js +833 -0
- package/dist/5756.index.js +2 -2
- package/dist/6257.index.js +1 -1
- package/dist/6907.index.js +248 -0
- package/dist/6994.index.js +1 -1
- package/dist/7033.index.js +33 -5
- package/dist/7439.index.js +1822 -0
- package/dist/7838.index.js +1 -1
- package/dist/9560.index.js +2318 -23
- package/dist/957.index.js +1 -1
- package/dist/9999.index.js +43 -7
- package/dist/agentic-security.mjs +12 -12
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +33 -6
- package/src/capabilities/CLAUDE.md +114 -0
- package/src/capabilities/address.js +73 -0
- package/src/capabilities/attack-coverage.js +93 -0
- package/src/capabilities/decide.js +256 -0
- package/src/capabilities/delegate.js +69 -0
- package/src/capabilities/egress.js +20 -0
- package/src/capabilities/hook-advice.js +50 -0
- package/src/capabilities/index.js +32 -0
- package/src/capabilities/manifest.js +344 -0
- package/src/capabilities/outbound.js +189 -0
- package/src/capabilities/paths.js +57 -0
- package/src/capabilities/probes.js +211 -0
- package/src/capabilities/proxy.js +182 -0
- package/src/capabilities/reasons.js +67 -0
- package/src/capabilities/receipts.js +356 -0
- package/src/capabilities/records.js +58 -0
- package/src/capabilities/recovery.js +367 -0
- package/src/capabilities/report.js +81 -0
- package/src/capabilities/runner.js +236 -0
- package/src/capabilities/secrets.js +26 -0
- package/src/capabilities/tool-gate.js +119 -0
- package/src/capabilities/tool-registry.js +60 -0
- package/src/dataflow/CLAUDE.md +29 -0
- package/src/dataflow/catalog.js +21 -0
- package/src/dataflow/engine.js +20 -0
- package/src/dataflow/guard-dominance.js +352 -0
- package/src/discovery/CLAUDE.md +9 -0
- package/src/discovery/index.js +15 -1
- package/src/discovery/lenses.js +18 -0
- package/src/engine.js +80 -15
- package/src/integrations/index.js +5 -2
- package/src/ir/CLAUDE.md +14 -0
- package/src/ir/implicit-handler-params.js +74 -0
- package/src/ir/parser-cs.js +20 -0
- package/src/ir/parser-go.js +30 -13
- package/src/ir/parser-java.js +11 -0
- package/src/language/nix-eval-isolation.js +33 -5
- package/src/lineage/CLAUDE.md +31 -0
- package/src/lineage/deployment/adapter-cloud.js +202 -0
- package/src/lineage/deployment/adapter-compose.js +90 -0
- package/src/lineage/deployment/adapter-kit.js +142 -0
- package/src/lineage/deployment/adapter-kubernetes.js +247 -0
- package/src/lineage/deployment/attack-paths.js +443 -0
- package/src/lineage/deployment/boundaries-run.js +223 -0
- package/src/lineage/deployment/boundary-graph.js +567 -0
- package/src/lineage/deployment/cross-repo.js +253 -0
- package/src/lineage/deployment/drift.js +383 -0
- package/src/lineage/deployment/ingest.js +221 -0
- package/src/lineage/deployment/projection.js +244 -0
- package/src/lineage/deployment/trace-correlation.js +541 -0
- package/src/lineage/source-registry.js +19 -0
- package/src/mcp/CLAUDE.md +5 -2
- package/src/mcp/invariant-tools.js +64 -0
- package/src/mcp/portfolio-tools.js +79 -0
- package/src/mcp/redact.js +15 -0
- package/src/mcp/server.js +24 -3
- package/src/mcp/tools.js +13 -1
- package/src/posture/CLAUDE.md +332 -1
- package/src/posture/accuracy-scorecard.js +95 -4
- package/src/posture/assurance/rollback.js +214 -0
- package/src/posture/assurance/rollout.js +200 -0
- package/src/posture/business-logic.js +26 -4
- package/src/posture/evaluation/bakeoff.js +148 -0
- package/src/posture/evaluation/custody.js +200 -0
- package/src/posture/evaluation/deployment-ablation.js +416 -0
- package/src/posture/evaluation/dev-cases.js +65 -0
- package/src/posture/evaluation/economics.js +85 -0
- package/src/posture/evaluation/gates.js +161 -0
- package/src/posture/evaluation/grouping.js +101 -0
- package/src/posture/evaluation/interval.js +132 -0
- package/src/posture/evaluation/invariant-ablation.js +325 -0
- package/src/posture/evaluation/labels.js +200 -0
- package/src/posture/evaluation/protocol.js +308 -0
- package/src/posture/evaluation/release-gate.js +209 -0
- package/src/posture/evaluation/report.js +158 -0
- package/src/posture/evaluation/runner.js +273 -0
- package/src/posture/evaluation/scan-child.js +80 -0
- package/src/posture/evaluation/score.js +174 -0
- package/src/posture/evaluation/synthetic.js +90 -0
- package/src/posture/evaluation/why-missed.js +219 -0
- package/src/posture/fleet.js +11 -2
- package/src/posture/invariants/cli.js +95 -0
- package/src/posture/invariants/coverage.js +164 -0
- package/src/posture/invariants/export.js +178 -0
- package/src/posture/invariants/expressions.js +46 -0
- package/src/posture/invariants/lifecycle.js +289 -0
- package/src/posture/invariants/project-input.js +55 -0
- package/src/posture/invariants/repair.js +340 -0
- package/src/posture/invariants/run.js +45 -0
- package/src/posture/invariants/scenarios.js +311 -0
- package/src/posture/invariants/schema.js +167 -0
- package/src/posture/invariants/shrink.js +407 -0
- package/src/posture/invariants/state-assertions.js +165 -0
- package/src/posture/logic-claims.js +12 -0
- package/src/posture/model-routing.js +40 -0
- package/src/posture/oracles/adapters.js +172 -1
- package/src/posture/oracles/conformance.js +3 -2
- package/src/posture/oracles/oracle.js +23 -3
- package/src/posture/oracles/registry.js +1 -1
- package/src/posture/portfolio/backend.js +279 -0
- package/src/posture/portfolio/bundle.js +314 -0
- package/src/posture/portfolio/cli.js +142 -0
- package/src/posture/portfolio/manifest.js +263 -0
- package/src/posture/portfolio/progress.js +267 -0
- package/src/posture/portfolio/resume.js +130 -0
- package/src/posture/portfolio/retention.js +207 -0
- package/src/posture/portfolio/scheduler.js +488 -0
- package/src/posture/portfolio/signing.js +155 -0
- package/src/posture/portfolio/wording.js +59 -0
- package/src/posture/portfolio/work-units.js +405 -0
- package/src/posture/replay/replay.js +1 -0
- package/src/posture/routing/adapter.js +140 -0
- package/src/posture/routing/calibration.js +278 -0
- package/src/posture/routing/canary.js +141 -0
- package/src/posture/routing/control.js +35 -0
- package/src/posture/routing/decide.js +211 -0
- package/src/posture/routing/drift.js +137 -0
- package/src/posture/routing/economics.js +185 -0
- package/src/posture/routing/feedback.js +207 -0
- package/src/posture/routing/index.js +31 -0
- package/src/posture/routing/outcomes.js +281 -0
- package/src/posture/routing/promotion.js +291 -0
- package/src/posture/routing/receipts.js +59 -0
- package/src/posture/routing/report.js +142 -0
- package/src/posture/routing/shadow.js +53 -0
- package/src/posture/specification-mining.js +26 -0
- package/src/posture/verification/patch-negative.js +16 -4
- package/src/posture/verification/patch-promotion.js +10 -2
- package/src/report/index.js +51 -1
- package/src/sandbox/CLAUDE.md +10 -0
- package/src/sandbox/backend-userspace.js +91 -13
- package/dist/5613.index.js +0 -500
- package/dist/6239.index.js +0 -469
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// invariant-tools.js: the MCP surface for bounded business-logic scenarios (X-407.AC03).
|
|
2
|
+
//
|
|
3
|
+
// One READ-ONLY tool, `invariant_scenario_export`: build the reproducible scenario package for a reviewer-supplied contract and a
|
|
4
|
+
// disposable fixture directory that both live inside the session root. It runs nothing and writes nothing. The same function
|
|
5
|
+
// the CLI calls (`posture/invariants/export.js` `exportFromFiles`) does the work, so the two surfaces cannot disagree.
|
|
6
|
+
//
|
|
7
|
+
// Guarantees, each with a test:
|
|
8
|
+
// - paths are confined to the session root by the server's own `_confine` (passed in; this file never resolves a path itself);
|
|
9
|
+
// - the export carries no tenant secret: scenarios holding secret-looking content are withheld, the fixture source is never
|
|
10
|
+
// included, and the serialized result is checked once more with the server's own secret-shape redactor, which BLOCKS the
|
|
11
|
+
// result rather than editing it (an edited scenario is a different scenario);
|
|
12
|
+
// - it never claims exhaustive correctness: every export carries the bounded-sample statement;
|
|
13
|
+
// - gated behind the `invariant-scenarios` feature (off by default, operator-only): with it off the tool reports it is
|
|
14
|
+
// disabled and builds nothing.
|
|
15
|
+
// The factory shape (rather than importing `_confine` from tools.js) avoids a cycle between this file and the registry.
|
|
16
|
+
import { exportFromFiles } from '../posture/invariants/export.js';
|
|
17
|
+
import { resolveAssuranceConfig } from '../posture/assurance/config.js';
|
|
18
|
+
import { redactSecretShapes } from './redact.js';
|
|
19
|
+
|
|
20
|
+
const MAX_RESPONSE_BYTES = 1_500_000;
|
|
21
|
+
|
|
22
|
+
export function makeInvariantTools({ confine, META }) {
|
|
23
|
+
const invariant_scenario_export = {
|
|
24
|
+
name: 'invariant_scenario_export',
|
|
25
|
+
description: 'Export the bounded stateful scenarios for one executable business-logic invariant as a reproducible package: scenario documents with content digests, the pinned fixture digest, replay manifests (when a commit is given), the bounds applied and the scenario families that could not be built. Read-only: runs nothing. The fixture source and any secret-looking content are never included, and the package states that it is a bounded sample, not a proof of correctness. Requires the invariant-scenarios feature (off by default).',
|
|
26
|
+
inputSchema: {
|
|
27
|
+
type: 'object',
|
|
28
|
+
additionalProperties: false,
|
|
29
|
+
properties: {
|
|
30
|
+
invariant_file: { type: 'string', minLength: 1, maxLength: 512 },
|
|
31
|
+
fixture_dir: { type: 'string', minLength: 1, maxLength: 512 },
|
|
32
|
+
ledger_file: { type: 'string', minLength: 1, maxLength: 512 },
|
|
33
|
+
commit: { type: 'string', pattern: '^[0-9a-f]{40}([0-9a-f]{24})?$' },
|
|
34
|
+
seed: { type: 'integer', minimum: 0, maximum: 4294967295 },
|
|
35
|
+
bounds: {
|
|
36
|
+
type: 'object', additionalProperties: false,
|
|
37
|
+
properties: {
|
|
38
|
+
actors: { type: 'integer', minimum: 1, maximum: 4 }, depth: { type: 'integer', minimum: 1, maximum: 12 }, requests: { type: 'integer', minimum: 1, maximum: 16 },
|
|
39
|
+
scenarios: { type: 'integer', minimum: 1, maximum: 6 }, timeBudgetMs: { type: 'integer', minimum: 1, maximum: 8000 },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
required: ['invariant_file', 'fixture_dir'],
|
|
44
|
+
},
|
|
45
|
+
async handler(args, ctx) {
|
|
46
|
+
let invariantPath; let fixturePath; let ledgerPath;
|
|
47
|
+
try {
|
|
48
|
+
invariantPath = confine(ctx.sessionRoot, args.invariant_file, 'invariant_file');
|
|
49
|
+
fixturePath = confine(ctx.sessionRoot, args.fixture_dir, 'fixture_dir');
|
|
50
|
+
if (args.ledger_file) ledgerPath = confine(ctx.sessionRoot, args.ledger_file, 'ledger_file');
|
|
51
|
+
} catch (e) {
|
|
52
|
+
return { _meta: META, ok: false, status: 'rejected', reason: `path refused: ${String(e.message).replace(ctx.sessionRoot, '<root>')}` };
|
|
53
|
+
}
|
|
54
|
+
const config = resolveAssuranceConfig({ scanRoot: ctx.sessionRoot, env: process.env });
|
|
55
|
+
const r = exportFromFiles({ invariantPath, fixturePath, ledgerPath, commit: args.commit, seed: args.seed, bounds: args.bounds, config });
|
|
56
|
+
if (r.status !== 'ok') return { _meta: META, ok: false, status: r.status, reason: String(r.reason || '').replace(ctx.sessionRoot, '<root>').slice(0, 400), withheld: r.withheld ?? [] };
|
|
57
|
+
const text = JSON.stringify(r.export);
|
|
58
|
+
if (redactSecretShapes(text).redactions > 0) return { _meta: META, ok: false, status: 'blocked', reason: 'the export contains secret-shaped content and was not returned; an edited scenario would be a different scenario' };
|
|
59
|
+
if (text.length > MAX_RESPONSE_BYTES) return { _meta: META, ok: false, status: 'blocked', reason: 'the export is too large for one response; lower the bounds' };
|
|
60
|
+
return { _meta: META, ok: true, status: 'ok', export: r.export, withheld: r.withheld };
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
return { invariant_scenario_export };
|
|
64
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// portfolio-tools.js: the MCP surface for portfolio progress and review queues (X-707).
|
|
2
|
+
//
|
|
3
|
+
// One READ-ONLY tool, `portfolio_progress`: the coverage-aware progress view of a durable portfolio store that lives inside the session
|
|
4
|
+
// root. It reads the store (verified; a corrupt store is an error, never a reset), the scheduler ledger beside it, and optionally a
|
|
5
|
+
// budgets file and a findings file, and writes nothing. The same function the CLI calls (`posture/portfolio/progress.js`
|
|
6
|
+
// `buildProgressView`) does the work, so the two surfaces cannot disagree.
|
|
7
|
+
//
|
|
8
|
+
// Guarantees, each with a test:
|
|
9
|
+
// - paths are confined to the session root by the server's own `_confine` (passed in; this file never resolves a path itself);
|
|
10
|
+
// - the view separates verified, blocked/failed/stale, coverage, budget and pending review, keeps stale workers visible, and never
|
|
11
|
+
// states that the portfolio passed (a finished controller is reported as finished);
|
|
12
|
+
// - the serialized result is checked with the server's secret-shape redactor and BLOCKED rather than edited if anything would change;
|
|
13
|
+
// - gated behind the `portfolio-assurance` feature (off by default): with it off the tool answers `status: 'disabled'` and reads nothing.
|
|
14
|
+
// The factory shape (rather than importing `_confine` from tools.js) avoids a cycle between this file and the registry.
|
|
15
|
+
import * as fs from 'node:fs';
|
|
16
|
+
import { resolveAssuranceConfig, featureStatus } from '../posture/assurance/config.js';
|
|
17
|
+
import { readStore } from '../posture/portfolio/work-units.js';
|
|
18
|
+
import { readLedger } from '../posture/portfolio/scheduler.js';
|
|
19
|
+
import { buildProgressView } from '../posture/portfolio/progress.js';
|
|
20
|
+
import { FEATURE_ID } from '../posture/portfolio/wording.js';
|
|
21
|
+
import { redactSecretShapes } from './redact.js';
|
|
22
|
+
|
|
23
|
+
const MAX_RESPONSE_BYTES = 1_500_000;
|
|
24
|
+
const MAX_INPUT_BYTES = 8 * 1024 * 1024;
|
|
25
|
+
|
|
26
|
+
function readJson(file) {
|
|
27
|
+
const st = fs.lstatSync(file);
|
|
28
|
+
if (st.isSymbolicLink() || !st.isFile() || st.size > MAX_INPUT_BYTES) throw new Error('not a regular JSON file within the size limit');
|
|
29
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function makePortfolioTools({ confine, META }) {
|
|
33
|
+
const portfolio_progress = {
|
|
34
|
+
name: 'portfolio_progress',
|
|
35
|
+
description: 'Show the progress of a resumable multi-repository portfolio audit as a coverage-aware view: verified units, blocked/failed/stale units, per-repository coverage, remaining budget, pending human review, worker liveness (stale workers stay visible) and, when a findings file is given, aggregate findings deduplicated by stable identity with per-repository/environment evidence and separate affected releases. A finished controller is reported as finished, never as passed. Read-only; paths must be inside the session root. Disabled unless the portfolio-assurance feature is on.',
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: 'object',
|
|
38
|
+
additionalProperties: false,
|
|
39
|
+
properties: {
|
|
40
|
+
store_file: { type: 'string', minLength: 1, maxLength: 512 },
|
|
41
|
+
budgets_file: { type: 'string', minLength: 1, maxLength: 512 },
|
|
42
|
+
findings_file: { type: 'string', minLength: 1, maxLength: 512 },
|
|
43
|
+
blocking_severity: { type: 'string', enum: ['info', 'low', 'medium', 'high', 'critical'] },
|
|
44
|
+
now: { type: 'integer', minimum: 0 },
|
|
45
|
+
},
|
|
46
|
+
required: ['store_file'],
|
|
47
|
+
},
|
|
48
|
+
async handler(args, ctx) {
|
|
49
|
+
const config = resolveAssuranceConfig({ scanRoot: ctx.sessionRoot, env: process.env });
|
|
50
|
+
const gate = featureStatus(config, FEATURE_ID);
|
|
51
|
+
if (gate.status !== 'ok') return { _meta: META, ok: false, status: gate.status, reason: `portfolio progress is not available: ${gate.reason ?? gate.code}` };
|
|
52
|
+
let storePath; let budgetsPath; let findingsPath;
|
|
53
|
+
try {
|
|
54
|
+
storePath = confine(ctx.sessionRoot, args.store_file, 'store_file');
|
|
55
|
+
if (args.budgets_file) budgetsPath = confine(ctx.sessionRoot, args.budgets_file, 'budgets_file');
|
|
56
|
+
if (args.findings_file) findingsPath = confine(ctx.sessionRoot, args.findings_file, 'findings_file');
|
|
57
|
+
} catch (e) {
|
|
58
|
+
return { _meta: META, ok: false, status: 'rejected', reason: `path refused: ${String(e.message).replace(ctx.sessionRoot, '<root>')}` };
|
|
59
|
+
}
|
|
60
|
+
let store; let ledger; let budgets = null; let findings = null;
|
|
61
|
+
try {
|
|
62
|
+
store = readStore(storePath);
|
|
63
|
+
if (!store) return { _meta: META, ok: false, status: 'rejected', reason: 'no portfolio store at that path' };
|
|
64
|
+
ledger = readLedger(storePath);
|
|
65
|
+
if (budgetsPath) budgets = readJson(budgetsPath);
|
|
66
|
+
if (findingsPath) findings = readJson(findingsPath);
|
|
67
|
+
} catch (e) {
|
|
68
|
+
return { _meta: META, ok: false, status: 'blocked', reason: `the store or an input failed verification: ${String(e.code ?? e.message).slice(0, 200)}` };
|
|
69
|
+
}
|
|
70
|
+
const r = buildProgressView({ store, ledger, budgets, now: Number.isInteger(args.now) ? args.now : Date.now(), findings, blockingSeverity: args.blocking_severity });
|
|
71
|
+
if (!r.ok) return { _meta: META, ok: false, status: 'rejected', reason: r.errors.map((e) => e.message).join('; ').slice(0, 400) };
|
|
72
|
+
const text = JSON.stringify(r.view);
|
|
73
|
+
if (redactSecretShapes(text).redactions > 0) return { _meta: META, ok: false, status: 'blocked', reason: 'the view contains secret-shaped content and was not returned' };
|
|
74
|
+
if (text.length > MAX_RESPONSE_BYTES) return { _meta: META, ok: false, status: 'blocked', reason: 'the view is too large for one response' };
|
|
75
|
+
return { _meta: META, ok: true, status: 'ok', view: r.view };
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
return { portfolio_progress };
|
|
79
|
+
}
|
package/src/mcp/redact.js
CHANGED
|
@@ -81,6 +81,21 @@ export function redactString(s) {
|
|
|
81
81
|
return out;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* The same provider-shape redaction as `redactString`, without its size caps:
|
|
86
|
+
* for a caller that must keep the whole payload and only remove secrets from it
|
|
87
|
+
* (the capability layer's outbound filtering). Returns the text and a count.
|
|
88
|
+
*/
|
|
89
|
+
export function redactSecretShapes(s) {
|
|
90
|
+
if (typeof s !== 'string' || !s) return { text: typeof s === 'string' ? s : '', redactions: 0 };
|
|
91
|
+
let out = s;
|
|
92
|
+
let redactions = 0;
|
|
93
|
+
for (const [re, kind] of PATTERNS) {
|
|
94
|
+
out = out.replace(re, () => { redactions++; return `[REDACTED:${kind}]`; });
|
|
95
|
+
}
|
|
96
|
+
return { text: out, redactions };
|
|
97
|
+
}
|
|
98
|
+
|
|
84
99
|
// Deep-redact every string in a finding-like object (mutates returned copy).
|
|
85
100
|
export function redactFinding(f) {
|
|
86
101
|
if (!f || typeof f !== 'object') return f;
|
package/src/mcp/server.js
CHANGED
|
@@ -16,6 +16,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
16
16
|
import { ALL_TOOLS } from './tools.js';
|
|
17
17
|
import { validate } from './validate.js';
|
|
18
18
|
import { auditCall } from './audit.js';
|
|
19
|
+
// X-505: tool calls are checked against the capability policy before a handler runs.
|
|
20
|
+
import { createToolGate } from '../capabilities/tool-gate.js';
|
|
19
21
|
|
|
20
22
|
const PROTOCOL_VERSION = '2025-03-26';
|
|
21
23
|
const SERVER_NAME = 'agentic-security';
|
|
@@ -43,7 +45,7 @@ const TOOLS_BY_NAME = Object.fromEntries(ALL_TOOLS.map(t => [t.name, t]));
|
|
|
43
45
|
function _codeFingerprint() {
|
|
44
46
|
try {
|
|
45
47
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
46
|
-
const files = ['server.js', 'tools.js', 'dataflow-tools.js', 'stdio.js', 'audit.js', 'validate.js', 'redact.js'];
|
|
48
|
+
const files = ['server.js', 'tools.js', 'dataflow-tools.js', 'invariant-tools.js', 'portfolio-tools.js', 'stdio.js', 'audit.js', 'validate.js', 'redact.js'];
|
|
47
49
|
const h = crypto.createHash('sha256');
|
|
48
50
|
for (const f of files) {
|
|
49
51
|
try { h.update(f); h.update(fs.readFileSync(path.join(here, f))); } catch {}
|
|
@@ -63,8 +65,12 @@ function _ok(id, result) {
|
|
|
63
65
|
return { jsonrpc: '2.0', id, result };
|
|
64
66
|
}
|
|
65
67
|
|
|
66
|
-
|
|
68
|
+
// `capabilityPolicy` is `{ bound, binding }`, supplied by the operator from the
|
|
69
|
+
// capability manifest facility. Absent, and with the `capability-enforcement`
|
|
70
|
+
// feature off, the gate is inactive and behaviour is unchanged.
|
|
71
|
+
export function createServer({ sessionRoot = process.cwd(), capabilityPolicy = null } = {}) {
|
|
67
72
|
const ctx = { sessionRoot };
|
|
73
|
+
const gate = createToolGate({ sessionRoot, policy: capabilityPolicy });
|
|
68
74
|
|
|
69
75
|
async function handleRequest(msg) {
|
|
70
76
|
if (!msg || typeof msg !== 'object') return _err(null, -32600, 'Invalid Request');
|
|
@@ -117,6 +123,21 @@ export function createServer({ sessionRoot = process.cwd() } = {}) {
|
|
|
117
123
|
auditCall({ sessionRoot, tool: name, args, outcome: 'rejected', reason: 'unknown-tool' });
|
|
118
124
|
return _err(id, -32602, `Unknown tool: ${name}`);
|
|
119
125
|
}
|
|
126
|
+
// X-505: the capability check, for the task identity bound to this server.
|
|
127
|
+
// An identity named in the request metadata can only be compared with it.
|
|
128
|
+
const gated = gate.check(name, { claimedTaskId: msg.params?._meta?.taskId });
|
|
129
|
+
if (!gated.allowed) {
|
|
130
|
+
auditCall({ sessionRoot, tool: name, args, outcome: 'rejected', reason: `capability-denied: ${gated.code}` });
|
|
131
|
+
const p = gated.proposal;
|
|
132
|
+
return _ok(id, {
|
|
133
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
134
|
+
blocked: true, code: gated.code, reason: gated.reason, missing: gated.missing,
|
|
135
|
+
proposedChange: p && p.proposable ? { review: p.review, change: p.change, nextPolicyVersion: p.nextPolicyVersion, requiresOperatorGrant: true } : null,
|
|
136
|
+
attemptsRemaining: gated.attemptsRemaining, exhausted: gated.exhausted,
|
|
137
|
+
}, null, 2) }],
|
|
138
|
+
isError: true,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
120
141
|
try { validate(tool.inputSchema, args); }
|
|
121
142
|
catch (e) {
|
|
122
143
|
auditCall({ sessionRoot, tool: name, args, outcome: 'rejected', reason: `invalid-args: ${e.message}` });
|
|
@@ -147,7 +168,7 @@ export function createServer({ sessionRoot = process.cwd() } = {}) {
|
|
|
147
168
|
}
|
|
148
169
|
}
|
|
149
170
|
|
|
150
|
-
return { handleRequest, sessionRoot };
|
|
171
|
+
return { handleRequest, sessionRoot, capabilityGate: gate };
|
|
151
172
|
}
|
|
152
173
|
|
|
153
174
|
// NOTE: no default-singleton export. Callers must use createServer({...})
|
package/src/mcp/tools.js
CHANGED
|
@@ -28,11 +28,14 @@ import { redactString, redactFinding } from './redact.js';
|
|
|
28
28
|
import { _remediationOf, normalizeFindings } from '../report/index.js';
|
|
29
29
|
// X-206: the one projection of the verification record, shared with the JSON report, the text report and the autopilot response.
|
|
30
30
|
import { verificationFields } from '../posture/verification/projection.js';
|
|
31
|
+
import { boundaryFields } from '../lineage/deployment/projection.js';
|
|
31
32
|
// Git-origin provenance (Finding Provenance M0/M1). Distinct from
|
|
32
33
|
// `finding.provenance` (AI-authorship) and from an SCA entry's `provenance`
|
|
33
34
|
// (Sigstore/SLSA attestation) — see report/index.js's import comment.
|
|
34
35
|
import { redactFindingProvenance } from '../posture/provenance/schema.js';
|
|
35
36
|
import { dataflow_get_graph, dataflow_get_node, dataflow_get_edge, dataflow_get_flow } from './dataflow-tools.js';
|
|
37
|
+
import { makeInvariantTools } from './invariant-tools.js';
|
|
38
|
+
import { makePortfolioTools } from './portfolio-tools.js';
|
|
36
39
|
|
|
37
40
|
// Lazy-loaded: these transitively pull in npm packages (@babel/core and
|
|
38
41
|
// friends) that aren't available in the plugin-cache install path
|
|
@@ -605,6 +608,10 @@ export const explain_finding = {
|
|
|
605
608
|
...(f.verificationRecord && typeof f.verificationRecord === 'object'
|
|
606
609
|
? { verificationRecord: f.verificationRecord, ...verificationFields(f.verificationRecord, { replay: f.verificationReplay }) }
|
|
607
610
|
: {}),
|
|
611
|
+
// X-307: the same boundary projection the JSON and text reports carry; absent when the finding has no context.
|
|
612
|
+
...(f.boundaryContext && typeof f.boundaryContext === 'object'
|
|
613
|
+
? boundaryFields(f.boundaryContext)
|
|
614
|
+
: {}),
|
|
608
615
|
};
|
|
609
616
|
},
|
|
610
617
|
};
|
|
@@ -1712,4 +1719,9 @@ export const apply_sca_upgrade = {
|
|
|
1712
1719
|
},
|
|
1713
1720
|
};
|
|
1714
1721
|
|
|
1715
|
-
|
|
1722
|
+
// X-407: read-only scenario export; the factory takes this file's confinement so the two modules do not import each other.
|
|
1723
|
+
const { invariant_scenario_export } = makeInvariantTools({ confine: _confine, META });
|
|
1724
|
+
// X-707: read-only portfolio progress view, same factory shape.
|
|
1725
|
+
const { portfolio_progress } = makePortfolioTools({ confine: _confine, META });
|
|
1726
|
+
|
|
1727
|
+
export const ALL_TOOLS = [scan_diff, query_taint, explain_finding, apply_fix, verify_fix, synthesize_fix, find_rule_module, append_scratchpad, read_scratchpad, append_agents_memory, read_agents_memory, lookup_cve, synthesize_sca_upgrade, apply_sca_upgrade, query_triage_memory, query_findings_memory, query_cache_telemetry, dataflow_get_graph, dataflow_get_node, dataflow_get_edge, dataflow_get_flow, invariant_scenario_export, portfolio_progress];
|