This website was compromised

For 6-months, this website was compromised. I am not sure what exactly happened, but it was most likely password-reuse, which lend itself to this problem. The problem became apparent when I first noticed an unusual link to a ride-sharing service. Later, I saw more of those links. That’s when I realized that I couldn’t merely sit and scan every blog post manually and decided to write a small interactive link checker tool. This tool whitelists the starting domain and allows you to whitelist URLs on a per-domain basis. The whitelist is persisted at the end of execution and will be used next time you use the tool.

Say, your website is example.com,

go run outbound-link-checker.go \
  -domain example.com \
  -starting-url https://example.com \
  -num-url-crawl-limit -1

The tool starts from the starting URL and scans all the links on the page. If any of those links are in the domain, they are scanned further. If they are not, then they are checked against the whitelist, the non-whitelisted domains would be prompted back to you for whitelisting.

Using the tool, I caught quite a few more such bad links.
Note: The tool does not execute Javascript. Thus, it will miss any dynamically generated links.

Android: Handling JPEG images with Exif orientation flags

A JPEG file can have Exif metadata which can provide the rotation/translation field information for a raw JPEG image. So, a landscape raw JPEG image could actually be a portrait because it’s EXIF orientation could be set to ORIENTATION_ROTATE_90, the best way to handle such scenarios is to either use a library like Picasso or Glide or at least learn from them. Here is a piece of code from Picasso which loads a JPEG as an in-memory bitmap and performs the right translation/rotation.

// Get the orientation
ExifInterface exifInterface = new ExifInterface(imageFilePath);
int exifOrientation = exifInterface.getAttributeInt(TAG_ORIENTATION, ORIENTATION_NORMAL)

// Take the source of these methods from 
// https://github.com/square/picasso/blob/31779ac2cb971c4534cc17bd437fab1aa0083d3d/picasso/src/main/java/com/squareup/picasso3/BitmapHunter.java#L625-L659
int exifRotation = getExifRotation(exifOrientation);
int exifTranslation = getExifTranslation(exifOrientation);

Matrix matrix = new Matrix();
if (exifRotation != 0) {
  matrix.preRotate(exifRotation);
}
if (exifTranslation != 1) {
  matrix.postScale(exifTranslation, 1);
}

// Now use this matrix to create a new Bitmap from the existing Bitmap

 

Mac OS: App Translocation and Android Studio updates failure

I installed Android Studio via homebrew “brew cask install android-studio” as a part of my automated Mac OS setup. Recently, Android Studio prompted me that an update is available. When I accepted to update, it failed with an error “Studio does not have write access to /private/var/folders/wt/rjv6_wcn4f97_2nth7fqftqh0000gn/T/AppTranslocation/19A80F28-865B-41FC-AA87-B8E43C826FCB/d/Android Studio.app/Contents. Please run it by a privileged user to update.” This error was confusing; I was running Android Studio as myself, a nonprivileged user and the same user owned this directory. Googling it a bit for AppTranslocation took me here.

From this point onward, the issue and the fix were relatively straightforward. Apple marked Android Studio as quarantine and hence was in a read-only directory. Quarantine status was confirmed by

$ xattr -l /Applications/Android\ Studio.app/
com.apple.quarantine: 00c1;5ad6b178;Chrome;9B843802-B7C1-4F48-BC68-09D8F81E6784

The fix was relatively simple as well

$ xattr -d -r -s com.apple.quarantine /Applications/Android\ Studio.app/

Update worked as expected after this.

Setting HOMEBREW_CASK_OPTS=–no-quarantine seems to enable this behavior for all the future installs.

Cross-language bridge error handling: JS-to-Java Example

All languages have certain semantics for dealing with error cases. C deals with them by setting error codes. Java deals with them by throwing exceptions. JavaScript deals with them by throwing exceptions as well but unlike Java, it does have any concept of checked Exceptions. The JS interpreter just stops. And this has some interesting implications in hybrid scenarios like a Webview based app.

