Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
66c31d7
🎨 Palette: Improved UX and layout for permission samples
google-labs-jules[bot] Mar 31, 2026
dc51f92
🛡️ Sentinel: Fix activity injection in sample app launcher
google-labs-jules[bot] Mar 31, 2026
14906b4
Merge pull request #2 from ubutatu/sentinel-fix-activity-injection-10…
ubutatu Mar 31, 2026
4b9f92f
Merge pull request #1 from ubutatu/palette-ux-improvements-permission…
ubutatu Mar 31, 2026
1a71118
🎨 Palette: Enhanced Permission Samples UX
google-labs-jules[bot] Apr 9, 2026
be8da5a
Harden sample app security posture
google-labs-jules[bot] Apr 9, 2026
efde5c6
Merge pull request #20 from ubutatu/sentinel/security-hardening-sampl…
ubutatu Apr 9, 2026
5048333
🛡️ Sentinel: Upgrade checksum algorithm to SHA-256
google-labs-jules[bot] Apr 10, 2026
a9d4ffe
Merge pull request #22 from ubutatu/sentinel/upgrade-checksum-to-sha2…
ubutatu Apr 11, 2026
8329697
Merge pull request #19 from ubutatu/palette/enhanced-permission-ux-16…
ubutatu Apr 11, 2026
a793cce
🛡️ Sentinel: Upgrade project metadata URLs to HTTPS
google-labs-jules[bot] Apr 12, 2026
2023ea3
🎨 Palette: Add trailing chevron to main screen list items
google-labs-jules[bot] Apr 12, 2026
c972285
Merge pull request #26 from ubutatu/palette-ux-improvement-chevron-10…
ubutatu Apr 12, 2026
8c5d442
Merge pull request #25 from ubutatu/sentinel/upgrade-metadata-https-7…
ubutatu Apr 12, 2026
4870bd5
🛡️ Sentinel: fix secret exposure in process list and harden scripts
google-labs-jules[bot] Apr 13, 2026
8bc345b
🎨 Palette: Enhance sample app navigation and accessibility
google-labs-jules[bot] Apr 13, 2026
b9ece27
Merge pull request #27 from ubutatu/sentinel/harden-scripts-616660838…
ubutatu Apr 15, 2026
9aa790f
Merge pull request #28 from ubutatu/palette/sample-navigation-ux-1035…
ubutatu Apr 15, 2026
2369dfd
🎨 Palette: Add selection feedback to adaptive samples
google-labs-jules[bot] Apr 15, 2026
c7221d2
🎨 Palette: Add selection feedback to adaptive samples & fix CI
google-labs-jules[bot] Apr 15, 2026
8021490
Merge pull request #32 from ubutatu/palette/navigation-ux-feedback-11…
ubutatu Apr 15, 2026
cb91f92
Add initial devcontainer configuration255d1437545d9bf67f9416e4443a6d2…
ubutatu Jun 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"image": "mcr.microsoft.com/devcontainers/universal:2",
"features": {}
}
12 changes: 9 additions & 3 deletions .github/workflows/build-snapshot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ jobs:
java-version: 17

- name: Decrypt secrets
run: release/signing-setup.sh ${{ secrets.ENCRYPT_KEY }}
run: release/signing-setup.sh
env:
ENCRYPT_KEY: ${{ secrets.ENCRYPT_KEY }}

- name: Generate cache key
run: ./checksum.sh checksum.txt
Expand Down Expand Up @@ -105,7 +107,9 @@ jobs:
java-version: 17

- name: Decrypt secrets
run: release/signing-setup.sh ${{ secrets.ENCRYPT_KEY }}
run: release/signing-setup.sh
env:
ENCRYPT_KEY: ${{ secrets.ENCRYPT_KEY }}

- name: Generate cache key
run: ./checksum.sh checksum.txt
Expand Down Expand Up @@ -180,7 +184,9 @@ jobs:
java-version: 17

- name: Decrypt secrets
run: release/signing-setup.sh ${{ secrets.ENCRYPT_KEY }}
run: release/signing-setup.sh
env:
ENCRYPT_KEY: ${{ secrets.ENCRYPT_KEY }}

