SEC.SI CARE

Dating-Safety · 4 Kern-Module

Tech-Architektur · v1.0

Senior-Entwickler Referenz · iOS + Android · Offline-first · Privacy-by-design

Voice-Trigger
Silent Audio
Fake-Call
Anti-Shutdown
MFCC-Feature-Extraktion → TFLite/CoreML-Inferenz on-device, kein Cloud-Upload. Speaker Verification (Cosine-Similarity ≥ 0.82) verhindert Fehlauslösung durch Dritte. Duty-Cycle AudioCapture schont Akku.

< 500ms

Latenz Trigger → SOS

~25mW

Akku Normal-Modus

85%

Konfidenz-Schwelle

Cosine 0.82

Speaker-Verifikation

kotlin + swift · voice-trigger-offline
// ═══════════════════════════════════════════════════════════════
// MODULE 1 · VOICE-TRIGGER · Offline Safe-Word Detection
// Stack: TFLite (Android) / CoreML (iOS) · No cloud required
// ═══════════════════════════════════════════════════════════════

// ── ARCHITECTURE OVERVIEW ────────────────────────────────────────
//
//  Microphone Input (16kHz mono)
//       │
//       ▼
//  [MFCC Feature Extractor]          ← on-device, < 2ms
//       │  40 coefficients, 1s window, 50ms hop
//       ▼
//  [TFLite / CoreML Inference]       ← NNAPI / ANE accelerated
//       │  keyword_model_v3.tflite   ← user-enrolled safe-word
//       │  threshold: 0.85 confidence
//       ▼
//  [Enrollment Engine]               ← Speaker Verification
//       │  cosine similarity ≥ 0.82 (same voice, not a recording)
//       ▼
//  SOS Dispatcher.trigger()

// ── ANDROID: KeywordService (Kotlin) ────────────────────────────

class KeywordListenerService : Service() {

    companion object {
        const val SAMPLE_RATE     = 16_000
        const val FRAME_MS        = 30          // 30ms frames
        const val KEYWORD_THRESH  = 0.85f
        const val SPEAKER_THRESH  = 0.82f       // Anti-Playback
        const val MODEL_FILE      = "safe_word_model.tflite"
        const val SPEAKER_MODEL   = "speaker_embed_model.tflite"
    }

    private lateinit var keywordInterp:  Interpreter
    private lateinit var speakerInterp:  Interpreter
    private lateinit var audioCapture:   BatteryAwareCapture
    private lateinit var enrolledEmbed:  FloatArray    // stored in EncryptedSharedPrefs

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        startForeground(NOTIF_ID, stealth_notification())   // invisible to user
        initModels()
        startListenLoop()
        return START_STICKY
    }

    private fun startListenLoop() {
        CoroutineScope(Dispatchers.Default).launch {
            audioCapture.stream { pcmFrame ->
                val mfcc      = MFCCExtractor.compute(pcmFrame, SAMPLE_RATE)
                val kwScore   = runTFLite(keywordInterp, mfcc)

                if (kwScore >= KEYWORD_THRESH) {
                    val speakerEmbed  = runTFLite(speakerInterp, mfcc)
                    val similarity    = cosineSim(speakerEmbed, enrolledEmbed)

                    if (similarity >= SPEAKER_THRESH) {
                        // ✅ Correct keyword + correct speaker
                        SOSDispatcher.trigger(source = "voice_keyword")
                    }
                }
            }
        }
    }

    // BATTERY: Duty-cycle audio capture
    // Normal  (>30% battery): continuous 16kHz
    // Eco     (≤30% battery): 200ms listen / 300ms pause @ 8kHz
    // Critical(≤15% battery): 150ms listen / 850ms pause @ 8kHz
    private fun stealth_notification() =
        NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_transparent)
            .setOngoing(true)
            .setPriority(NotificationCompat.PRIORITY_MIN)
            .setShowWhen(false)
            .setVisibility(NotificationCompat.VISIBILITY_SECRET)
            .build()
}

// ── iOS: KeywordEngine (Swift) ────────────────────────────────────

class KeywordEngine: NSObject, SNResultsObserving {

