Securely Importing and Exporting Files in Air-Gapped and Network-Segmented Environments

IT EngineersDevelopers

Getting Started

Core Concept

Importing and exporting approved files through defined paths and procedures

In separated network environments such as air-gapped and external networks, manage file transfer directions, destination paths, and processing criteria according to your operational requirements.

File import and export can be configured so that users request files for transfer, which then pass through defined approval and validation procedures before being transferred to the designated environment.

For example, files intended for use in an external environment can be imported into an air-gapped network, or result files generated during internal operations can be exported to an external environment after going through the required approval process.

Transfer Flow

Connect the process from file request through approval and validation to final import or export

File transfers in network-segmented environments follow a defined procedure from file selection through delivery to the target environment.

text
File Request
   ↓  

Transfer Target Confirmation
   ↓  

Apply Approval Criteria
   ↓  

File Validation
   ↓  

Import or Export
   ↓  

Record Transfer Result

Apply the required approval and validation criteria based on the transfer direction, and review the processed results in the execution history.

This allows you to configure file flows between internal and external environments according to operational requirements and manage the status and results of each transfer together.

Operational Benefits

Manage file movement, validation, and processing history as a single workflow

In network-segmented environments, you need an operational workflow that shows which files were processed and in which direction, alongside the transfer process itself.

By configuring import and export operations, you can manage file requests, approval results, validation information, and transfer status as a single execution flow.

Management ItemFile Import/Export Flow
Transfer RequestManage requests according to the user and business requirements
Transfer DirectionManage import/export paths between internal and external networks
ApprovalApply the approval workflow according to defined criteria
File ValidationLink inspection results to the transfer operation
Execution HistoryReview everything from the request to the final processing result

This extends separate file import and export management into an operational workflow that runs from file request through validation and transfer result review.

IT Engineer

Environment Setup

Connect transfer paths between internal and external networks

First, connect the network environments and transfer devices used to import or export files.

Specify the file paths and destination locations for each environment, then configure import and export flows according to the direction in which files are processed.

text
External Environment
Import
Import
Approval · Validation
┌──────────┐
│ Approval · Validation │
Internal Environment
    │
    ▼
Internal Environment

Export operations move files generated in the internal environment to the external environment according to the defined approval and validation criteria.

text
Internal Environment
Export
Export
Approval · Validation
┌──────────┐
│ Approval · Validation │
External Environment
    │
    ▼
External Environment

Approval and Validation Settings

Configure processing procedures based on files, users, and business requirements

For file import and export operations, define which files are processed and under what criteria.

Configure approval workflows based on file type, user, business purpose, and transfer direction, and connect file validation steps when required.

For example, you can require files with specific extensions to go through a separate approval process, or configure the next step to run only after approval from a designated business owner.

text
Transfer Request
   │
Check Approval Criteria
   │
   ▼
Approval Processing
   │
   ▼
File Validation
   │
   ▼
Execute Transfer

Import and Export Flow

Deliver files to the designated environment based on approval and validation results

After configuring the transfer environments and processing criteria, configure the workflow so approved files are imported or exported through the designated paths.

For both import and export operations, you can specify the required target environment and storage location, while managing the file processing results in a single execution record.

text
Import Request → Approval → Validation → Internal Network Storage

Export Request → Approval → Validation → External Network Storage

Although the two flows are distinguished by transfer direction and destination path, approval, validation, and execution results can be managed under the same operational standards.

Review History

Manage everything from the request to the final transfer result through execution records

Once an import or export operation runs, review the request information, approval result, validation status, and file processing result in the execution history.

Each operation can include the following information.

Review ItemDetails
Request InformationRequesting user and files
Transfer DirectionImport or export
Approval ResultApproval status
Validation ResultFile inspection and processing result
Transfer StatusCurrent progress and completion result
Execution HistoryProcessing time and complete history for each operation
text
Request
   ↓  

Approval
   ↓  

Validation
   ↓  

Transfer
   ↓  

Completed

This lets you trace, at the execution level, which procedures a specific file went through and which environment it was processed into.