- name: Generate cache key
run: ./checksum.sh checksum.txt
Expand Down
12 changes: 9 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ jobs:
java-version: 17

- name: Decrypt secrets
run: release/signing-setup.sh ${{ secrets.ENCRYPT_KEY }}
run: release/signing-setup.sh
env:
ENCRYPT_KEY: ${{ secrets.ENCRYPT_KEY }}

- name: Setup Gradle
uses: gradle/gradle-build-action@v2
Expand Down Expand Up @@ -109,7 +111,9 @@ jobs:
java-version: 17

- name: Decrypt secrets
run: release/signing-setup.sh ${{ secrets.ENCRYPT_KEY }}
run: release/signing-setup.sh
env:
ENCRYPT_KEY: ${{ secrets.ENCRYPT_KEY }}

- name: Setup Gradle
uses: gradle/gradle-build-action@v2
Expand Down Expand Up @@ -187,7 +191,9 @@ jobs:
java-version: 17

- name: Decrypt secrets
run: release/signing-setup.sh ${{ secrets.ENCRYPT_KEY }}
run: release/signing-setup.sh
env:
ENCRYPT_KEY: ${{ secrets.ENCRYPT_KEY }}

- name: Setup Gradle
uses: gradle/gradle-build-action@v2
Expand Down
11 changes: 11 additions & 0 deletions .jules/palette.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## 2024-05-22 - [Permission Samples UX]
**Learning:** Enhancing permission rationale screens with illustrative icons and specific action-oriented button labels (e.g., "Allow camera access" vs. "Request permission") improves user trust and clarity. Using `Arrangement.spacedBy` in `Column` ensures consistent visual rhythm without manual spacers.
**Action:** Use `Icons.Default.Face` for rationale prompts and `Icons.Default.Done` with success colors for granted states in Compose-based permission flows.

## 2024-06-12 - [Hierarchical Sample Navigation]
**Learning:** In sample apps with nested categories, static TopAppBar titles and missing back buttons disorient users. Contextual titles (last segment of path) and a standard back icon improve orientation and navigation flow.
**Action:** Use `Icons.AutoMirrored.Filled.ArrowBack` for back buttons and always add `Role.Button` to clickable list items for accessibility.

## 2024-12-19 - [Navigation Feedback in Samples]
**Learning:** Interactive navigation components (NavRail, NavDrawer) in sample apps feel "broken" if they don't provide immediate feedback in the main content area. Lifting selection state and displaying the selected item's identity (icon/label) validates user interaction.
**Action:** Always provide a visual response in the main content area when a navigation item is selected, and set `contentDescription = null` for icons accompanied by labels to avoid redundant screen reader announcements.
14 changes: 14 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## 2025-05-14 - [Activity Injection in Sample App Launcher]
**Vulnerability:** The sample application's `MainActivity` used `queryIntentActivities` to dynamically populate its list of samples based on an intent category (`com.google.accompanist.sample.SAMPLE_CODE`) without verifying the package name of the returned activities.
**Learning:** Malicious external applications could register activities with the same category and inject themselves into the sample app's UI, potentially leading to phishing or UI redressing attacks.
**Prevention:** Always verify that `info.activityInfo.packageName` matches the application's own `packageName` when dynamically loading components from `PackageManager` based on shared intent categories.

## 2024-05-15 - [Insecure Hashing and Shell Script Weakness]
**Vulnerability:** The `checksum.sh` script used MD5 for integrity checks and lacked variable quoting, making it susceptible to collisions and word-splitting vulnerabilities.
**Learning:** Legacy scripts often use older hashing algorithms like MD5 which are cryptographically broken. Additionally, unquoted variables in shell scripts can lead to unintended execution or script failure if paths contain special characters.
**Prevention:** Use SHA-256 for all integrity and security-sensitive hashing. Always wrap shell variables in double quotes (e.g., `"$VAR"`) to prevent word-splitting and globbing attacks.