    private let audioEngine  = AVAudioEngine()
    private let mlModel      = try! SecSIKeyword_v3(configuration: {
        let c = MLModelConfiguration()
        c.computeUnits = .cpuAndNeuralEngine  // ANE: max efficiency
        return c
    }()).model

    func startListening(onTrigger: @escaping () -> Void) throws {
        let session = AVAudioSession.sharedInstance()
        // .mixWithOthers: doesn't interrupt Spotify/podcasts
        try session.setCategory(.playAndRecord, options: [.mixWithOthers, .allowBluetooth])
        try session.setActive(true)

        let request = try SNClassifySoundRequest(mlModel: mlModel)
        request.windowDuration    = CMTimeMakeWithSeconds(1.0, preferredTimescale: 44100)
        request.overlapFactor     = 0.5

        let analyzer = SNAudioStreamAnalyzer(format: audioEngine.inputNode.outputFormat(forBus: 0))
        try analyzer.add(request, withObserver: self)

        audioEngine.inputNode.installTap(onBus: 0, bufferSize: 8192, format: nil) { buf, time in
            analyzer.analyze(buf, atAudioFramePosition: time.sampleTime)
        }
        try audioEngine.start()
    }

    func request(_ req: SNRequest, didProduce result: SNResult) {
        guard let r = result as? SNClassificationResult,
              let top = r.classifications.first,
              top.identifier == "safe_word",
              top.confidence >= 0.85 else { return }
        // Verified → dispatch SOS
        SOSDispatcher.shared.trigger(source: .voiceKeyword)
    }
}

// ── ENROLLMENT FLOW (one-time setup) ────────────────────────────
// User records safe-word 5×  →  average speaker embedding stored
// in device EncryptedSharedPreferences / iOS Keychain (never leaves device)
EnrollmentManager.enroll(samples: 5, keyword: userChosenWord) { embedding in
    Keychain.store("speaker_embedding", AES256.encrypt(embedding))
}
WebRTC PeerConnection (send-only) startet sofort. Display dunkel via UIScreen.brightness=0 / DeviceAdmin.lockNow(). Decoy-App-Modus startet täuschende Activity. 30s RAM-Ringbuffer sichert Pre-Trigger-Audio.

Opus 24kbps

Codec

4kbps GPRS

Fallback Bitrate

30 Sekunden

Pre-Trigger Buffer

AES-256-GCM

Verschlüsselung

kotlin + swift · silent-audio-stealth
// ═══════════════════════════════════════════════════════════════
// MODULE 2 · SILENT LIVE-AUDIO + STEALTH MODE
// Stack: WebRTC (one-way) · AES-256-GCM · Display Stealth
// ═══════════════════════════════════════════════════════════════

// ── SIGNAL FLOW ──────────────────────────────────────────────────
//
//  SOS Trigger
//       │
//       ▼
//  [30s RAM Ring-Buffer flush]       ← pre-trigger audio saved
//       │
//       ▼
//  [WebRTC Peer Connection init]
//       │  STUN/TURN: fra.turn.sec-si.de
//       │  Offer SDP → Signal Server → Guardian
//       ▼
//  [AudioTrack: encrypted one-way stream]
//       │  Codec: Opus 24kbps (low bandwidth mode available)
//       │  E2E: ECDH P-384 key exchange → AES-256-GCM per chunk
//       ▼
//  Guardian receives audio (read-only, no mic opened on their side)

// ── ANDROID: SilentAudioService (Kotlin) ─────────────────────────

class SilentAudioService : Service() {

    private lateinit var peerConnection: PeerConnection
    private lateinit var ringBuffer:     CircularAudioBuffer  // 30s RAM

