Run your plugins before data reaches storage

Platform-agnostic processing gateway. Point any S3 client at Maskura to filter, redact, encrypt, or convert supported object content with your Wasm plugins.

# Get an API key from the dashboard or use demo mode # Upload — data runs through the plugin pipeline curl -X PUT http://localhost:9000/ingest/data.jsonl \ -H "x-maskura-access-key: YOUR_KEY_ID" \ -H "x-maskura-secret-key: YOUR_SECRET" \ --data-binary @data.jsonl # Read — filtered data comes back curl http://localhost:9000/ingest/data.jsonl \ -H "x-maskura-access-key: YOUR_KEY_ID" \ -H "x-maskura-secret-key: YOUR_SECRET"
Everything you need to process data in transit
Multi-cloud storage
Maskura Service Storage distributes objects across AWS, R2, and B2 using consistent hashing. Dual-write to primary + replica. Cross-cloud resilience by default.
📁
Full S3 API
PUT, GET, DELETE, HEAD, LIST — standard S3 operations run through the plugin pipeline. Drop-in compatible with AWS SDK, boto3, and any S3 tool.
🔑
Simple ACL
Maskura API keys (maskura_xxx / maskura_secret_xxx) authenticate S3 requests. Create, revoke, set expiry per key. No IAM policy complexity.
🌐
Cloud agnostic
Presigned URL proxy works with S3, R2, B2, MinIO, or any S3-compatible storage. Bring your own backend or use Maskura's managed buckets.
🧩
Plugin system
Wasm filter plugins: import, enable, disable, reorder at runtime. Ordered pipeline processes data through multiple plugins — ship custom transforms without redeploying.
🛡
Zero trust
IAM Role assumption (Fivetran/Airbyte model). No long-lived credentials stored. Unique External ID per workspace prevents confused deputy attacks.

Get Started