Security Operations

Manage file flows based on access scope and transfer policies

During operations, manage access scope by user and device, transferable files and paths, and approval and validation criteria together.

When the business environment or operating standards change, adjust the relevant policies and configure the updated criteria to apply to subsequent import and export operations.

Management AreaOperating Standard
UserScope for file requests and operation execution
DeviceTransfer environments that can be connected
FileProcessing targets and file types
PathImport/export destination locations
ApprovalProcessing procedure by business operation
ValidationFile inspection and result criteria

Operational Response

Review processing status and rerun required operations

For operations that require review, check the processing details based on the import/export direction, approval status, validation result, connection status, and destination path.

text
Review Execution History
      ↓  

Review Processing Stage
      ↓  

Check Approval, Validation, and Connection Status
      ↓  

Adjust Environment or Policy
      ↓  

Rerun Operation
      ↓  

Confirm Final Result

After rerunning the operation, use the new execution record to confirm that the files were successfully delivered to the designated environment.

This configuration connects file request → approval → validation → import/export → execution history management in a network-segmented environment as a single workflow. You can manage the processing criteria and results for each step together while building an operational file transfer flow between internal and external environments.

Developer

Connect import/export requests to approval, inspection, and transfer steps while retaining a complete history

Integration Setup

Separate clients by network and prepare status values

import os
import requests

BASE_URL = os.getenv("INNORIX_BASE_URL", "https://app.innorix.com").rstrip("/")
TOKEN = os.environ["INNORIX_ACCESS_TOKEN"]
WORKSPACE_ID = os.getenv("INNORIX_WORKSPACE_ID")   # optional; falls back to the current workspace

STATUS_COMPLETE = 2
TERMINAL = {2, 4, 5, 9, 99}          # complete / error / cancelled / partial / failed
NOT_SUCCEEDED = {4, 5, 9, 99}


def api(method, path, body=None, params=None):
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {TOKEN}",
    }

    if WORKSPACE_ID:
        headers["x-workspace-id"] = WORKSPACE_ID

    response = requests.request(
        method, BASE_URL + path,
        headers=headers, json=body, params=params, timeout=30,
    )

    payload = response.json() if response.content else {}

    if not response.ok:
        raise RuntimeError(payload.get("message") or f"HTTP {response.status_code}")

    return payload.get("data")


def is_terminal(detail):
    return detail.get("isTerminal", detail.get("status") in TERMINAL)

Transfer status is determined using the values below. There are five terminal states, and the successful state is Complete (2).

StatusMeaningTerminal
2CompleteYes
4ErrorYes
5CancelledYes
9Partially CompleteYes
99FailedYes
1 · 6 · 12 · 13Started · Transferring · Synchronizing · ReceivingNo

If internal and external networks use separate workspaces, specify the workspace identifier for each request. Keep the clients separate so code running on one side cannot access resources on the other.

def client_for(workspace_id):
    def call(method, path, body=None, params=None):
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {TOKEN}",
            "x-workspace-id": workspace_id,
        }

        response = requests.request(method, BASE_URL + path, headers=headers,
                                    json=body, params=params, timeout=30)

        payload = response.json() if response.content else {}

        if response.status_code == 403:
            raise PermissionError(f"{workspace_id}: {path}")

        if not response.ok:
            raise RuntimeError(payload.get("message"))

        return payload.get("data")

    return call


external = client_for(EXTERNAL_WORKSPACE)   # external network
internal = client_for(INTERNAL_WORKSPACE)   # internal network

Specifying a workspace without access permission returns 403. Handling this as a separate exception lets you distinguish policy violations from general errors in your records.

Create a Request

Turn transfer requests into business records and validate their paths

Import and export follow the same procedure; only the direction differs. Treating requests through a common structure allows approval and validation logic to be shared.

import uuid


def create_request(user_id, direction, files, purpose):
    request_id = str(uuid.uuid4())

    db.insert("transfer_requests", {
        "id": request_id,
        "userId": user_id,
        "direction": direction,          # "import" or "export"
        "files": files,
        "purpose": purpose,
        "state": "pending",
    })

    return request_id

