A plain account of how CrossConnect discovers, stores, protects, and audits your network data, with enough technical detail to stand up to review. Written for the IT and security professionals who must approve it before it touches their network.
This reference is built to answer the questions a security review asks, before you ask them. Each section names the actual mechanism, not just an adjective: the algorithm, the protocol, the port, the library and version, the exact behavior. Where a control is something you turn on at install rather than a default, it is labelled as such.
CrossConnect is a network source-of-truth and operational-intelligence platform. It builds one model of your network, the devices, IP space, VLANs, cabling, circuits, routing, and the live state behind them, and lets your team ask it questions in plain language. Four facts frame every control in this document:
Discovery uses read-only credentials and read-only protocol operations. CrossConnect never writes configuration to a network device. No packet capture, no payload inspection, it reads switch-derived signals and the announcements gear already broadcasts.
Self-hosted in your data center, private cloud, or a managed instance you control. Your network data stays in your PostgreSQL system of record. There is no mandatory vendor cloud in the data path.
Secrets are encrypted with AES-256-GCM under an envelope scheme: a key-encryption key (KEK) wraps a data-encryption key (DEK). The master key never ships in code. It is read from your environment, secret manager, or KMS (key management service), and it rotates without re-encrypting data.
Every change is written to a hash-chained, HMAC-signed audit trail. Altering any historical entry breaks the chain and is detectable on verification. The chain is the integrity evidence behind every answer.
CrossConnect is a single deployable application backed by PostgreSQL, plus an optional formal-analysis sidecar (a helper process for whole-network config analysis). It reaches into the customer network only with outbound, read-only connections. It accepts operator and API traffic only on its own inbound application port. The diagram marks every edge with its transport and direction.
flowchart LR
subgraph CN["CUSTOMER NETWORK"]
direction TB
DEV["Switches / routers
SNMP · SSH · LLDP"]
WL["Wireless / cloud-managed
vendor REST (HTTPS)"]
AV["AV / media endpoints
mDNS / SSDP"]
OP["Operators
browser / API via SSO"]
end
subgraph CC["CROSSCONNECT DEPLOYMENT, you control"]
direction TB
APP["CrossConnect app
REST · UI · collectors · grounded AI"]
PG[("PostgreSQL
system of record")]
BF["Batfish sidecar
(optional)"]
end
subgraph EXT["EXTERNAL SERVICES"]
direction TB
IDP["SSO IdP
OIDC"]
SIEM["SIEM / webhook
HMAC-signed"]
LLM["LLM endpoint
your key, optional"]
end
DEV -- "read-only · 161/UDP · 22 · 443" --> APP
WL --> APP
AV --> APP
OP -- "inbound HTTPS (app port)" --> APP
APP -- "JDBC / TLS" --> PG
APP <-- "RPC" --> BF
APP --> IDP
APP --> SIEM
APP --> LLM
classDef store fill:#1797b3,stroke:#0d7d90,color:#ffffff;
classDef ext fill:#ffffff,stroke:#9aa8c0,color:#173a6b;
class PG store;
class DEV,WL,AV,OP,IDP,SIEM,LLM,BF ext;
| Trust boundary | What lives there | How it is crossed |
|---|---|---|
| Customer network | Managed devices, wireless/cloud controllers, AV endpoints, operators | Outbound read-only discovery; inbound operator/API sessions to the app port |
| CrossConnect deployment | Application, PostgreSQL system of record, optional Batfish sidecar, encryption keys | Internal only; JDBC over TLS to Postgres, single-session RPC to Batfish |
| External services | Your SSO IdP, your SIEM/webhook receivers, an optional LLM endpoint | OIDC redirect, HMAC-signed outbound webhooks, AI calls under your own key |
CrossConnect collects network state and inventory facts. It does not collect user content, application payloads, or the contents of packets. Everything below is metadata about the network and the devices on it: facts the equipment already publishes through its control plane, read straight from there.
| Category | Facts collected | Source |
|---|---|---|
| Device inventory | sysName, sysDescr, model, serial, software version, role, DRAM/flash, uptime, reachability | SNMP system & ENTITY MIB |
| Interfaces | name/alias/index, type, MTU, speed, MAC, admin/oper status, PoE draw, stacking | SNMP ifTable / POE-MIB |
| Layer-2 topology | LLDP neighbor adjacencies: remote chassis/port id, remote system name | SNMP LLDP-MIB |
| VLANs & VRFs | static VLAN ids + names, VRF names + route distinguishers, port assignments | SNMP Q-BRIDGE / MPLS-L3VPN MIB |
| IP address management | configured IPs + masks, prefixes, ARP/forwarding-derived endpoints (MAC→IP) | SNMP IP-MIB / BRIDGE-MIB |
| Routing | BGP sessions (local/peer AS, state), OSPF adjacencies (neighbor id, state) | SNMP BGP4 / OSPF MIB, Batfish |
| Multicast | IGMP querier state, group memberships per interface | SNMP IGMP-MIB |
| Precision timing | PTP domain, profile, grandmaster, clock class, port membership | SNMP PTPBASE-MIB |
| Environment | temperature, humidity, power-supply status sensors | SNMP ENTITY-SENSOR-MIB |
| Running configuration | full device config text for drift detection and formal analysis opt-in | SSH read of show running-config |
| Cloud-managed facts | VLANs, switch-port profiles, L3 firewall intent, SSIDs from vendor dashboards | Vendor REST API (HTTPS) |
| AV & media | service announcements (Dante/AES67/NDI/Q-SYS), media flows, vendor by MAC OUI | mDNS / WS-Discovery / SSDP |
| Wireless & occupancy | AP/SSID inventory, radio config, zone counts when integrated | Wireless vendor cloud APIs |
| Operator & external input | documented records, bulk imports, asserted facts from other systems | UI / REST / inbound event API |
Every collection path is a real protocol implementation with a fast read timeout and a clean way to fail. A device it cannot reach is logged and counted, it does not block the rest of the sweep, and it never falls back to writing anything. The exact libraries and pinned versions are listed below so you can match them against your own CVE (vulnerability) feeds.
| Mechanism | What it does | Library (pinned) | Default |
|---|---|---|---|
| SNMP v1/v2c/v3 | Read-only GET / GETBULK walks of the MIBs in §3. v3 USM supports authPriv (SHA/AES). | snmp4j 3.8.2 | on |
| SSH config read | Interactive shell, runs read-only show-class commands, captures config text only. | sshj 0.38.0 | opt-in |
| Vendor REST | HTTPS pulls from cloud-managed dashboards; bearer token; redirects blocked; guarded against SSRF (server-side request forgery). | JDK HttpClient | on credential |
| Formal analysis | Whole-fleet config model for reachability, ACL, and IPAM-conflict questions. | Batfish sidecar (RPC) | optional |
| Link-local discovery | Passive listen for mDNS / WS-Discovery / SSDP service announcements. | JDK NIO multicast | off |
| Inbound event API | Other systems POST facts they assert; these are held as proposals and never applied automatically. | Spring REST | authn-gated |
| Platform component | Version | Role |
|---|---|---|
| Java | 21 (LTS) | Runtime |
| Spring Boot | 3.4.0 | Application framework, REST, filters |
| PostgreSQL JDBC | 42.7.4 | System-of-record driver |
| Flyway | 10.20.1 | Versioned schema migrations |
| Vaadin Flow | 24.5.7 (LTS) | Server-rendered operator UI |
| LangChain4j | 0.36.2 | AI orchestration (provider-pluggable) |
| OpenTelemetry | 1.43.0 | Tracing / observability export |
Most security teams turn here first, because this is a system that holds credentials and reaches into the network. CrossConnect is built so the answer to every concern is structural (something the design makes impossible) rather than procedural (something we promise to do).
flowchart LR CC["CrossConnect
read-only credentials
fast-timeout reads"] DEV["Network device
SNMP agent / SSH"] CC -- "GET / GETBULK · show running-config" --> DEV DEV -. "no config write path exists" .-x CC classDef app fill:#173a6b,stroke:#0f2a4f,color:#ffffff; classDef ext fill:#ffffff,stroke:#9aa8c0,color:#173a6b; class CC app; class DEV ext;
set, a configure, or any
state-changing command to a managed device.Provision a dedicated, least-privilege service account. Read-only SNMP and a read-only SSH/show role are sufficient. The platform requests nothing more.
The collector has no “apply” path to a device. It is collection-only software; remediation is advice for a human to act on, never an automated change.
The SNMP OID families and the read-only command set the SSH collector issues are enumerated in Appendix B, so you can scope and audit the account precisely.
Use SNMPv3 USM (authPriv) instead of v1/v2c community strings, and SSH public keys instead of passwords. The platform supports the hardened option on every path.
Device credentials are stored AES-256-GCM encrypted (§11). They are decrypted only in memory, only at the moment of a probe, and are never logged or repeated back in an answer.
Discovery only ever connects outward from the platform. Optional collection on an isolated segment uses an outbound-only relay with no inbound listening ports.
This is the complete list of connections. The only things that come in to the platform are the application port and the internal database port. Everything that touches the network goes outward. The full matrix is in Appendix A.
| Direction | Port / protocol | Purpose | Auth |
|---|---|---|---|
| Outbound → devices | 161/UDP SNMP | Read MIBs | v2c community / v3 USM |
| Outbound → devices | 22/TCP SSH | Config read (opt-in) | key or password |
| Outbound → vendor cloud | 443/TCP HTTPS | Cloud-managed facts | bearer API key |
| Inbound (app) | 8080/443 TCP HTTPS | UI, REST | SSO session / JWT / API key |
| Internal | 5432/TCP JDBC | System of record | DB credential, TLS |
| Internal | RPC to sidecar | Formal analysis | private network only |
| Outbound → you | 443/TCP HTTPS | Webhooks, SIEM sinks | HMAC-SHA256 signed |
| Outbound → IdP/LLM | 443/TCP HTTPS | SSO, optional AI | OIDC, your LLM key |
PostgreSQL is the single system of record. There is no secondary data lake, no analytics warehouse, and no copy held on the vendor side. Each kind of data is stored separately, so the protection can match how sensitive it is.
| Data class | Stored as | Protection |
|---|---|---|
| Source of truth | Canonical entities: device, interface, cable, ip_address, prefix, vlan, vrf, circuit, network_service | Storage-level encryption (§10 L1); tenant-scoped |
| Observations (staging) | Append-only discovered_* rows, newest-per-key operative | Storage-level; auto-purged (§18) |
| Secrets | snmp_credential, ssh_credential, secret, cloud tokens, webhook secrets | Field-level AES-256-GCM (§10 L2) |
| Audit trail | event_audit, system_event_audit, ai_audit_entry | Hash-chained + HMAC-signed (§14) |
| Derived results | None persisted, compliance, quality, reachability are pure functions of a snapshot, short-TTL cached only | In-memory cache; no new facts written |
Multi-tenancy. The tenant is the line that keeps data separate. Every row carries a tenant_id,
every query filters on it, and foreign-key constraints stop any row from belonging to the wrong tenant. A request
filter rejects any /api/v1/* call that does not resolve to an active tenant. A single-organization
deployment simply runs as one tenant, and the same boundary governs instances that serve many organizations.
A discovered fact is never quietly accepted as truth. Observations land in a staging area first, where they are compared against the documented model and given a confidence score. They move into the source of truth only by a human action or an explicit rule that confirms them. This gate is the platform’s central integrity boundary, and it is the reason an answer can be trusted.
flowchart LR O["1 · Observe
staging, untrusted"] --> G{"2 · Validate
confidence-score · commit"} G --> S["3 · Documented
source of truth"] S --> D["4 · Derived
pure functions"] D --> OUT["5 · Output
cited answers"] classDef gate fill:#fdf0dd,stroke:#e0892a,color:#173a6b; classDef truth fill:#173a6b,stroke:#0f2a4f,color:#ffffff; classDef store fill:#e3f3f6,stroke:#1797b3,color:#173a6b; class G gate; class S truth; class O,D,OUT store;
X-Content-Type-Options, X-Frame-Options) are
applied by a response filter. GAEncryption at rest works in layers. Storage-level encryption covers all data. On top of that, field-level envelope encryption protects secrets with managed keys you can rotate. The underlying algorithm is AES-256-GCM (AEAD), with a fresh 12-byte random IV (initialization vector) per value and a 128-bit authentication tag. If a value has been tampered with, the tag fails on decrypt, so the change is caught rather than silently accepted.
| Layer | Covers | Mechanism |
|---|---|---|
| L1 · Storage | All operational, audit, and inventory data | Encrypted volume / managed-DB encryption (your KMS or full-disk encryption) |
| L2 · Field | All secrets (device creds, API tokens, webhook secrets, AI keys) | AES-256-GCM via an application cipher, keyed by a per-deployment data key |
| L3 · Keys | The keys that protect L2 | Envelope encryption: the KEK (key-encryption key) wraps the DEK (data-encryption key); the KEK is read from env / command / KMS |
flowchart LR
subgraph SRC["KEK source, priority order"]
direction TB
K1["1 · env variable (256-bit)"]
K2["2 · command → KMS / Vault"]
K3["3 · key file (0600)"]
K4["prod + no key → fail closed"]
end
KEK(["KEK (master)
never in code"])
DEK(["DEK
wrapped by KEK"])
P["plaintext secret
in memory only"]
CT["stored ciphertext
keyId : base64(IV ‖ ct ‖ tag)"]
SRC --> KEK
KEK -- "unwraps" --> DEK
DEK -- "AES-256-GCM" --> P
P --> CT
classDef key fill:#1797b3,stroke:#0d7d90,color:#ffffff;
classDef store fill:#e3f3f6,stroke:#1797b3,color:#173a6b;
classDef plain fill:#ffffff,stroke:#2f6fb0,color:#173a6b;
class KEK,DEK key;
class CT store;
class P plain;
The master key is looked up in priority order: (1) an explicit environment variable, (2) an external command
(a hook with a 15-second time limit that can fetch the key from HashiCorp Vault, AWS or GCP KMS, or a secret
manager) deployment option, (3) an auto-generated key file created with
0600 permissions on first boot. If a production profile starts with no key configured, the application
fails closed and refuses to start, and a placeholder sentinel value always fails closed too. No encryption
key is ever compiled into the build.
| Secret | At rest | In use |
|---|---|---|
| SNMP community / v3 keys | AES-256-GCM, per-tenant | Decrypted in memory at probe time; never logged |
| SSH password / private key | AES-256-GCM | Decrypted only for a read session |
| Vendor cloud API tokens | AES-256-GCM | Decrypted for an HTTPS pull; SSRF-validated target |
| Webhook signing secret | AES-256-GCM | Used to HMAC outbound payloads; rotatable |
| AI provider key | AES-256-GCM (or env) | Loaded for the model call only; never echoed |
No secret is stored in plaintext, written to application logs, or returned in an API response or AI answer. If you prefer, credentials can be pulled at runtime from your secret manager instead of the application database, using the same key-command mechanism described in §10. The platform admin secret is stored as a SHA-256 hash and compared in constant time (so timing cannot leak it); the token-signing secret is held as the HMAC key that signs sessions and the audit chain, and session signatures are likewise verified in constant time.
Your identity provider says who the user is, their role says what they can do, and every request is scoped to the tenant. When SSO (single sign-on) is enabled, operators get no local passwords at all. The IdP (identity provider) stays the system of record for identity and group membership.
flowchart LR IDP["Your IdP
OIDC"] --> SSO["SSO + PKCE / nonce
groups → role claim"] SSO --> JWT["Signed session
tenant · role · user"] JWT --> RBAC{"RBAC + tenant filter
per request"} RBAC --> DATA["tenant-scoped data
least privilege"] classDef ext fill:#ffffff,stroke:#9aa8c0,color:#173a6b; classDef gate fill:#fdf0dd,stroke:#e0892a,color:#173a6b; classDef truth fill:#173a6b,stroke:#0f2a4f,color:#ffffff; classDef store fill:#e3f3f6,stroke:#1797b3,color:#173a6b; class IDP ext; class SSO,JWT truth; class RBAC gate; class DATA store;
iss,
aud, exp, and nonce (which defends against replay attacks). Group and role
claims map to platform roles. Configurable| Role | Grants |
|---|---|
| Viewer | Read-only across inventory, compliance, and secrets metadata |
| Editor | Viewer + create/update/delete records, config and IPAM writes, report management |
| Admin | All of the above + user management, key rotation, and administrative endpoints |
Roles are rank-ordered, meaning an action requires at least a certain role, and request filters enforce this:
any request that changes data requires Editor or above, user management requires Admin, and administrative
endpoints sit behind a separate admin credential that can be set to fail closed if it is not configured. IdP groups
map onto these roles, so access is governed centrally by the groups your directory already maintains. Every
/api/v1/* request is also tied to an active tenant, and an optional hardening flag rejects any request
that does not present a signed session or API key.
flowchart LR CC["CrossConnect
event payload"] --> H["HMAC-SHA256(secret,
raw body)"] H --> SIG["signed delivery
X-CrossConnect-Signature header"] SIG --> RX["Your receiver
verify constant-time"] classDef app fill:#173a6b,stroke:#0f2a4f,color:#ffffff; classDef store fill:#e3f3f6,stroke:#1797b3,color:#173a6b; classDef ext fill:#ffffff,stroke:#9aa8c0,color:#173a6b; class CC app; class H,SIG store; class RX ext;
X-CrossConnect-Signature header, so the receiver can confirm
they are genuine even apart from TLS, and you can rotate the shared secret with an overlap period (so there is no
downtime).The signing secret is stored encrypted, and each delivery carries the HMAC-SHA256 signature of the raw body in an
X-CrossConnect-Signature header. On the
receiving side, verification should use a constant-time comparison. Inbound webhooks the platform accepts (for
example presence updates or facts asserted by another system) are themselves authenticated and held as proposals.
They are never applied to the source of truth without passing the trust gate in §8.
Every meaningful action, a record change, a config apply, a secret edit, a discovery run, a key rotation, an AI prompt, is captured in an audit trail. That trail is not just append-only: each entry is cryptographically linked to the one before it, so quiet tampering can be detected, not merely discouraged.
flowchart LR E1["entry n-1
hash = H(… · prev)
HMAC signature"] --> E2["entry n
hash = H(… · prev)
HMAC signature"] E2 --> E3["entry n+1
hash = H(… · prev)
HMAC signature"] classDef link fill:#fbfcfe,stroke:#1797b3,color:#173a6b; class E1,E2,E3 link;
contentHash = SHA-256(tenant · kind · time · actor · payload · previousHash). Change any past record and
every link after it breaks. A verification pass recomputes the chain and reports exactly where the break is.The assistant is grounded and tightly constrained. It answers questions about the source of truth, and it does not touch the network.
Every answer cites the record it used or says “I don’t know.” A citation validator rejects any answer that points to a record the tools did not actually return, so the model cannot invent devices, IPs, or relationships.
The assistant sees only the records the user is allowed to see. It honors RBAC and tenant isolation exactly as the rest of the API does.
It explains, highlights, and tells you how to fix. Any change is queued as an intent for confirm-before-commit by a human. It never executes a change on a device.
Every prompt, the records retrieved, and the output are logged to a tenant-scoped AI audit entry, so any answer can be reconstructed and reviewed.
Provider & data flow. You choose the AI provider. It can run against a model endpoint you name, under your own API key, or you can switch it off entirely, in which case the platform falls back to fixed, non-AI responses. Only the question and the specific records needed to answer it are sent to the model. Secrets and ciphertext are never included. If data residency for AI matters to you, point the assistant at a private or in-region model endpoint. Configurable
| Data | Default retention | Mechanism |
|---|---|---|
Observations (discovered_*) | rolling window (operator-set) | Scheduled staging purge sweep |
| Audit chain | rolling window, integrity-preserving | Chain-aware purge sweep |
| Health / sensor samples | latest-per-sensor + window | Retention sweep |
| Source of truth | lifecycle of the record | Soft-delete (deleted_at), hard-delete on request |
| Occupancy / wireless | windowed | Retention service when integrated |
Residency. Because the deployment and its PostgreSQL live in the region and account you choose, data residency is whatever you set it to. No network data has to be processed on the vendor side. The only optional external data path is an AI model endpoint, which you pick (you can keep it in-region or private) or turn off.
CrossConnect ships as a set of containers (the application, PostgreSQL, and the optional analysis sidecar) and runs under Docker Compose, Kubernetes, or a managed-database setup. The recommended production hardening checklist follows:
Set the admin and signing secrets (enable fail-closed validation); enable SSO; require a signed session or API key on the API; map IdP groups to roles.
Provide the master key from your KMS/secret manager or persist and back up the generated key file separately from database backups; schedule KEK rotation.
Terminate TLS at the app or LB; enable PostgreSQL TLS; place the database volume on encrypted storage or a managed encrypted instance.
Use a dedicated read-only service account; prefer SNMPv3 authPriv and SSH keys; scope the account to Appendix B; keep config collection opt-in where not needed.
Export the audit stream to your SIEM and OpenTelemetry traces to your collector; monitor audit-chain verification and key-rotation events.
Allow only the outbound discovery ports the deployment needs; the platform opens no inbound listeners toward the customer network.
We describe where we stand in precise, defensible language. CrossConnect’s controls line up with the recognized frameworks, and we claim no certification we do not actually hold.
| Framework | Posture & phrasing |
|---|---|
| SOC 2 | An attestation, not a certification. The control design (access, encryption, logging, change management) is aligned to the Trust Services Criteria, and a report is being pursued as the program matures. |
| ISO 27001 | Controls aligned to Annex A; certification is a roadmap item, stated as “in progress” rather than achieved until an accredited audit completes. |
| NIST CSF 2.0 / 800-53 | Aligned to (voluntary, not certifiable): Govern, Identify, Protect, Detect, Respond, Recover map to the controls in §§5–17. |
| Source | Destination | Port / proto | Dir | Encrypted | Auth |
|---|---|---|---|---|---|
| Platform | Managed device | 161/UDP SNMP | out | v3 (authPriv) | community / USM |
| Platform | Managed device | 22/TCP SSH | out | yes | key / password |
| Platform | Vendor cloud | 443/TCP HTTPS | out | yes | bearer token |
| Operator / API client | Platform | 8080 or 443/TCP | in | TLS | SSO / JWT / API key |
| Platform | PostgreSQL | 5432/TCP JDBC | internal | TLS (recommended) | DB credential |
| Platform | Batfish sidecar | RPC | internal | private net | n/a (isolated) |
| Platform | Webhook / SIEM | 443/TCP HTTPS | out | yes | HMAC-SHA256 |
| Platform | IdP | 443/TCP HTTPS | out | yes | OIDC |
| Platform | LLM endpoint (optional) | 443/TCP HTTPS | out | yes | your API key |
The collector account needs only read access. SNMP collection issues GET / GETBULK against these MIB families,
and the SSH path (opt-in) issues read-only show-class commands to capture the configuration text. No
OID that would change state, and no configuration command, is ever issued.
| Area | MIB / source | Operation |
|---|---|---|
| System / inventory | system group, ENTITY-MIB | GET |
| Interfaces / PoE / stack | IF-MIB, POE-MIB | GETBULK walk |
| Neighbors | LLDP-MIB | GETBULK walk |
| VLAN / VRF | Q-BRIDGE-MIB, MPLS-L3VPN-MIB | GETBULK walk |
| IP / endpoints | IP-MIB, BRIDGE-MIB | GETBULK walk |
| Routing | BGP4-MIB, OSPF-MIB | GETBULK walk |
| Multicast / timing / sensors | IGMP-MIB, PTPBASE-MIB, ENTITY-SENSOR-MIB | GETBULK walk |
| Configuration (opt-in) | SSH | show running-config (read) |
Where the common assessment instruments (CAIQ/CCM, SIG Lite, VSAQ) ask, this document answers:
| Questionnaire domain | Answered in |
|---|---|
| Identity & Access Management | §12 (SSO, RBAC, grouping, tenant isolation) |
| Cryptography, Encryption & Key Mgmt | §9, §10, §11 |
| Logging & Monitoring | §14 (tamper-evident audit, SIEM export) |
| Application / Product Security & SDLC | §16 |
| Threat & Vulnerability Management | §17 |
| Network / Infrastructure Security | §2, §5, §6, §19 |
| Data Security & Privacy Lifecycle | §3, §7, §18 |
| AI / Model Governance | §15 |
| Supply Chain / Sub-processors | Self-hosted; no required sub-processor in the data path (§1, §18) |
| Governance, Risk & Compliance | §20 |
Hardening is driven by environment settings. A representative set of controls follows. The defaults favor working out of the box, while a production setup turns on the fail-closed flags:
| Control | Setting | Production |
|---|---|---|
| Master encryption key | CROSSCONNECT_CREDENTIALS_AES_KEY / …_KEY_COMMAND / …_KEY_FILE | from KMS or backed-up key file |
| Token signing secret | crossconnect.auth.signing-secret | set, stable |
| Admin secret | crossconnect.auth.admin-secret | set |
| Require credential on API | crossconnect.auth.require-credential | true |
| Require admin secret | crossconnect.auth.require-admin-secret | true |
| Fail closed on missing secrets | CROSSCONNECT_SECURITY_REQUIRE_SECRETS | true |
| Session lifetime | CROSSCONNECT_AUTH_TOKEN_TTL_HOURS | tuned to policy |
| SSO (OIDC) | CROSSCONNECT_OIDC_ENABLED + issuer/client | true |
| Config collection | crossconnect.discovery.collect-config | only if needed |