How Secure Is a Diary App Built with Vibe Coding?
Introduction
These days, anyone can create a fairly convincing application just by asking AI, “Build me an app.” If you search for “vibe coding” on X, you can easily find development tips, stories from non-developers building apps, and even examples of released products. Having an idea, talking it through with AI, adding features, building screens, and shipping something to an app store no longer feels unusual.
But how secure is an application built this way?
This is not an argument that “vibe coding is dangerous, so we should stop using it.” In fact, during this experiment, AI proved to be a fairly powerful tool for security analysis. When I showed it existing code and asked, “What vulnerabilities are in this code?”, it quickly identified the key issues.
The interesting part starts there. If I ask the same AI, without any security requirements, to “build an app with these features,” it may write code that it would later flag as vulnerable during review. In other words, the ability to analyze code and find problems is not the same as the ability to avoid those problems from the beginning. I wanted to explore that gap by building a personal diary and habit tracker app.
Vibe Coding the App
Personal record apps are a common example for vibe coding. Features like diaries, habit tracking, photo attachments, notifications, and sharing may look simple, but they involve private user data, which means there are many security details to consider.
I asked Codex to build an Android app with diary and habit tracking features. I deliberately did not mention security. I only provided a feature-focused prompt: sign-up, login, diary saving, habit check-ins, photo attachments, PIN lock, sharing, backup, and notifications.
The result was a functional app built from a single prompt. It looked reasonably convincing as an app, although the UI was not exactly beautiful.
Of course, this app has the limitations of something generated from a single prompt. It would be strange to expect it to be perfect in terms of completeness or stability. What matters in this experiment is not simply that vulnerabilities existed, but what level of vulnerabilities appeared.
Analysis
The analysis did not require complex reverse engineering or advanced exploits. I used only basic tools such as adb, run-as, logcat, and jadx. The checks were also the kind of things you would look at first in a basic mobile app security review.
In total, I found 12 security issues. Here are five of the most impactful and fundamental ones.
1. Plaintext Local Storage + Backup Exposure
The first issue was that the app stored its core data in SharedPreferences without encryption. On the device, the app’s internal storage contained email addresses, login session data, habit names, diary contents, PIN hashes, and photo paths inside habit_diary_store.xml.
<string name="accountHash">8616976d6509ed6cb509a2cf1a29c124db662264cd734115bd4fd2c6c1c68e64</string>
<string name="sessionEmail">test@example.com</string>
<string name="pinHash">be544cdba4a755e5dc01269ca1ca654837dab39f7b17caf744de53b7d8c17de9</string>
<string name="diaries">[{"date":"2026-07-21","text":"test_diary1","photoPaths":[]}]</string>
Because this is a personal diary and habit tracking app, the diary content itself is sensitive. If users record their health, emotions, schedules, relationships, or similar details, this is not just app configuration data. It is personal information.
The bigger problem was the backup configuration. Backups were enabled in the manifest, and the backup rules included both SharedPreferences and the attachments directory.
android:allowBackup="true"
<include domain="sharedpref" path="." />
<include domain="file" path="attachments/" />
If this app were released as-is, sensitive diary data and photo paths could be extracted through device backups, device migration, or rooted environments. Users might assume they are safe because the app has a lock screen, but the data itself is still stored unencrypted.
2. Weak Hashing for PINs and Account Passwords
The 4-digit app PIN was not stored in plaintext, but it was stored as a simple SHA-256 hash.
fun setPin(pin: String): Result<Unit> {
prefs.edit().putString("pinHash", hash(pin)).apply()
}
fun verifyPin(pin: String): Boolean =
prefs.getString("pinHash", null) == hash(pin)
A 4-digit PIN has only 10,000 possible values, from 0000 to 9999. Once the hash is obtained, brute forcing it takes very little time. In my test, I was able to recover the original PIN from the stored pinHash.
The account password was stored in the same way.
.putString("accountHash", hash(password))
Both the account password and PIN lacked a salt, and the app did not use a slow KDF such as PBKDF2, bcrypt, or Argon2. The fixed string "habit-diary:" may look like a salt, but because it is the same for every user and visible once the APK is decompiled, it provides no meaningful protection.
If released this way, an attacker who obtains pinHash or accountHash from local storage or a backup file could perform offline guessing attacks. Users who choose common values such as 1234, 0000, or password123 would be compromised almost immediately.
3. App Lock Bypass and PIN Removal
The app had a PIN lock screen, but it did not function as a real security boundary. I confirmed a flow where logging out and logging back in bypassed the PIN lock.
The cause was in the state management logic. logout() only removed the session email, while leaving the PIN hash, diary data, habit data, and account information intact.
fun logout() {
prefs.edit().remove("sessionEmail").apply()
}
At the same time, the UI unlock state, unlocked, was managed only as an in-memory value. Once the app was unlocked, logging out did not clearly reset that state. As a result, logging back in could lead directly to the home screen without showing the PIN screen again.
There was also a PIN reset flow that removed the PIN if the login password matched.
if (prefs.getString("accountHash", null) == hash(password)) {
prefs.edit().remove("pinHash").apply()
}
Because the account password hash was also weakly stored, exposure of accountHash could lead to removal of the PIN protection. Even though the app appeared to have multiple layers of protection, including PIN, password, and biometrics, the whole structure depended on a weak link.
4. Release APK Signed with Debug Config + No Integrity Protection
The release build configuration also had issues. The release APK used the debug signing config, and minification/obfuscation was disabled. As a result, opening the APK with JADX revealed key strings and authentication logic such as AppRepository, pinHash, accountHash, and SHA-256.
This is not a data theft vulnerability by itself, but it makes the other issues much easier to discover and exploit. An attacker can decompile the APK, see that the PIN hash is generated as SHA-256("habit-diary:" + pin), and immediately write an offline cracking script. There was also no root detection, tamper detection, signature verification, or similar runtime protection.
Obfuscation and root detection are not the essence of security, and client apps can always be analyzed eventually. Still, using debug signing and no obfuscation in a release build is a dangerous default for a real app.
5. Deep Links and Share Intents Processed Before Unlock
The app accepted deep links in the form habitdiary://record?date=YYYY-MM-DD and could also receive images via ACTION_SEND. MainActivity was exported.
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="habitdiary" android:host="record" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
The problem was that these external intents were processed before the app lock.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handleIncomingIntent(intent) // Runs before the PIN screen
setContent { ... }
}
handleIncomingIntent() applied the deep link date parameter without validation and copied shared images into internal storage without size limits, MIME validation, or count limits. In testing, shared images were stored under files/attachments regardless of the lock state, and their paths accumulated in diaries.photoPaths.
If released this way, a malicious app could imitate the normal sharing flow and repeatedly send large files to fill storage, or pollute a diary entry with images the user never intentionally attached. In this case, the assumption that “the app is locked, so it is safe” does not hold.
None of these five issues required a zero-day or difficult reverse engineering. They were basic problems that anyone with some security knowledge could point out. That is the point of this experiment. If I asked the same AI that wrote the code to “analyze this code from a security perspective,” it would likely catch issues at this level. It misses them during generation, but finds them during review.
What If This Were a Different App?
In this case, the app was a personal diary, so the damage may look limited to diary entries, habits, and photo paths. But if the same code patterns handled different data, the risk would change completely.
If this were a finance or budgeting app, plaintext storage and backup exposure could reveal account details and spending patterns. If the PIN were used as part of a transfer or payment flow, the lock bypass could lead directly to financial loss.
If this were a dating or social app, processing external intents before unlock could lead to unintended navigation, state changes, or attachment pollution. Combined with plaintext storage and backup exposure, this could increase the risk of leaking profiles, contacts, or messages.
The vulnerability patterns are the same: plaintext storage, weak hashing, backup exposure, and unvalidated external inputs. What changes is the kind of data placed on top of those patterns.
Real-World Vibe Coding Incidents
Security issues from vibe coding are no longer just hypothetical. They are already being reported in the real world. As AI makes it easier to build apps and websites, unverified services are reaching real user data faster as well.
##A representative case is Moltbook, an AI-agent social network disclosed in early 2026. Due to a misconfigured Supabase database, about 1.5 million API tokens, 35,000 email addresses, and private messages between agents were exposed. The root cause was a missing Row-Level Security policy. The important point was not simply that a Supabase key appeared in client-side code, but that the database behind it did not enforce proper Row-Level Security. A single missing access control policy made a large amount of data externally readable.
Source: Wiz Research, “Hacking Moltbook: The AI Social Network Any Human Can Control”
https://www.wiz.io/blog/exposed-moltbook-database-reveals-millions-of-api-keys
This was not just one unlucky service. RedAccess reportedly analyzed around 380,000 publicly accessible assets built with tools such as Lovable, Base44, Replit, and Netlify, and found that about 5,000 contained sensitive corporate information. The exposed data included medical records, financial information, corporate documents, and customer support conversations.
Source: VentureBeat, “5,000 vibe-coded apps just proved shadow AI is the new S3 bucket crisis”
https://venturebeat.com/security/vibe-coded-apps-shadow-ai-s3-bucket-crisis-ciso-audit-framework
In other words, the scenario from the previous section is already appearing in reality. Development is easier and deployment is faster, but unverified code is also meeting real data faster. Once a vibe-coded project moves beyond a personal experiment and becomes a real service, basic issues such as plaintext storage, weak authentication, and missing access control can quickly turn into data leaks and exposure of internal information.
How to Vibe Code More Safely
The conclusion is not “AI-built apps are dangerous.” It is closer to this: if you do not ask for security, AI will not necessarily handle it for you. As seen above, AI is good at finding problems in completed code. So we should pull that ability into the development process.
-
1. Identify sensitive data and include it explicitly in the prompt.
-
2. Ask how data is stored and whether it is encrypted.
-
3. After implementing features, ask the same AI to review the code from a security perspective.
-
4.Manage release settings such as allowBackup, release signing keys, and obfuscation as a separate checklist.
-
5. Use basic tools such as adb, logcat, and jadx at least once to inspect what is actually stored and shipped.
The third point is especially important. The 12 issues found in this experiment were all at a level the same AI could likely identify if asked to review the code. Simply separating generation and review into two stages, even within the same conversation or project, would likely have caught many of these issues before release.
Conclusion
Vibe coding is clearly powerful. I was able to build a working app in a short amount of time, and the speed of feature implementation was impressive.
But the issues found in this experiment were not advanced vulnerabilities. They were basic problems: plaintext storage, weak hashing, backup exposure, and unvalidated external inputs. These are things that could have been avoided with a small amount of security awareness. AI can identify these patterns when asked to analyze code, but when asked only to implement features, it may not apply that security judgment from the start.
The point of this post is not to discourage people from vibe coding. I hope more people use AI to quickly turn ideas into reality. But security should not be pushed aside as something to handle later, or something AI will automatically take care of. Simply being aware of where sensitive data is stored, how authentication values are handled, and how external inputs are validated can significantly change the result. I hope this post helps people think about security more naturally while vibe coding.