Checking that the files actually exist when the request is created prevents failures during execution after approval.

def validate_request(call, source_id, target_id, files, target_path):
    # sourceItems reads filePath, not path
    result = call("POST", "/api/transfers/validate-path", {
        "sourceId": source_id,
        "targetId": target_id,
        "sourceItems": [{"filePath": p} for p in files],
        "targetPath": target_path,
    }) or {}

    if result.get("invalidSourcePaths"):
        raise ValueError(f"missing source paths: {result['invalidSourcePaths']}")

    if result.get("validTargetPath") is False:
        raise ValueError(f"target path not found: {target_path}")

    return result
Record ItemDetails
userIdRequesting user
directionImport or export
filesFiles and paths to transfer
purposeBusiness purpose
stateCurrent processing stage

Use the approval system result as a condition for transfer execution

Once the approval result is received, proceed to the next step.

def on_approval(request_id, approved, approver):
    request = db.get("transfer_requests", request_id)

    db.update("transfer_requests", request_id, {
        "state": "approved" if approved else "rejected",
        "approver": approver,
    })

    if not approved:
        return None

    return send_for_scan(request)


def ensure_approved(request):
    if request["state"] not in ("approved", "validated"):
        raise PermissionError(f"request not approved: {request['id']}")

To prevent unapproved requests from being executed, the function that creates the transfer must check the request status first.

Send files to the inspection folder first and wait for the result

File inspection is performed by a separate system. First send the files to the inspection folder, then use the result to determine the next step.

def send_for_scan(request, scan_device, scan_path):
    ensure_approved(request)

    transfer = external("POST", "/api/transfers/manual", {
        "sourceDevice": request["sourceDevice"],
        "targetDevice": scan_device,
        "targetPath": f"{scan_path}/{request['id']}",
        "sourceItem": [{"path": p, "isDir": False} for p in request["files"]],
        "sendAllFolder": False,
        "checkIntegrity": True,
        "transferOptions": {"target-action": "numbering"},
    })

    db.update("transfer_requests", request["id"], {
        "scanMonitorId": transfer["monitorId"],
        "state": "scanning",
    })

    return transfer["monitorId"]

The agent marks the transfer complete only after the file size stops changing and writing has finished. This prevents the inspection system from opening a file that is still being written and producing an incorrect result.

The integrity of the transfer itself is checked through the validation API.

def verify(monitor_id, timeout=1800, interval=10):
    api("POST", f"/api/transfers/{monitor_id}/verification", {})

    deadline = time.time() + timeout

    while time.time() < deadline:
        result = api("GET", f"/api/transfers/{monitor_id}/verification") or {}

        if result.get("verified"):
            return result

        time.sleep(interval)

    raise TimeoutError(monitor_id)

Execute Import and Export

Send only files that pass inspection to the target network

def execute(request, scan_result):
    if not scan_result.get("passed"):
        db.update("transfer_requests", request["id"], {"state": "blocked"})
        return None

    verification = verify(request["scanMonitorId"])

    if not verification.get("checksumMatched"):
        db.update("transfer_requests", request["id"], {"state": "corrupted"})
        return None

    transfer = internal("POST", "/api/transfers/manual", {
        "sourceDevice": request["scanDevice"],
        "targetDevice": request["targetDevice"],
        "targetPath": request["targetPath"],
        "sourcePaths": [f"{request['scanPath']}/{request['id']}"],
        "sendAllFolder": True,
        "checkIntegrity": True,
        "transferOptions": {"target-action": "numbering"},
    })

    db.update("transfer_requests", request["id"], {
        "monitorId": transfer["monitorId"],
        "state": "transferring",
    })

    return transfer["monitorId"]

Recording the state at each step makes it possible to determine later where the process stopped.

StateMeaning
pendingAwaiting approval
approvedApproved
scanningInspection in progress
blockedInspection failed
corruptedIntegrity mismatch
transferringTransferring to the target network
done · failedFinal result