    fun startStream(guardianSignalId: String) {

        // 1. Create peer connection (send-only)
        val rtcConfig = PeerConnection.RTCConfiguration(listOf(
            PeerConnection.IceServer.builder("turn:fra.turn.sec-si.de:3478")
                .setUsername(SESSION.turnUser).setPassword(SESSION.turnPass).createIceServer()
        ))
        peerConnection = factory.createPeerConnection(rtcConfig, peerObserver)!!

        // 2. Audio source: microphone (system-level, bypasses mute)
        val audioConstraints = MediaConstraints()
        val audioSource      = factory.createAudioSource(audioConstraints)
        val localAudioTrack  = factory.createAudioTrack("audio0", audioSource)
        peerConnection.addTrack(localAudioTrack, listOf("stream0"))

        // 3. SDP offer
        peerConnection.createOffer(object : SdpObserver {
            override fun onCreateSuccess(sdp: SessionDescription) {
                peerConnection.setLocalDescription(this, sdp)
                SignalServer.send(guardianSignalId, sdp)
            }
        }, MediaConstraints())
    }

    // ── STEALTH DISPLAY CONTROL ───────────────────────────────────
    fun activateStealthMode(mode: StealthMode) = when (mode) {

        StealthMode.BLACK_SCREEN -> {
            // Screen off via DeviceAdmin, service continues
            (getSystemService(POWER_SERVICE) as PowerManager)
            devicePolicyManager.lockNow()
        }

        StealthMode.DECOY_APP -> {
            // Launch decoy activity that looks like Instagram/Maps
            startActivity(Intent(this, DecoyActivity::class.java).apply {
                addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TOP)
            })
            // Audio stream runs in background service — invisible
        }

        StealthMode.FAKE_SHUTDOWN -> {
            // See Module 4
            startActivity(Intent(this, FakeShutdownActivity::class.java))
        }
    }
}

// ── iOS: SilentAudioManager (Swift) ──────────────────────────────

class SilentAudioManager {

    var peerConnection: RTCPeerConnection?
    let factory = RTCPeerConnectionFactory()

    func startStream(guardianId: String) {
        let config      = RTCConfiguration()
        config.iceServers = [RTCIceServer(urlStrings: ["turn:fra.turn.sec-si.de:3478"],
                                          username: session.turnUser, credential: session.turnPass)]
        config.sdpSemantics = .unifiedPlan

        let constraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
        peerConnection  = factory.peerConnection(with: config, constraints: constraints, delegate: self)

        // Send-only audio track
        let audioSource = factory.audioSource(with: constraints)
        let audioTrack  = factory.audioTrack(with: audioSource, trackId: "audio0")
        peerConnection?.add(audioTrack, streamIds: ["stream0"])

        // AVAudioSession: background audio + screen dark
        try? AVAudioSession.sharedInstance().setCategory(
            .playAndRecord,
            mode: .voiceChat,
            options: [.mixWithOthers, .allowBluetooth]
        )
        // UIScreen brightness → 0 without turning off audio
        UIScreen.main.brightness = 0
    }

    // Adaptive bitrate: poor connectivity fallback
    func setBitrate(kbps: Int) {
        let params = peerConnection?.senders.first?.parameters
        params?.encodings.first?.maxBitrateBps = (kbps * 1000) as NSNumber
        peerConnection?.senders.first?.parameters = params!
    }
    // 24kbps normal → 8kbps (GPRS) → 4kbps (edge-network fallback)
}

// ── PRE-TRIGGER RING BUFFER ───────────────────────────────────────
// Always running → on SOS trigger: flush last 30s to encrypted file
// then upload as evidence alongside live stream

class CircularAudioBuffer(val durationSeconds: Int = 30, sampleRate: Int = 16000) {
    private val capacity = durationSeconds * sampleRate
    private val buffer   = ShortArray(capacity)
    private var writePos = 0

    fun write(samples: ShortArray) {
        samples.forEach { buffer[writePos++ % capacity] = it }
    }

    fun flush(): ShortArray {
        // Returns ordered 30s snapshot (oldest → newest)
        val result = ShortArray(capacity)
        for (i in 0 until capacity) result[i] = buffer[(writePos + i) % capacity]
        return result
    }
}
Guardian wählt Szenario → WebSocket-Relay an Opfer-Device in < 400ms. iOS nutzt offizielles CallKit (natives Call-Interface auf Sperrbildschirm). Android verwendet FLAG_SHOW_WHEN_LOCKED Overlay + STREAM_RING. Nach Call: Maps öffnet ÖPNV-Route nach Hause.

