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

Move files through air-gapped environments with controlled approvals, security checks, transfer history, and operational verification.

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.

  1. File Request
  2. Transfer Target Confirmation
  3. Apply Approval Criteria
  4. File Validation
  5. Import or Export
  6. 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 Item File Import/Export Flow
Transfer Request Manage requests according to the user and business requirements
Transfer Direction Manage import/export paths between internal and external networks
Approval Apply the approval workflow according to defined criteria
File Validation Link inspection results to the transfer operation
Execution History Review 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.

External Environment
Import
Approval, Validation
Internal Environment

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

Internal Environment
Export
Approval, Validation
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.

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.

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 Item Details
Request Information Requesting user and files
Transfer Direction Import or export
Approval Result Approval status
Validation Result File inspection and processing result
Transfer Status Current progress and completion result
Execution History Processing time and complete history for each operation
  1. Request
  2. Approval
  3. Validation
  4. Transfer
  5. 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 Area Operating Standard
User Scope for file requests and operation execution
Device Transfer environments that can be connected
File Processing targets and file types
Path Import/export destination locations
Approval Processing procedure by business operation
Validation File 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.

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).

Status Meaning Terminal
2 Complete Yes
4 Error Yes
5 Cancelled Yes
9 Partially Complete Yes
99 Failed Yes
1, 6, 12, 13 Started, Transferring, Synchronizing, Receiving No

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 Item Details
userId Requesting user
direction Import or export
files Files and paths to transfer
purpose Business purpose
state Current 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.

State Meaning
pending Awaiting approval
approved Approved
scanning Inspection in progress
blocked Inspection failed
corrupted Integrity mismatch
transferring Transferring to the target network
done, failed Final 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}"
Category Symptom Response
Approval Rejected rejected Notify the requester of the reason
Inspection Failed blocked Check the files and submit a new request
Integrity Mismatch corrupted Check the source state
Transfer Failed failed Retransmit the failed files
Review Item Details
Request Approval status and target files
Inspection Inspection-folder transfer and result
Integrity File count and checksum match status
Execution Transfer status to the target network
History Transfer records for audit purposes