Skip to main content

Security Scanner Architecture

The security scanner is a cohesive bounded context inside the existing AI Primitives Hub packages. It is not a delivery-specific linter and it does not introduce a fifth architectural layer.

Dependency direction​

flowchart LR
CLI["CLI: security scan"] --> APP["app: SecurityScanUseCase"]
EXT["VS Code commands and service"] --> APP
APP --> CORE["core: security domain and ports"]
APP --> INFRA["infra: filesystem and report adapters"]
INFRA --> CORE
GHA["GitHub Actions"] --> CLI

The existing package dependency direction remains authoritative:

CLI / Extension → app → infra → core

core does not import vscode, node:fs, process execution, or network clients. infra implements external capabilities. app composes and orchestrates. Delivery layers translate user interaction and presentation only.

Components​

flowchart TB
subgraph Delivery["Delivery"]
CLI["Clipanion command"]
EXT["VS Code service, commands, diagnostics"]
end

subgraph Application["@ai-primitives-hub/app"]
UC["SecurityScanUseCase"]
RESULT["Normalized scan result"]
end

subgraph Domain["@ai-primitives-hub/core"]
PORT_ENGINE["SecurityScanEngine port"]
PORT_INPUT["SecurityScanInput port"]
PORT_REPORT["SecurityReportStore port"]
RULES["Pure rule engine and rule families"]
POLICY["Fingerprints, suppression, policy, redaction"]
end

subgraph Infrastructure["@ai-primitives-hub/infra"]
INPUT["NodeSecurityScanInput"]
WORKER["IsolatedSecurityScanEngine"]
STORE["SecureAtomicSecurityReportStore"]
FS[("Local filesystem")]
end

CLI --> UC
EXT --> UC
UC --> PORT_INPUT
UC --> PORT_ENGINE
UC --> PORT_REPORT
UC --> POLICY
PORT_ENGINE -. implemented by .-> RULES
PORT_INPUT -. implemented by .-> INPUT
PORT_ENGINE -. wrapped by .-> WORKER
PORT_REPORT -. implemented by .-> STORE
INPUT --> FS
STORE --> FS
RULES --> RESULT
POLICY --> RESULT

Scan flow​

sequenceDiagram
participant D as Delivery adapter
participant A as App use case
participant I as Input port
participant E as Engine port
participant P as Core policy
participant R as Report store

D->>A: run(request, dependencies)
A->>I: collect roots, extensions, excludes, trust mode
I-->>A: candidates, ignored/skipped/errors, declarations
loop each bounded candidate
A->>I: read(candidate, limits)
I-->>A: document and metadata
A->>E: scanDocument(document, options, cancellation)
E-->>A: normalized findings
end
A->>P: fingerprint, suppress, select, redact, evaluate policy
P-->>A: active findings, suppression summary, completeness
opt report requested
A->>R: atomic redacted report write
end
A-->>D: immutable SecurityScanResult

The input adapter never executes candidate content. Directory traversal uses lstat, rejects symbolic links/special files, enforces containment and size/depth limits, and sorts candidates deterministically. Report storage uses owner-only temporary files and atomic replacement.

Ports and responsibilities​

SecurityScanInput​

Core owns the interface. The Node adapter supplies bounded filesystem discovery and reads. An editor-buffer adapter can be added later without changing rule logic. The input result partitions candidates into scanned, ignored, skipped, and error states.

SecurityScanEngine​

Core owns the engine contract and the pure built-in rule behavior. The engine descriptor reports its ID, implementation version, rule-pack version, and digest. Alternative engines must normalize into the same finding model and explicitly declare capabilities.

The CLI and extension compose the built-in engine behind IsolatedSecurityScanEngine. The worker boundary is required because synchronous JavaScript regular expressions cannot be interrupted from the calling thread. Each engine reuses one worker across documents, serializes requests, and replaces the worker after a failure or cancellation. Missing packaged workers fail closed; there is no synchronous fallback.

Engine instances supplied to runSecurityScan have scan-scoped ownership: the use case calls their optional dispose() in finally, including on cancellation or input failure. Create a new isolated engine for each scan. A timeout produces an incomplete scan with coverage errors.

