{"record":{"id":"0e9111f73e4b33aa","repo":"shwenzhang/AndResGuard","slug":"at-least-one-signer-must-be-specified","errorCode":null,"errorMessage":"At least one signer must be specified","messagePattern":"At least one signer must be specified","errorType":"validation","errorClass":"ParameterException","httpStatus":null,"severity":"error","filePath":"AndResGuard-core/src/main/java/apksigner/ApkSignerTool.java","lineNumber":190,"sourceCode":"        signerParams.keyFile = optionsParser.getRequiredValue(\"Private key file\");\n      } else if (\"cert\".equals(optionName)) {\n        signerParams.certFile = optionsParser.getRequiredValue(\"Certificate file\");\n      } else if ((\"v\".equals(optionName)) || (\"verbose\".equals(optionName))) {\n        verbose = optionsParser.getOptionalBooleanValue(true);\n      } else {\n        throw new ParameterException(\"Unsupported option: \"\n                                     + optionOriginalForm\n                                     + \". See --help for supported\"\n                                     + \" options.\");\n      }\n    }\n    if (!signerParams.isEmpty()) {\n      signers.add(signerParams);\n    }\n    signerParams = null;\n\n    if (signers.isEmpty()) {\n      throw new ParameterException(\"At least one signer must be specified\");\n    }\n\n    params = optionsParser.getRemainingParams();\n    if (inputApk != null) {\n      // Input APK has been specified via preceding parameters. We don't expect any more\n      // parameters.\n      if (params.length > 0) {\n        throw new ParameterException(\"Unexpected parameter(s) after \" + optionOriginalForm + \": \" + params[0]);\n      }\n    } else {\n      // Input APK has not been specified via preceding parameters. The next parameter is\n      // supposed to be the path to input APK.\n      if (params.length < 1) {\n        throw new ParameterException(\"Missing input APK\");\n      } else if (params.length > 1) {\n        throw new ParameterException(\"Unexpected parameter(s) after input APK (\" + params[1] + \")\");\n      }\n      inputApk = new File(params[0]);","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/shwenzhang/AndResGuard/blob/e4df245d82f27d9a2d0dd108260a3510cbaba849/AndResGuard-core/src/main/java/apksigner/ApkSignerTool.java#L172-L208","documentation":"Validation guard in ApkSignerTool.sign: after parsing all --ks/--key/--cert options, no signer configuration was assembled. apksigner refuses to run because signing is impossible without at least one private key/certificate pair. Fires when the user omits --ks/--key or provides only non-signer options like --in, --out, or --min-sdk-version.","triggerScenarios":"Running `apksigner sign --in app.apk --out out.apk` with no --ks, --ks-key-alias, or --key/--cert options; or providing only --next-signer separators without any actual signer data.","commonSituations":"Forgetting keystore options entirely; assuming a default debug keystore is picked up automatically (it is not); scripts that drop --ks arguments when variables are empty.","solutions":["Add signer options: --ks <keystore> --ks-key-alias <alias> (plus passwords)","Or use raw files: --key <pk8> --cert <pem>","Verify the script actually passes the keystore variables (quote/echo them)"],"exampleFix":"// before\napksigner sign --in app.apk --out app-signed.apk\n// after\napksigner sign --ks release.jks --ks-key-alias release --ks-pass env:KS_PASS --in app.apk --out app-signed.apk","handlingStrategy":"validation","validationCode":"boolean hasSigner = false;\nfor (String a : args) {\n    if (a.equals(\"--ks\") || a.equals(\"--key\") || a.equals(\"--ks-key-alias\") || a.equals(\"--next-signer\")) { hasSigner = true; break; }\n}\nif (!hasSigner) throw new IllegalArgumentException(\"sign requires --ks/--ks-key-alias or --key/--cert\");","typeGuard":null,"tryCatchPattern":"try { signCmd(args); } catch (ParameterException e) { System.err.println(\"Missing signer config: \" + e.getMessage()); exit(1); }","preventionTips":["Always pass --ks (or --key/--cert) with sign","Ensure script variables holding keystore options are non-empty","Provide --ks-key-alias along with --ks"],"tags":["cli","signing","missing-argument"],"backgroundTag":"missing-required-argument","analyzedSha":"e4df245d82f27d9a2d0dd108260a3510cbaba849","analyzedAt":"2026-09-12T17:49:07.798Z","contentChangedAt":"2026-09-12T17:49:07.798Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}