Consider a simple Android app where most of the code is in JavaScript but is making a request to Java layer.

<html>
    <head/>
    <!-- Invoke getContacts() on the Javascript bridge object referenced via “JsInterface” tag on click -->
    <Button onClick='ContactJsInterface.getContacts()'>Click me</Button>
</html>
// MainActivity in Kotlin
class MainActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
        ....
        // Load the above string into webview
        webView.loadDataWithBaseURL("about:blank", htmlPage, "text/html", null, null)
        webView.settings.javaScriptEnabled = true
        // Add a WebkitJsInterface object and tag it with "ContactJsInterface", so that,
        // JsInterface.getContacts maps to WebKitJsInterface.getContacts
        webView.addJavascriptInterface(new WebkitJsInterface(this), "ContactJsInterface")
    }
}
// The Javascript interface object in Kotlin
class WebkitJsInterface(context: Context) {
    private val mContext = context

    // ContactJsInterface.getContacts() maps to this method
    @JavascriptInterface
    fun getContacts() {
        mContext.contentResolver.query(
            ContactsContract.Contacts.CONTENT_URI,
            null, null, null, null)
    }
}

 

After starting the app, check that the READ_CONTACTS is revoked or revoke it with adbe

adbe permissions revoke net.ashishb.jstojavademo contacts

Now, when you click the “Click me” button, you will see a SecurityException in the logs but the app won’t crash. If you check Thread’s name via Thread.currentThread().name, it will return JavaBridge. It seems any Exception thrown on this thread is simply swallowed. This won’t show up in analytics or crash log reports. Your Javascript code on return simply won’t be executed. And your app will appear unusable. This is worse than crashes. crashes at least give the app a chance to get out of a bad state.

This is worse than crashes. crashes at least give the app a chance to get out of a bad state.

Remedy

Sending error information across languages is hard. At the bare minimum, every such call should be encapsulated with a try-catch which catches Exception. For severe unexpected errors, it might not be bad to let the app crash, as you would, while writing the Java code.

@JavascriptInterface
fun getContacts() {
    try {
        getContactsUnsafe()
    } catch (e : Exception) {
        if (isSevereException(e)) {
            rethrowOnMainThread(e)
        } else {
            logError(e)
        }
    }
}

private fun logError(e: Exception) {
    Log.e("WebkitJsInterface", "Error occurred", e)
}

private fun rethrowOnMainThread(e: Exception) {
    Handler(Looper.getMainLooper()).post { throw e }
}

private fun getContactsUnsafe() {
    mContext.contentResolver.query(
        ContactsContract.Contacts.CONTENT_URI,
        null, null, null, null
    )
}

 

Full code for this blog post is posted on Github

Startup founders: How not to write an email

Consider this email,

And now consider this one,

Hi Ashish,

You signed up for the Orchard beta not too long ago, and we’re excited to finally send you an invite!

(Just to jog your memory, Orchard helps you make the most of your relationships, keeping you up to date on where you’re spending your time and who you need to catch up with. It’s somewhere between a personal CRM and a todo list for your connections.) [Emphasis mine]

You should receive an invite from Apple’s Testflight service in the next few minutes. It will contain a code or link you need to install the app. You’ll also need to have the TestFlight app installed on your phone. If you need us to send the invite to a different email address, just reply to this message and let us know.

Once you’ve checked out the app, please send us your feedback — this is a beta and we need your help to improve! Be honest, frank, and opinionated: you won’t hurt our feelings. Bugs, ideas, concerns, etc all fair game. You can reply to me or send to team@orchard.ai.

Thank you!

Brian and the entire Orchard team

brian@orchard.ai

Which one do you think your early adopters have a higher likelihood of understanding and responding to?
Don’t forget while you might live and breathe your startup, your early adopters have probably signed up to try several such services, and unless you remind them what your product is about, they might as well delete the email and move on.