## 2024-05-16 - [Secret Exposure in Process List via openssl -k]
**Vulnerability:** The `release/signing-setup.sh` script used the legacy `openssl -k` flag to pass an encryption key as a command-line argument, making it visible to all users on the system via process monitoring tools.
**Learning:** Command-line arguments are inherently public on most systems. Using them for secrets is a major security risk, especially in shared CI/CD environments.
**Prevention:** Always use environment variables, files, or descriptor-based methods to pass secrets to CLI tools. In `openssl`, use `-pass env:VARIABLE_NAME` to read the secret from an environment variable securely.
29 changes: 18 additions & 11 deletions checksum.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,32 @@
# See the License for the specific language governing permissions and
# limitations under the License.

RESULT_FILE=$1
set -e

if [ -f $RESULT_FILE ]; then
rm $RESULT_FILE
RESULT_FILE="$1"

if [ -f "$RESULT_FILE" ]; then
rm "$RESULT_FILE"
fi
touch $RESULT_FILE
touch "$RESULT_FILE"

checksum_file() {
echo $(openssl md5 $1 | awk '{print $2}')
# Use SHA-256 for better security and collision resistance compared to MD5.
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$1" | awk '{print $1}'
else
openssl dgst -sha256 -r "$1" | awk '{print $1}'
fi
}

FILES=()
while read -r -d ''; do
FILES+=("$REPLY")
while read -r -d '' FILE; do
FILES+=("$FILE")
done < <(find . -type f \( -name "build.gradle*" -o -name "*.versions.toml" -o -name "gradle-wrapper.properties" \) -print0)

# Loop through files and append MD5 to result file
for FILE in ${FILES[@]}; do
echo $(checksum_file $FILE) >> $RESULT_FILE
# Loop through files and append SHA-256 to result file
for FILE in "${FILES[@]}"; do
echo "$(checksum_file "$FILE")" >> "$RESULT_FILE"
done
# Now sort the file so that it is idempotent
sort $RESULT_FILE -o $RESULT_FILE
sort "$RESULT_FILE" -o "$RESULT_FILE"
6 changes: 3 additions & 3 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ POM_DESCRIPTION=Utilities for Jetpack Compose

POM_URL=https://github.com/google/accompanist/
POM_SCM_URL=https://github.com/google/accompanist/
POM_SCM_CONNECTION=scm:git:git://github.com/google/accompanist.git
POM_SCM_DEV_CONNECTION=scm:git:git://github.com/google/accompanist.git
POM_SCM_CONNECTION=scm:git:https://github.com/google/accompanist.git
POM_SCM_DEV_CONNECTION=scm:git:https://github.com/google/accompanist.git

POM_LICENCE_NAME=The Apache Software License, Version 2.0
POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
POM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt
POM_LICENCE_DIST=repo

POM_DEVELOPER_ID=google
Expand Down
13 changes: 8 additions & 5 deletions release/signing-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.

ENCRYPT_KEY=$1
set -e

if [[ ! -z "$ENCRYPT_KEY" ]]; then
ENCRYPT_KEY="${ENCRYPT_KEY:-$1}"

if [[ -n "$ENCRYPT_KEY" ]]; then
# Decrypt GnuPG keyring
openssl aes-256-cbc -md sha256 -d -in release/secring.gpg.aes -out release/secring.gpg -k ${ENCRYPT_KEY}
openssl aes-256-cbc -md sha256 -d -in release/secring.gpg.aes -out release/secring.gpg -pass env:ENCRYPT_KEY

# Decrypt Play Store key
openssl aes-256-cbc -md sha256 -d -in release/signing.properties.aes -out release/signing.properties -k ${ENCRYPT_KEY}
openssl aes-256-cbc -md sha256 -d -in release/signing.properties.aes -out release/signing.properties -pass env:ENCRYPT_KEY

else
echo "ENCRYPT_KEY is empty"
echo "ENCRYPT_KEY is empty, skipping decryption"
exit 0
fi
25 changes: 15 additions & 10 deletions sample/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@android:style/Theme.Material.NoActionBar">
android:theme="@android:style/Theme.Material.NoActionBar"
android:usesCleartextTraffic="false">