Create an account or sign in
or with email
Create your first API key
S3-compatible credentials. Works with AWS SDK, CLI, or any S3 tool.
Key label
Expiry
Where should Maskura write filtered data?
Maskura uses the Fivetran/Airbyte model: you create an IAM role (or API token) granting Maskura access to your bucket. No long-lived credentials stored.
Step 1: Create an IAM role for Maskura
Run this in your AWS account. Maskura will assume this role to write filtered data to your bucket.
TRUST_POLICY=$(cat <<'EOF' { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": {"AWS": "arn:aws:iam::MASKURA_ACCOUNT_ID:role/maskura-gateway"}, "Action": "sts:AssumeRole", "Condition": {"StringEquals": {"sts:ExternalId": "MASKURA_EXTERNAL_ID"}} }] } EOF ) aws iam create-role --role-name maskura-pii-filter \ --assume-role-policy-document "$TRUST_POLICY" aws iam put-role-policy --role-name maskura-pii-filter \ --policy-name maskura-bucket-access \ --policy-document '{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["s3:PutObject","s3:GetObject","s3:ListBucket"], "Resource": ["arn:aws:s3:::YOUR_BUCKET","arn:aws:s3:::YOUR_BUCKET/*"] }] }' echo "Role ARN: $(aws iam get-role --role-name maskura-pii-filter --query 'Role.Arn' --output text)"
Step 2: Paste your Role ARN
Copy the Role ARN from the output above and paste it below, together with the region and an external ID for confused-deputy protection.
Role ARN
Region
External ID (optional)
Create an R2 API Token
In the Cloudflare dashboard: R2 → Manage R2 API Tokens → Create API Token → select your bucket → grant Read + Write.
# Get your Account ID from the Cloudflare dashboard ACCOUNT_ID="your-account-id" BUCKET="your-bucket" # The endpoint is always: echo "https://${ACCOUNT_ID}.r2.cloudflarestorage.com"
R2 Endpoint
R2 API Token
Create a B2 Application Key
In the B2 console: App Keys → Add a New Application Key → select your bucket → grant Read + Write.
# Install B2 CLI: brew install backblaze-b2 (or pip install b2) b2 authorize-account b2 get-account-info # Note your accountId. Endpoint is: echo "https://s3.us-west-004.backblazeb2.com"
B2 S3 Endpoint
Key ID
Application Key
Connect to your MinIO instance
Maskura connects directly to your MinIO server using access key + secret key.
MinIO Endpoint
Access Key
Secret Key
Connect your tools
Generate a presigned URL for your bucket, then pipe it through Maskura for plugin processing.
export MASKURA_KEY=maskura_YOUR_KEY_ID export MASKURA_SECRET=maskura_secret_YOUR_SECRET export MASKURA=http://localhost:9000 # Generate a presigned PUT URL using your AWS credentials PRESIGNED=$(aws s3 presign s3://my-bucket/uploads/data.jsonl --expires-in 604800) # Send through Maskura; the plugin pipeline runs before the bucket write curl -X PUT "$MASKURA/my-bucket/uploads/data.jsonl" \ -H "x-maskura-access-key: $MASKURA_KEY" \ -H "x-maskura-secret-key: $MASKURA_SECRET" \ -H "x-maskura-backend-url: $PRESIGNED" \ --data-binary @data.jsonl
import boto3, requests # Generate presigned URL with your AWS credentials s3 = boto3.client('s3') presigned = s3.generate_presigned_url( 'put_object', Params={'Bucket': 'my-bucket', 'Key': 'uploads/data.jsonl'}, ExpiresIn=604800 ) # Send through Maskura r = requests.put( 'http://localhost:9000/my-bucket/uploads/data.jsonl', headers={ 'x-maskura-access-key': 'maskura_YOUR_KEY_ID', 'x-maskura-secret-key': 'maskura_secret_YOUR_SECRET', 'x-maskura-backend-url': presigned }, data=open('data.jsonl', 'rb') )
use aws_sdk_s3::{Client, presigning::PresigningConfig}; // Generate presigned URL let config = aws_config::load_from_env().await; let s3 = Client::new(&config); let presigned = s3.put_object() .bucket("my-bucket") .key("uploads/data.jsonl") .presigned(PresigningConfig::expires_in( std::time::Duration::from_secs(604800) )).await?; // Send through Maskura let client = reqwest::Client::new(); let resp = client.put("http://localhost:9000/my-bucket/uploads/data.jsonl") .header("x-maskura-access-key", "maskura_YOUR_KEY_ID") .header("x-maskura-secret-key", "maskura_secret_YOUR_SECRET") .header("x-maskura-backend-url", presigned.uri()) .body(std::fs::read("data.jsonl")?) .send().await?;
// Generate presigned URL import software.amazon.awssdk.services.s3.presigner.S3Presigner; import software.amazon.awssdk.services.s3.model.PutObjectRequest; import java.net.http.*; var presigner = S3Presigner.create(); var req = PutObjectRequest.builder() .bucket("my-bucket").key("uploads/data.jsonl").build(); var presigned = presigner.presignPutObject(p -> p .putObjectRequest(req) .signatureDuration(java.time.Duration.ofDays(7)) ); // Send through Maskura var client = HttpClient.newHttpClient(); var httpReq = HttpRequest.newBuilder() .uri(URI.create("http://localhost:9000/my-bucket/uploads/data.jsonl")) .header("x-maskura-access-key", "maskura_YOUR_KEY_ID") .header("x-maskura-secret-key", "maskura_secret_YOUR_SECRET") .header("x-maskura-backend-url", presigned.url().toString()) .PUT(HttpRequest.BodyPublishers.ofFile(Path.of("data.jsonl"))) .build(); client.send(httpReq, HttpResponse.BodyHandlers.ofString());
Hybrid wire format; client packaging in progress
Current gateways accept Maskura hybrid X25519 + ML-KEM-768 public keys for new encrypted writes. The released Python and TypeScript high-level encryption helpers still implement the legacy RSA envelope and cannot provision a key on this gateway. Redaction and ordinary SDK object operations are unaffected. See the current client compatibility boundary.
Objects in memory
Objects flowing through the gateway. Upload more via S3 API.