< 400ms

End-to-End Latenz

5 Audio-Assets

Szenarien

CallKit CXProvider

iOS API

ÖPNV Maps

Smart Exit

kotlin + swift · remote-fake-call
// ═══════════════════════════════════════════════════════════════
// MODULE 3 · REMOTE FAKE-CALL
// Stack: WebSocket relay · CallKit (iOS) · Android Overlay
// Latency: Guardian click → call appears on victim < 400ms
// ═══════════════════════════════════════════════════════════════

// ── SIGNAL FLOW ──────────────────────────────────────────────────
//
//  Guardian Dashboard
//       │  "trigger_fake_call" + scenario + victim_session_id
//       ▼
//  WSS Hub (sec-si.de)               ← JWT validated relay
//       │  routes to victim socket only
//       ▼
//  Victim device receives FAKE_CALL event
//       │
//       ├─ iOS  → CallKit CXCallUpdate (native call UI, locked screen)
//       └─ Android → FLAG_SHOW_WHEN_LOCKED overlay + RingtoneManager
//       │
//       ▼
//  User answers → scenario audio plays (local .m4a asset)
//       │
//       ▼
//  Call ends → Smart Exit: Maps open with transit route home

// ── SCENARIOS (local audio assets, no streaming) ─────────────────
const SCENARIOS = [
  { id: "worried_mom",   caller: "Mama",             audio: "alibi_worried_mom.m4a",   urgency: "high" },
  { id: "crying_child",  caller: "Kita Sonnenschein", audio: "alibi_crying_child.m4a",  urgency: "high" },
  { id: "boss_urgent",   caller: "Dr. Hoffmann",      audio: "alibi_boss_urgent.m4a",   urgency: "high" },
  { id: "friend_pickup", caller: "Lisa",              audio: "alibi_friend_pickup.m4a", urgency: "low"  },
  { id: "doctor",        caller: "Praxis Dr. Meier",  audio: "alibi_doctor.m4a",        urgency: "high" },
]

// ── ANDROID: IncomingCallActivity ────────────────────────────────

class IncomingFakeCallActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val payload = intent.extras!!

        // Override lockscreen + keep screen on
        window.addFlags(
            WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
            WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON   or
            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
        )
        setContentView(R.layout.incoming_call_ui)

        // Populate UI with scenario data
        findViewById<TextView>(R.id.caller_name).text   = payload.getString("caller_name")
        findViewById<TextView>(R.id.caller_number).text = payload.getString("caller_number")

        // Ringtone: override silent mode via STREAM_RING
        val ringtone = RingtoneManager.getRingtone(this, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE))
        ringtone.streamType = AudioManager.STREAM_RING
        ringtone.play()

        // Buttons
        findViewById<Button>(R.id.btn_accept).setOnClickListener { onAccepted(payload) }
        findViewById<Button>(R.id.btn_decline).setOnClickListener { finish() }
    }

    private fun onAccepted(payload: Bundle) {
        // Play local audio asset (no network needed)
        val mp = MediaPlayer.create(this, assets.openFd(payload.getString("audio_file")!!))
        mp.start()
        mp.setOnCompletionListener { onCallEnded() }
    }

    private fun onCallEnded() {
        // Smart Exit: open Maps with transit route home
        val homeAddr  = EncryptedPrefs.get("home_address")
        val mapsIntent = Intent(Intent.ACTION_VIEW,
            Uri.parse("google.navigation:q=${Uri.encode(homeAddr)}&mode=transit"))
        startActivity(mapsIntent)
        // Update session: status = "navigating_home"
        SessionRepository.updateStatus("navigating_home")
        finish()
    }
}

// ── CROSS-PLATFORM SECURE LINK GENERATION ────────────────────────

// JavaScript / Node.js (Dashboard / Backend):
const homeAddr = "Notfall-Standort"; // Variable für die Adresse
const secureMapUrl = `https://sec.si/nav/${encodeURIComponent(homeAddr)}`;
console.log("Gesicherte Notfall-URL generiert:", secureMapUrl);