Confirm the Result

Reflect the transfer result in the request status

def wait(monitor_id, timeout=3600, interval=3):
    deadline = time.time() + timeout

    while time.time() < deadline:
        detail = api("GET", f"/api/transfers/{monitor_id}")

        if is_terminal(detail):
            return detail

        time.sleep(interval)

    raise TimeoutError(monitor_id)


def failed_files(monitor_id):
    result = api("GET", f"/api/transfers/{monitor_id}/files", params={
        "state": "any", "size": 500,
    }) or {}

    return [r for r in (result.get("children") or [])
            if r.get("status") in NOT_SUCCEEDED]


def retry_failed(monitor_id):
    rows = failed_files(monitor_id)

    if not rows:
        return 0

    api("POST", f"/api/transfers/{monitor_id}/retry", {
        "filesRetry": [
            {"filePath": r["sourceFilePath"], "isDir": bool(r.get("isFolder"))}
            for r in rows
        ]
    })

    return len(rows)
def finalize(request_id):
    request = db.get("transfer_requests", request_id)
    detail = internal("GET", f"/api/transfers/{request['monitorId']}")

    if not is_terminal(detail):
        return None

    succeeded = detail["status"] == STATUS_COMPLETE

    db.update("transfer_requests", request_id, {
        "state": "done" if succeeded else "failed",
        "finalStatus": detail["status"],
    })

    return succeeded

Partially Complete (9) and Cancelled (5) are also terminal states. If every terminal state is treated as a success, requests that transferred only part of their files will be recorded as completed.

Retrieve History

Review import/export records for audit purposes

Transfer history provides evidence of which files were processed and in which direction.

from datetime import datetime, timedelta, timezone


def paginate(call, path, params=None, limit=200, max_pages=50):
    query = dict(params or {})
    query["limit"] = limit
    cursor = None

    for _ in range(max_pages):
        if cursor:
            query["cursor"] = cursor

        result = call("GET", path, params=query) or {}

        for record in result.get("data") or []:
            yield record

        pagination = result.get("pagination") or {}

        if not pagination.get("hasMore"):
            return

        cursor = pagination.get("nextCursor")

        if not cursor:
            return


end = datetime.now(timezone.utc)
fmt = "%Y-%m-%dT%H:%M:%SZ"

for row in paginate(internal, "/api/transfer-history", params={
    "startDate": (end - timedelta(days=30)).strftime(fmt),
    "endDate": end.strftime(fmt),
}):
    print(row.get("startDate"), row.get("statusName"),
          row.get("sourceDeviceName"), "->", row.get("targetDeviceName"))

If you need a file for an audit submission, use CSV export.

params = {
    "periodDays": 30,
    "page": 1,
    "size": 10000,
    "filter": "[]",              # the server parses this as a JSON string, so send an empty array
    "sort": "startDate:desc",
}

Recording the transfer monitorId together with the business request identifier lets you trace in both directions: from execution history to the request and from the request to the execution history.

Exception Handling

Review interrupted requests step by step and reprocess them

A failed inspection and a failed transfer require different responses. The former indicates a problem with the file itself and cannot be fixed by retransmission; the latter can be recovered through retransmission.

def review(request_id):
    request = db.get("transfer_requests", request_id)
    state = request["state"]

    if state == "rejected":
        return "rejected - notify the requester with the reason"

    if state == "blocked":
        return "scan blocked - check the files and request again"

    if state == "corrupted":
        return "integrity mismatch - check the source"

    if state == "transferring":
        return f"retried {retry_failed(request['monitorId'])} failed files"

    return f"current state: {state}"
CategorySymptomResponse
Approval RejectedrejectedNotify the requester of the reason
Inspection FailedblockedCheck the files and submit a new request
Integrity MismatchcorruptedCheck the source state
Transfer FailedfailedRetransmit the failed files
Review ItemDetails
RequestApproval status and target files
InspectionInspection-folder transfer and result
IntegrityFile count and checksum match status
ExecutionTransfer status to the target network
HistoryTransfer records for audit purposes