SecurityReportStore​

Core owns a narrow persistence intent contract. Infra enforces no-follow destination checks, bounded writes, exclusive owner-only temporary creation, atomic rename, and overwrite policy. Report rendering must not leak raw secret evidence.

Domain model​

classDiagram
class SecurityDocument {
+string id
+string rootId
+string displayPath
+string content
+FileMetadata metadata
}
class SecurityFinding {
+string ruleId
+string title
+SecuritySeverity severity
+SecurityConfidence confidence
+string category
+string file
+number line
+string fingerprint
+string canonicalFingerprint
+string vulnerableContent
}
class SecurityScanResult {
+boolean complete
+EngineDescriptor engine
+Coverage coverage
+Summary summary
+SecurityFinding[] findings
+Timing timing
}
class SecurityRulePack {
+string id
+string version
+string digest
+RuleDescriptor[] rules
}
SecurityDocument --> SecurityFinding : evaluated into
SecurityFinding --> SecurityScanResult : collected by
SecurityRulePack --> SecurityFinding : describes

Legacy fingerprints remain compatible with the approved internal MD Security Scanner behavior. The internal tool is not publicly disclosed and is not redistributed or linked from this repository. A stronger identity may be added as a new field, but cannot silently replace .markdown.ignore compatibility keys.

Planned installation-pipeline gate​

Custom sources are user-controlled inputs and may contain content that has not passed the project’s authoring or governance process. The scanner is intended to be integrated into InstallPipeline after download/extraction and manifest integrity validation, but before target files, MCP configuration, lockfiles, or installation state are written:

sequenceDiagram
participant S as Custom source
participant P as InstallPipeline
participant V as Manifest/integrity validation
participant G as Security gate
participant W as Target/MCP writers

S->>P: Bundle archive
P->>V: Extract and validate
V-->>P: Validated files
P->>G: Scan extracted content
alt Policy passes
G-->>P: Allowed
P->>W: Perform installation side effects
else Finding or incomplete scan
G-->>P: Block or warn according to policy
P-->>S: Return findings without writes when blocking
end

This is an architectural intention, not a claim that all current installation paths already invoke the scanner. The future stage should scan extracted bytes through an in-memory input adapter and make report-only, warning, and blocking policy explicit. The default blocking policy requires a separate rollout and UX decision because it can affect existing custom sources.

Delivery boundaries​

CLI​

packages/cli/src/commands/security-scan.ts parses flags, composes the built-in engine/input/report adapters, invokes app once, serializes output, and maps complete/policy/incomplete outcomes to exit codes. It does not contain detection rules.

VS Code​

apps/vscode-extension/src/services/security-scan-service.ts delegates to app. Extension code owns command registration, settings, trusted-workspace checks, save debounce, scan cancellation, diagnostics, and notifications. New command IDs use the established lowercase promptregistry.* machine identity; existing historical IDs are unchanged.

GitHub Actions​

Actions consume the published CLI. The scanner itself needs no token or network access. Workflows select changed files safely, invoke --ci --ignore-trust none, use least-privilege permissions, and upload only redacted reports through separately reviewed artifact/reporting steps.

Security design constraints​

  • artifact text, paths, frontmatter, and ignore files are untrusted data;
  • no artifact execution, dynamic imports, network lookup, or repository plugin loading;
  • no symbolic-link traversal by default;
  • bounded files, bytes, depth, findings, report size, and duration;
  • worker isolation for blocking evaluation;
  • fixed [REDACTED] secret evidence in default sinks;
  • no raw source content in errors, logs, or telemetry;
  • repository ignore trust is explicit in CI;
  • report/webview output is destination-escaped;
  • incomplete scans cannot be reported as clean.

Testing and maintenance​

Core tests cover pure behavior and fingerprint vectors. Infra tests cover path safety and atomic persistence. App tests cover orchestration and policy. CLI tests cover argv, output, and exit contracts. Extension tests cover registration, trust, diagnostics, debounce, and cancellation.

Rule updates are made one family at a time, with a frozen reference commit, positive/negative cases, adversarial long-input cases, differential results, mapping/remediation review, and a rule-pack digest update. See the Security Scanner Maintenance guide.

See also​