Scanner Surface — Attack-Surface Analysis Framework
Contract ID:
CONTRACT-SCANNER-SURFACE-014Status: Published (reconciled against implementation 2026-05-30) Version: 1.1.0 · Published: 2025-12-05 Owners: Scanner Guild Source of truth:src/Scanner/__Libraries/StellaOps.Scanner.Surface(+.Env,.FS,.Secrets,.Validation)
Overview
This contract defines the Stella Ops Scanner Surface analysis framework: pattern-based attack-surface enumeration and entry-point discovery in StellaOps.Scanner.Surface. It maps the exposed surface of a scanned artifact — network endpoints, process execution, secret access, external calls, and the application’s entry points — into deterministic, attestable records that feed risk scoring and policy signals.
Audience: engineers extending the surface analyzer (new collectors, languages, or signals) and consumers of its JSON/NDJSON output. Sections below are reconciled against the implementation; each section’s “Source:” note points at the authoritative .cs file, and roadmap items are marked not implemented / roadmap.
Scope
SCANNER-SURFACE-01 establishes the foundational surface analysis patterns:
- Entry-point discovery via
IEntryPointCollector(Node/JS-TS implemented; other languages on roadmap) - Attack-surface enumeration and classification via
ISurfaceEntryCollector(pattern-based) - Policy signal emission for surface findings (
SurfaceSignalEmitter; logging-only today) - Determinism + JSON/NDJSON output (
SurfaceAnalysisWriter)
The sibling libraries
Surface.FS,Surface.Env, andSurface.Secretsare general-purpose Scanner surface-storage / environment / secrets components, not direct collaborators of this analyzer — see Integration Points.
Surface Analysis Model
Surface Types
The SurfaceType enum has eight members. Detection today is regex/pattern-based (PatternBasedSurfaceCollector) and route-pattern-based for entry points (NodeJsEntryPointCollector); call-graph, VFS, and data-flow methods are not yet implemented (see Implementation Status).
SurfaceType | Description | Built-in collector | Detection method |
|---|---|---|---|
NetworkEndpoint | Exposed ports, listeners, endpoints | NetworkEndpointCollector (surface.network-endpoint) | Regex patterns |
FileOperation | File-system operations, sensitive file access | none built-in | Regex patterns (when registered) |
ProcessExecution | Command execution, subprocess spawn | ProcessExecutionCollector (surface.process-execution) | Regex patterns |
CryptoOperation | Cryptographic operations, key handling | none built-in | Regex patterns (when registered) |
AuthenticationPoint | Authentication points, session handling | none built-in | Regex patterns (when registered) |
InputHandling | User input handling, injection points | none built-in | Regex patterns (when registered) |
SecretAccess | Secret/credential access patterns | SecretAccessCollector (surface.secret-access) | Regex patterns |
ExternalCall | External service calls, outbound connections | ExternalCallCollector (surface.external-call) | Regex patterns |
Built-in collectors registered by
AddSurfaceAnalysisWithDefaultCollectors():NetworkEndpointCollector,SecretAccessCollector,ProcessExecutionCollector,ExternalCallCollector(surface entries) andNodeJsEntryPointCollector(entry points). The remaining types (FileOperation,CryptoOperation,AuthenticationPoint,InputHandling) are valid enum values but have no built-in collector wired by default; register a customPatternBasedSurfaceCollectorviaAddSurfaceCollector<T>()to emit them.
Surface Entry
Source: src/Scanner/__Libraries/StellaOps.Scanner.Surface/Models/SurfaceEntry.cs, Models/SurfaceType.cs.
public sealed record SurfaceEntry
{
public required string Id { get; init; } // SHA256(type|path|context), lowercase hex
public required SurfaceType Type { get; init; }
public required string Path { get; init; } // File path or endpoint path
public required string Context { get; init; } // Function/method context
public required ConfidenceLevel Confidence { get; init; } // enum, not a 0..1 double
public IReadOnlyList<string> Tags { get; init; } = [];
public required SurfaceEvidence Evidence { get; init; }
// Deterministic ID helper.
public static string ComputeId(SurfaceType type, string path, string context);
}
public sealed record SurfaceEvidence
{
public required string File { get; init; }
public required int Line { get; init; }
public required string Hash { get; init; } // SHA256("file:line:content"), lowercase hex
public string? Snippet { get; init; }
public IReadOnlyDictionary<string, string>? Metadata { get; init; }
}
public enum SurfaceType
{
NetworkEndpoint,
FileOperation,
ProcessExecution,
CryptoOperation,
AuthenticationPoint,
InputHandling,
SecretAccess,
ExternalCall
}
// Confidence is an ordinal enum (NOT a 0.0..1.0 double on the entry).
public enum ConfidenceLevel
{
Low, // ~0.25 numeric value used for threshold/risk math
Medium, // ~0.50
High, // ~0.75
VeryHigh // ~1.00
}
The eight
SurfaceTypemembers above match the implementation exactly. Theidis computed asSHA256(type|path|context)(lowercase hex viaSurfaceEntry.ComputeId) — see Determinism Requirements.
Orchestration API
ISurfaceAnalyzer is the entry point. It runs all registered surface-entry collectors and (optionally) entry-point collectors, sorts results for determinism, computes a risk score, emits signals, and writes output.
Source: src/Scanner/__Libraries/StellaOps.Scanner.Surface/SurfaceAnalyzer.cs.
public interface ISurfaceAnalyzer
{
Task<SurfaceAnalysisResult> AnalyzeAsync(
string scanId,
string rootPath,
SurfaceAnalysisOptions? options = null,
CancellationToken cancellationToken = default);
}
public sealed record SurfaceAnalysisOptions
{
public SurfaceCollectorOptions CollectorOptions { get; init; } = new();
public SurfaceOutputOptions OutputOptions { get; init; } = new();
public bool EmitSignals { get; init; } = true;
public bool DiscoverEntryPoints { get; init; } = true;
public IReadOnlySet<string> Languages { get; init; } = new HashSet<string>();
}
public sealed record SurfaceCollectorOptions
{
public int MaxDepth { get; init; } = 3; // reserved; pattern collectors do not yet walk a call graph
public double MinimumConfidence { get; init; } = 0.7; // threshold against ConfidenceLevel's numeric value
public IReadOnlySet<SurfaceType> IncludeTypes { get; init; } = new HashSet<SurfaceType>();
public IReadOnlySet<SurfaceType> ExcludeTypes { get; init; } = new HashSet<SurfaceType>();
public bool IncludeSnippets { get; init; } = true;
public int MaxSnippetLength { get; init; } = 500;
}
Risk score (summary.riskScore, 0.0–1.0): a confidence-weighted average over entries (per-type weights — SecretAccess 1.0 down to FileOperation 0.3 — times the entry’s ConfidenceLevel numeric value), plus a flat contribution from the entry-point count, clamped to [0, 1]. Computed by SurfaceAnalyzer.CalculateRiskScore.
DI:
AddSurfaceAnalysis()registers the analyzer/registry/emitter/writer;AddSurfaceAnalysisWithDefaultCollectors()additionally registers the built-in collectors and aSurfaceCollectorInitializerwhoseInitialize()must be called once at startup to populate the registry.
Integration Points
Scope note.
StellaOps.Scanner.Surface.FS,.Env, and.Secretsare general-purpose Scanner surface-storage / environment / secrets libraries used across Scanner.Worker and Zastava. They are not dedicated outputs of this surface-analysis framework, and their public APIs differ substantially from earlier drafts of this contract. TheStellaOps.Scanner.Surfaceproject references only.FSand.Env(not.Secretsand notEntryTrace).
Surface.FS Integration
StellaOps.Scanner.Surface.FS publishes a canonical surface manifest (artifact index + determinism metadata), not a list of SurfaceEntry objects.
Source: src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS/ISurfaceManifestWriter.cs, SurfaceManifestModels.cs.
public interface ISurfaceManifestWriter
{
Task<SurfaceManifestPublishResult> PublishAsync(
SurfaceManifestDocument document,
CancellationToken cancellationToken = default);
}
// Canonical manifest (schema "stellaops.surface.manifest@1").
public sealed record SurfaceManifestDocument
{
public const string DefaultSchema = "stellaops.surface.manifest@1";
public string Schema { get; init; } = DefaultSchema;
public string Tenant { get; init; } = "";
public string? ImageDigest { get; init; }
public string? ScanId { get; init; }
public DateTimeOffset GeneratedAt { get; init; }
public SurfaceManifestSource? Source { get; init; }
public IReadOnlyList<SurfaceManifestArtifact> Artifacts { get; init; }
public string? DeterminismMerkleRoot { get; init; }
public SurfaceDeterminismMetadata? Determinism { get; init; }
public ReplayBundleReference? ReplayBundle { get; init; }
public SurfaceFacetSeals? FacetSeals { get; init; } // per-facet drift seals
}
The .FS library also exposes ISurfaceManifestReader, ISurfaceCache / FileSurfaceCache (content cache with quota), IFacetSealExtractor, and SurfaceManifestDeterminismVerifier.
Surface.Env Integration
StellaOps.Scanner.Surface.Env resolves component-scoped surface settings from environment-variable prefixes (first match wins) — it does not define the STELLA_SURFACE_* variables shown in earlier drafts.
Source: src/Scanner/__Libraries/StellaOps.Scanner.Surface.Env/ISurfaceEnvironment.cs, SurfaceEnvironmentSettings.cs, SurfaceEnvironmentOptions.cs.
public interface ISurfaceEnvironment
{
SurfaceEnvironmentSettings Settings { get; }
IReadOnlyDictionary<string, string> RawVariables { get; }
}
public sealed record SurfaceEnvironmentSettings(
Uri SurfaceFsEndpoint,
string SurfaceFsBucket,
string? SurfaceFsRegion,
DirectoryInfo CacheRoot,
int CacheQuotaMegabytes,
bool PrefetchEnabled,
IReadOnlyCollection<string> FeatureFlags,
SurfaceSecretsConfiguration Secrets,
string Tenant,
SurfaceTlsConfiguration Tls);
SurfaceEnvironmentOptions carries the logical ComponentName (default "Scanner.Worker"), the ordered prefix list (via AddPrefix), RequireSurfaceEndpoint (default true), an optional TenantResolver, and a set of KnownFeatureFlags.
Surface.Secrets Integration
StellaOps.Scanner.Surface.Secrets is a secret resolution/provider framework (it hands out secret material such as CAS-access tokens, registry credentials, and attestation keys to surface components). It does not scan files for leaked secrets, and there is no ISurfaceSecretScanner / SecretFinding / SecretScanOptions / IPhysicalFileProvider type anywhere in the codebase.
Source: src/Scanner/__Libraries/StellaOps.Scanner.Surface.Secrets/ISurfaceSecretProvider.cs, SurfaceSecretRequest.cs, SurfaceSecretHandle.cs.
public interface ISurfaceSecretProvider
{
SurfaceSecretHandle Get(SurfaceSecretRequest request);
ValueTask<SurfaceSecretHandle> GetAsync(
SurfaceSecretRequest request,
CancellationToken cancellationToken = default);
}
public sealed record SurfaceSecretRequest(
string Tenant,
string Component,
string SecretType,
string? Name = null);
// SurfaceSecretHandle wraps secret bytes (pooled, zeroed on Dispose) or an
// X509Certificate2Collection plus metadata.
Built-in providers include FileSurfaceSecretProvider, InlineSurfaceSecretProvider, InMemorySurfaceSecretProvider, OfflineSurfaceSecretProvider, and the decorators CompositeSurfaceSecretProvider, CachingSurfaceSecretProvider, and AuditingSurfaceSecretProvider. Known secret types: CAS access, registry access, and attestation (CasAccessSecret, RegistryAccessSecret, AttestationSecret).
Surface.Validation
StellaOps.Scanner.Surface.Validation (not referenced in earlier drafts) provides ISurfaceValidator / ISurfaceValidatorRunner with built-in validators (SurfaceCacheValidator, SurfaceEndpointValidator, SurfaceSecretsValidator) that check resolved surface configuration and emit SurfaceValidationIssues.
Policy Signals
Surface Signal Keys
Source: src/Scanner/__Libraries/StellaOps.Scanner.Surface/Signals/SurfaceSignalEmitter.cs.
public static class SurfaceSignalKeys
{
public const string NetworkEndpoints = "surface.network.endpoints";
public const string ExposedPorts = "surface.network.ports";
public const string FileOperations = "surface.file.operations";
public const string ProcessSpawns = "surface.process.spawns";
public const string CryptoUsage = "surface.crypto.usage";
public const string AuthPoints = "surface.auth.points";
public const string InputHandlers = "surface.input.handlers";
public const string SecretAccess = "surface.secrets.access";
public const string ExternalCalls = "surface.external.calls";
public const string TotalSurfaceArea = "surface.total.area";
public const string RiskScore = "surface.risk.score";
public const string EntryPointCount = "surface.entrypoints.count";
}
BuildSignals(result)always emitsTotalSurfaceArea,RiskScore, andEntryPointCount, plus one per-type count keyed by the entries actually detected.ExposedPortsis declared but not emitted byBuildSignalstoday.
Signal Emission
The default SurfaceSignalEmitter logs the signals (it does not yet emit to a message bus or the policy engine — the production wiring is a TODO in the source).
public interface ISurfaceSignalEmitter
{
Task EmitAsync(
string scanId,
IDictionary<string, object> signals,
CancellationToken cancellationToken = default);
}
Entry Point Discovery
Language Analyzer Integration
Entry-point discovery is implemented by IEntryPointCollectors registered in the ISurfaceEntryRegistry. Today exactly one collector is shipped and registered by default: NodeJsEntryPointCollector (CollectorId = "entrypoint.nodejs", SupportedLanguages = { javascript, typescript, js, ts }). It detects routes for:
| Analyzer | Status | Frameworks detected |
|---|---|---|
| Node (JS/TS) | Implemented (NodeJsEntryPointCollector) | Express, Fastify, Koa, Hapi, NestJS |
| .NET | Not implemented (roadmap — Phase 2) | Controllers, Minimal APIs, SignalR hubs |
| Java | Not implemented (roadmap — Phase 2) | Servlets, JAX-RS, Spring MVC |
| Python | Not implemented (roadmap — Phase 2) | Flask/Django views, FastAPI |
| Go | Not implemented (roadmap) | HTTP handlers, gRPC |
| PHP | Not implemented (roadmap) | Routes, controller actions |
| Deno | Not implemented (roadmap) | HTTP handlers, permissions |
The Node collector’s
EntryPoint.Idis a 16-char prefix ofSHA256("file:method:path"), and it always setsLanguage = "javascript"(even for TypeScript inputs) — the original source language is reflected inFramework/file extension, not inLanguage.
Entry Point Model
Source: src/Scanner/__Libraries/StellaOps.Scanner.Surface/Models/EntryPoint.cs.
public sealed record EntryPoint
{
public required string Id { get; init; }
public required string Language { get; init; }
public string? Framework { get; init; } // nullable
public required string Path { get; init; } // URL path or route
public string? Method { get; init; } // HTTP method or RPC, nullable
public required string Handler { get; init; } // function/method name
public required string File { get; init; }
public int Line { get; init; }
public IReadOnlyList<string> Parameters { get; init; } = [];
public IReadOnlyList<string> Middlewares { get; init; } = [];
}
Output Schema
Surface Analysis Result
SurfaceAnalysisWriter serializes SurfaceAnalysisResult with camelCase property names and a JsonStringEnumConverter(camelCase) — so type and confidence serialize as lowercase enum names (e.g. "networkEndpoint", "high"), not as numbers, and byType keys are camelCase enum names. riskScore lives under summary (there is no top-level riskScore). Null members (e.g. an absent snippet/metadata) are omitted. Output is compact JSON by default; set SurfaceOutputOptions.PrettyPrint for indented output.
{
"scanId": "scan-abc123",
"timestamp": "2025-12-05T12:00:00Z",
"summary": {
"totalEntries": 42,
"byType": {
"networkEndpoint": 15,
"fileOperation": 10,
"processExecution": 5,
"cryptoOperation": 8,
"secretAccess": 4
},
"riskScore": 0.65
},
"entries": [
{
"id": "9f1c…",
"type": "networkEndpoint",
"path": "src/Controllers/UserController.cs",
"context": "GetUsers",
"confidence": "veryHigh",
"tags": ["network", "listener", "port"],
"evidence": {
"file": "src/Controllers/UserController.cs",
"line": 42,
"hash": "…",
"metadata": { "pattern_id": "net-listen-port", "match": "…" }
}
}
],
"entryPoints": []
}
SurfaceAnalysisWriter writes to {OutputPath}/surface-{scanId}.json when WriteToFile is true and OutputPath is set. It also exposes WriteNdjsonAsync(result) which streams one { "type": "summary"|"entry"|"entrypoint", "data": … } record per line.
Analysis Store Key
The store-key constant is defined on SurfaceAnalysisResult, not as a free-standing SurfaceAnalysisKey field:
// SurfaceAnalysisResult.StoreKey
public const string StoreKey = "scanner.surface.analysis";
Determinism Requirements
- Stable IDs: Surface-entry IDs computed as
SHA256(type|path|context), lowercase hex (SurfaceEntry.ComputeId). Node entry-point IDs use a 16-char prefix ofSHA256("file:method:path"). - Sorted Output:
SurfaceAnalyzersorts bothentriesandentryPointsbyIdusingStringComparison.Ordinalbefore building the result. - Reproducible Hashes: All hashing is SHA-256 — evidence hashes (
SHA256("file:line:content")), entry/entry-point IDs, and the.FSmanifest Merkle root (SurfaceManifestDeterminismVerifiercomputessha256:…digests). BLAKE3 is not used. - JSON serialization: Output uses
System.Text.Jsonwith camelCase naming andJsonStringEnumConverter. Keys are not re-sorted into a canonical order beyond the record’s declaration order; the determinism guarantee comes from the sorted entry/entry-point arrays, not from key canonicalization.
Implementation Status
Phase 1 (core framework) is implemented in StellaOps.Scanner.Surface. Phases 2–3 are partially complete; the boxes below reflect the current state of the code.
Phase 1: Core Framework — implemented
- [x]
SurfaceEntry/SurfaceEvidence/EntryPointmodels - [x] Collector registry (
ISurfaceEntryRegistry/SurfaceEntryRegistry) and DI wiring - [x] Pattern-based collectors + Node entry-point collector
- [x] Signal emission (
SurfaceSignalEmitter— logging only; bus/policy wiring is TODO) - [x] JSON + NDJSON output writer (
SurfaceAnalysisWriter) - [ ]
.FSmanifest writer wired into the analyzer output path (the analyzer writes its own JSON file; it does not callISurfaceManifestWriter)
Note: the
.FSISurfaceManifestWriter/ manifest model is a separate Scanner surface-storage concern (used by Scanner.Worker), not invoked bySurfaceAnalyzer.
Phase 2: Language Integration — partial
- [x] Node (JS/TS) entry-point discovery (
NodeJsEntryPointCollector) - [ ] .NET entry-point discovery (roadmap)
- [ ] Java entry-point discovery (roadmap)
- [ ] Python entry-point discovery (roadmap)
Phase 3: Advanced Analysis — not started
- [ ] Data-flow tracking
- [ ] Crypto / auth / input / file collectors (enum values exist; no built-in collectors)
- [ ] Attack-path enumeration
- [ ] EntryTrace / call-graph integration (no dependency on
StellaOps.Scanner.EntryTracetoday)
Project Structure
src/Scanner/__Libraries/StellaOps.Scanner.Surface/
├── StellaOps.Scanner.Surface.csproj
├── SurfaceAnalyzer.cs # ISurfaceAnalyzer + risk scoring
├── SurfaceServiceCollectionExtensions.cs # AddSurfaceAnalysis[WithDefaultCollectors]
├── Models/
│ ├── SurfaceEntry.cs # SurfaceEntry + SurfaceEvidence
│ ├── SurfaceType.cs # SurfaceType + ConfidenceLevel
│ └── EntryPoint.cs # EntryPoint + Summary + Result
├── Discovery/
│ ├── ISurfaceEntryCollector.cs # + IEntryPointCollector, options, context
│ └── SurfaceEntryRegistry.cs
├── Collectors/
│ ├── PatternBasedSurfaceCollector.cs # base class + SurfacePattern
│ ├── NetworkEndpointCollector.cs
│ ├── SecretAccessCollector.cs
│ ├── ProcessExecutionCollector.cs
│ ├── ExternalCallCollector.cs
│ └── NodeJsEntryPointCollector.cs
├── Signals/
│ └── SurfaceSignalEmitter.cs # + SurfaceSignalKeys
├── Output/
│ └── SurfaceAnalysisWriter.cs # + SurfaceOutputOptions
└── TASKS.md
There is no
README.mdin this project; status/notes live inTASKS.md.
Dependencies
The StellaOps.Scanner.Surface.csproj references exactly two sibling libraries (plus DI/Logging/Options abstractions):
StellaOps.Scanner.Surface.FS— surface manifest store + content cacheStellaOps.Scanner.Surface.Env— component-scoped surface environment settings
Related libraries that are not referenced by this project (used elsewhere in Scanner, listed for context):
StellaOps.Scanner.Surface.Secrets— secret-resolution/provider framework (not a scanner of leaked secrets)StellaOps.Scanner.Surface.Validation— surface configuration validatorsStellaOps.Scanner.EntryTrace— entry-point tracing (referenced by earlier drafts; not a dependency today)
Related Contracts
- RichGraph v1 — function-level reachability graph schema (the call-graph surface that EntryTrace/reachability integration will eventually feed).
- Sealed Mode — air-gap operation with CAS.
- Mirror Bundle — offline transport format.
- Verification Policy — DSSE verification rules.
Changelog
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0.0 | 2025-12-05 | Scanner Guild | Initial contract |
| 1.1.0 | 2026-05-30 | Docs reconciliation | Reconciled against StellaOps.Scanner.Surface implementation: corrected SurfaceEntry/EntryPoint/SurfaceType models (sealed records, required, enum ConfidenceLevel); rewrote .FS/.Env/.Secrets integration to match real APIs (manifest writer, ISurfaceEnvironment, secret-provider framework — removed fabricated ISurfaceSecretScanner/STELLA_SURFACE_*); added missing signal keys (ExternalCalls, RiskScore, EntryPointCount); documented ISurfaceAnalyzer/options/risk score; fixed output JSON (camelCase + string enums); corrected determinism (SHA-256, not BLAKE3); listed built-in collectors + Node-only entry-point discovery; fixed project structure (no README; added Collectors) and dependencies (only .FS+.Env; no .Secrets/EntryTrace). |