The “key” problem in cryptocurrency

All cryptocurrencies are eventually tied to a “private” key. You lose this key, and the funds are gone, forever. Millions worth of bitcoins have disappeared from the circulation due to lost keys. You can memorize the key by mapping it into passphrase consisting of memorizable words but if you forget that, like many others, the coins are unrecoverable. An alternative is to trust a centralized service like Coinbase, but then all the benefits of investing in a decentralized currency are gone. Lastly, one can use a hardware wallet, but again, if you lose the wallet, the key is lost. If you keep the key on your device, then a malware might target and try to steal it someday. Thus, even if you are bullish on cryptocurrencies, there are no good decentralized ways of holding a significant chunk of your net worth in cryptocurrencies.

The problems are compounded further by the fact that to a great extent, the key access is all or none. If you lose your key, you lose all the funds. If I got hold of your key, I could transfer 100 % of your funds, irrevocably. Compare this to your ATM debit card, if I get hold of it, along with your pin, there is only so much damage I can do before the ATM limit hits. Further, your credit card used on a malicious website can hit you with a limited amount of fraud before the fraud warnings block the card to prevent further loss of funds.

An ideal approach, hopefully, will consist of decentralized smart contracts comprised of multiple keys tied to the same passphrase with a different set of transfer limits. So that, if one forgets one word in the passphrase and cannot access 100% of the funds now but can access at most 1% funds every day and slowly drain out their account to a newer account. Some of these unlocking codes will be held at different places, some centralized services for custody and ease of use; some kept on the personal device for ease of use where even if they get stolen the damage is contained. A further addition could be a beneficiary account where the funds would be transferred to in case the account lays dormant for a certain period.

Introducing adb-enhanced: A swiss army knife for Android development

Android development requires tons of disconnected approaches for development and testing. Consider some scenarios

  1. To test runtime permission – Go to Settings -> Applications -> Application info of the app you are looking for and disable that permission.
  2. To test a fresh install – adb shell pm clear-data com.example
  3. To test your app under the battery-saver mode – turn on the battery saver mode by expanding the notification bar
  4. To stop the execution of an app –  kill it via activity manager, adb shell am kill com.example
  5. To test your app under doze mode – first, make the device believe that it is unplugged via “adb shell dumpsys battery unplug”, then, make it think that it is discharging via “adb shell dumpsys battery set status 3”, and then enable doze mode via “adb shell dumpsys deviceidle force-idle”. And don’t forget to execute a set of unrelated complementary commands once you are done to bring the device back to its normal state.
  6. To see the overdraw of the app – Go to the developer options and enable/disable it there.
Over time, this became a significant mental burden that I first wrote some of these flows in a text file and then converted them to automated shell scripts. But when even that felt insufficient, I created a tool for myself called adb-enhanced.
How it works:
First, install the tool. I wrote this in Python, so, if the following command does not work, install Python
pip3 install adb-enhanced # Python2 is no longer supported since 2021

Now, let’s look at the about use-cases again with this tool:

  1. To test runtime permission :
    # Use grant instead of revoke to grant the permission 
    adbe permission revoke com.example camera # See all possible such permissions via "adbe -h"
  2. To test a fresh install –
    # Unlike adb shell pm clear-data com.example, this command will 
    # produce an error if com.example is not installed 
    # which is good for catching typos
    adbe clear-data com.example
  3. To test your app under the battery saver mode –
    # As you would guess, use "off" to turn the battery saver off
    adbe battery saver on
  4. To stop the execution of an app –
    # For a more aggressive kill, try adbe force-stop com.example
    adbe stop com.example
  5. To test your app under doze mode
    adbe doze on # Use "off" to turn the doze mode off
  6. To see the overdraw of the app
    adbe overdraw on

     

I open-sourced the code at https://github.com/ashishb/adb-enhanced. See the GitHub repository for what all this tool can do. Feedbacks and pull requests are welcome.