<profileable android:shell="true"
tools:targetApi="q" />
Expand All @@ -51,10 +52,14 @@
</intent-filter>
</activity>

<!--
Sample activities are launched via explicit intents from MainActivity.
They do not need to be exported, reducing the attack surface.
-->
<activity
android:name=".permissions.RequestPermissionSample"
android:label="@string/permissions_title_one"
android:exported="true">
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.google.accompanist.sample.SAMPLE_CODE" />
Expand All @@ -64,7 +69,7 @@
<activity
android:name=".permissions.RequestMultiplePermissionsSample"
android:label="@string/permissions_title_multiple"
android:exported="true">
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.google.accompanist.sample.SAMPLE_CODE" />
Expand All @@ -74,7 +79,7 @@
<activity
android:name=".permissions.RequestLocationPermissionsSample"
android:label="@string/permissions_title_location"
android:exported="true">
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.google.accompanist.sample.SAMPLE_CODE" />
Expand All @@ -84,7 +89,7 @@
<activity
android:name=".adaptive.BasicTwoPaneSample"
android:label="@string/adaptive_two_pane_basic"
android:exported="true">
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.google.accompanist.sample.SAMPLE_CODE" />
Expand All @@ -94,7 +99,7 @@
<activity
android:name=".adaptive.HorizontalTwoPaneSample"
android:label="@string/adaptive_two_pane_horizontal"
android:exported="true">
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.google.accompanist.sample.SAMPLE_CODE" />
Expand All @@ -104,7 +109,7 @@
<activity
android:name=".adaptive.VerticalTwoPaneSample"
android:label="@string/adaptive_two_pane_vertical"
android:exported="true">
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.google.accompanist.sample.SAMPLE_CODE" />
Expand All @@ -114,7 +119,7 @@
<activity
android:name=".adaptive.NavRailFoldAwareColumnSample"
android:label="@string/adaptive_fold_aware_column_nav_rail"
android:exported="true">
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.google.accompanist.sample.SAMPLE_CODE" />
Expand All @@ -124,7 +129,7 @@
<activity
android:name=".adaptive.NavDrawerFoldAwareColumnSample"
android:label="@string/adaptive_fold_aware_column_nav_drawer"
android:exported="true">
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.google.accompanist.sample.SAMPLE_CODE" />
Expand All @@ -134,7 +139,7 @@
<activity
android:name=".adaptive.DraggableFoldAwareColumnSample"
android:label="@string/adaptive_fold_aware_column_draggable"
android:exported="true">
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.google.accompanist.sample.SAMPLE_CODE" />
Expand Down
20 changes: 17 additions & 3 deletions sample/src/main/java/com/google/accompanist/sample/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.ui.res.stringResource

/**
* A list which automatically populates the list of sample activities in this app
Expand All @@ -32,13 +33,20 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)

enableEdgeToEdge()
val data = getData(intent.getStringExtra(EXTRA_PATH))
val path = intent.getStringExtra(EXTRA_PATH)
val data = getData(path)

setContent {
AccompanistSampleTheme {
MainScreen(
listData = data,
onItemClick = { startActivity(it) }
onItemClick = { startActivity(it) },
title = path?.substringAfterLast('/') ?: stringResource(R.string.app_name),
onBackClick = if (path != null) {
{ finish() }
} else {
null
}
)
}
}
Expand Down Expand Up @@ -66,6 +74,8 @@ class MainActivity : ComponentActivity() {
val entries = mutableMapOf<String, Boolean>()

list.forEach { info ->
if (info.activityInfo.packageName != packageName) return@forEach

val labelSeq = info.loadLabel(packageManager)
val label = labelSeq?.toString() ?: info.activityInfo.name

Expand Down Expand Up @@ -117,4 +127,8 @@ class MainActivity : ComponentActivity() {
}
}

private const val EXTRA_PATH = "com.example.android.apis.Path"
/*
* Use a project-specific prefix for intent extras to avoid collisions
* and follow security best practices.
*/
private const val EXTRA_PATH = "com.google.accompanist.sample.PATH"
Loading
Loading