// Flutter / Dart (App):
final secureUri = Uri.parse('https://sec.si/nav/${Uri.encodeComponent(homeAddr)}');

// ── SEC.SI DNS - Security Layer: Location Encryption ─────────────

const crypto = require('crypto'); // Standard-Verschlüsselung

function generateSecureSOS(gpsData, secretKey) {
    try {
        // Wir verschlüsseln die GPS-Daten (Breitengrad, Längengrad)
        const cipher = crypto.createCipher('aes-256-cbc', secretKey);
        let encrypted = cipher.update(gpsData, 'utf8', 'hex');
        encrypted += cipher.final('hex');

        // Der fertige, sichere Link für das Guardian-Dashboard
        return `https://sec.si/sos/${encrypted}`;
    } catch (e) {
        console.error("Verschlüsselungsfehler im SEC.SI Kernmodul:", e);
        // Fallback: Nur verschlüsselte Koordinaten senden
        return "ERROR_SECURE_LAYER_FAILED";
    }
}

// ── iOS: CallKit Integration (Swift) ─────────────────────────────

class FakeCallProvider: NSObject, CXProviderDelegate {

    private let provider: CXProvider
    private var activeCallUUID = UUID()
    private var player: AVAudioPlayer?

    override init() {
        let config = CXProviderConfiguration()
        config.ringtoneSound         = nil           // system default ringtone
        config.supportsVideo         = false
        config.maximumCallsPerCallGroup = 1
        provider = CXProvider(configuration: config)
        super.init()
        provider.setDelegate(self, queue: nil)
    }

    func incomingCall(callerName: String, callerNumber: String, audioFile: String) {
        activeCallUUID = UUID()
        let update     = CXCallUpdate()
        update.remoteHandle          = CXHandle(type: .phoneNumber, value: callerNumber)
        update.localizedCallerName   = callerName
        update.hasVideo              = false
        update.supportsHolding       = false
        // Reports incoming call → native iOS call UI appears (even on lock screen)
        provider.reportNewIncomingCall(with: activeCallUUID, update: update) { [weak self] error in
            if error == nil { self?.pendingAudioFile = audioFile }
        }
    }

    func providerDidActivateAudioSession(_ provider: CXProvider) {
        // User answered → play scenario audio
        if let file = pendingAudioFile,
           let url = Bundle.main.url(forResource: file, withExtension: nil) {
            player = try? AVAudioPlayer(contentsOf: url)
            player?.play()
        }
    }

    func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
        player?.stop()
        action.fulfill()
        SmartExit.navigate()  // Open Maps transit route home
    }

    private var pendingAudioFile: String?
}
Schwarz-Overlay mit 800ms Fade imitiert echtes Ausschalten (inkl. Vibration). GPS, Audio und WebSocket laufen im Service weiter. Duress-PIN sieht identisch zu normalem PIN aus — löst aber silent Polizei-Dispatch aus.

800ms

Fade-Dauer

Normal-Deaktiv.

PIN A

🚔 Silent Police

PIN B (Duress)

Bleiben aktiv

Services

kotlin + swift · fake-shutdown + duress-pin
// ═══════════════════════════════════════════════════════════════
// MODULE 4 · ANTI-SHUTDOWN · Fake Power-Off Overlay
// Stack: Android FLAG_SHOW_WHEN_LOCKED + DeviceAdmin · iOS UIKit
// GPS + Audio remain active while screen appears off
// ═══════════════════════════════════════════════════════════════

// ── TRIGGER ──────────────────────────────────────────────────────
// Activated by:
//   A) User holds power button → app intercepts volume+power combo
//   B) Duress PIN entered → automatic fake-shutdown
//   C) Guardian triggers remotely via WebSocket

// ── ANDROID: FakeShutdownActivity ────────────────────────────────

class FakeShutdownActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        requestWindowFeature(Window.FEATURE_NO_TITLE)
        window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                        WindowManager.LayoutParams.FLAG_FULLSCREEN)
        window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
                        WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
        setContentView(R.layout.fake_shutdown)   // pure black layout

        // Imitate real power-off: fade to black over 800ms
        val overlay = findViewById<View>(R.id.overlay)
        overlay.alpha = 0f
        overlay.animate().alpha(1f).setDuration(800).withEndAction {
            // Drop KEEP_SCREEN_ON → display goes dark naturally
            window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
        }.start()

        // Haptic: single 200ms buzz (matches real power-off)
        (getSystemService(VIBRATOR_SERVICE) as Vibrator)
            .vibrate(VibrationEffect.createOneShot(200, VibrationEffect.DEFAULT_AMPLITUDE))

        // CRITICAL: SafetyMonitoringService keeps running (START_STICKY)
        // GPS interval: unchanged
        // Audio ring-buffer: unchanged
        // WebSocket heartbeat: unchanged
    }

    // Intercept back/home — nothing should exit fake-shutdown
    override fun onBackPressed() {}
    override fun onUserLeaveHint() {}
}

// ── iOS: FakeShutdownViewController ──────────────────────────────

class FakeShutdownViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .black
        view.alpha = 0

        // Fade to black over 0.8s
        UIView.animate(withDuration: 0.8) { self.view.alpha = 1 } completion: { _ in
            // Keep AVAudioSession active (Background Audio capability)
            // CLLocationManager continues (Always Authorization)
            // UIScreen brightness → 0 (display dark, CPU/GPS running)
            UIScreen.main.brightness = 0
            UIApplication.shared.isIdleTimerDisabled = false  // allow sleep
        }

        // Vibration
        UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
    }

    // Swipe-up / home gesture → swallow
    override var prefersHomeIndicatorAutoHidden: Bool { true }
}

// ── DURESS PIN LOGIC ──────────────────────────────────────────────
// PIN A (normal) → deactivates alarm visibly
// PIN B (duress) → looks identical but silently:
//     1. Sends Priority_RED to server → police dispatch
//     2. Activates FakeShutdown overlay
//     3. Continues GPS + audio recording
// Attacker sees "alarm off" — actual escalation invisible

fun validatePIN(input: String): PINResult {
    val normalPIN = EncryptedPrefs.get("pin_normal")
    val dressPIN  = EncryptedPrefs.get("pin_duress")
    return when (input) {
        normalPIN -> PINResult.DEACTIVATE_NORMAL
        dressPIN  -> { triggerSilentPoliceDispatch(); PINResult.DEACTIVATE_DURESS }
        else      -> { failCount++; if (failCount >= 3) triggerSilentPoliceDispatch(); PINResult.WRONG }
    }
}

Was das für dich bedeutet · Einfache Zusammenfassung

Dein persönliches Zauberwort

Du legst ein geheimes Wort fest (z.B. "Ananas"). Sagst du es laut – auch wenn dein Handy in der Tasche ist – löst die App sofort einen stillen Notruf aus. Kein Antippen, kein Entsperren. Nur dein Wort.

Dein Notfallkontakt hört mit – unsichtbar

Sobald der Alarm ausgelöst wird, hört dein Notfallkontakt live alles, was um dich herum passiert. Dein Display bleibt dabei dunkel oder zeigt eine harmlose App – niemand sieht, dass du Hilfe gerufen hast.

Dein Rettungsanruf auf Knopfdruck

Dein Notfallkontakt kann dir mit einem Klick einen täuschend echten Anruf schicken – von "Mama", dem Chef oder der Kita. Du hebst ab, hörst ein vorgefertigtes Gespräch und hast den perfekten Grund zu gehen.

Selbst Ausschalten schützt dich

Zwingt dich jemand, dein Handy auszuschalten? Die App zeigt den Ausschalt-Bildschirm – aber GPS und Audio laufen still weiter. Dein Standort wird weiter übermittelt, obwohl das Handy "aus" aussieht.

Dein Datenschutz ist garantiert

Alles läuft auf deinem Gerät – kein Stichwort verlässt dein Handy. Nur im Notfall wird ein verschlüsseltes Signal gesendet. Wir als Betreiber können deinen Audio-Stream nicht mithören.

SEC.SI GERMANY · Core Modules Architecture v1.0 · Confidential

Sicherheit Macht SEC.SI